Lesson 09 / 11
Connecting to a Remote Server
Counting the step a connection adds when the server moves beyond the network: the same three-tool chain pays 6 requests, 6 responses, 179 envelope tokens and 6 round trips when all three servers are on the same machine; when two servers move to remote transport the round trips rise to 9 with the message count unchanged, and when one identity step and one authorization step are added per remote server the count climbs to 10 requests, 10 responses, 289 tokens and 17 round trips. The negotiated tool set is three tools in all three setups and the result is the same in all three; of the 110 added tokens, 64 are only the envelope constant. The measure is independent of the run: the count comes out identical for three separate subscribers.
Published Updated
In this lesson
- Defines the identity and authorization steps of a remote connection as a message count and counts them side by side with the local setup.
- Shows, as a separate column, how remote transport multiplies the round trips at the same request count.
- Verifies with the negotiated tool set and the computed result that the added cost does not change the outcome.
Contents
In the previous lesson the server stood on the same machine as the client and the connection was nothing more than a process boundary. That boundary did two jobs at once: it carried the envelope, and it removed the question of who the other side was, because the client itself had started the process. When the server moves beyond the network, this togetherness breaks. Transport keeps carrying the same envelope, but who the other side is and what it is authorized to do are now two separate questions that have to be asked. The procedure of authentication and authorization was measured in the Authentication and Authorization course of the Backend Development curriculum and is not repeated here. The only thing measured is the step the remote connection adds.
- IM41. The environment, the three servers, the envelope count and capability negotiation are fictional; no real server, client library or runtime is called. Seed 20260218.
- IM42. The identity step and the authorization step are, for each server on remote transport, one request and response pair. No token format, flow or standard is set up; what is counted is the message.
- IM43. The authorization step does not change the result: the authorization granted is the allowlist the client already applies. The measure is isolated this way — the only thing that changes across the three setups is the cost.
- IM44. The “all local” setup is the comparison baseline: the same three servers, the same versions, with the transport field as the only difference.
- IM45. The baseline without the protocol counts no envelope: the tool is defined inside the lesson, there is no handshake, the three functions are called directly. In every number column of this lesson it is zero.
The Same Work, Two Setups
The block sets up the common definition: the fictional measurement network, three fictional servers, the envelope counter, capability negotiation and the tool call. olcum (measurement server) is on local transport, tarife (tariff server) and kayit (records server) are on remote transport.
# FICTIONAL. The subscriber records of the measurement network, three servers, the envelope counter # and capability negotiation are defined inside the lesson; no real server or client is called. SEED, M32 = 20260218, 0xFFFFFFFF def rng(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def next_value(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return next_value def tokenize(m): b = [] for s in str(m).lower().replace(",", " , ").split(): while len(s) > 5: b.append(s[:5]) s = s[5:] b.append(s) return b REGION = ["dogu", "bati", "kuzey", "guney", "merkez"] TYPE = ["mesken", "isyeri"] SUBSCRIBERS = {} for i in range(60): r = rng(SEED + 31 * i) previous = 10000 + int(r() * 80000) SUBSCRIBERS[f"A{i + 1:03d}"] = {"bolge": REGION[int(r() * 5)], "tip": TYPE[int(r() * 2)], "onceki": previous, "simdiki": previous + int(r() * 70)} TIERS, ABOVE, COMMERCIAL_FACTOR = ((10, 4), (25, 7), (40, 11)), 16, 2 def _charge(m3, type_): remaining, lower, total = m3, 0, 0 for upper, unit in TIERS: band = min(remaining, upper - lower) if band <= 0: break total += band * unit remaining -= band lower = upper return (total + remaining * ABOVE) * (COMMERCIAL_FACTOR if type_ == "isyeri" else 1) def t_meter(abone=None, **_): a = SUBSCRIBERS.get(abone) return {"hata": "abone yok"} if a is None else { "onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"]} def t_consumption(onceki=None, simdiki=None, **_): if onceki is None or simdiki is None: return {"hata": "eksik alan"} f = simdiki - onceki return {"m3": f + 100000 if f < 0 else f} def t_tariff(m3=None, tip="mesken", **_): return {"hata": "eksik alan"} if m3 is None else {"tutar": _charge(m3, tip)} def t_region(bolge=None, **_): if bolge not in REGION: return {"hata": "bolge yok"} n = sum(1 for a in SUBSCRIBERS.values() if a["bolge"] == bolge) return {"bolge": bolge, "abone": n} def t_record(abone=None, sutun=None, deger=None, **_): if not (abone and sutun and deger is not None): return {"hata": "eksik alan"} return {"yazildi": True} SERVERS = { "olcum": {"version": 2, "transport": "local", "tools": {"sayac_oku": {"function": t_meter, "fields": ("abone",), "side_effect": False}, "tuketim": {"function": t_consumption, "fields": ("onceki", "simdiki"), "side_effect": False}}, "resources": ("abone_listesi",), "prompts": ()}, "tarife": {"version": 1, "transport": "remote", "tools": {"tarife": {"function": t_tariff, "fields": ("m3", "tip"), "side_effect": False}}, "resources": ("basamak_tablosu",), "prompts": ("bedel_aciklamasi",)}, "kayit": {"version": 3, "transport": "remote", "tools": {"bolge_ozet": {"function": t_region, "fields": ("bolge",), "side_effect": False}, "kayit_yaz": {"function": t_record, "fields": ("abone", "sutun", "deger"), "side_effect": True}}, "resources": (), "prompts": ()}, } CLIENT_VERSION = {"olcum": 2, "tarife": 1, "kayit": 2} # the kayit server is ahead ALLOW = ("sayac_oku", "tuketim", "tarife", "bolge_ozet") # kayit_yaz is not on the allowlist COUNT = {"request": 0, "response": 0, "notification": 0, "tokens": 0, "round_trips": 0} def message(kind, body, transport="local"): """FICTIONAL envelope. No real protocol is implemented; what is counted is the cost of the envelope.""" COUNT[kind] += 1 b = len(tokenize(body)) + 8 # envelope constant COUNT["tokens"] += b if kind == "request": COUNT["round_trips"] += 2 if transport == "remote" else 1 return b def handshake(client_version=None, allow=None): """Capability negotiation: a version mismatch and the allowlist narrow the tool set.""" client_version = CLIENT_VERSION if client_version is None else client_version allow = ALLOW if allow is None else allow agreed, dropped = {}, [] for name, s in SERVERS.items(): message("request", f"yetenek sor {name}", s["transport"]) message("response", f"yetenek bildir {name} surum {s['version']} " + " ".join(s["tools"]) + " " + " ".join(s["resources"])) if client_version.get(name, 0) < s["version"]: dropped.append((name, "version", s["version"], client_version.get(name, 0))) continue for t, spec in s["tools"].items(): if t not in allow: dropped.append((name, "allowlist", t, None)) continue agreed[t] = dict(spec, server=name, transport=s["transport"]) return agreed, dropped def call(agreed, name, state): if name not in agreed: return {"hata": "arac bildirilmedi"} t = agreed[name] kw = {a: state[a] for a in t["fields"] if a in state} message("request", f"{name} " + " ".join(f"{k} {v}" for k, v in kw.items()), t["transport"]) s = t["function"](**kw) message("response", " ".join(f"{k} {v}" for k, v in s.items())) return s
The Identity Step, the Authorization Step, the Round Trip
The second block counts the cost of the same chain in three setups. The middle setup is the separator: the servers are on remote transport but the identity and authorization steps have not yet been added, so only the difference of the transport itself shows.
REMOTE = {name: dict(s) for name, s in SERVERS.items()} LOCAL = {name: dict(s, transport="local") for name, s in SERVERS.items()} CHAIN = ("sayac_oku", "tuketim", "tarife") def auth_steps(servers): """One identity step and one authorization step for every server on remote transport.""" identity = authorization = 0 for name, s in servers.items(): if s["transport"] != "remote": continue message("request", f"kimlik sun {name}", "remote") message("response", f"kimlik kabul {name}") identity += 1 message("request", f"yetki iste {name} arac kumesi", "remote") message("response", f"yetki verildi {name} izin listesi") authorization += 1 return identity, authorization def scenario(servers, with_auth, subscriber="A017"): """Temporarily replaces the server table with the setup's own, resets the counters.""" table = {name: dict(s) for name, s in servers.items()} saved = {name: dict(s) for name, s in SERVERS.items()} for k in COUNT: COUNT[k] = 0 SERVERS.clear() SERVERS.update(table) try: identity, authorization = auth_steps(SERVERS) if with_auth else (0, 0) agreed, _ = handshake() state = {"abone": subscriber} for name in CHAIN: state.update(call(agreed, name, state)) finally: SERVERS.clear() SERVERS.update(saved) return dict(COUNT, identity=identity, authorization=authorization, tools=len(agreed), result=state.get("tutar")) print("setup identity authorization request response tokens round trips negotiated result") for label, table, on in (("all local", LOCAL, False), ("remote, no auth steps", REMOTE, False), ("remote, auth steps", REMOTE, True)): s = scenario(table, on) print(f"{label:<21} {s['identity']:>8} {s['authorization']:>13} {s['request']:>7} {s['response']:>8}" f" {s['tokens']:>6} {s['round_trips']:>11} {s['tools']:>10} {s['result']:>6}") keys = ("request", "response", "tokens", "round_trips", "tools") three = [scenario(REMOTE, True, a) for a in ("A017", "A042", "A003")] print("\nthree separate subscribers, same setup. count identical:", len({tuple(s[k] for k in keys) for s in three}) == 1, " results:", [s["result"] for s in three])
setup identity authorization request response tokens round trips negotiated result all local 0 0 6 6 179 6 3 844 remote, no auth steps 0 0 6 6 179 9 3 844 remote, auth steps 2 2 10 10 289 17 3 844 three separate subscribers, same setup. count identical: True results: [844, 68, 211]
Reading the Numbers
The last two columns of the three rows are the same: the negotiated tool set is three tools, the result of the computation is 844. Moving to remote did not give the client a new capability, and did not take one it had. What changes is only the cost, and the cost comes from two separate places.
The first is the transport itself. In the middle row the requests are 6, the responses 6 and the tokens 179, identical to the first row; only the round trips rise from 6 to 9. Three of the six requests go to remote servers (the capability query to the tarife and kayit servers, the call to the tarife tool) and a remote request counts as two round trips instead of one. The difference is +3 and it appears without a single message being added. This is the example that falls to this lesson of the second claim of the course: the same contract, the same message count, a more expensive connection.
The second is the identity and authorization steps. In the third row, for each of the two remote servers one identity step and one authorization step run: 2 identity, 2 authorization, that is, 4 requests and 4 responses. The message count rises from 6 to 10, the tokens from 179 to 289, the round trips from 9 to 17. All 8 added messages are remote, so the contribution to the round trips is 8. 64 of the 110 added tokens are only the envelope constant (8 messages, 8 tokens per message); the remaining 46 tokens are the bodies of the steps themselves. The cost of counting the connection as secure is more the envelope itself than the information carried.
The total difference against the local baseline is this: +4 requests, +4 responses, +110 tokens, +11 round trips and zero tools gained. The baseline without the protocol sits even lower in this table: when the tool is defined inside the lesson, the chain produces the same 844 with 0 requests, 0 responses, 0 tokens and 0 round trips. What the remote connection buys is not visible in this lesson, because there is only one client.
The result of the authorization step in this setup is the same as the allowlist, and this is not a coincidence but a decision made to isolate the measure. The remote side could give the client a set narrower than its own allowlist; had it done so, the difference would have shown in the negotiated tools column, not in the cost columns. That the column stays at 3 in all three rows says that what this lesson measures is the step itself, not the content of the authorization. The case where the authorization is narrowing is left to the last lesson of the course and is opened there as a count.
The last line shows that the measure is independent of the run. The same setup is run for three separate subscribers: the count is identical in all four columns, and the results are 844, 68 and 211. What is counted is the shape of the contract, not the size of the data carried; that is why there is no seed-dependent band in the table.
Tying the Step to the Session
The table above is for a single chain. How many times the identity and authorization steps are paid is a separate decision: the step can be tied to the session and run once for the whole connection, or it can run again for every piece of work. The two options do the same work; where they differ is the count.
CHAIN_SUBSCRIBERS = ("A017", "A042", "A003", "A055") def in_session(count, every_chain): """Runs count chains in one session. If every_chain is True the identity and authorization steps are paid again for every chain, otherwise once per session.""" for k in COUNT: COUNT[k] = 0 steps = 0 if not every_chain: identity, authorization = auth_steps(SERVERS) steps += identity + authorization agreed, _ = handshake() for subscriber in CHAIN_SUBSCRIBERS[:count]: if every_chain: identity, authorization = auth_steps(SERVERS) steps += identity + authorization state = {"abone": subscriber} for name in CHAIN: state.update(call(agreed, name, state)) return dict(COUNT, steps=steps) print("chains step renewal steps request response tokens round trips") for count in (1, 2, 4): for every in (False, True): s = in_session(count, every) print(f"{count:>6} {'every chain' if every else 'per session':<14}" f" {s['steps']:>5} {s['request']:>7} {s['response']:>8} {s['tokens']:>6}" f" {s['round_trips']:>11}")
chains step renewal steps request response tokens round trips
1 per session 4 10 10 289 17
1 every chain 4 10 10 289 17
2 per session 4 13 13 369 21
2 every chain 8 17 17 479 29
4 per session 4 19 19 529 29
4 every chain 16 31 31 859 53
With a single chain the two rows are identical; the distinction cannot be measured there. As the number of chains grows the two columns separate: at four chains the steps are 4 against 16, the requests 19 against 31, the tokens 529 against 859 and the round trips 29 against 53. The difference is +12 requests, +12 responses, +330 tokens and +24 round trips; the work is the same, the results are the same. In the step tied to the session the fixed cost stays at four messages and the share per chain shrinks; in the step renewed for every chain there is no such thing as a fixed cost, because the item that is fixed is paid again each time.
For the distinction to be measurable the number of chains has to be greater than one. In a measurement with a single chain the difference between the two designs is zero, and this does not mean there is no difference: the set is not enough to show it. The scale of this course is limited to three servers and five declared tools; unless it is written in the table at what size each measured difference becomes visible, the number is incomplete.
Summary
- Remote transport enlarges the round trips without changing the message count: the same 6 requests count 6 round trips locally and 9 remotely.
- The identity and authorization steps are two requests and two responses per remote server; with two servers they add 8 messages in total, 110 tokens and 8 round trips.
- 64 of the added 110 tokens are the envelope constant; most of the cost is the envelope, not the information carried.
- In all three setups the negotiated tool set is three tools and the result is 844: the cost rose, the capability did not.
- Tying the step to the session gains 12 requests, 330 tokens and 24 round trips at four chains; at a single chain the difference between the two designs cannot be measured.
- The count is identical for three separate subscribers; the measure is independent of the run.
Next Step
The table of this lesson is for a single client, and for that client the protocol is pure cost: it pays 10 requests, 10 responses, 289 tokens and 17 round trips and receives the same result that a tool defined inside the lesson gives. What the definition standing on the other side returns has not been counted yet. When the same server is plugged into a second client, what happens: is this cost paid again, or was a part of it paid once? The next lesson connects the same server to four separate kinds of client and keeps two numbers apart: how many lines of definition are rewritten and how many messages are paid again; this is the measure of portability.
To keep your progress and take notes, Log in
My notes
Log in to take notes.