@Override
    protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) throws Exception {
        if (addForwardedToRequestHeaders) {
            req = addForwarded(ctx, req);
            ctx.updateRequest(req);
        }

        // We can just send the request from a browser to a backend and the response from the backend
        // to the browser. You don't have to implement your own backpressure control because Armeria handles
        // it by nature. Let's take a look at how a response is transferring from the backend to the browser:
        //
        //                         +-----------------------------------------+
        //               streaming |                            Proxy server |   streaming
        //   Browser <-------------|(socket1)     HttpResponse      (socket2)|<------------ (socket3) Backend
        //                         |             internal queue              |
        //                         |   <-  (((((((...data...((((((((() <-    |
        //                         |                                         |
        //                         +-----------------------------------------+
        //
        // 1. A streaming data is read from the socket2 which is connected to the backend.
        // 2. The data is stored into the internal queue in the HttpResponse.
        // 3. The first streaming data in the queue is written to the socket1 which is connected to the browser.
        // 4. If it succeeds, the proxy server fetches one more data from the internal queue and writes it to
        //    the socket1 again. (The proxy server does not pull the data from the internal queue
        //    until the previous write succeeds.)
        // 5. If the browser cannot receive the data fast enough, the send buffer of socket1 is going to be
        //    full due to the flow control of TCP (or HTTP/2 if you are using).
        // 6. The proxy server does not write data to socket1 but just stores data in the queue.
        // 7. If the amount of the data in the queue exceeds a certain threshold, the proxy server pauses to
        //    read data automatically from socket2. (This prevents the queue growing infinitely.)
        // 8. If the amount of the data in the queue reduces by sending data to the browser again, the proxy
        //    server resumes reading data automatically from socket2.
        // 9. If the receive buffer of socket2 is full, then send buffer of socket3 is going to be full as well.
        //    So the backend can pause to produce streaming data.
        //
        // This applies to the request in the same way.
        final HttpResponse res = loadBalancingClient.execute(req);
        if (addViaToResponseHeaders) {
            return addViaHeader(res);
        }
        return res;
    }
We used hardcoded backend addresses. But you can use other service discovery mechanisms to configure
backends dynamically using {@link DnsServiceEndpointGroup}, ZooKeeper or Central Dogma.
See <a href="https://armeria.dev/docs/client-service-discovery">service discovery</a>,
<a href="https://armeria.dev/docs/advanced-zookeeper">Service discovery
with ZooKeeper</a> and <a href="https://line.github.io/centraldogma/">centraldogma</a>.
examples/proxy-server/src/main/java/example/armeria/proxy/ProxyService.java