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
Specify rack.provisional_response to send 1xx responses #1701
Conversation
…upting the request cycle
If you don't think the existing check headers makes sense then maybe split it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are better off just supporting rack.early_hints
, until such point as a different provisional response with headers makes sense. I think this will only be used by early hints and nothing else, and trying to add flexibility just for a possible theoretical future use that may never come to pass smells bad. YAGNI. We have at least 3 servers and 3 web frameworks supporting rack.early_hints, and having Rack standardize a different approach will just create churn for all of them. We can always add this more flexible approach later if there is another provisional response that is standardized that would be useful.
In terms of implementation if we do go with this approach, I think we should specify that the headers (2nd argument) provided to rack.provisional_response
should have the same format as the headers (2nd element) of a rack response. This reduces duplication and will make it so if we require the rack response headers to be a hash, then that will apply to this case as well. I can't think of a reason we would want the provisional response headers to have a different format than normal response headers.
I understand the sentiment, but this really isn't about YAGNI. It was much more about clean abstractions.
Yes I agree. It's just unfortunate I guess, but this is the pragmatic way to go.
That's kinda what I did, but I could make it more explicit indeed. The problem is that there are things in |
Absent existing support for rack.early_hints in web browsers and frameworks, this approach seems fine. However, considering existing support for rack.early_hints, it seems different/incompatible for no practical benefit. This is definitely not the pragmatic way to go. The pragmatic way to go is rack.early_hints since that is what is already supported. This is trying to guess possible future needs and trying to accommodate them in advance should they materialize. As @tenderlove stated: |
FWIW I don't have a particularly strong feeling. Since the web servers already support Maybe we should pull I started off saying I don't have strong feelings, but the more I write the more I'm convinced we should lift |
I need to make it clear again, falcon does not support 103 early hints. It turns early hints into push promises, and the interface defined here is cumbersome for that task. |
@ioquatix Just for clarity, would you also prefer |
"can't handle" is not quite right. It's more like "is it standardised yet?". What is the value of early hints/provisional response? I don't know any browser supports it, I don't know any HTTP client supports it. Do any load balancers support it? It seems like Fastly supports it? If you have a Rack app running behind Nginx or ELB or some other load balancer... does it work? Is the 1xx response transited correctly? When I implemented it, I was interested in a way for Rack apps to send push promises via HTTP/2. Because that has been standardised and is supported by browsers. Even if it is probably a useless feature all things considered. The I have no problem with the proposal here in general - I think we should be considering ways to improve the performance of rack applications. However, early hints/provisional response:
I'm not against this at all, I think the PR is great, but I just think we don't quite know what form this is going to take yet. Assuming that provisional response is something we want to implement and the above issues are resolved satisfactorily, I think what we should be thinking about is:
The answer might be:
Finally, neither of those answers actually solves the problem of "Make sure the user loads these resources as quickly as possible if required." They hoist all the complexity of dealing with "What resources does the user have already and what resources do I want the user to have" into the application. Is that the right approach? I don't know if it should be the responsibility of a rack application to deal with that. |
Yes it's what I was saying.
No. Why I believe this is better than But I also understand that it's silly to look at the existing support and just throw it away. Even myself I'd feel stupid going back to Eric Wong with a new patch. |
IMHO:
They are different.
It's a possibility, you do it in Falcon, H2O does it, but it's kind of a middleware / reverse proxy job. I don't think it should be forced that way on users, and I think the Rails implementation is wrong to assume they will be converted to push promises. For your Falcon use case, what I think would be an elegant way to handle this would be to expose |
I don't want to implement |
It looks like Chrome will remove support for push promises: https://groups.google.com/a/chromium.org/forum/?authuser=0#!msg/blink-dev/K3rYLvmQUBY/vOWBKZGoAQAJ Do any mainstream browsers support 1xx responses? |
@ioquatix your link describes that Chromium will support 103 Early Hints if this: https://chromium.googlesource.com/chromium/src/+/master/docs/early-hints.md experiment goes well. |
As we discussed before in this thread or the other, the problem with Chrome's experiment it that it causes a chicken and egg. I tried to push Early Hints at Shopify, but quickly got asked: what are the gains? The answer being none, added to the risk of breaking the service for older browser made it so that while we didn't totally trash it, I'm having a hard time convincing people to help me put it in production. Same thing here in this discussion, we circle back between: let's not implement it in X because no browser supports it. And chrome saying they might implement it if some service start using it. So I really don't know how to get out of this situation. |
I implemented a lot of it in falcon, so I'm not in the camp of "let's not implement it in X". However, my experience is that it's simply not a useful feature. At best, you save 1RTT with HTTP/2. With HTTP/1, it's less obvious. At worst, you waste all the effort of sending the additional responses. It fundamentally circumvents the request/response interface of HTTP, and so I think many clients (and servers) have a hard time knowing (1) how to proxy requests, (2) what kind of interface to expose and (3) how to do it efficiently. I think this is just a case where the value is not exceeded by the complexity. To that end, I'm even considering removing HTTP/2 push from falcon & async-http. It adds so much complexity and yet I don't think I've seen a single use case. |
It wasn't meant as an attack on anyone. Just a general annoyance about this deadlocked situation. I just wish all parties would simply implement 1xx provisional responses support simply because it's how the HTTP spec works and not consider wether there's a use case or not. I just want to be able to be able to craft any valid HTTP response, including |
@casperisfine I understand your position, and I didn't see it as an attack, and I feel your frustration too. 1xx informational responses are not completely trivial. In a normal interface, you'd have something like:
However, with 1xx responses, this is no longer sufficient. The only idea I came up with was something like this:
There are obviously ways you can control this (flags, etc). However, it does impact the client significantly. Bearing in mind this needs to work transparently in proxies, etc. In async-http with HTTP/2 push promises it looks like this:
The response containing multiple responses is fundamentally a different semantic model. Do you have any ideas about how we can make that part work? i.e. from the client side? |
IMHO, the level 0 of support client side would be to simply disregard the 1xx response, and continue reading (possibly with a reset timeout). That's what
e.g. you know the response will take a lot of time to be ready to send, so you send a Then for extended support, meaning allowing client code to act upon the client.on_informational_response(103) do |response|
preload(response.headers['Link'])
end
response = client.request(path, ...) If no handled is assigned for a code, then it can simply be ignored:
|
Push promises have been basically abandoned, so I'm planning to remove this functionality. However, I'm open to exploring what an interface for provisional responses looks like, I'm just not sure how to adopt it or what advantage it holds over a streaming response with |
A streaming response mean you have to commit to a response code (e.g.
Chrome 95 yes: https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/docs/early-hints.md |
HTTP/2 solves the inflight request failure case with stream error semantics. |
You mean https://httpwg.org/specs/rfc7540.html#StreamErrorHandler ? I don't see how that solves the problem. Besides, assuming HTTP/2 from Rack seems wrong to me. Even with you want to serve your app with HTTP/2 it's very popular to terminate it with SSL at the load balancer or reverse proxy layer, and then go HTTP/1.1. |
@casperisfine Considering this and that we plan to merge support for |
As discussed in #1692
Context
Three years ago the
rack.early_hints
callback was added to puma. Since then Falcon and Unicorn followed suit, and frameworks such as Rails and Hanami started using it, so I wanted to make it part of the spec in #1692.However after discussing it, I believe that interface is a bit too narrow, and we might as well allow to return any 1xx status code, with arbitrary headers.
This spec.
The naming is based on the HTTP/1.1 RFC, the 1xx section is named "Informational", but then says:
I think
rack.provisional_response
is the better name in Rack context, but I'd be happy to rename torack.informational_response
or something else.The callback signature is
call(status, headers)
, it should only be called with1xx
statuses.The callback doesn't accept a body as the spec is pretty clear that it's not valid:
Early Hints
Server which wish to sill expose
rack.early_hints
can easily redefine it as->(headers) { env['rack.provisional_response'].call(103, headers) }
.Interested parties and Refs:
Implementation details
I didn't delegate to
check_headers
because there are two assertions that I don't think make sense in this context:cc @jeremyevans