Re:The problem with Go’s default HTTP handlers
by morelisp on Tuesday August 09, 2022 @03:13PM
(#32402626)
There 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.
Re:The problem with Go’s default HTTP handlers
by dap on Tuesday August 09, 2022 @04:54PM
(#32404152)
What 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.
Re:The problem with Go’s default HTTP handlers
by morelisp on Tuesday August 09, 2022 @05:11PM
(#32404377)
If 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.
Re:The problem with Go’s default HTTP handlers
by TheDong on Tuesday August 09, 2022 @06:31PM
(#32405395)
> 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
Re:The problem with Go’s default HTTP handlers
by morelisp on Tuesday August 09, 2022 @07:03PM
(#32405752)
Now 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.
Re:The problem with Go’s default HTTP handlers
by suremarc on Tuesday August 09, 2022 @08:28PM
(#32406478)
Just return an io.Reader that wraps the DB cursor and outputs CSV data... I fail to see what makes this so difficult. If the "mutable" approach involves passing down a mutable *http.ResponseWriter, then the dual approach of passing an io.Reader upstream is equivalent.
Re:The problem with Go’s default HTTP handlers
by int_19h on Wednesday August 10, 2022 @02:03AM
(#32408257)
This all is much more straightforward in languages with syntactic sugar for iterators, such as "yield" in C# and Python:
https://flask.palletsprojects.com/en/2.2.x/patterns/streamin...
Re:The problem with Go’s default HTTP handlers
by morelisp on Wednesday August 10, 2022 @05:16AM
(#32409482)
Love too make those async thunks!
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. 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/) (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.