Web servers, why design the response as a return value instead of a side effect?

Recently I built a service at the company I work for using Axum, it is an api gateway that, among other functionalities, logs the elapsed time between the start and end of the request, so I needed to get the time after the request was finished (the data was sent to the client). I was a bit tricky but I managed to do it and I am already preparing to upgrade to Hyper 1.0.

Then I decided to take a look at Go (I still prefer Rust btw :slight_smile:) and its package net/http works differently than most web frameworks, in that you write to the response instead of returning a response.

Working with server side web applications in Rust can be challenging when you need to return distinct response types, for example, return a streaming response or a full body response based on the request parameters, and the "write to the response" approach looks a bit better for the Rust ecosystem, since you wouldn't need to box the response trait.

So, is there really a reason to prefer the approach chosen in the Rust ecosystem?

There isn't any fundamental reason why. Both models are equivalent: a Golang-style handler could call a Rust-style callback and then write its response. A Rust-style handler can return a Stream implementation, and take a Golang-style callback that can keep writing to that stream whenever it wants to.

In case of Rust, returning a response seems to fit better its functional style. An impl Response solution also gives flexibility of implementing it on various types, so handlers can return anything from a simple hardcoded string to a stream collecting data from another thread.

2 Likes

I haven’t used Go, but the Java servlet response writer approach makes it nearly impossible for a ServletFilter to correctly modify the output or handle errors, let alone both, and supporting async servlets makes that even harder. Most recent Java web frameworks, from Spring Boot to Ktor, have the handlers return response objects instead.

3 Likes

AspNetCore offers this as well, but it's a mess. In particular, it means that it's possible for code to start writing a response before other code has finished writing headers, which makes all the ordering weird.

Thanksfully AspNetCore also lets you return responses that get written using response writers at the correct point in the pipeline, and that works way better. Much nicer in the documentation, too, when you can see in the type signature what type a route is going to return. And it means that content type negotiation doesn't need to happen in the route handler, but in the body serialization, which is much nicer.

I much prefer the "it returns something" pattern, even outside Rust.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.