void finalostream_send(h2o_ostream_t *_self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, int is_final) { struct st_h2o_http1_finalostream_t *self = (void *)_self; struct st_h2o_http1_conn_t *conn = (struct st_h2o_http1_conn_t *)req->conn; h2o_iovec_t *bufs = alloca(sizeof(h2o_iovec_t) * (inbufcnt + 1)); int bufcnt = 0; assert(self == &conn->_ostr_final); if (!self->sent_headers) { conn->req.timestamps.response_start_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); /* build headers and send */ const char *connection = req->http1_is_persistent ? "keep-alive" : "close"; bufs[bufcnt].base = h2o_mem_alloc_pool( &req->pool, flatten_headers_estimate_size(req, conn->super.ctx->globalconf->server_name.len + strlen(connection))); bufs[bufcnt].len = flatten_headers(bufs[bufcnt].base, req, connection); ++bufcnt; self->sent_headers = 1; } memcpy(bufs + bufcnt, inbufs, sizeof(h2o_iovec_t) * inbufcnt); bufcnt += inbufcnt; if (bufcnt != 0) { h2o_socket_write(conn->sock, bufs, bufcnt, is_final ? on_send_complete : on_send_next_push); } else { on_send_complete(conn->sock, 0); } }
void h2o_process_request(h2o_req_t *req) { h2o_context_t *ctx = req->conn->ctx; h2o_get_timestamp(ctx, &req->pool, &req->processed_at); /* setup host context */ req->host_config = ctx->global_config->hosts.entries; if (req->authority.base != NULL) { if (ctx->global_config->hosts.size != 1) { h2o_hostconf_t *hostconf = ctx->global_config->hosts.entries, *end = hostconf + ctx->global_config->hosts.size; for (; hostconf != end; ++hostconf) { if (h2o_memis(req->authority.base, req->authority.len, hostconf->hostname.base, hostconf->hostname.len)) { req->host_config = hostconf; break; } } } } else { /* set the authority name to the default one */ req->authority = req->host_config->hostname; } { /* call any of the handlers */ h2o_handler_t **handler = req->host_config->handlers.entries, **end = handler + req->host_config->handlers.size; for (; handler != end; ++handler) { if ((*handler)->on_req(*handler, req) == 0) return; } } h2o_send_error(req, 404, "File Not Found", "not found"); }
static void finalostream_start_pull(h2o_ostream_t *_self, h2o_ostream_pull_cb cb) { struct st_h2o_http1_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http1_conn_t, _ostr_final.super, _self); const char *connection = conn->req.http1_is_persistent ? "keep-alive" : "close"; size_t bufsz, headers_len; assert(conn->req._ostr_top == &conn->_ostr_final.super); assert(!conn->_ostr_final.sent_headers); conn->req.timestamps.response_start_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); /* register the pull callback */ conn->_ostr_final.pull.cb = cb; /* setup the buffer */ bufsz = flatten_headers_estimate_size(&conn->req, conn->super.ctx->globalconf->server_name.len + strlen(connection)); if (bufsz < MAX_PULL_BUF_SZ) { if (MAX_PULL_BUF_SZ - bufsz < conn->req.res.content_length) { bufsz = MAX_PULL_BUF_SZ; } else { bufsz += conn->req.res.content_length; } } conn->_ostr_final.pull.buf = h2o_mem_alloc_pool(&conn->req.pool, bufsz); /* fill-in the header */ headers_len = flatten_headers(conn->_ostr_final.pull.buf, &conn->req, connection); conn->_ostr_final.sent_headers = 1; proceed_pull(conn, headers_len); }
static size_t flatten_headers(char *buf, h2o_req_t *req, const char *connection) { h2o_context_t *ctx = req->conn->ctx; h2o_timestamp_t ts; char *dst = buf; h2o_get_timestamp(ctx, &req->pool, &ts); assert(req->res.status <= 999); /* send essential headers with the first chars uppercased for max. interoperability (#72) */ if (req->res.content_length != SIZE_MAX) { dst += sprintf(dst, "HTTP/1.1 %d %s\r\nDate: %s\r\nConnection: %s\r\nContent-Length: %zu\r\n", req->res.status, req->res.reason, ts.str->rfc1123, connection, req->res.content_length); } else { dst += sprintf(dst, "HTTP/1.1 %d %s\r\nDate: %s\r\nConnection: %s\r\n", req->res.status, req->res.reason, ts.str->rfc1123, connection); } if (ctx->globalconf->server_name.len) { dst += sprintf(dst, "Server: %s\r\n", ctx->globalconf->server_name.base); } { /* flatten the normal headers */ size_t i; for (i = 0; i != req->res.headers.size; ++i) { const h2o_header_t *header = req->res.headers.entries + i; if (header->name == &H2O_TOKEN_VARY->buf) { /* replace Vary with Cache-Control: private; see the following URLs to understand why this is necessary * - http://blogs.msdn.com/b/ieinternals/archive/2009/06/17/vary-header-prevents-caching-in-ie.aspx * - https://www.igvita.com/2013/05/01/deploying-webp-via-accept-content-negotiation/ */ if (is_msie(req)) { static h2o_header_t cache_control_private = {&H2O_TOKEN_CACHE_CONTROL->buf, {H2O_STRLIT("private")}}; header = &cache_control_private; } } memcpy(dst, header->name->base, header->name->len); dst += header->name->len; *dst++ = ':'; *dst++ = ' '; memcpy(dst, header->value.base, header->value.len); dst += header->value.len; *dst++ = '\r'; *dst++ = '\n'; } *dst++ = '\r'; *dst++ = '\n'; } return dst - buf; }
static size_t flatten_headers(char *buf, h2o_req_t *req, const char *connection) { h2o_context_t *ctx = req->conn->ctx; h2o_timestamp_t ts; char *dst = buf; h2o_get_timestamp(ctx, &req->pool, &ts); assert(req->res.status <= 999); if (req->res.content_length != SIZE_MAX) { dst += sprintf( dst, "HTTP/1.1 %d %s\r\ndate: %s\r\nserver: %s\r\nconnection: %s\r\ncontent-length: %zu\r\n", req->res.status, req->res.reason, ts.str->rfc1123, ctx->globalconf->server_name.base, connection, req->res.content_length); } else { dst += sprintf( dst, "HTTP/1.1 %d %s\r\ndate: %s\r\nserver: %s\r\nconnection: %s\r\n", req->res.status, req->res.reason, ts.str->rfc1123, ctx->globalconf->server_name.base, connection); } { /* flatten the normal headers */ const h2o_header_t *header = req->res.headers.entries, * end = header + req->res.headers.size; for (; header != end; ++header) { memcpy(dst, header->name->base, header->name->len); dst += header->name->len; *dst++ = ':'; *dst++ = ' '; memcpy(dst, header->value.base, header->value.len); dst += header->value.len; *dst++ = '\r'; *dst++ = '\n'; } *dst++ = '\r'; *dst++ = '\n'; } return dst - buf; }
static void bind_conf(h2o_req_t *req) { h2o_context_t *ctx; h2o_hostconf_t *hostconf; h2o_pathconf_t *pathconf; if (req->pathconf != NULL) { /* already bound */ return; } ctx = req->conn->ctx; h2o_get_timestamp(ctx, &req->pool, &req->processed_at); /* find the host context */ if (req->authority.base != NULL) { h2o_hostconf_t *end = ctx->globalconf->hosts.entries + ctx->globalconf->hosts.size; for (hostconf = ctx->globalconf->hosts.entries; hostconf != end; ++hostconf) { if (h2o_memis(req->authority.base, req->authority.len, hostconf->hostname.base, hostconf->hostname.len)) goto HostFound; } hostconf = ctx->globalconf->hosts.entries; HostFound: ; } else { /* set the authority name to the default one */ hostconf = ctx->globalconf->hosts.entries; req->authority = hostconf->hostname; } /* find the path context (as well as building path_normalized) */ if (hostconf->paths.size != 0) { size_t i = 0; req->path_normalized = h2o_normalize_path(&req->pool, req->path.base, req->path.len); do { pathconf = hostconf->paths.entries + i; if (req->path.len >= pathconf->path.len && memcmp(req->path.base, pathconf->path.base, pathconf->path.len) == 0) goto PathFound; } while (++i != hostconf->paths.size); pathconf = &hostconf->fallback_path; PathFound: ; } else { pathconf = &hostconf->fallback_path; } req->pathconf = pathconf; }
void h2o_accept(h2o_accept_ctx_t *ctx, h2o_socket_t *sock) { struct timeval connected_at = *h2o_get_timestamp(ctx->ctx, NULL, NULL); if (ctx->expect_proxy_line || ctx->ssl_ctx != NULL) { create_accept_data(ctx, sock, connected_at); if (ctx->expect_proxy_line) { h2o_socket_read_start(sock, on_read_proxy_line); } else { h2o_socket_ssl_server_handshake(sock, ctx->ssl_ctx, on_ssl_handshake_complete); } } else { h2o_http1_accept(ctx, sock, connected_at); } }
static void send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) { h2o_timestamp_t ts; h2o_get_timestamp(conn->super.ctx, &stream->req.pool, &ts); /* FIXME the function may return error, check it! */ h2o_hpack_flatten_headers( &conn->_write.buf, &conn->_output_header_table, stream->stream_id, conn->peer_settings.max_frame_size, &stream->req.res, &ts, &conn->super.ctx->globalconf->server_name); h2o_http2_conn_request_write(conn); stream->state = H2O_HTTP2_STREAM_STATE_SEND_BODY; }
static void on_send_complete(h2o_socket_t *sock, int status) { struct st_h2o_http1_conn_t *conn = sock->data; assert(conn->req._ostr_top == &conn->_ostr_final.super); conn->req.timestamps.response_end_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); if (!conn->req.http1_is_persistent) { /* TODO use lingering close */ close_connection(conn, 1); return; } /* handle next request */ init_request(conn, 1); h2o_buffer_consume(&conn->sock->input, conn->_reqsize); conn->_prevreqlen = 0; conn->_reqsize = 0; reqread_start(conn); }
static void handle_incoming_request(struct st_h2o_http1_conn_t *conn) { size_t inreqlen = conn->sock->input->size < H2O_MAX_REQLEN ? conn->sock->input->size : H2O_MAX_REQLEN; int reqlen, minor_version; struct phr_header headers[H2O_MAX_HEADERS]; size_t num_headers = H2O_MAX_HEADERS; ssize_t entity_body_header_index; h2o_iovec_t expect; /* need to set request_begin_at here for keep-alive connection */ if (conn->req.timestamps.request_begin_at.tv_sec == 0) conn->req.timestamps.request_begin_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); reqlen = phr_parse_request(conn->sock->input->bytes, inreqlen, (const char **)&conn->req.input.method.base, &conn->req.input.method.len, (const char **)&conn->req.input.path.base, &conn->req.input.path.len, &minor_version, headers, &num_headers, conn->_prevreqlen); conn->_prevreqlen = inreqlen; switch (reqlen) { default: // parse complete conn->_reqsize = reqlen; if ((entity_body_header_index = fixup_request(conn, headers, num_headers, minor_version, &expect)) != -1) { conn->req.timestamps.request_body_begin_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); if (expect.base != NULL) { if (!h2o_lcstris(expect.base, expect.len, H2O_STRLIT("100-continue"))) { set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); h2o_send_error(&conn->req, 417, "Expectation Failed", "unknown expectation", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); return; } static const h2o_iovec_t res = {H2O_STRLIT("HTTP/1.1 100 Continue\r\n\r\n")}; h2o_socket_write(conn->sock, (void *)&res, 1, on_continue_sent); } if (create_entity_reader(conn, headers + entity_body_header_index) != 0) { return; } if (expect.base != NULL) { /* processing of the incoming entity is postponed until the 100 response is sent */ h2o_socket_read_stop(conn->sock); return; } conn->_req_entity_reader->handle_incoming_entity(conn); } else { set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); process_request(conn); } return; case -2: // incomplete if (inreqlen == H2O_MAX_REQLEN) { // request is too long (TODO notify) close_connection(conn, 1); } return; case -1: // error /* upgrade to HTTP/2 if the request starts with: PRI * HTTP/2 */ if (conn->super.ctx->globalconf->http1.upgrade_to_http2) { /* should check up to the first octet that phr_parse_request returns an error */ static const h2o_iovec_t HTTP2_SIG = {H2O_STRLIT("PRI * HTTP/2")}; if (conn->sock->input->size >= HTTP2_SIG.len && memcmp(conn->sock->input->bytes, HTTP2_SIG.base, HTTP2_SIG.len) == 0) { h2o_accept_ctx_t accept_ctx = {conn->super.ctx, conn->super.hosts}; h2o_socket_t *sock = conn->sock; struct timeval connected_at = conn->super.connected_at; /* destruct the connection after detatching the socket */ conn->sock = NULL; close_connection(conn, 1); /* and accept as http2 connection */ h2o_http2_accept(&accept_ctx, sock, connected_at); return; } } close_connection(conn, 1); return; } }
static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) { h2o_timestamp_t ts; h2o_get_timestamp(conn->super.ctx, &stream->req.pool, &ts); /* cancel push with an error response */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (400 <= stream->req.res.status) goto CancelPush; if (stream->cache_digests != NULL) { ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); if (etag_index != -1) { h2o_iovec_t url = h2o_concat(&stream->req.pool, stream->req.input.scheme->name, h2o_iovec_init(H2O_STRLIT("://")), stream->req.input.authority, stream->req.input.path); h2o_iovec_t *etag = &stream->req.headers.entries[etag_index].value; if (h2o_cache_digests_lookup_by_url_and_etag(stream->cache_digests, url.base, url.len, etag->base, etag->len) == H2O_CACHE_DIGESTS_STATE_FRESH) goto CancelPush; } } } /* reset casper cookie in case cache-digests exist */ if (stream->cache_digests != NULL && stream->req.hostconf->http2.casper.capacity_bits != 0) { h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, H2O_STRLIT("h2o_casper=; Path=/; Expires=Sat, 01 Jan 2000 00:00:00 GMT")); } /* CASPER */ if (conn->casper != NULL) { /* update casper if necessary */ if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, 1)) { /* cancel if the pushed resource is already marked as cached */ if (h2o_http2_stream_is_push(stream->stream_id)) goto CancelPush; } } if (stream->cache_digests != NULL) goto SkipCookie; /* browsers might ignore push responses, or they may process the responses in a different order than they were pushed. * Therefore H2O tries to include casper cookie only in the last stream that may be received by the client, or when the * value become stable; see also: https://github.com/h2o/h2o/issues/421 */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (!(conn->num_streams.pull.open == 0 && (conn->num_streams.push.half_closed - conn->num_streams.push.send_body) == 1)) goto SkipCookie; } else { if (conn->num_streams.push.half_closed - conn->num_streams.push.send_body != 0) goto SkipCookie; } h2o_iovec_t cookie = h2o_http2_casper_get_cookie(conn->casper); h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, cookie.base, cookie.len); SkipCookie:; } if (h2o_http2_stream_is_push(stream->stream_id)) { /* for push, send the push promise */ if (!stream->push.promise_sent) h2o_http2_stream_send_push_promise(conn, stream); /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ if (is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } else { /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based * prioritization (e.g. that of Firefox) */ if (conn->num_streams.priority.open == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && h2o_http2_scheduler_get_parent(&stream->_refs.scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } /* send HEADERS, as well as start sending body */ if (h2o_http2_stream_is_push(stream->stream_id)) h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, H2O_STRLIT("pushed")); h2o_hpack_flatten_response(&conn->_write.buf, &conn->_output_header_table, stream->stream_id, conn->peer_settings.max_frame_size, &stream->req.res, &ts, &conn->super.ctx->globalconf->server_name, stream->req.res.content_length); h2o_http2_conn_request_write(conn); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); return 0; CancelPush: h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, H2O_STRLIT("cancelled")); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_refs.link); if (stream->push.promise_sent) { #ifndef _MSC_VER h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); #else h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, H2O_HTTP2_ERROR_INTERNAL); #endif h2o_http2_conn_request_write(conn); } return -1; }
static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) { h2o_timestamp_t ts; size_t num_casper_entries_before_push = 0; h2o_get_timestamp(conn->super.ctx, &stream->req.pool, &ts); /* cancel push with an error response */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (400 <= stream->req.res.status) goto CancelPush; h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-pushed"), 0, H2O_STRLIT("1")); } if (stream->req.hostconf->http2.casper.capacity_bits != 0) { /* extract the client-side cache fingerprint */ if (conn->casper == NULL) h2o_http2_conn_init_casper(conn, stream->req.hostconf->http2.casper.capacity_bits); size_t header_index = -1; while ((header_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_COOKIE, header_index)) != -1) { h2o_header_t *header = stream->req.headers.entries + header_index; h2o_http2_casper_consume_cookie(conn->casper, header->value.base, header->value.len); } num_casper_entries_before_push = h2o_http2_casper_num_entries(conn->casper); /* update casper if necessary */ if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); h2o_iovec_t etag = etag_index != -1 ? stream->req.headers.entries[etag_index].value : (h2o_iovec_t){}; if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, etag.base, etag.len, 1)) { /* cancel if the pushed resource is already marked as cached */ if (h2o_http2_stream_is_push(stream->stream_id)) goto CancelPush; } } } if (h2o_http2_stream_is_push(stream->stream_id)) { /* for push, send the push promise */ if (!stream->push.promise_sent) h2o_http2_stream_send_push_promise(conn, stream); /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ if (is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } else { /* for pull, push things requested, as well as send the casper cookie if modified */ if (conn->peer_settings.enable_push) { size_t i; for (i = 0; i != stream->req.http2_push_paths.size; ++i) h2o_http2_conn_push_path(conn, stream->req.http2_push_paths.entries[i], stream); /* send casper cookie if it has been altered (due to the __stream itself__ or by some of the pushes) */ if (conn->casper != NULL && num_casper_entries_before_push != h2o_http2_casper_num_entries(conn->casper)) { h2o_iovec_t cookie = h2o_http2_casper_build_cookie(conn->casper, &stream->req.pool); h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, cookie.base, cookie.len); } } /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based * prioritization (e.g. that of Firefox) */ if (conn->num_streams.open_priority == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && h2o_http2_scheduler_get_parent(&stream->_refs.scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } /* send HEADERS, as well as start sending body */ h2o_hpack_flatten_response(&conn->_write.buf, &conn->_output_header_table, stream->stream_id, conn->peer_settings.max_frame_size, &stream->req.res, &ts, &conn->super.ctx->globalconf->server_name); h2o_http2_conn_request_write(conn); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); return 0; CancelPush: h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_refs.link); if (stream->push.promise_sent) { h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, H2O_HTTP2_ERROR_INTERNAL); h2o_http2_conn_request_write(conn); } return -1; }