Part 01 · Backend Mindset

Client vs Server

ClientServer

কে কোন দায়িত্ব নেয়, আর browser আর backend এর মাঝের boundary আসলে কোথায়।

⏱ 1 ঘণ্টা · Station 2 / 153

দাম ছিল 500। কেউ দিলো 1।

তোমার একটা ছোট online বইয়ের দোকান। একটা বইয়ের দাম 500 টাকা। এক সকালে order গুলো দেখতে গিয়ে পেলে, একজন customer দুটো বই কিনেছে আর দিয়েছে 2 টাকা।

কেউ তোমার server hack করেনি। কেউ password আন্দাজ করেনি। App টা order এর সাথে দামটাও পাঠাতো, qty=2&price=500, আর server যা পেতো তাই গুণ করতো। এই customer শুধু price=1 পাঠিয়েছে।

তোমার app এ তো দাম লেখার কোনো ঘরই নেই, তাহলে এটা সম্ভব হলো কীভাবে? আর দোষটা কার: app এর, নাকি server এর?

এই lesson দুটো প্রশ্নেরই উত্তর দেয়, তুমি যত system বানাবে তার প্রতিটার মাঝখান দিয়ে একটা লাইন টেনে: client আর server এর মাঝের লাইন।

সমস্যা: দুটো program, দুজন মালিক

Software যখন অনেক user কে serve করে (lesson 1), তখন সেটা দুটো program এ ভাগ হয়ে যায়, যারা একদম আলাদা জায়গায় চলে।

ClientServer
কোথায় চলেUser এর phone, laptop বা browser এতোমার নিয়ন্ত্রণে থাকা একটা machine এ
কে নিয়ন্ত্রণ করেUser। সে এটা খুলে দেখতে পারে, বদলাতে পারে, পুরোটা অন্য কিছু দিয়ে বদলেও দিতে পারেতুমি, শুধু তুমি
কয়টা copyপ্রতি user এ একটা, লাখ লাখ, অনেকগুলো পুরনো versionএকটা, বা অল্প কয়েকটা একই রকম
কীসে ভালোজিনিস দেখানো, tap আর key এ সাথে সাথে সাড়া দেওয়াShared data রাখা, rule check করা, secret লুকিয়ে রাখা
বিশ্বাস করা যায়?নাহ্যাঁ, যদি যত্ন করে বানাও

যে লাইনটা মানুষকে অবাক করে সেটা হলো "কে নিয়ন্ত্রণ করে"। তোমার app একজন অচেনা মানুষের device এ install করা। ওখানে যাওয়ার পর ওটা তার। সে app এর code পড়তে পারে, app যত request পাঠায় সব দেখতে পারে, আর curl এর মতো tool দিয়ে নিজেই শুরু থেকে request লিখতে পারে, তোমার app কে পুরো বাদ দিয়ে।

তাই server আসলে কখনো তোমার app এর সাথে কথা বলে না। সে কথা বলে যে program ই তাকে bytes পাঠায় তার সাথে।

একটা restaurant ভাবো

Restaurant এ তুমি menu হাতে টেবিলে বসো। পছন্দ করো, আঙুল দিয়ে দেখাও, প্রশ্ন করো। এটাই client: যেখানে তুমি interact করো, আর যেভাবে খুশি ব্যবহার করতে পারো।

রান্নাঘরটা হলো server। ওখানে থাকে বাজার-সদাই (data), রেসিপি (rule) আর ক্যাশ (টাকা)। তুমি রান্নাঘরে ঢোকো না। তুমি একটা order পাঠাও, আর আসলে কী হবে সেটা রান্নাঘর ঠিক করে।

এবার ভাবো order slip এ একটা লাইন আছে যেখানে তুমি দাম লেখো। বেশিরভাগ customer সৎভাবে menu থেকে দাম তুলবে। একজন লিখবে "1 টাকা"। রান্নাঘর যদি slip এ যা লেখা তাই নেয়, সেটা customer এর চালাকি না, restaurant এর design এর ভুল।

client: your deviceserver: your machinetrust boundaryshop appshows, collects tapscurlanyone can write a requestserverbook = 500 takaqty=2charged 1000qty=2&price=1price ignored: still 1000the server decides
App শুধু user যা বেছেছে সেটা পাঠায়। হাতে লেখা একটা request ভুয়া দাম নিয়ে একই তার দিয়ে আসে, আর server দামটা পাত্তাই দেয় না, কারণ সে কখনো client এর কাছে দাম চায়নি।

Analogy টা কোথায় খাটে না। একজন waiter customer কে দেখে কিছু অদ্ভুত লাগলে টের পায়। Server কাউকে দেখতে পায় না: প্রতিটা request শুধু text, আর attacker এর লেখা request দেখতে হুবহু তোমার app এর পাঠানো request এর মতো। দেখার মতো কোনো "চেহারা" নেই। তাই rule টা server এর ভেতরেই বানিয়ে রাখতে হয়।

মূল জিনিসগুলো

যেকোনো app কে খুলে ফেললে একই কয়েকটা টুকরো পাবে।

  1. Request। Client একটা message পাঠায়: "user এটা বেছেছে।" বাস্তবে সেটা একটা HTTP request: একটা method, একটা path, কিছু header আর হয়তো একটা body। (Module 3।)
  2. Response। Server উত্তর দেয়: "এটা হলো।" একটা status code, header আর body।
  3. Boundary। Client এর দিকের সবকিছু input: কাজের, কিন্তু check করা না। Server এর দিকের সবকিছু সিদ্ধান্ত: দাম কত, তুমি এটা করতে পারবে কিনা, কী save হবে।
  4. দায়িত্ব। প্রতিটা দিকের একটা কাজ আছে যেটায় সে সবচেয়ে ভালো:
Client এর কাজServer এর কাজ
Screen আঁকা, layout, animationদাম, মোট টাকা, discount, stock
সাথে সাথে feedback ("email টা ভুল মনে হচ্ছে")আসল check, প্রতিবার
খোলা tab বা draft মনে রাখাUser দের মধ্যে shared data
যে button user ব্যবহার করতে পারবে না সেটা লুকানোতবুও চেষ্টা করলে কাজটা ফিরিয়ে দেওয়া
গোপন কিছুই নাAPI key, password, অন্যদের data
  1. মূল নিয়ম। Client এ check করো user এর সুবিধার জন্য; server এ check করো যাতে জিনিসটা আসলেই ঠিক থাকে। Form বলতে পারে "quantity 1 থেকে 10 হতে হবে", যাতে সৎ user তাড়াতাড়ি feedback পায়। Server কে একই জিনিস আবার check করতে হবে, কারণ form টা এড়িয়ে যাওয়া যায়।
আগে অনুমান করো

তোমার checkout form JavaScript দিয়ে 10 এর বেশি quantity আটকায়। একজন user browser এর developer tools খুলে সরাসরি qty=500 পাঠালো। Order টা যাবে কিনা কে ঠিক করবে?

ভেতরে কী হয়: একটা order, দুটো পথ

একই order দুবার আসছে: একবার তোমার app থেকে, একবার হাতে লেখা।

ধাপে ধাপে দেখো

একই order, দুইভাবে পাঠানো

Shop appcurlServerPrice list

ধাপ 1/6 User "২টা কিনবো" তে tap করলো

User যা বেছেছে তা দিয়ে app একটা request বানায়। যত্নশীল app শুধু পছন্দটা পাঠায়: কোন বই আর কয়টা। দাম না।

request
GET /checkout?item=book&qty=2

মাথায় রাখার ছবি

তোমার user আর তোমার server এর মাঝে একটা খাড়া লাইন আঁকো। নাম দাও trust boundary।

  • লাইনের বাঁ দিকে: তোমার app এর হাজারটা copy, এমন device এ যেগুলো তোমার নিয়ন্ত্রণে নেই, কিছু বদলানো, কিছু পুরনো, কিছু তো তোমার app ই না।
  • লাইনের ডান দিকে: একটা program যেটা তোমার নিয়ন্ত্রণে, data আর rule সহ।
  • বাঁ থেকে ডানে যা কিছু লাইন পার হয় সব একেকটা দাবি: "user এটা চায়"। Server প্রতিটা দাবি check করে, প্রতিবার।

যখনই নতুন feature যোগ করবে, নিজেকে প্রশ্ন করো: কেউ যদি আমার app বাদ দিয়ে হাতে এই request পাঠায়, সবচেয়ে খারাপ কী করতে পারবে? উত্তর যদি হয় "দাম বদলানো", "অন্যের data পড়া" বা "payment এড়িয়ে যাওয়া", তাহলে ওই logic লাইনের ভুল দিকে আছে।

চেষ্টা করো: কোন দিক সিদ্ধান্ত নেবে?

প্রতিটা card একটা real app এর এক টুকরো logic। উত্তর পড়ার আগে ঠিক করো এটা কোথায় থাকা উচিত।

প্রতিটা case চালিয়ে দেখো

Client, server, নাকি দুটোই?

client: টাইপ করার সময়ই feedback
server: submit এর সময় email আবার check করে
কাজ করে: দুটোই। Client এর check form টাকে সহজ করে; গোনা হয় server এর check। ওটা না থাকলে curl থেকে আসা request আবর্জনা save করে ফেলতে পারে।

গোড়া থেকে code

দুটো endpoint সহ একটা ছোট দোকানের server, শুধু standard library দিয়ে বানানো:

  • /trust client যা quantity আর দাম পাঠায় তাই গুণ করে।
  • /checkout শুধু item আর quantity পড়ে, দাম নিজে খোঁজে, আর quantity check করে।

তারপর তিনটা request হাতে লেখা হয়, TCP socket এ raw text হিসেবে, ঠিক যেভাবে curl বা একজন attacker এর script লিখতো। প্রথমটা তোমার app যা পাঠায়। বাকি দুটো না।

package main

import (
	"bufio"
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
)

// The price lives on the server. The client never gets to decide it.
var prices = map[string]int{"book": 500}

// /trust believes whatever the client sent, price included.
func trust(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	qty, _ := strconv.Atoi(q.Get("qty"))
	price, _ := strconv.Atoi(q.Get("price"))
	fmt.Fprintf(w, "charged %d", qty*price)
}

// /checkout takes only what the user chose, and decides everything else itself.
func checkout(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	price, ok := prices[q.Get("item")]
	if !ok {
		http.Error(w, "no such item", http.StatusNotFound)
		return
	}
	qty, err := strconv.Atoi(q.Get("qty"))
	if err != nil || qty < 1 || qty > 10 {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, "qty must be 1 to 10")
		return
	}
	fmt.Fprintf(w, "charged %d", qty*price)
}

// send writes an HTTP request by hand, the way any program could, and returns "status body".
func send(addr, path string) string {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	fmt.Fprintf(conn, "GET %s HTTP/1.1\r\nHost: shop\r\nConnection: close\r\n\r\n", path)
	reply := bufio.NewReader(conn)
	status, _ := reply.ReadString('\n') // "HTTP/1.1 200 OK"
	for {                               // skip the headers: they end at the first empty line
		line, err := reply.ReadString('\n')
		if line == "\r\n" || err != nil {
			break
		}
	}
	body, _ := reply.ReadString(0)
	return strings.Fields(status)[1] + " " + strings.TrimSpace(body)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/trust", trust)
	mux.HandleFunc("/checkout", checkout)
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	go http.Serve(ln, mux)
	addr := ln.Addr().String()

	fmt.Println("a book costs 500 taka; that price is stored on the server")
	fmt.Printf("%-6s %-22s %-20s %s\n", "sender", "asks for", "/trust", "/checkout")
	for _, c := range [][2]string{
		{"app", "item=book&qty=2&price=500"},
		{"curl", "item=book&qty=2&price=1"},
		{"curl", "item=book&qty=-3&price=500"},
	} {
		fmt.Printf("%-6s %-22s %-20s %s\n", c[0], strings.TrimPrefix(c[1], "item=book&"), send(addr, "/trust?"+c[1]), send(addr, "/checkout?"+c[1]))
	}
}

Output (পাঁচটা language এই একই):

a book costs 500 taka; that price is stored on the server
sender asks for               /trust               /checkout
app    qty=2&price=500        200 charged 1000     200 charged 1000
curl   qty=2&price=1          200 charged 2        200 charged 1000
curl   qty=-3&price=500       200 charged -1500    400 qty must be 1 to 10

Output টা পড়ি:

  • app, qty=2&price=500: দুটো endpoint ই 1000 নেয়। সৎ client এর সাথে দুটো design আলাদা করে চেনাই যায় না। এজন্যই এই bug ship করে ফেলা এত সহজ।
  • curl, price=1: /trust নেয় 2 টাকা। /checkout নেয় 1000, কারণ সে দামটা কখনো পড়েইনি।
  • curl, qty=-3: /trust নেয় −1500, যেটা আসল payment flow এ একটা refund হয়ে যেতে পারে। /checkout উত্তর দেয় 400: quantity এর rule server এ থাকে, তাই request যেই পাঠাক, ওটা চলবেই।
  • খেয়াল করো, কোন request কে পাঠিয়েছে server কে সেটা জানতেই হয়নি। Client থেকে শুধু পছন্দ নিয়ে আর বাকি সব নিজে হিসাব করে সে নিরাপদ থেকেছে।

আসল engineering

  • Client কে কখনো বিশ্বাস করবে না। Backend security এর প্রথম নিয়ম এটা, আর এর মধ্যে পড়ে দাম, user id, role, file এর নাম আর size। Client validate করলেও server এ validate করো। (Module 5 আর Module 9।)
  • Fact না, id পাঠাও। ভালো client পাঠায় item=book বা productId=42; নাম, দাম আর stock server খুঁজে নেয়। যে client price বা isAdmin=true পাঠায়, তাকে দিয়ে মিথ্যা বলানো খুব সহজ।
  • যা গুরুত্বপূর্ণ, তার জন্য পাতলা client, মোটা server। Team গুলো টাকা, permission আর shared data রাখে server এ, আর client কে রাখে দ্রুত আর সহজ। কিছু logic ইচ্ছা করেই দুই জায়গায় থাকে: client check করে গতির জন্য, server check করে সত্যের জন্য।
  • পুরনো client চিরকাল বেঁচে থাকে। User রা app update করে না। বছর আগে ship করা version এর request ও তোমার server কে উত্তর দিয়ে যেতে হবে, এজন্যই API এর version থাকে। (Module 3, lesson 15।)
  • Mobile app গোপন কিছু না। App এ যা ship করো (key, লুকানো URL, "admin" flag) সব বের করে ফেলা যায়। আসল system secret রাখে server এ, আর client কে দেয় অল্প সময়ের token।

যখন ভাঙে

এর প্রতিটা real product এ ঘটেছে।

প্রতিটা case চালিয়ে দেখো

একটা failure বাছো

POST /orders {"item": 42, "price": 1}
order saved, charged 1 taka
ভাঙে: যে fact server এর নিজে খোঁজা উচিত ছিল, সেটা সে বিশ্বাস করেছে। সমাধান: শুধু product id পাঠাও; দাম server এ হিসাব করো। ধরার উপায়: কাটা টাকা price list এর সাথে মিলিয়ে দেখা।

পড়ার আগে ভাবো

তোমার API শুধু তোমার নিজের mobile app ই ব্যবহার করে, আর API এর URL কোথাও public এ লেখা নেই। আপাতত server এ check না করলে কি চলে?

উত্তর দেখো

না। API এর URL app এর ভেতরেই আছে, আর app এর network traffic দেখে যে কেউ সেটা বের করে ফেলতে পারে, একটা free proxy tool দিয়ে মোটামুটি পাঁচ মিনিটে। "কেউ URL জানে না" কোনো সুরক্ষা না। Server কে ধরে নিতে হবে প্রতিটা request হাতে লেখা হতে পারে, কারণ কিছু তো হবেই।

নিজেকে check করো

1. এক বাক্যে, client আর server এর পার্থক্য কী?

উত্তর

Client চলে user এর device এ আর নিয়ন্ত্রণ করে user; server চলে তোমার নিয়ন্ত্রণের machine এ, আর shared data আর rule রাখে।

2. Client কেন price না পাঠিয়ে productId পাঠাবে?

উত্তর

কারণ client যা পাঠায় সব বদলানো যায়। Id শুধু একটা পছন্দ; আসল দাম server নিজের data থেকে খোঁজে, তাই id বদলালে শুধু অন্য product বাছা যায়, অন্য দাম না।

3. Server যদি সব check করেই, তাহলে client এ validate করার দরকার কী?

উত্তর

User এর জন্য। সাথে সাথে feedback পাওয়া একটা round trip এর চেয়ে দ্রুত আর আরামের, আর server এর কিছু অকাজের request বাঁচে। শুধু এটা security না।

4. দুটো জিনিসের নাম বলো যা কখনো client এ রাখা যাবে না।

উত্তর

যেমন: secret API key, password বা অন্য user দের data, আর দাম বা permission ঠিক করার ক্ষমতা।

তোমার challenge: ফুটোটা বন্ধ করো

উপরের program এ তৃতীয় একটা endpoint যোগ করো, /discount?item=book&qty=2&coupon=HALF।

  • Server এর নিজের coupon list আছে: HALF এ 50% ছাড়, TEN এ 10% ছাড়।
  • অজানা coupon এর উত্তর 400 unknown coupon।
  • Client যেন discount এর পরিমাণ পাঠাতে না পারে, শুধু coupon এর code।

চালানোর আগে আন্দাজ করো /discount?item=book&qty=2&coupon=HALF&discount=100 কী ফেরত দেওয়া উচিত।

সফল হয়েছো যখন তোমার হাতে লেখা request গুলো HALF এর জন্য 200 charged 500, TEN এর জন্য 200 charged 900, FREE এর জন্য 400 unknown coupon print করে, আর বাড়তি discount=100 কিছুই বদলায় না।

Mental model

Client হলো user এর program, user এর device এ, user এর নিয়ন্ত্রণে; server তোমার। লাইন পার হয়ে যা আসে সব দাবি, fact না। Client দেখাক আর প্রস্তাব দিক; server সিদ্ধান্ত নিক, হিসাব করুক আর মনে রাখুক। Client এ check করো গতির জন্য, server এ সত্যের জন্য।

Station stamp

Chapter শেষ করো

পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।

Pro নিয়ে XP পাও →