WEB / WEB-16.01
Web server, runtime, and execution model
The server accepting HTTP and the runtime executing application decisions serve different roles. Trace a request from proxy to callback.
1. Core · Mechanism
Read every scenario without motion
Proxy upstream
- Public read ready
Duru prepares GET /notes; neither edge nor application has received this request yet.
- Client / Duru
- GET /notes ready
- Edge server
- No request
- Application runtime
- No request
- Execution state
- No handler
- Edge arrival
The client GET reaches the edge while upstream state still has no request.
- Client / Duru
- Waiting response
- Edge server
- GET received
- Application runtime
- No request
- Execution state
- No handler
- Select upstream
The edge locally selects upstream routing for /notes and prepares forwarding.
- Client / Duru
- Waiting response
- Edge server
- Upstream selected
- Application runtime
- No request
- Execution state
- No handler
- Upstream arrival
The second HTTP GET arrives at the application runtime, not as an in-process callback arrow.
- Client / Duru
- Waiting response
- Edge server
- Waiting upstream
- Application runtime
- GET /notes received
- Execution state
- Handler pending
- Local handler
The runtime local handler produces Runtime note with 12 UTF-8 content bytes.
- Client / Duru
- Waiting response
- Edge server
- Waiting upstream
- Application runtime
- Response200 ready
- Execution state
- Handler result Runtime note
- Upstream response
Application response200/content reaches the edge while the client has not received its response.
- Client / Duru
- Waiting response
- Edge server
- 200 Runtime note received
- Application runtime
- Response delivered upstream hop
- Execution state
- Handler complete
- Client response
The edge response200/content reaches the client, preserving completion of two distinct HTTP hops.
- Client / Duru
- 200 Runtime note received
- Edge server
- Response delivered client hop
- Application runtime
- Upstream exchange complete
- Execution state
- Handler complete
Edge static
- Static read ready
The client selects a public /static/note read with no application request available.
- Client / Duru
- GET static ready
- Edge server
- No request
- Application runtime
- No request
- Execution state
- Unused
- Edge arrival
GET reaches the edge, where the selected route belongs to static serving.
- Client / Duru
- Waiting response
- Edge server
- GET static received
- Application runtime
- No request
- Execution state
- Unused
- Local static read
The edge produces harmless Static note from its selected content without upstream dispatch.
- Client / Duru
- Waiting response
- Edge server
- 200 Static note ready
- Application runtime
- No request
- Execution state
- Static source supplied
- Client response
Static response200/content reaches the client without application-runtime participation.
- Client / Duru
- 200 Static note received
- Edge server
- Client response delivered
- Application runtime
- No request
- Execution state
- Static source supplied
- Owner outcome
The client has received the 200 Static note body. No upstream dispatch is known from this example’s edge routing decision; status and body alone do not tell the client whether an application handler ran. This scene shows neither DOM nor rendered pixels.
- Client / Duru
- 200 Static note received
- Edge server
- Exchange complete
- Application runtime
- No request
- Execution state
- Static ownership recorded
Async CPU section
- Bounded CPU job
The selected Node callback context prepares a 10000-term sum and queued event callback.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- Job ready; event queued
- Execution state
- Sum pending
- Async entry
The async function enters CPU work without an initial await; the queued callback cannot intervene.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- cpu-start; main JS active
- Execution state
- Sum in progress
- CPU completes
The 1..10000 sum completes as 50005000 with no other-CPU or timer-duration claim.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- cpu-end; main JS active
- Execution state
- Sum50005000
- Caller resumes
Caller-after-call is recorded after the function returns a Promise; computation is already complete.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- caller-after-call
- Execution state
- Sum50005000
- Await continuation
In this selected context, awaiting the resolved result continues before the queued event callback.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- caller-after-await
- Execution state
- Sum50005000
- Queued callback
The event callback runs after synchronous CPU work; async does not imply automatic parallel execution.
- Client / Duru
- No HTTP modeled
- Edge server
- Unused
- Application runtime
- event-callback observed
- Execution state
- Sum50005000; duration unmeasured
ACTORS · STATE · DATA FLOW
Server roles are broader than product names
A web server is the role that accepts HTTP requests and returns appropriate responses, serving static content or forwarding work. An application server connects requests to application logic and produces its result. These roles do not always require two machines, two processes, or separate products. A Node http.createServer callback lets one process handle both HTTP serving and application execution. A framework organizes routing and middleware; it does not replace the runtime code-execution and I/O capabilities.
For Duru public /notes route, the edge forwards a request upstream and the application runtime produces Runtime note. For /static/note, the edge returns Static note itself without an application request. Both can exist under one external origin. Assuming every response came from backend business logic, or that a web server only serves files, loses this distinction.
Python HTTPServer listens at a socket and dispatches to a handler; ThreadingHTTPServer uses ThreadingMixIn for threaded handling. This does not mean every Python application has that execution model. Similarly, a web/application server label alone does not identify which process listens at a port or which runtime executes application code.
A reverse proxy creates another HTTP hop
A reverse proxy accepts the client request, sends its own request to a selected upstream, receives the upstream response, and responds to the client. Client-to-edge and edge-to-application are separate exchanges, not one socket or an in-process function call. The fixture preserves method/path and harmless content. Real proxies may also map URIs, cache, buffer, or apply connection policies, depending on configuration.
A TLS-terminating edge and plaintext upstream do not automatically share the same confidentiality boundary; these local HTTP hops do not measure a TLS deployment. Proxy routing does not automatically establish application user/object authorization. Accepting identity or forwarded metadata across trust boundaries needs a deployment contract; proxy-trust details belong to WEB-18.
The Static note response never goes upstream. In the normal proxy branch, Runtime note can reach the edge before the client receives it. The scene preserves these different arrivals. Seeing a request at the edge alone proves neither handler execution nor browser consumption of the response.
Async work is not automatic CPU parallelism
Default Node JavaScript callbacks run on one event-loop thread. Suitable network I/O can be offloaded to the kernel, and selected filesystem/crypto APIs use the libuv worker pool. This does not automatically move a long application JavaScript loop into that pool. An async function executes synchronously until its first await; returning a Promise does not place that part on another CPU.
The artifact sum of 1..10000 is 50005000. The selected async function finishes this bounded computation without an await, so a queued event callback cannot run in its synchronous middle. No duration or throughput is invented. worker_threads offers a separate execution choice for CPU-heavy JavaScript; pool/thread overhead and result messaging remain decisions. More processes or threads do not guarantee unlimited capacity or correct shared state.
A process has its own application-memory state; changing a local variable in a separate process does not automatically change the parent. Threads relate to one process resources, while each JavaScript Worker can have separate globals/state and explicit sharing/transfer mechanisms. Worker_threads, the libuv worker pool, and browser Web Workers are different APIs. Sharing state across deployment replicas needs explicit ownership/store contracts rather than an in-memory variable assumption.
2. Red · Trust & Impact
Read every scenario without motion
Async CPU
- The async computation was selected
The fixture sums 1..10000 in an async function with no first await. The async keyword alone does not create CPU parallelism.
- Call/route context
- Async function
- Runtime/edge owner
- CPU not started
- Callback/policy boundary
- Callback pending
- The synchronous section began
The trace starts with cpu-start. This section runs in current JavaScript execution; the callback has not intervened.
- Call/route context
- Call continuation pending
- Runtime/edge owner
- cpu-start
- Callback/policy boundary
- Callback pending
- The bounded CPU section completed
cpu-end gives sum=50005000. Returning a Promise has not moved the computation onto a separate thread.
- Call/route context
- Call continuation pending
- Runtime/edge owner
- cpu-end; sum=50005000
- Callback/policy boundary
- Callback pending
- Caller continuation was separated
caller-after-call precedes caller-after-await. Await does not retroactively make the earlier synchronous CPU section nonblocking.
- Call/route context
- caller-after-await
- Runtime/edge owner
- CPU completed
- Callback/policy boundary
- Callback pending
- The queued callback ran later
event-callback ends this selected trace. With durationMs=null, the ordering is not a production-latency measurement.
- Call/route context
- Caller continued
- Runtime/edge owner
- CPU completed
- Callback/policy boundary
- event-callback
- Capacity controls stayed tied to the execution model
Callback work should be bounded; CPU ownership can be assigned to a worker when needed. The fixture keeps parallelCPUClaim=false and runs no load attack.
- Call/route context
- Execution order known
- Runtime/edge owner
- Automatically parallel=false
- Callback/policy boundary
- Latency/capacity not measured
Static role
- The static-response role was selected
Duru selects /static/note. The edge owns the response in this public example; an application handler is not automatically called.
- Call/route context
- /static/note
- Runtime/edge owner
- Route not examined
- Callback/policy boundary
- No application outcome
- The static-route outcome was read
staticExchange has upstreamReached=false. Edge content serving does not prove that Node application identity or object policy ran.
- Call/route context
- /static/note
- Runtime/edge owner
- Static owner: edge
- Callback/policy boundary
- Upstream not reached
- The representation was separated by its public scope
The body is Static note with responseStatus=200. This normal public response does not establish safe use of the same serving contract for personalized data.
- Call/route context
- Public static representation
- Runtime/edge owner
- Status=200; 11 content octets
- Callback/policy boundary
- Application policy not shown
- Status was not promoted to authority
Edge 200 describes only the selected static representation. Application permission checks or session acceptance are not inferred from it.
- Call/route context
- Public static representation
- Runtime/edge owner
- Edge source known
- Callback/policy boundary
- No inferred authenticated grant
- The sensitive-route contract stayed separate
Personalized responses need explicit route ownership, identity and resource policy. A proxy or web-server product name is not proof of that contract.
- Call/route context
- Public static fixture only
- Runtime/edge owner
- Route boundary explicit
- Callback/policy boundary
- Sensitive policy requires separate evidence
- The serving conclusion closed with its source
This case explains no-upstream behavior for a selected static branch. It does not create a general rule treating a missing application log as proof of no upstream dispatch.
- Call/route context
- Known: static branch
- Runtime/edge owner
- Known: no upstream dispatch
- Callback/policy boundary
- No application authorization claim
ASSUMPTION · BOUNDARY · CONTROL
Shared execution capacity is a boundary
Synchronous JavaScript in one handler can delay the same event-loop callback queue. The broken assumption is that the async keyword makes all work nonblocking; sufficiently large normal computation can affect other requests. Impact is availability latency depending on runtime, concurrency, and workload. This 10000-term example is neither a load attack nor a production benchmark.
Choose bounded callback work, suitable I/O APIs, and CPU-work ownership separately. The worker pool also has limited capacity; a new thread for every job is not an automatic solution. Worker execution needs result correlation and lifecycle, without assuming memory is shared across replicas. State invisible to another process alone proves neither data loss nor compromise.
Identify the owner of served content
Treating static content as the result of an application permission check confuses hop and handler ownership. Public Static note is normal; with personalized data the same assumption can misinterpret confidentiality or stale results. Letting an edge route/cache choice stand in for application policy requires an explicit correct contract. A product name or edge status 200 is not proof of that contract.
Successfully forwarding a request still leaves upstream failure and incomplete client delivery as separate conditions. Keep correlation metadata per hop, distinguish static versus upstream content, and read response arrivals separately. This keeps the component producing a result distinct from the component receiving it rather than collapsing both into one success label.
3. Blue · Secure development & defense
Read every scenario without motion
Response hops
- The proxy-exchange evidence was selected
proxyExchange carries clientPath and upstreamPath for /notes. Client status and upstream observation are not the same observation point.
- Runtime observation
- Proxy exchange /notes
- Hop/process review
- Hop map not examined
- Evidence scope
- Client knowledge not read
- The upstream-dispatch field was read
upstreamReached=true is the routing outcome for this branch. A missing correlation log does not by itself establish that it was false.
- Runtime observation
- Proxy exchange /notes
- Hop/process review
- Upstream reached=true
- Evidence scope
- Client knowledge not read
- Body source and byte count were separated
The Runtime note representation is 12 UTF-8 content octets. This is not the size of HTTP wrappers, headers or network packets.
- Runtime observation
- Runtime note
- Hop/process review
- Content octets=12
- Evidence scope
- Body source: runtime
- Response-arrival ordering was compared
upstreamArrivalBeforeClientArrival=true describes the order in this two-hop example. Edge receipt is not yet the moment the client learns the body.
- Runtime observation
- Declared proxy exchange
- Hop/process review
- Upstream arrival before client
- Evidence scope
- Knowledge boundaries separate
- Browser-render evidence was not added
Declared response status/body does not show DOM or rendered pixels. Runtime, edge and browser observations are correlated using their own sources.
- Runtime observation
- Status/body declared
- Hop/process review
- Hop ordering declared
- Evidence scope
- UNKNOWN: DOM/pixels
- The proxy-evidence scope was closed
This ledger explains ordinary forwarding owners; it is not a production proxy access-log schema or throughput result.
- Runtime observation
- Known: runtime body source
- Hop/process review
- Known: selected hop order
- Evidence scope
- Runtime/browser generalization withheld
Process memory
- The process-state observation was selected
The memory fixture uses separate aliases proc-main and proc-child. Ordinary objects in two processes are not one mutable state.
- Runtime observation
- proc-main / proc-child
- Hop/process review
- State not read
- Evidence scope
- No shared-memory evidence
- Initial parent state was read
parentStateBefore=7 describes parent ownership. A future child update is not assumed to transfer automatically to the parent.
- Runtime observation
- Parent before=7
- Hop/process review
- Child separate process
- Evidence scope
- No shared-memory evidence
- The child outcome was read separately
childStateAfter becomes 42. This ordinary state change belongs to the child process; it is not shown as shared-store state or an explicit message outcome.
- Runtime observation
- Parent before=7
- Hop/process review
- Child after=42
- Evidence scope
- SharedMemoryUsed=false
- Parent after-state was compared
parentStateAfter remains 7. The child change did not mutate the parent; calling this alone data loss or compromise would be incorrect.
- Runtime observation
- Parent after=7
- Hop/process review
- Child after=42
- Evidence scope
- Ownership scope consistent
- Worker and process roles were not equated
Worker threads, separate processes and shared memory are different choices. This fixture is limited to a separate process with sharedMemoryUsed=false.
- Runtime observation
- Separate-process profile
- Hop/process review
- No implicit shared object
- Evidence scope
- Other IPC/worker behavior unknown
- The memory conclusion remained bounded
The selected before/after states support separate ownership. Replicas, durable storage, restart recovery or all cancellation behavior cannot be inferred from these two numbers.
- Runtime observation
- Known: parent retained 7
- Hop/process review
- Known: child reached 42
- Evidence scope
- No distributed durability claim
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Build the role and hop ledger
First observe the listening endpoint and where the selected request dispatches. For /notes, correlate harmless edge/application request observations; for /static/note, expect no increase in application request count. Client status/body and edge upstream observation are different telemetry points. Do not present native API event names as a real proxy access-log schema; field mapping depends on the server product.
Read method/path, response status, and bytes separately in the request callback. Runtime note is 12 UTF-8 bytes; preserve its producing hop and the encoded content measured. Read WEB-16.03 for why response finish differs from client arrival. Missing correlation does not automatically prove the upstream never ran; in the selected static branch, the routing decision supports no-upstream evidence.
Separate execution model from measurement
Record entry/exit, caller continuation, and queued callback order for the async CPU fixture. Independently check the sum with n(n+1)/2; looking fast is not the pass criterion. Reporting queue effects as production latency needs actual workload and timing measurements. Small bounded work is enough to explain an async function synchronous portion.
Keep parent state of 7 and separate-process state of 42 as different ownership records; aliases suffice without actual PIDs. Name the thread/worker API explicitly. Without shared memory, ordinary object mutation is not shared state. Moving long computation to a worker also needs normal result, cancellation, and failure boundaries; adding async alone does not create that lifecycle.
The artifact XPath counts work observations marked synchronousCPU=true together with automaticallyParallel=true. Normal async CPU evidence yields 0 and an incorrect-interpretation fixture yields 1. This XML is an instructional schema, not an event-loop telemetry provider. A missing work element also yields 0 and cannot prove correct runtime behavior.
Bound execution cost at the request boundary
Async does not remove CPU cost. Define the accepted request size and maximum work before entering the handler; avoid unbounded computation on the event loop. Use appropriate asynchronous APIs for network and disk I/O, while CPU work may move to a worker pool. Bound its pending jobs and define time limits and failure behavior; unlimited worker creation is not a safe capacity policy.
The correct sum for 1..10000 and callback order explain this small operation. For work outside an admitted bound, separately verify that no handler or worker dispatch occurred; worker failure must not produce a normal result. Observe accepted output separately from queue delay. Missing dispatch or completion evidence does not prove absence of work. This ordering example does not measure service capacity, enforce timeouts or establish worker isolation.
NOW APPLY IT TO THE CASE
Which authority is not supplied by async or an edge response?
A bounded CPU trace and public static route show different execution boundaries. Hop sources and process-memory ownership are read using their own evidence.
Reconstruct decision order for input, server processing and response in the case. Parsed or valid data is not a completed operation; identify each owner and its condition.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Server-role and execution ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"roles": {
"client": "Duru",
"edge": "HTTP reverse-proxy or static-serving role",
"runtime": "Node application execution role",
"framework": "not installed; manual handler organization"
},
"proxyExchange": {
"clientPath": "/notes",
"upstreamPath": "/notes",
"upstreamReached": true,
"responseStatus": 200,
"body": "Runtime note",
"contentOctets": 12,
"upstreamArrivalBeforeClientArrival": true
},
"staticExchange": {
"clientPath": "/static/note",
"upstreamReached": false,
"responseStatus": 200,
"body": "Static note",
"contentOctets": 11
},
"cpu": {
"n": 10000,
"sum": 50005000,
"asyncFunctionFirstAwaitAbsent": true,
"selectedTrace": [
"cpu-start",
"cpu-end",
"caller-after-call",
"caller-after-await",
"event-callback"
],
"parallelCPUClaim": false,
"durationMs": null
},
"memory": {
"parentProcessAlias": "proc-main",
"separateProcessAlias": "proc-child",
"parentStateBefore": 7,
"childStateAfter": 42,
"parentStateAfter": 7,
"sharedMemoryUsed": false
},
"selector": {
"language": "XPath",
"xml": "<observations><work async=\"true\" synchronousCPU=\"true\" automaticallyParallel=\"false\"/></observations>",
"xpath": "count(/observations/work[@synchronousCPU=\"true\" and @automaticallyParallel=\"true\"])",
"expected": 0,
"positiveFixtureXml": "<observations><work async=\"true\" synchronousCPU=\"true\" automaticallyParallel=\"true\"/></observations>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- roles
- HTTP, runtime, and framework roles need not be one product/process.
- proxyExchange / staticExchange
- Upstream arrival and edge-owned responses follow different paths.
- cpu / memory
- Bounded sum/order and ordinary process-memory ownership.
Inert public-note ledger; selected timer order is context-bound. It does not measure TLS, production capacity, or arbitrary process isolation.
Two reality checks
An async function automatically moves a long CPU loop to another thread.
JavaScript runs synchronously until the first await; CPU worker execution is a separate choice.
Web and application servers are always separate machines.
They are roles: one process can serve both, or a reverse proxy can create a separate hop.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
An async function performs a CPU sum before its first await. Is CPU work automatically parallel while the caller waits for the call to return?
- Yes; the async label moves the whole function body to a worker thread.
- No; the selected trace has cpu-start/cpu-end before caller-after-call.
Read the explained answer
No; the selected trace has cpu-start/cpu-end before caller-after-call. — The async keyword does not move synchronous CPU to another thread; parallelCPUClaim=false.
The child process set state 42; why did parent state 7 stay unchanged?
Selected lesson artifact fields; teaching example
- memory
- {"parentProcessAlias":"proc-main","separateProcessAlias":"proc-child","parentStateBefore":7,"childStateAfter":42,"parentStateAfter":7,"sharedMemoryUsed":false}
- Process-local states are separate; sharedMemoryUsed=false.
- A shared application name provides one heap across all processes.
Read the explained answer
Process-local states are separate; sharedMemoryUsed=false. — A child-state mutation does not change parent-object ownership; IPC/shared memory must be established separately.
What does the upstreamReached difference show between static serving and reverse-proxy exchange? Why are Web/application server roles not host counts?
Read the explained answer
The static branch shows the edge supplying a body without upstream access; the proxy branch shows an upstream response reaching the edge before the client. Web-server/application-runtime roles may run in the same or different processes/hosts. Manual handler organization does not prove a framework is installed, and the sum trace is not a performance benchmark.
□ I separated role from host/process placement.
□ I kept upstream and client arrivals separate.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- Node.js v24.13.0 — HTTP APIHTTP · http.createServer([options][, requestListener]) · Class: http.ServerResponse · http.ServerResponse Event: finish · response.writableEnded · response.writableFinished
- Node.js Learn — Anatomy of an HTTP TransactionCreate the Server · Method, URL and Headers · HTTP Status Code · Sending Response Body
- Node.js Learn — The Node.js Event LoopWhat is the Event Loop? · Event Loop Explained · Phases Overview
- Node.js Learn — Do not block the event loopWhat code runs on the Event Loop? · What code runs on the Worker Pool? · Why should I avoid blocking the Event Loop and the Worker Pool?
- Node.js v24.13.0 — Worker threadsWorker threads · new Worker(filename[, options]) · workerData
- nginx — Beginner’s GuideServing Static Content · Setting Up a Simple Proxy Server
- Python — http.serverhttp.server.HTTPServer · http.server.ThreadingHTTPServer · http.server.BaseHTTPRequestHandler
- Node.js v24.13.0 — Child processeschild_process.fork(modulePath[, args][, options]) · child_process.spawnSync(command[, args][, options])