The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Architecture and operations
How is a server calling another server different from calling a method?
A large service sometimes splits its features among several servers. If the order server that takes orders and the payment server that handles payments are different programs that run separately, the order server has to send a request (POST /pay) to the payment server over the network to make a payment. When you call a method in the same program, the one you call is always in the same program and the call finishes right away. But when you call a server across a network, the other side may be off and refuse the connection, or the other side may be slow and you may have to wait. So the code of the order server has to decide how long to wait for an answer (a timeout) and what to answer the client with when the call fails. In this lab, you change the state of the payment server and whether the waiting time is set, and see the response the client receives, the time it took, and what happened to the order server. The code box is not a program but a list of who sends what to whom, and the real run started an order server and a payment server with these settings and exchanged requests between them.
Checked against real execution · Started an order server and a payment server on Node.js 24.18.0, ran six combinations, and checked them
# Client → order server (8080): POST /orders # Order server → payment server (9000): POST /pay (waits up to 1s) # Payment server: replies "Payment complete" after 0.1s # Order server: answers 502 for a connection failure and 504 for a timeout
The code box is not a program that was run. It is text that lists what the client, the order server, and the payment server exchanged. The real run started the order server and the payment server separately on 127.0.0.1 with the http module of Node.js 24.18.0 (the ports were free ports chosen by the operating system, and 8080 and 9000 on the screen are example numbers), and made the client send POST /orders to the order server. The order server sends POST /pay to the payment server (it does not reuse connections), and we wrote it to answer 200 "Order complete" when the payment server answers, 502 on a connection failure (ECONNREFUSED), and 504 on a timeout. The payment server answers 200 "Payment complete" after 0.1s or after 3s, and a payment server that is off was imitated by starting a server and closing it to make a port that nobody listens to. Waiting for up to 1s was implemented by giving AbortSignal.timeout(1000) as the signal of http.request, and not setting it was implemented by not giving a signal. We ran all six combinations and checked against the model the response the client received and the time it took, the kind of error the order server met, whether the payment server received the request and when it finished processing the payment, and whether the connection was already cut off at that time; for each combination we also checked that the code on the screen is the same text as the text made from the request line the servers actually received, the waiting limit set on the order server, the delay setting of the payment server, and the status code for each error. The time it took is written on the screen as a rough value, and the values measured on the PC used to write this lab were about 0.1s (0.117s), immediately (0.003s or less), about 1s (1.01s), and about 3s (3.02s). When the payment server was off, the same result came out whether or not the waiting time was set. When a payment server that replies after 3s was given a 1s limit, this payment server finished processing the payment after 3s even after the order server stopped waiting. This payment server was written for this lab to finish processing without checking whether the connection was cut off, which does not mean that every server behaves like that. When the waiting time was not set and the payment server received the request but never answered, the order server did not give up even after 4s and the client did not receive a response either, but we did not look at what happens if you wait any longer. These are behaviors observed in this version of Node.js. What we checked against the documentation is section 3.3 of RFC 9110 (the definitions of client and server, and that the same program can be both), section 15.6.3 (502 is for when a server acting as a gateway or proxy receives an invalid response from an inbound server), and section 15.6.5 (504 is for when a server acting as a gateway or proxy does not receive a timely response from an inbound server), and http.request in the Node.js documentation (the signal option aborts a request in progress, while the timeout option does not abort the request and only adds a timeout event). Strictly speaking, the order server is not a proxy but a server doing its own work, so using 502 for a connection failure and 504 for a timeout is a choice of this code that follows the use defined by the specification in a broad way, and some servers may answer with another status code (for example, 503). That a method call in the same program has no step called connecting is an explanation that we did not measure by running. The screen covered only the simplest case of calling once, and it did not cover sending a failed call again, skipping a server that keeps failing instead of calling it, or undoing work that spans several servers. The difference between a connect timeout and a read timeout is covered in the in-depth lab "Calling external APIs and timeouts", a device that protects against a failing server in "Circuit breaker", how to undo the work of several servers in "Saga and outbox", and how to receive requests in one place and split them in "Gateway and gradual migration".
Previous lab: Processes · Next lab: Load balancers
Struck-through text was removed from the first version of the code; highlighted text was added.