Skip to content
academia.sh

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.

Start typing to search.

↑↓ ↵ Esc navigate · open · close