#include "http_data.h" #include "http_server.h" #include "string_utils.h" #include #include #include #include #include #include #include #include "raft_server.h" #include "logger.h" HttpServer::HttpServer(const std::string & version, const std::string & listen_address, uint32_t listen_port, const std::string & ssl_cert_path, const std::string & ssl_cert_key_path, const uint64_t ssl_refresh_interval_ms, bool cors_enabled, ThreadPool* thread_pool): SSL_REFRESH_INTERVAL_MS(ssl_refresh_interval_ms), exit_loop(false), version(version), listen_address(listen_address), listen_port(listen_port), ssl_cert_path(ssl_cert_path), ssl_cert_key_path(ssl_cert_key_path), cors_enabled(cors_enabled), thread_pool(thread_pool) { accept_ctx = new h2o_accept_ctx_t(); h2o_config_init(&config); hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), 65535); register_handler(hostconf, "/", catch_all_handler); listener_socket = nullptr; // initialized later signal(SIGPIPE, SIG_IGN); h2o_context_init(&ctx, h2o_evloop_create(), &config); ctx.globalconf->server_name.base = nullptr; // initialized later message_dispatcher = new http_message_dispatcher; message_dispatcher->init(ctx.loop); ssl_refresh_timer.timer.expire_at = 0; // used during destructor metrics_refresh_timer.timer.expire_at = 0; // used during destructor accept_ctx->ssl_ctx = nullptr; } void HttpServer::on_accept(h2o_socket_t *listener, const char *err) { HttpServer* http_server = reinterpret_cast(listener->data); h2o_socket_t *sock; if (err != NULL) { return; } if ((sock = h2o_evloop_socket_accept(listener)) == NULL) { return; } h2o_accept(http_server->accept_ctx, sock); } void HttpServer::on_metrics_refresh_timeout(h2o_timer_t *entry) { h2o_custom_timer_t* custom_timer = reinterpret_cast(entry); AppMetrics::get_instance().window_reset(); HttpServer *hs = static_cast(custom_timer->data); // link the timer for the next cycle h2o_timer_link( hs->ctx.loop, AppMetrics::METRICS_REFRESH_INTERVAL_MS, &hs->metrics_refresh_timer.timer ); } void HttpServer::on_ssl_refresh_timeout(h2o_timer_t *entry) { h2o_custom_timer_t* custom_timer = reinterpret_cast(entry); LOG(INFO) << "Refreshing SSL certs from disk."; HttpServer *hs = static_cast(custom_timer->data); SSL_CTX* old_ssl_ctx = hs->accept_ctx->ssl_ctx; bool refresh_success = initialize_ssl_ctx(hs->ssl_cert_path.c_str(), hs->ssl_cert_key_path.c_str(), hs->accept_ctx); if (refresh_success) { // delete the old SSL context but after some time, to allow existing connections to drain h2o_custom_timer_t* ssl_ctx_delete_timer = new h2o_custom_timer_t(old_ssl_ctx); h2o_timer_init(&ssl_ctx_delete_timer->timer, on_ssl_ctx_delete_timeout); uint64_t delete_lag = std::max(60 * 1000, hs->SSL_REFRESH_INTERVAL_MS / 2); h2o_timer_link(hs->ctx.loop, delete_lag, &ssl_ctx_delete_timer->timer); } else { LOG(ERROR) << "SSL cert refresh failed."; } // link the timer for the next cycle h2o_timer_link(hs->ctx.loop, hs->SSL_REFRESH_INTERVAL_MS, &hs->ssl_refresh_timer.timer); } void HttpServer::on_ssl_ctx_delete_timeout(h2o_timer_t *entry) { LOG(INFO) << "Deleting old SSL context."; h2o_custom_timer_t* custom_timer = reinterpret_cast(entry); SSL_CTX* old_ssl_ctx = static_cast(custom_timer->data); SSL_CTX_free(old_ssl_ctx); delete custom_timer; } int HttpServer::setup_ssl(const char *cert_file, const char *key_file) { // Set up a timer to refresh SSL config from disk. Also, initializing upfront so that destructor works ssl_refresh_timer = h2o_custom_timer_t(this); h2o_timer_init(&ssl_refresh_timer.timer, on_ssl_refresh_timeout); h2o_timer_link(ctx.loop, SSL_REFRESH_INTERVAL_MS, &ssl_refresh_timer.timer); LOG(INFO) << "SSL cert refresh interval: " << (SSL_REFRESH_INTERVAL_MS / 1000) << "s"; if(!initialize_ssl_ctx(cert_file, key_file, accept_ctx)) { return -1; } return 0; } int HttpServer::create_listener() { struct sockaddr_in addr; int fd, reuseaddr_flag = 1; if(!ssl_cert_path.empty() && !ssl_cert_key_path.empty()) { int ssl_setup_code = setup_ssl(ssl_cert_path.c_str(), ssl_cert_key_path.c_str()); if(ssl_setup_code != 0) { return -1; } } ctx.globalconf->server_name = h2o_strdup(nullptr, "", SIZE_MAX); ctx.globalconf->http2.active_stream_window_size = ACTIVE_STREAM_WINDOW_SIZE; ctx.globalconf->http2.idle_timeout = REQ_TIMEOUT_MS; ctx.globalconf->max_request_entity_size = (size_t(3) * 1024 * 1024 * 1024); // 3 GB ctx.globalconf->http1.req_timeout = REQ_TIMEOUT_MS; ctx.globalconf->http1.req_io_timeout = REQ_TIMEOUT_MS; accept_ctx->ctx = &ctx; accept_ctx->hosts = config.hosts; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(listen_port); inet_pton(AF_INET, listen_address.c_str(), &(addr.sin_addr)); if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 || setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 || bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, SOMAXCONN) != 0) { return -1; } listener_socket = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ); listener_socket->data = this; h2o_socket_read_start(listener_socket, on_accept); return 0; } int HttpServer::run(ReplicationState* replication_state) { this->replication_state = replication_state; metrics_refresh_timer = h2o_custom_timer_t(this); h2o_timer_init(&metrics_refresh_timer.timer, on_metrics_refresh_timeout); h2o_timer_link(ctx.loop, AppMetrics::METRICS_REFRESH_INTERVAL_MS, &metrics_refresh_timer.timer); if (create_listener() != 0) { LOG(ERROR) << "Failed to listen on " << listen_address << ":" << listen_port << " - " << strerror(errno); return 1; } else { LOG(INFO) << "Typesense has started listening on port " << listen_port; } message_dispatcher->on(STOP_SERVER_MESSAGE, HttpServer::on_stop_server); while(!exit_loop) { h2o_evloop_run(ctx.loop, INT32_MAX); } return 0; } bool HttpServer::on_stop_server(void *data) { // do nothing return true; } std::string HttpServer::get_version() { return version; } void HttpServer::clear_timeouts(const std::vector & timers, bool trigger_callback) { for(h2o_timer_t* timer: timers) { h2o_timer_unlink(timer); } } void HttpServer::stop() { if(listener_socket != nullptr) { h2o_socket_read_stop(listener_socket); h2o_socket_close(listener_socket); } // this will break the event loop exit_loop = true; // send a message to activate the idle event loop to exit, just in case message_dispatcher->send_message(STOP_SERVER_MESSAGE, nullptr); } h2o_pathconf_t* HttpServer::register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *)) { // See: https://github.com/h2o/h2o/issues/181#issuecomment-75393049 h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); h2o_custom_req_handler_t *handler = reinterpret_cast(h2o_create_handler(pathconf, sizeof(*handler))); handler->http_server = this; handler->super.on_req = on_req; // Enable streaming request body handler->super.supports_request_streaming = 1; compress_args.min_size = 256; // don't gzip less than this size compress_args.brotli.quality = -1; // disable, not widely supported compress_args.gzip.quality = 1; // fastest h2o_compress_register(pathconf, &compress_args); return pathconf; } uint64_t HttpServer::find_route(const std::vector & path_parts, const std::string & http_method, route_path** found_rpath) { for (const auto& index_route : routes) { const route_path & rpath = index_route.second; if(rpath.path_parts.size() != path_parts.size() || rpath.http_method != http_method) { continue; } bool found = true; for(size_t j = 0; j < rpath.path_parts.size(); j++) { const std::string & rpart = rpath.path_parts[j]; const std::string & given_part = path_parts[j]; if(rpart != given_part && rpart[0] != ':') { found = false; break; } } if(found) { *found_rpath = const_cast(&rpath); return index_route.first; } } return static_cast(ROUTE_CODES::NOT_FOUND); } void HttpServer::on_res_generator_dispose(void *self) { h2o_custom_generator_t* custom_generator = *static_cast(self); //LOG(INFO) << "on_res_generator_dispose fires, req use count " << custom_generator->req().use_count(); destroy_request_response(custom_generator->req(), custom_generator->res()); /*LOG(INFO) << "Deleting custom_generator, res: " << custom_generator->res(); << ", refcount: " << custom_generator->res().use_count();*/ delete custom_generator; //LOG(INFO) << "Deleted custom_generator"; } int HttpServer::catch_all_handler(h2o_handler_t *_h2o_handler, h2o_req_t *req) { h2o_custom_req_handler_t* h2o_handler = (h2o_custom_req_handler_t *)_h2o_handler; const std::string & http_method = std::string(req->method.base, req->method.len); const std::string & path = std::string(req->path.base, req->path.len); std::vector path_with_query_parts; StringUtils::split(path, path_with_query_parts, "?"); const std::string & path_without_query = path_with_query_parts[0]; std::string metric_identifier = http_method + " " + path_without_query; AppMetrics::get_instance().increment_count(metric_identifier, 1); // Handle CORS if(h2o_handler->http_server->cors_enabled) { h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("access-control-allow-origin"), 0, NULL, H2O_STRLIT("*")); if(http_method == "OPTIONS") { // locate request access control headers const char* ACL_REQ_HEADERS = "access-control-request-headers"; ssize_t acl_header_cursor = h2o_find_header_by_str(&req->headers, ACL_REQ_HEADERS, strlen(ACL_REQ_HEADERS), -1); if(acl_header_cursor != -1) { h2o_iovec_t &acl_req_headers = req->headers.entries[acl_header_cursor].value; h2o_generator_t generator = {NULL, NULL}; h2o_iovec_t res_body = h2o_strdup(&req->pool, "", SIZE_MAX); req->res.status = 200; req->res.reason = http_res::get_status_reason(200); h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("access-control-allow-methods"), 0, NULL, H2O_STRLIT("POST, GET, DELETE, PUT, PATCH, OPTIONS")); h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("access-control-allow-headers"), 0, NULL, acl_req_headers.base, acl_req_headers.len); h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("access-control-max-age"), 0, NULL, H2O_STRLIT("3600")); h2o_start_response(req, &generator); h2o_send(req, &res_body, 1, H2O_SEND_STATE_FINAL); return 0; } } } std::vector path_parts; StringUtils::split(path_without_query, path_parts, "/"); h2o_iovec_t query = req->query_at != SIZE_MAX ? h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at) : h2o_iovec_init(H2O_STRLIT("")); std::string query_str(query.base, query.len); std::map query_map = StringUtils::parse_query_string(query_str); // Extract auth key from header. If that does not exist, look for a GET parameter. std::string api_auth_key_sent = ""; ssize_t auth_header_cursor = h2o_find_header_by_str(&req->headers, http_req::AUTH_HEADER, strlen(http_req::AUTH_HEADER), -1); if(auth_header_cursor != -1) { h2o_iovec_t & slot = req->headers.entries[auth_header_cursor].value; api_auth_key_sent = std::string(slot.base, slot.len); } else if(query_map.count(http_req::AUTH_HEADER) != 0) { api_auth_key_sent = query_map[http_req::AUTH_HEADER]; } route_path *rpath = nullptr; uint64_t route_hash = h2o_handler->http_server->find_route(path_parts, http_method, &rpath); if(route_hash == static_cast(ROUTE_CODES::NOT_FOUND)) { std::string message = "{ \"message\": \"Not Found\"}"; return send_response(req, 404, message); } const std::string& root_resource = (path_parts.empty()) ? "" : path_parts[0]; //LOG(INFO) << "root_resource is: " << root_resource; bool needs_readiness_check = (root_resource == "collections") || !( root_resource == "health" || root_resource == "debug" || root_resource == "stats.json" || root_resource == "metrics.json" || root_resource == "sequence" || root_resource == "operations" ); if(needs_readiness_check) { bool write_op = is_write_request(root_resource, http_method); bool read_op = !write_op; std::string message = "{ \"message\": \"Not Ready or Lagging\"}"; if(read_op && !h2o_handler->http_server->get_replication_state()->is_read_caught_up()) { return send_response(req, 503, message); } else if(write_op && !h2o_handler->http_server->get_replication_state()->is_write_caught_up()) { return send_response(req, 503, message); } } // iterate and extract path params for(size_t i = 0; i < rpath->path_parts.size(); i++) { const std::string & path_part = rpath->path_parts[i]; if(path_part[0] == ':') { query_map.emplace(path_part.substr(1), path_parts[i]); } } const std::string & body = std::string(req->entity.base, req->entity.len); bool authenticated = h2o_handler->http_server->auth_handler(query_map, body, *rpath, api_auth_key_sent); if(!authenticated) { std::string message = std::string("{\"message\": \"Forbidden - a valid `") + http_req::AUTH_HEADER + "` header must be sent.\"}"; return send_response(req, 401, message); } std::shared_ptr request = std::make_shared(req, rpath->http_method, path_without_query, route_hash, query_map, body); std::shared_ptr response = std::make_shared(); // add custom generator with a dispose function for cleaning up resources h2o_custom_generator_t* custom_gen = new h2o_custom_generator_t; custom_gen->super = h2o_generator_t {response_proceed, response_abort}; custom_gen->request = request; custom_gen->response = response; custom_gen->rpath = rpath; custom_gen->h2o_handler = h2o_handler; response->generator = &custom_gen->super; h2o_custom_generator_t** allocated_generator = static_cast( h2o_mem_alloc_shared(&req->pool, sizeof(*allocated_generator), on_res_generator_dispose) ); *allocated_generator = custom_gen; //LOG(INFO) << "Init res: " << custom_gen->response << ", ref count: " << custom_gen->response.use_count(); // routes match and is an authenticated request // do any additional pre-request middleware operations here if(rpath->action == "keys:create") { // we enrich incoming request with a random API key here so that leader and replicas will use the same key request->metadata = StringUtils::randstring(AuthManager::GENERATED_KEY_LEN); } if(req->proceed_req == nullptr) { // Full request body is already available, so we don't care if handler is async or not //LOG(INFO) << "Full request body is already available: " << req->entity.len; request->last_chunk_aggregate = true; return process_request(request, response, rpath, h2o_handler); } else { // Only partial request body is available. // If rpath->async_req is true, the request handler function will be invoked multiple times, for each chunk //LOG(INFO) << "Partial request body length: " << req->entity.len; req->write_req.cb = async_req_cb; req->write_req.ctx = custom_gen; req->proceed_req(req, req->entity.len, H2O_SEND_STATE_IN_PROGRESS); } return 0; } bool HttpServer::is_write_request(const std::string& root_resource, const std::string& http_method) { if(http_method == "GET") { return false; } bool write_free_request = (root_resource == "multi_search" || root_resource == "operations"); if(!write_free_request && (http_method == "POST" || http_method == "PUT" || http_method == "DELETE" || http_method == "PATCH")) { return true; } return false; } int HttpServer::async_req_cb(void *ctx, h2o_iovec_t chunk, int is_end_stream) { // NOTE: this callback is triggered multiple times by HTTP 2 but only once by HTTP 1 // This quirk is because of the underlying buffer/window sizes. We will have to deal with both cases. h2o_custom_generator_t* custom_generator = static_cast(ctx); const std::shared_ptr& request = custom_generator->req(); const std::shared_ptr& response = custom_generator->res(); std::string chunk_str(chunk.base, chunk.len); request->body += chunk_str; request->chunk_len += chunk.len; //LOG(INFO) << "async_req_cb, chunk.len=" << chunk.len << ", aggr_chunk_len=" << request->chunk_len // << ", is_end_stream=" << is_end_stream; //LOG(INFO) << "request->body.size(): " << request->body.size() << ", request->chunk_len=" << request->chunk_len; // LOG(INFO) << "req->entity.len: " << request->_req->entity.len << ", request->chunk_len=" << request->chunk_len; bool async_req = custom_generator->rpath->async_req; /* On HTTP2, the request body callback is invoked multiple times with chunks of 16,384 bytes until the `active_stream_window_size` is reached. For the first iteration, `active_stream_window_size` includes initial request entity size and as well as chunk sizes On HTTP 1, though, the handler is called only once with a small chunk size and requires process_req() to be called for fetching further chunks. We need to handle this difference. */ bool exceeds_chunk_limit; if(!request->is_http_v1() && request->first_chunk_aggregate) { exceeds_chunk_limit = ((request->chunk_len + request->_req->entity.len) >= ACTIVE_STREAM_WINDOW_SIZE); } else { exceeds_chunk_limit = (request->chunk_len >= ACTIVE_STREAM_WINDOW_SIZE); } bool can_process_async = async_req && exceeds_chunk_limit; /*if(is_end_stream == 1) { LOG(INFO) << "is_end_stream=1"; }*/ // first let's handle the case where we are ready to fire the request handler if(can_process_async || is_end_stream) { // For async streaming requests, handler should be invoked for every aggregated chunk // For a non streaming request, buffer body and invoke only at the end if(request->first_chunk_aggregate) { request->first_chunk_aggregate = false; } // default value for last_chunk_aggregate is false request->last_chunk_aggregate = (is_end_stream == 1); process_request(request, response, custom_generator->rpath, custom_generator->h2o_handler); return 0; } // we are not ready to fire the request handler, so that means we need to buffer the request further // this could be because we are a) dealing with a HTTP v1 request or b) a synchronous request if(request->is_http_v1()) { // http v1 callbacks fire on small chunk sizes, so fetch more to match window size of http v2 buffer size_t written = chunk.len; request->_req->proceed_req(request->_req, written, H2O_SEND_STATE_IN_PROGRESS); } if(!async_req) { // progress ONLY non-streaming type request body since // streaming requests will call proceed_req in an async fashion size_t written = chunk.len; request->_req->proceed_req(request->_req, written, H2O_SEND_STATE_IN_PROGRESS); } return 0; } int HttpServer::process_request(const std::shared_ptr& request, const std::shared_ptr& response, route_path *rpath, const h2o_custom_req_handler_t *handler) { //LOG(INFO) << "process_request called"; const std::string& root_resource = (rpath->path_parts.empty()) ? "" : rpath->path_parts[0]; bool is_write = is_write_request(root_resource, rpath->http_method); if(is_write) { handler->http_server->get_replication_state()->write(request, response); return 0; } auto http_server = handler->http_server; auto message_dispatcher = handler->http_server->get_message_dispatcher(); // LOG(INFO) << "Before enqueue res: " << response handler->http_server->get_thread_pool()->enqueue([http_server, rpath, message_dispatcher, request, response]() { // call the API handler //LOG(INFO) << "Wait for response " << response.get() << ", action: " << rpath->_get_action(); (rpath->handler)(request, response); if(!rpath->async_res) { // lifecycle of non async res will be owned by stream responder auto req_res = new deferred_req_res_t(request, response, http_server, true); message_dispatcher->send_message(HttpServer::STREAM_RESPONSE_MESSAGE, req_res); response->wait(); } //LOG(INFO) << "Response done " << response.get(); }); return 0; } void HttpServer::on_deferred_process_request(h2o_timer_t *entry) { h2o_custom_timer_t* custom_timer = reinterpret_cast(entry); deferred_req_res_t* deferred_req_res = static_cast(custom_timer->data); //LOG(INFO) << "on_deferred_process_request " << deferred_req_res->req.get(); route_path* found_rpath = nullptr; deferred_req_res->server->get_route(deferred_req_res->req->route_hash, &found_rpath); const std::shared_ptr request = deferred_req_res->req; const std::shared_ptr response = deferred_req_res->res; HttpServer* server = deferred_req_res->server; // done with timer, so we can clear timer and data h2o_timer_unlink(&deferred_req_res->req->defer_timer.timer); delete deferred_req_res; request->defer_timer.data = nullptr; if(found_rpath) { // must be called on a separate thread so as not to block http thread server->thread_pool->enqueue([found_rpath, request, response]() { //LOG(INFO) << "Sleeping for 5s req count " << deferred_req_res->req.use_count(); //std::this_thread::sleep_for(std::chrono::seconds(5)); //LOG(INFO) << "on_deferred_process_request, calling handler, req use count " << request.use_count(); found_rpath->handler(request, response); }); } } void HttpServer::defer_processing(const std::shared_ptr& req, const std::shared_ptr& res, size_t timeout_ms) { //LOG(INFO) << "defer_processing, exit_loop: " << exit_loop << ", req: " << req.get() << ", use count: " << req.use_count(); if(req->defer_timer.data == nullptr) { //LOG(INFO) << "req->defer_timer.data is null"; auto deferred_req_res = new deferred_req_res_t(req, res, this); //LOG(INFO) << "req use count " << req.use_count(); req->defer_timer.data = deferred_req_res; h2o_timer_init(&req->defer_timer.timer, on_deferred_process_request); } else { // This should not happen as data is cleared when defer handler is run LOG(ERROR) << "HttpServer::defer_processing, timer data is NOT null"; h2o_timer_unlink(&req->defer_timer.timer); } h2o_timer_link(ctx.loop, timeout_ms, &req->defer_timer.timer); if(exit_loop) { // otherwise, replication thread could be stuck waiting on a future req->_req = nullptr; req->notify(); res->notify(); } } void HttpServer::send_message(const std::string & type, void* data) { message_dispatcher->send_message(type, data); } int HttpServer::send_response(h2o_req_t *req, int status_code, const std::string & message) { h2o_generator_t generator = {nullptr, nullptr}; h2o_iovec_t body = h2o_strdup(&req->pool, message.c_str(), SIZE_MAX); req->res.status = status_code; req->res.reason = http_res::get_status_reason(req->res.status); h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, H2O_STRLIT("application/json; charset=utf-8")); h2o_start_response(req, &generator); h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL); return 0; } void HttpServer::send_response(const std::shared_ptr& request, const std::shared_ptr& response) { //LOG(INFO) << "send_response, request->_req=" << request->_req; if(request->_req == nullptr) { // indicates serialized request and response return ; } h2o_req_t* req = request->_req; h2o_generator_t* generator = static_cast(response->generator); h2o_iovec_t body = h2o_strdup(&req->pool, response->body.c_str(), SIZE_MAX); req->res.status = response->status_code; req->res.reason = http_res::get_status_reason(response->status_code); h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, response->content_type_header.c_str(), response->content_type_header.size()); h2o_start_response(req, generator); h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL); } void HttpServer::response_abort(h2o_generator_t *generator, h2o_req_t *req) { LOG(INFO) << "response_abort called"; h2o_custom_generator_t* custom_generator = reinterpret_cast(generator); custom_generator->req()->_req = nullptr; custom_generator->res()->final = true; // returns control back to caller (raft replication or follower forward) //LOG(INFO) << "response_abort: fulfilling req & res proceed."; } void HttpServer::response_proceed(h2o_generator_t *generator, h2o_req_t *req) { //LOG(INFO) << "response_proceed called"; h2o_custom_generator_t* custom_generator = reinterpret_cast(generator); //LOG(INFO) << "proxied_stream: " << custom_generator->response->proxied_stream; //LOG(INFO) << "response.final: " << custom_generator->response->final; custom_generator->res()->notify(); if(custom_generator->res()->proxied_stream) { // request progression should not be tied to response generation //LOG(INFO) << "Ignoring request proceed"; return ; } // if the request itself is async, we will proceed the request to fetch input content if (custom_generator->rpath->async_req) { auto stream_state = (custom_generator->res()->final) ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS; // `written` is ignored by http1.1 implementation, so meant only for http 2+ size_t written = custom_generator->req()->chunk_len; custom_generator->req()->chunk_len = 0; if(custom_generator->req()->_req->proceed_req) { //LOG(INFO) << "response_proceed: proceeding req"; custom_generator->req()->_req->proceed_req(custom_generator->req()->_req, written, stream_state); } } else { // otherwise, call the handler since it will be the handler that will be producing content // (streaming response but not request) custom_generator->h2o_handler->http_server->defer_processing(custom_generator->req(), custom_generator->res(), 1); } } void HttpServer::stream_response(const std::shared_ptr& request, const std::shared_ptr& response) { //LOG(INFO) << "stream_response called " << response.get(); if(request->_req == nullptr) { // raft log replay or when underlying request is aborted //LOG(INFO) << "stream_response, request._req == nullptr"; response->notify(); return; } h2o_req_t* req = request->_req; h2o_custom_generator_t* custom_generator = reinterpret_cast(response->generator); response->status_code = (response->status_code == 0) ? 503 : response->status_code; // just to be sure if(custom_generator->rpath->async_req && custom_generator->res()->final && !custom_generator->req()->last_chunk_aggregate) { // premature termination of async request: handle this explicitly as otherwise, request is not being closed LOG(INFO) << "Premature termination of async request."; req->res.status = response->status_code; req->res.reason = http_res::get_status_reason(response->status_code); h2o_iovec_t body = h2o_strdup(&req->pool, response->body.c_str(), SIZE_MAX); if (req->_generator == nullptr) { h2o_start_response(req, &custom_generator->super); } if(request->is_http_v1()) { h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL); h2o_dispose_request(req); } else { h2o_send(req, &body, 1, H2O_SEND_STATE_ERROR); } return ; } if (req->res.status == 0) { //LOG(INFO) << "h2o_start_response, content_type=" << response.content_type_header // << ",response.status_code=" << response.status_code; req->res.status = response->status_code; req->res.reason = http_res::get_status_reason(response->status_code); h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, response->content_type_header.c_str(), response->content_type_header.size()); h2o_start_response(req, &custom_generator->super); } /*LOG(INFO) << "stream_response, body_size: " << response->body.size() << ", response_final=" << custom_generator->response->final;*/ h2o_iovec_t body = h2o_strdup(&req->pool, response->body.c_str(), SIZE_MAX); response->body = ""; const h2o_send_state_t state = custom_generator->res()->final ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS; h2o_send(req, &body, 1, state); // LOG(INFO) << "stream_response after send"; } void HttpServer::destroy_request_response(const std::shared_ptr& request, const std::shared_ptr& response) { //LOG(INFO) << "destroy_request_response, req use count: " << request.use_count() << " req " << request.get(); /*LOG(INFO) << "destroy_request_response, response->proxied_stream=" << response->proxied_stream << ", request->_req=" << request->_req << ", response->await=" << &response->await;*/ //LOG(INFO) << "destroy_request_response, response: " << response << ", response->auto_dispose: " << response->auto_dispose; //LOG(INFO) << "after destroy_request_response, req use count: " << request.use_count() << " req " << request.get(); request->_req = nullptr; response->final = true; request->notify(); response->notify(); } void HttpServer::set_auth_handler(bool (*handler)(std::map& params, const std::string& body, const route_path& rpath, const std::string& auth_key)) { auth_handler = handler; } void HttpServer::get(const std::string & path, bool (*handler)(const std::shared_ptr&, const std::shared_ptr&), bool async_req, bool async_res) { std::vector path_parts; StringUtils::split(path, path_parts, "/"); route_path rpath("GET", path_parts, handler, async_req, async_res); routes.emplace_back(rpath.route_hash(), rpath); } void HttpServer::post(const std::string & path, bool (*handler)(const std::shared_ptr&, const std::shared_ptr&), bool async_req, bool async_res) { std::vector path_parts; StringUtils::split(path, path_parts, "/"); route_path rpath("POST", path_parts, handler, async_req, async_res); routes.emplace_back(rpath.route_hash(), rpath); } void HttpServer::put(const std::string & path, bool (*handler)(const std::shared_ptr&, const std::shared_ptr&), bool async_req, bool async_res) { std::vector path_parts; StringUtils::split(path, path_parts, "/"); route_path rpath("PUT", path_parts, handler, async_req, async_res); routes.emplace_back(rpath.route_hash(), rpath); } void HttpServer::patch(const std::string & path, bool (*handler)(const std::shared_ptr&, const std::shared_ptr&), bool async_req, bool async_res) { std::vector path_parts; StringUtils::split(path, path_parts, "/"); route_path rpath("PATCH", path_parts, handler, async_req, async_res); routes.emplace_back(rpath.route_hash(), rpath); } void HttpServer::del(const std::string & path, bool (*handler)(const std::shared_ptr&, const std::shared_ptr&), bool async_req, bool async_res) { std::vector path_parts; StringUtils::split(path, path_parts, "/"); route_path rpath("DELETE", path_parts, handler, async_req, async_res); routes.emplace_back(rpath.route_hash(), rpath); } void HttpServer::on(const std::string & message, bool (*handler)(void*)) { message_dispatcher->on(message, handler); } HttpServer::~HttpServer() { delete message_dispatcher; if(ssl_refresh_timer.timer.expire_at != 0) { // avoid callback since it recreates timeout clear_timeouts({&ssl_refresh_timer.timer}, false); } if(metrics_refresh_timer.timer.expire_at != 0) { // avoid callback since it recreates timeout clear_timeouts({&metrics_refresh_timer.timer}, false); } h2o_timerwheel_run(ctx.loop->_timeouts, 9999999999999); h2o_context_dispose(&ctx); if(ctx.globalconf->server_name.base != nullptr) { free(ctx.globalconf->server_name.base); ctx.globalconf->server_name.base = nullptr; } // Flaky, sometimes assertion on timeouts occur, preventing a clean shutdown //h2o_evloop_destroy(ctx.loop); h2o_config_dispose(&config); SSL_CTX_free(accept_ctx->ssl_ctx); delete accept_ctx; } http_message_dispatcher* HttpServer::get_message_dispatcher() const { return message_dispatcher; } ReplicationState* HttpServer::get_replication_state() const { return replication_state; } bool HttpServer::is_alive() const { return replication_state->is_alive(); } bool HttpServer::get_route(uint64_t hash, route_path** found_rpath) { for (auto& hash_route : routes) { if(hash_route.first == hash) { *found_rpath = &hash_route.second; return true; } } return false; } uint64_t HttpServer::node_state() const { return replication_state->node_state(); } bool HttpServer::on_stream_response_message(void *data) { //LOG(INFO) << "on_stream_response_message"; auto req_res = static_cast(data); stream_response(req_res->req, req_res->res); if(req_res->destroy_after_stream_response) { //LOG(INFO) << "delete req_res"; delete req_res; } return true; } bool HttpServer::on_request_proceed_message(void *data) { //LOG(INFO) << "on_request_proceed_message"; deferred_req_res_t* req_res = static_cast(data); auto stream_state = (req_res->req->last_chunk_aggregate) ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS; size_t written = req_res->req->chunk_len; req_res->req->chunk_len = 0; if(req_res->req->_req && req_res->req->_req->proceed_req) { req_res->req->_req->proceed_req(req_res->req->_req, written, stream_state); } return true; } bool HttpServer::on_deferred_processing_message(void *data) { //LOG(INFO) << "on_deferred_processing_message"; defer_processing_t* defer = static_cast(data); //LOG(INFO) << "defer req count: " << defer->req.use_count(); defer->server->defer_processing(defer->req, defer->res, defer->timeout_ms); //LOG(INFO) << "req use count: " << defer->req.use_count() << ", req " << defer->req.get(); delete defer; return true; } bool HttpServer::has_exited() const { return exit_loop; } void HttpServer::do_snapshot(const std::string& snapshot_path, const std::shared_ptr& req, const std::shared_ptr& res) { return replication_state->do_snapshot(snapshot_path, req, res); } bool HttpServer::trigger_vote() { return replication_state->trigger_vote(); } ThreadPool* HttpServer::get_thread_pool() const { return thread_pool; } bool HttpServer::initialize_ssl_ctx(const char *cert_file, const char *key_file, h2o_accept_ctx_t* accept_ctx) { SSL_CTX* new_ctx = SSL_CTX_new(SSLv23_server_method()); // As recommended by: // https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites SSL_CTX_set_cipher_list(new_ctx, "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:" "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:" "ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"); // Without this, DH and ECDH ciphers will be ignored by OpenSSL int nid = NID_X9_62_prime256v1; EC_KEY *key = EC_KEY_new_by_curve_name(nid); if (key == nullptr) { LOG(ERROR) << "Failed to create DH/ECDH."; return -1; } SSL_CTX_set_tmp_ecdh(new_ctx, key); EC_KEY_free(key); SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv3); SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1); SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_1); SSL_CTX_set_options(new_ctx, SSL_OP_SINGLE_ECDH_USE); if (SSL_CTX_use_certificate_chain_file(new_ctx, cert_file) != 1) { LOG(ERROR) << "An error occurred while trying to load server certificate file: " << cert_file; SSL_CTX_free(new_ctx); return false; } if (SSL_CTX_use_PrivateKey_file(new_ctx, key_file, SSL_FILETYPE_PEM) != 1) { LOG(ERROR) << "An error occurred while trying to load private key file: " << key_file; SSL_CTX_free(new_ctx); return false; } if(SSL_CTX_check_private_key(new_ctx) != 1) { LOG(ERROR) << "Private key validation failed for: " << key_file; SSL_CTX_free(new_ctx); return false; } h2o_ssl_register_alpn_protocols(new_ctx, h2o_http2_alpn_protocols); accept_ctx->ssl_ctx = new_ctx; return true; } void HttpServer::persist_applying_index() { return replication_state->persist_applying_index(); }