Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Originate HTTP 103 Early Hints #5005

Closed
mholt opened this issue Sep 1, 2022 · 4 comments · Fixed by #5006
Closed

Originate HTTP 103 Early Hints #5005

mholt opened this issue Sep 1, 2022 · 4 comments · Fixed by #5006
Assignees
Labels
feature ⚙️ New feature or request
Milestone

Comments

@mholt
Copy link
Member

mholt commented Sep 1, 2022

HTTP 103 Early Hints (draft RFC 8297) is a mechanism for servers to immediately send Link headers to clients so the clients can immediately start requests for those resources.

It is similar to HTTP/2 server push which immediately pushes resources to the client without an extra round trip, but instead of pushing the whole resource right away, the server tells the client, "Hey I'm still getting you the final response, but while you're waiting here's some resources you can download right now."

Enabled clients will read the Link headers from the HTTP 103 response and download those resources. Despite the extra round trip, this has a couple notable advantages over HTTP/2:

  • The client can choose to ignore the download if the resource is already cached locally
  • The server can suggest resources that it does not originate

In practice, HTTP/2 server push is difficult to get right because the server has to predict what the client needs to download. Pushing unnecessary resources actually worsened performance, according to Chrome data, but there was no reliable, efficient way of knowing what the client actually needed ahead of time.

103 responses are small, so despite the extra round trip, letting the client choose whether to initiate other requests to potentially multiple origins is actually a net performance benefit. This gain is amplified the longer the server takes to generate the final response.

I would suggest that Server Push and Early Hints have different purposes:

  • Push when you know the client WILL have to REQUEST a resource FROM YOU
  • Hint when you know the client MAY have to USE a resource

(Notice the differences: only push when the client "WILL REQUEST FROM YOU", whereas you can hint when the client "MAY USE" a resource.)

Since HTTP/2 server push and HTTP 103 have different purposes, we should probably have both in Caddy.

Early Hints is still technically expeirmental, so we'll document this feature in Caddy as experimental as well. That means it is subject to change and/or removal.

According to @dunglas at https://dunglas.fr/2021/02/using-the-103-early-hints-status-code-in-go-applications/, it's very simple to perform Early Hints in Go (Go 1.19+ required):

w.Header().Add("Link", "</style.css>; rel=preload; as=style")
w.Header().Add("Link", "</script.js>; rel=preload; as=script")
w.WriteHeader(http.StatusEarlyHints)

then you proceed to write your full response.

The question is, how do we best expose this for the user to configure?

Essentially it's just one or more HTTP headers, so the header handler seems fitting. The only trick is that we have to immediately write the response with a 103 status. header can't flush a response, but the static_response handler can:

{
    "handler": "static_response",
    "status_code": 103,
    "headers": {
        "Link": [
            "</style.css>; rel=preload; as=style",
            "</script.js>; rel=preload; as=script"
        ]
    }
}

All we have to do is tweak the static_response handler to not terminate the handler chain after it writes a 103 response.

Technically, HTTP/1.1 clients can support 103 Early Hints, but it could also break them if they're not expecting a 1xx response without an Expect header. The RFC recommends only sending 103s for HTTP/2 (or HTTP/3) connections, as those protocols do not depend on headers to determine the end of the response.

Fastly has a page for testing your client's HTTP Early Hints support: https://early-hints.fastlylabs.com/

I will upgrade the protocol matcher to support specific HTTP versions, such as http/2 or http/2+ to match only HTTP/2 or HTTP/2 and newer, respectively.

Apache's implementation of Early Hints has the option of also pushing the hinted resources, but as I said earlier, I think the two have different purposes and times to use them, so I don't think I'll combine push and hint, unless we see convincing evidence that it is actually beneficial.

As for the Caddyfile, the respond directive should be able to work fine in conjunction with the header directive. (The respond directive does not currently support setting headers, because that is what the header directive does, and it amounts to the exact same number of lines of config -- actually more if used in respond since you have the closing brace on a separate line.)

@mholt mholt added the feature ⚙️ New feature or request label Sep 1, 2022
@mholt mholt added this to the v2.6.0-beta.1 milestone Sep 1, 2022
@mholt mholt self-assigned this Sep 1, 2022
@pc-erin
Copy link

pc-erin commented Sep 1, 2022

So, in caddyfile format, that would look like this?

header {
  Link </style.css>; rel=preload; as=style
  Link </script.js>; rel=preload; as=script
}
respond 103

Also, is nopush necessary, or is that just the default mode for go/caddy?

@mholt
Copy link
Member Author

mholt commented Sep 1, 2022

@pc-erin Yep, basically (don't forget quotes around the header value)! See #5006 for a practical example that excludes likely-incompatible clients.

Edit: nopush is not necessary, unless you use the push directive too.

@dunglas
Copy link
Collaborator

dunglas commented Sep 1, 2022

As Chrome is going to remove Server Push support, I don't think we need to bother with it anymore, and we could probably deprecate this feature.

The nopush directive may still be necessary if you use a reverse proxy in front of Caddy that honors preload hints (e.g. Cloudflare).

I like your examples @mholt. WDYT about a less verbose alternative:

headers {
    Link 103 </style.css>; rel=preload; as=style
    Link 103 </script.js>; rel=preload; as=script
    MyFoo bar
} 

Because a 1XX status code is set, both, an informational response is set immediately and includes the Link headers, then a 200 response is sent which includes the Link and the MyFoo headers.

@mholt
Copy link
Member Author

mholt commented Sep 1, 2022

Thanks for reading and the feedback @dunglas 😊

As Chrome is going to remove Server Push support, I don't think we need to bother with it anymore, and we could probably deprecate this feature.

I thought about it, but reading up on the proponents of HTTP/2 Server Push made me realize that there's apparently a number of non-browser clients out there that use it. So I think we might as well keep it, but I've updated the docs (in my PR) to discourage its use if the goal is to speed up web pages loading in browsers.

The nopush directive may still be necessary if you use a reverse proxy in front of Caddy that honors preload hints (e.g. Cloudflare).

Good point. Thanks for mentioning that!

WDYT about a less verbose alternative:

I thought about the headers directive doing it all, but I feel like it's really not that handler's job to write responses to the client, only to set/manipulate headers. Plus I have a feeling parsing that and organizing that into JSON might be a little complicated in terms of extra state, etc.

I thought about a separate hint directive that does the headers and the respond all in one, but it doesn't look much less verbose because you still have to basically define all the parameters of the Link headers anyway:

hint {
    </style.css>; rel=preload; as=style
    </script.js>; rel=preload; as=script
}

So, I dunno. I think I like the clearer config that shows you exactly what it's doing, especially when it's just about equally verbose.

@mholt mholt modified the milestones: v2.6.0-beta.1, v2.6.0 Sep 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature ⚙️ New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants