Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upService API makes request outcomes not observable to applications #2181
Labels
Comments
Additional patterns enabled through handling the whole request inside a handlerBesides observability being able to run the whole request inside the handler also enables some other patterns. One of those is limiting the number of concurrent requests on a handler. In order to do this, we can implement a handler as: void abc_handler(Request req, ResponseWriter res) {
if (!abc_semaphore.try_acquire(1)) {
res.sendHeaders(status: 500, body: "Limit exceeded");
return;
}
try {
return do_all_the_business_logic();
} finally {
abc_semaphore.release(1);
}
}Of course using guards/destructors and without finally blocks in Rust :-) In order to implement the same functionality using the current handler signature, we would need to store the permit inside |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I did some exploration work with Hyper in the last weeks, and tried to model some typical application workflows with it.
Compared to other (web) frameworks, I run into a limitation that I would like to explain:
Hypers request handler has the signature:
A handler will be invoked by the Hyper server when a new request was received, and contains the application code. However not all application code runs inside the scope of this function:
If the handler wants to send a response body back to the user, the handler barely returns "instructions on how to send the body", which are encoded in the
Bodytype. The actual transmission of theBodywill however be performed inside Hyper and outside of the request handler. While that doesn't seem to matter at first since Hyper will do a good job on driving the request to completion, it has some impact on the observability of the outcome of the request.I will provide some examples here:
Logging the outcome of a request
A basic example requirement that most services will have is logging the outcome of a request - mostly in form a metric. This usually happens by doing doing something along:
or a try/catch/finally block for languages which support it:
This does not work out in Hypers current Service functions, since the outcome of sending the response is only known to Hyper. For small requests it might be easy to assume that it will mostly succeed, but for large bodies (GB sizes) there is actually a high probability the connection breaks down before everything was transmitted.
There might also be clients which forcefully abort the request, or issues in the HTTP stack that lead to a preemption. In all cases we want to track the correct result.
Logging the timing of a request
Another common requirement for request handlers is to log the time they needed to execute, in order to check for any issues in their implementation (or downstream services).
One easy way to do this if the handler contains all request logic is:
This is not possible using the current Hyper service API, since the requests processing ends outside of the scope of the applications request handler. If we would integrate timing like this, it would mostly measure the time-to-first-byte, but not time-to-last-byte.
Logging the amount of transmitted bytes
Another common requirement is to be able to log how many bytes of a response body had been passed into the socket, in order to be able to diagnose better what lead a request to be fail. Failures after lots of bytes had been transferred with a high speed typically have different root-causes than ones which occur immediately.
Since Hyper pulls bytes, the application can observe the send status inside the handler.
Logging of errors while sending the response body fails
Sending a response can fail for a variety of reasons, especially when big bodies are used:
We typically want to keep track of the error reasons in order to troubleshoot issues. This is however not possible using the current Hyper APIs, since the the error is only visible to Hyper and not forwarded to the application.
Workarounds
Before jumping into improvements, I want to mention that there exists a workaround which at least addresses the concerns partially:
Custom response stream
I think currently there exists a workaround for this, which brings some visibility back to the application: It could implement
Bodyas a customStream, and e.g. emit metrics when theStreamis destructed. At this point the application knows that no more bytes will get sent.If this happens before the
Streamwas read to completion, the application is aware that the request was cancelled.For the request outcome, the application will get an approximation of the outcome. It knows whether Hyper pulled all bytes, but not whether sending the last chunk succeeded. This might be a bad approximation for small responses, since the complete response fits into a single chunk.
For latency metrics, it will also be an approximation, since the sending/flushing the last chunk of data would not be covered.
The exact transport error would however still be unknown to the application.
Improvement ideas
I think the best way to address these issues is by having an API which makes sure that the complete handling of the request occurs within the request handler.
This can e.g. be achieved by passing a type into the handler which allows to actively send the response, instead of returning the response. Go's HTTP Handler callback makes use of such a mechanisms:
Here the handler would directly call
w.Send(bodyChunk)until the request is finished. Since every send call returns an error (which can be derived from an error that a lower layer returned), the outcome of all writes will be observable to the handler.And since the handler would not return until all data is sent adding metrics around success and timing gets easy in a central place again.
In Rust terminology, the type which needs to be passed to the handler would be a type which
Sink<Bytes/Buf>to stream the body through owned buffers or simplyAsyncWriteto write slices