gin
gin copied to clipboard
103 early hints
related https://github.com/golang/go/issues/51914
I've got curious and checked how Go 1.19 now allows HTTP 104 Early Hint.
Research: How to HTTP 103 in Go
In this article I found a snippet:
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Link", "</style.css>; rel=preload; as=style")
w.Header().Add("Link", "</script.js>; rel=preload; as=script")
w.WriteHeader(103)
// do your heavy tasks such as DB or remote APIs calls here
w.WriteHeader(200)
io.WriteString(w, "<!doctype html>\n[... snip snip ...]")
}
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil))
}
This boils down to three steps:
- Attach the
Link
headers to the response. - Write the status line
HTTP 103 Early Hint
- Enforce the writing the early hint with
w.WriteHeaderNow()
Reproduction with Gin
I then was able to reproduce this with Gin:
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
r.GET("/testing", func(c *gin.Context) {
c.Writer.Header().Add("Link", "</assets/file1.js>; rel=preload; as=script")
c.Writer.Header().Add("Link", "<https://gphrase.de/r/>; rel=preconnect")
c.Writer.WriteHeader(http.StatusEarlyHints)
c.Writer.WriteHeaderNow()
// server thinking time
time.Sleep(5 * time.Second)
c.String(200, "success")
})
err := r.RunTLS(":443", "cert.pem", "key.pem")
log.Err(err).Msg("server ended")
}
$ curl -v https://localhost/testing
# ... snip snip ...
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 103
< link: </assets/file1.js>; rel=preload; as=script
< link: <https://gphrase.de/r/>; rel=preconnect
< HTTP/2 200
< content-type: text/plain; charset=utf-8
< link: </assets/file1.js>; rel=preload; as=script
< link: <https://gphrase.de/r/>; rel=preconnect
< content-length: 7
< date: Sat, 08 Oct 2022 17:32:16 GMT
<
* Connection #0 to host localhost left intact
success* Closing connection 0
Proposal
We could encapsule the above attaching of Link
headers, the writing of HTTP status 103, and forcing the write to the client.
func (c *Context) EarlyHint(links ...string) {
if len(links) == 0 {
return
}
for _, l := range links {
c.Writer.Header().Add("Link", l)
}
c.Writer.WriteHeader(http.StatusEarlyHints)
c.Writer.WriteHeaderNow()
}
With that the above example would become:
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
r.GET("/testing", func(c *gin.Context) {
c.EarlyHint("</assets/file1.js>; rel=preload; as=script", "<https://someserver.com/r/>; rel=preconnect")
// server thinking time
time.Sleep(5 * time.Second)
c.String(200, "success")
})
err := r.RunTLS(":443", "cert.pem", "key.pem")
log.Err(err).Msg("server ended")
}