4 ms·
> In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly? I think that approach used by cloj
by gorjusborg 4y ago
> In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly?
I think that approach used by clojure's ring shows an elegant way to represent http responses https://github.com/ring-clojure/ring/wiki/Concepts#responses https://github.com/ring-clojure/ring/wiki/Concepts#responses. They are essentially structs with the following fields:
status := number
headers := map of string->string
body := stream | string | seq | inputstream
Request handlers are handed a request struct that is similar. The handler is a function that maps a request to a response (it doesn't actually write to streams itself).
I like this style for an http library for a couple of reasons:
1. HTTP resources can be viewed as functions whose domain is the request, and range is the response. Having the abstraction match that makes for really nice code.
2. If you model the request/response structs symmetrically and expose your http client as a handler itself, you can write proxies very easily. For an example of this, see http4k (https://www.http4k.org/documentation/ https://www.http4k.org/documentation/) (a kotlin library).
I won't argue that the net/http abstraction should change, but I do agree with the author's take on the desired shape of an http library. I'd probably just write my own abstraction on top to fill the gap.
- morelisp 4y agoThere are major performance advantages, especially once you're past HTTP/1.1, to passing a mutable response (i.e. in Go a ResponseWriter) to the handler rather than expecting the handler to return something created whole-cloth.
- dap 4y agoWhat advantages are you thinking of? The obvious one is that you might want to stream the response rather than construct it in-memory at once. But as I read the parent (and as I've used similar interfaces in Rust), you don't have to have constructed the whole response with this interface. You can return something whose body is a stream.
- morelisp 4y agoIf you return something whose body is a stream you still have to construct that thing, including the stream. And if you return something whose body is a stream you didn't fill in yet, you need to create entire async thunks or threads to fill that data in. You have also gained ~nothing.
- TheDong 4y ago> There are major performance advantages You didn't outline the performance advantage. You've got "you need to create entire async thunks or threads to fill that data in", but that absolutely isn't true. Let's look at how we might stream a file in response in go with the current model vs this new model, and you can point out where the performance difference is: // current func responseHandler(rw http.ResponseWriter, req *http.Request) { fi, err := os.OpenFile("/tmp/file") if err != nil { rw.WriteHeader(500) rw.Write([]byte("error opening file")) } io.Copy(rw, fi) fi.Close() } // returning a streaming Response object func responseHandler(req *http.Request) *http.HandlerResponse { fi, err := os.OpenFile("/tmp/file") if err != nil { return &http.HandlerResponse{Status: 500, Body: ioutil.NopCloser(bytes.NewReader([]byte("error opening file"))} } return &http.HandlerResponse{Status: 200, Body: fi} } In the happy path, there's no difference in number of goroutines or anything, right? In both cases, this all runs in the goroutine or thread or whatever that the caller _already_ created for the request handler. What difference does it make if the request handler calls `io.Copy(response, body)` or if your function does, in both case it's the same goroutine/thread. > You have also gained ~nothing. You were the one that claimed you lose massive performance, and that's what we're asking about. The ergonomics are a separate thing from performance
- morelisp 4y agoNow do a body that requires literally any transformation or generation at all. Say, a series of DB query results as a CSV. (Oh, and make sure to cancel work when the request disconnects!) Or heck, just try adding some error handling from the `io.Copy` result to what you have.