-
Notifications
You must be signed in to change notification settings - Fork 868
forward informational responses #1727
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
Conversation
|
Thank you for working on the enhancement. As discussed offline, we might want to simplify the approach by using The downside will be that it would be impossible to pass informational response down the filters asynchronously (since we cannot allow a filter to touch them while the generator tries to fill the field with the set of header fields). But I think we can require all filters to either pass or drop informational responses synchronously, considering the fact that we want to send 103 to the client as early as possible. |
…hing sending former 103
kazuho
left a comment
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.
This is beautiful. Beautiful than I had expected. Thank you for the PR.
I have left my comments, please let me know what you think.
lib/core/request.c
Outdated
| assert(req->_generator == NULL); | ||
| assert(req->_ostr_top->next == NULL && req->_ostr_top->send_early_hints != NULL); | ||
|
|
||
| req->res.status = 103; |
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 would appreciate it if you could clarify whether the purpose of h2o_send_early_hints, on_send_early_hints, send_early_hints are to send an informational header that is neither 100 or 101, or if it specifically targets handling 103.
In case it is the former, the names of the function should not indicate that it is related to "early_hints", and the caller of this function should set the req->res.status to 103.
In the case of the latter, we should not set the status code to 103 here or assert that it is is 103 in the protocol implementations.
include/h2o.h
Outdated
| void (*on_context_dispose)(struct st_h2o_filter_t *self, h2o_context_t *ctx); | ||
| void (*dispose)(struct st_h2o_filter_t *self); | ||
| void (*on_setup_ostream)(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot); | ||
| void (*on_send_early_hints)(struct st_h2o_filter_t *self, h2o_req_t *req); |
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.
We might want to rename it to on_early_hints. How it is being to be handled (e.g. "send") is what each protocol handler decides.
include/h2o.h
Outdated
| */ | ||
| h2o_iovec_t h2o_push_path_in_link_header(h2o_req_t *req, const char *value, size_t value_len); | ||
|
|
||
| void h2o_send_early_hints(h2o_req_t *req); |
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.
Please add doc-comment to the public functions.
lib/core/request.c
Outdated
| { | ||
| /* 103 must be sent before h2o_start_response is called*/ | ||
| assert(req->_generator == NULL); | ||
| assert(req->_ostr_top->next == NULL && req->_ostr_top->send_early_hints != NULL); |
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 should allow req->_ostr_top->send_early_hints being NULL for performance reasons, since for the most of the users H2O would not be emitting early hints for HTTP/1.
We can do if (req->_ostr_top->send_early_hints == NULL) return; here, and avoid the cost of running the header filters.
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.
so what do you think about how and where should we set req->_ostr_top->send_early_hints?
the most of the users H2O would not be emitting early hints for HTTP/1.
Do you mean we should have a configuration knob which allows the users to turn off 103 forwarding only when HTTP/1 is used?
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.
You can continue setting up the send_early_hints callback in the protocol implementations as they are. OTOH, protocol implementations that are not interested in using 103 can set send_early_hints to NULL so that the h2o_send_early_hints function can bail out earlier.
Do you mean we should have a configuration knob which allows the users to turn off 103 forwarding only when HTTP/1 is used?
I had thought that we had agreed to a knob that turns of sending 103 in any case for H1 (or both H1 and H2). Was that a discussion of a different issue?
|
@kazuho Done! |
kazuho
left a comment
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.
Thank you for the changes. I have left my comments in-line. I would also appreciate if you could consider #1727 (comment).
include/h2o.h
Outdated
| */ | ||
| uint64_t first_byte_timeout; | ||
| /** | ||
| * enum indicating that the proxy forward 103 response from upstream |
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.
Please state which enum is used, or preferably, use a typed enum.
include/h2o.h
Outdated
| */ | ||
| void h2o_proxy_register_configurator(h2o_globalconf_t *conf); | ||
|
|
||
| enum { H2O_PROXY_FORWARD_EARLY_HINTS_NONE, H2O_PROXY_FORWARD_EARLY_HINTS_EXCEPT_H1, H2O_PROXY_FORWARD_EARLY_HINTS_ALL }; |
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.
This should not be part of the proxy, since any module (e.g. header filter) could try to emit an early hint.
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.
you're right
|
@kazuho Thank you for the review. I addessed the issues. Regarding #1727 (comment), I fixed it at
Latter. those functions specifically target handling 103. |
include/h2o.h
Outdated
|
|
||
| typedef H2O_VECTOR(h2o_status_handler_t) h2o_status_callbacks_t; | ||
|
|
||
| typedef enum h2o_forward_early_hint { |
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.
should be "early hints" throughout the PR, especially when dealing with a set of header fields. This is because each header field of an 103 response is a "hint".
lib/core/proxy.c
Outdated
| } | ||
|
|
||
| if (status == 103) { | ||
| self->src_req->res.status = 103; |
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.
h2o_send_early_hints is a function specific to handling 103. That would mean that it sends a set of header fields and not the status code itself as an argument.
Therefore, we should not set h2o_req_t::res.status to 103 or expect the value to be that.
lib/handler/configurator/proxy.c
Outdated
| h2o_configurator_define_command(&c->super, "proxy.max-buffer-size", | ||
| H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, | ||
| on_config_max_buffer_size); | ||
| h2o_configurator_define_command(&c->super, "proxy.forward-early-hints", |
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.
Please adjust how the configuration directives and possible other parts of the code that implies the feature to be specific to the reverse proxy.
ba7d52f to
4124fcd
Compare
|
@kazuho Thank you. I addressed the issues. |
kazuho
left a comment
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.
Thank you for the changes. With the changes, I think that the code better reflects what an ideal proxy should do (in conformance to RFC).
I have left my ideas regarding the design of the protocol implementation. Please let me know what you think.
lib/core/request.c
Outdated
| /* 1xx must be sent before h2o_start_response is called*/ | ||
| assert(req->_generator == NULL); | ||
| assert(req->_ostr_top->next == NULL); | ||
| assert(100 <= req->res.status && req->res.status <= 199); |
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.
Maybe add assert(req->res.status != 101) as well?
lib/http1.c
Outdated
| struct { | ||
| h2o_iovec_t *inbufs; | ||
| size_t inbufcnt; | ||
| h2o_send_state_t send_state; |
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 can see that we need to buffer the content passed from the generator, now that informational responses could be in flight.
Then, can't we simplify the logic by using the same buffer for storing any pending data, including when we receive an additional 103 while a previous 103 is in flight?
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.
@kazuho Thank you for the comment. By this comment I found that req->res.headers can be modified while sending 1xx responses, and it'll cause some problems. I fixed that issue at bd7e9a1.
OTOH,
Then, can't we simplify the logic by using the same buffer for storing any pending data, including when we receive an additional 103 while a previous 103 is in flight?
I tried to simplify by buffering final responses too, but it may cause some performance disadvantages, so I left pending_final as it is.
kazuho
left a comment
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.
Thank you for the changes.
I have left my tentative comments. Please let me know what you think.
include/h2o.h
Outdated
| H2O_FORWARD_INFORMATIONAL_NONE, | ||
| H2O_FORWARD_INFORMATIONAL_EXCEPT_H1, | ||
| H2O_FORWARD_INFORMATIONAL_ALL | ||
| } h2o_forward_informational_t; |
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.
Current name looks like a callback rather than a enum, because "forward" is a verb.
Please consider changing the phrase "h2o_forward_informational" to "h2o_forward_informational_mode" or something (i.e. change the name to h2o_forward_informational_mode_t, ..._NONE to ..._MODE_NONE).
lib/core/proxy.c
Outdated
| /* copy the response (note: all the headers must be copied; http1client discards the input once we return from this callback) */ | ||
| req->res.status = status; | ||
| req->res.reason = h2o_strdup(&req->pool, msg.base, msg.len).base; | ||
| req->res.headers = (h2o_headers_t){NULL, 0, 0}; |
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.
Do we need to reset fields other than size?
Consider the case where the server sends an informational response containing 50 header fields, and then sending a final response containing the same number of header fields. I would prefer not to expand the buffer twice to store the headers. This is a weak preference, but I do not see why it can simply be done by not resetting the other fields.
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 would prefer not to expand the buffer twice to store the headers.
No, when sending 1xx we're not allocating or expanding any vectors or buffers
Line 578 in b43efc4
| self->src_req->res.headers = (h2o_headers_t){headers, num_headers, num_headers}; |
If there're no performance reasons, I'd prefer to clear whole struct explicitly rather than leaving broken pointer (req->res.headers.entries).
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.
Or, we might should clear req->res.headers at the end of h2o_send_informational..
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.
IMO it would depend on how an output filter (e.g. the headers handler) initiates Early Hints, when a generator that does not immediately return a final response is used.
Clearing req->res.headers would be fine if h2o_send_informational is going to be used for such purposes as well. Otherwise, we should clear the field in each protocol implementation, at the moment they serialize the headers to send them.
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.
Clearing req->res.headers would be fine if h2o_send_informational is going to be used for such purposes as well.
Yes, I think h2o_send_informational will be used for such use cases. d6e1e3b made it clear req->res
include/h2o.h
Outdated
| /** | ||
| * enum indicating that h2o forward 1xx response from upstream | ||
| */ | ||
| h2o_forward_informational_t forward_informational; |
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.
The doc-comment says the enum indicates if 1xx from upstream is forwarded.
Is that true?
My understanding is that this function controls if protocol handlers sends 1xx responses. In other words, this property controls what h2o sends rather than what it forwards (consider the hypothetical case of a file handler emitting 1xx; it's not "upstream", since in such case h2o is the origin).
Considering that, I'd prefer renaming this from "forward_informational_mode" to "send_informational_mode" (see my other comment about appending "_mode").
lib/core/configurator.c
Outdated
|
|
||
| static int on_config_forward_informational(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) | ||
| { | ||
| int value; |
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.
Can't the type of the value be the enum that is being typedef'ed? I'd assume that it can be, and that you could then omit the cast when assigning the value to globalconf.
lib/http1.c
Outdated
| *dst++ = '\r'; | ||
| *dst++ = '\n'; | ||
| } | ||
| dst += flatten_normal_headers(dst, req->res.headers.entries, req->res.headers.size, is_msie(req)); |
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.
This change causes performance degradation.
In current code, is_msie (which is a slow function) is called only when a vary header is being emitted. The PR is changing the function to be always called.
|
@kazuho thank you for the comments. I addressed the issues. |
| H2O_HEADERS_CMD_WHEN_FINAL, | ||
| H2O_HEADERS_CMD_WHEN_EARLY, | ||
| H2O_HEADERS_CMD_WHEN_ALL, | ||
| } h2o_headers_command_when_t; |
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.
What's the default value? I would suggest moving the default value to the top so that it would be assigned zero.
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.
Yes, default value is H2O_HEADERS_CMD_WHEN_FINAL
t/50reverse-proxy-informational.t
Outdated
| (my $eh, $resp) = split(/\r\n\r\n/, $resp, 2); | ||
| like $eh, qr{^link: </index.js>; rel=preload}mi if $link; | ||
|
|
||
| if ($status == 103) { |
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.
Don't we need to test that the server is actually sending 103 here?
I believe that the test would succeed even if the server did not send 103, all the 103-related tests are under if ($status == 103).
PS. In particular, I am thinking how we test the behavior of the HTTP/2 protocol implementation.
|
Thank you for working on the feature! |
Let the proxy handler forward 1xx responses from upstream, when
forward-informationalis notnone(default:except-h1)TODOs: