Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@i110
Copy link
Contributor

@i110 i110 commented Apr 13, 2018

Let the proxy handler forward 1xx responses from upstream, when forward-informational is not none (default: except-h1)

TODOs:

  • add tests

@kazuho
Copy link
Member

kazuho commented Apr 19, 2018

Thank you for working on the enhancement.

As discussed offline, we might want to simplify the approach by using h2o_req_t::res.headers for passing around the informational headers.

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.

@i110
Copy link
Contributor Author

i110 commented Apr 20, 2018

@kazuho Thank you for your comment. I did it in cbee249, fixed other problems and write some tests.

Copy link
Member

@kazuho kazuho left a 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.

assert(req->_generator == NULL);
assert(req->_ostr_top->next == NULL && req->_ostr_top->send_early_hints != NULL);

req->res.status = 103;
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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.

{
/* 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);
Copy link
Member

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.

Copy link
Contributor Author

@i110 i110 Apr 22, 2018

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?

Copy link
Member

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?

@i110
Copy link
Contributor Author

i110 commented Apr 23, 2018

@kazuho Done!

Copy link
Member

@kazuho kazuho left a 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
Copy link
Member

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 };
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right

@i110
Copy link
Contributor Author

i110 commented Apr 23, 2018

@kazuho Thank you for the review. I addessed the issues.

Regarding #1727 (comment), I fixed it at
82607e3

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.

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 {
Copy link
Member

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;
Copy link
Member

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.

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",
Copy link
Member

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.

@i110 i110 changed the title forward early hints forward informational responses Apr 24, 2018
@i110 i110 force-pushed the i110/forward-early-hints branch from ba7d52f to 4124fcd Compare April 24, 2018 11:13
@i110
Copy link
Contributor Author

i110 commented Apr 24, 2018

@kazuho Thank you. I addressed the issues.

Copy link
Member

@kazuho kazuho left a 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.

/* 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);
Copy link
Member

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;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@kazuho kazuho left a 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;
Copy link
Member

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};
Copy link
Member

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.

Copy link
Contributor Author

@i110 i110 May 11, 2018

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

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).

Copy link
Contributor Author

@i110 i110 May 11, 2018

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..

Copy link
Member

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.

Copy link
Contributor Author

@i110 i110 May 12, 2018

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;
Copy link
Member

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").


static int on_config_forward_informational(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
{
int value;
Copy link
Member

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));
Copy link
Member

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.

@i110
Copy link
Contributor Author

i110 commented May 11, 2018

@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;
Copy link
Member

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.

Copy link
Contributor Author

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

(my $eh, $resp) = split(/\r\n\r\n/, $resp, 2);
like $eh, qr{^link: </index.js>; rel=preload}mi if $link;

if ($status == 103) {
Copy link
Member

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.

@kazuho kazuho merged commit 6c7c850 into master May 21, 2018
@kazuho
Copy link
Member

kazuho commented May 21, 2018

Thank you for working on the feature!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants