From d654ad0cc69ec32b799e0b49b5800ef75dae4a5c Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 4 Jun 2012 23:44:18 +0900 Subject: [PATCH 01/21] Fixed bug on_ctrl_recv_callback not called for RST_STREAM --- lib/spdylay_session.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index e428af7..4c4aa16 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -1815,6 +1815,8 @@ int spdylay_session_on_rst_stream_received(spdylay_session *session, if(!spdylay_session_check_version(session, frame->rst_stream.hd.version)) { return 0; } + spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_RST_STREAM, + frame); if(session->server && !spdylay_session_is_my_stream_id(session, frame->rst_stream.stream_id) && frame->rst_stream.status_code == SPDYLAY_CANCEL) { From fad7f51f8dd3effcae911cab5f256ceaa5711ae2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 4 Jun 2012 23:48:31 +0900 Subject: [PATCH 02/21] Initial commit of shrpx: SPDY/HTTPS to HTTP reverse proxy Put libhtparse in examples/htparse --- configure.ac | 8 + examples/.gitignore | 1 + examples/Makefile.am | 20 +- examples/shrpx.cc | 323 ++++++++++++++++++ examples/shrpx.h | 37 ++ examples/shrpx_client_handler.cc | 213 ++++++++++++ examples/shrpx_client_handler.h | 64 ++++ examples/shrpx_config.cc | 63 ++++ examples/shrpx_config.h | 74 ++++ examples/shrpx_downstream.cc | 412 ++++++++++++++++++++++ examples/shrpx_downstream.h | 111 ++++++ examples/shrpx_downstream_queue.cc | 67 ++++ examples/shrpx_downstream_queue.h | 52 +++ examples/shrpx_error.h | 38 +++ examples/shrpx_http.cc | 98 ++++++ examples/shrpx_http.h | 42 +++ examples/shrpx_https_upstream.cc | 464 +++++++++++++++++++++++++ examples/shrpx_https_upstream.h | 76 +++++ examples/shrpx_listen_handler.cc | 78 +++++ examples/shrpx_listen_handler.h | 51 +++ examples/shrpx_log.cc | 49 +++ examples/shrpx_log.h | 60 ++++ examples/shrpx_spdy_upstream.cc | 532 +++++++++++++++++++++++++++++ examples/shrpx_spdy_upstream.h | 71 ++++ examples/shrpx_upstream.h | 55 +++ examples/util.cc | 15 + examples/util.h | 2 + 27 files changed, 3073 insertions(+), 3 deletions(-) create mode 100644 examples/shrpx.cc create mode 100644 examples/shrpx.h create mode 100644 examples/shrpx_client_handler.cc create mode 100644 examples/shrpx_client_handler.h create mode 100644 examples/shrpx_config.cc create mode 100644 examples/shrpx_config.h create mode 100644 examples/shrpx_downstream.cc create mode 100644 examples/shrpx_downstream.h create mode 100644 examples/shrpx_downstream_queue.cc create mode 100644 examples/shrpx_downstream_queue.h create mode 100644 examples/shrpx_error.h create mode 100644 examples/shrpx_http.cc create mode 100644 examples/shrpx_http.h create mode 100644 examples/shrpx_https_upstream.cc create mode 100644 examples/shrpx_https_upstream.h create mode 100644 examples/shrpx_listen_handler.cc create mode 100644 examples/shrpx_listen_handler.h create mode 100644 examples/shrpx_log.cc create mode 100644 examples/shrpx_log.h create mode 100644 examples/shrpx_spdy_upstream.cc create mode 100644 examples/shrpx_spdy_upstream.h create mode 100644 examples/shrpx_upstream.h diff --git a/configure.ac b/configure.ac index 734f6f9..4d6a04a 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,14 @@ if test "x${have_openssl}" = "xno"; then AC_MSG_NOTICE([The example programs will not be built.]) fi +# libevent_openssl +PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.6], + [have_libevent_openssl=yes], [have_libevent_openssl=no]) +if test "x${have_libevent_openssl}" = "xno"; then + AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS) + AC_MSG_NOTICE([Shrpx example program will not be built.]) +fi + # libxml2 (for examples/spdycat) AM_PATH_XML2(2.7.7, [have_libxml2=yes], [have_libxml2=no]) if test "x${have_libxml2}" = "xyes"; then diff --git a/examples/.gitignore b/examples/.gitignore index 8f77826..e6b6a0f 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -2,3 +2,4 @@ spdycat spdyd spdynative spdycli +shrpx diff --git a/examples/Makefile.am b/examples/Makefile.am index 392c819..42d97a4 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -25,11 +25,11 @@ if ENABLE_EXAMPLES AM_CFLAGS = -Wall AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \ - @OPENSSL_CFLAGS@ @XML_CPPFLAGS@ @DEFS@ -AM_LDFLAGS = @OPENSSL_LIBS@ @XML_LIBS@ + @OPENSSL_CFLAGS@ @XML_CPPFLAGS@ @LIBEVENT_OPENSSL_CFLAGS@ @DEFS@ +AM_LDFLAGS = @OPENSSL_LIBS@ @XML_LIBS@ @LIBEVENT_OPENSSL_LIBS@ LDADD = $(top_builddir)/lib/libspdylay.la -bin_PROGRAMS = spdycat spdyd +bin_PROGRAMS = spdycat spdyd shrpx HELPER_OBJECTS = uri.cc util.cc spdylay_ssl.cc HELPER_HFILES = uri.h util.h spdylay_ssl.h @@ -65,6 +65,20 @@ spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_HFILES} \ spdyd.cc +shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ + shrpx_config.cc shrpx_config.h \ + shrpx.cc \ + shrpx_listen_handler.cc shrpx_listen_handler.h \ + shrpx_client_handler.cc shrpx_client_handler.h \ + shrpx_upstream.h \ + shrpx_spdy_upstream.cc shrpx_spdy_upstream.h \ + shrpx_https_upstream.cc shrpx_https_upstream.h \ + shrpx_downstream_queue.cc shrpx_downstream_queue.h \ + shrpx_downstream.cc shrpx_downstream.h \ + shrpx_log.cc shrpx_log.h \ + shrpx_http.cc shrpx_http.h \ + htparse/htparse.c htparse/htparse.h + noinst_PROGRAMS = spdycli spdycli_SOURCES = spdycli.c diff --git a/examples/shrpx.cc b/examples/shrpx.cc new file mode 100644 index 0000000..ba57abb --- /dev/null +++ b/examples/shrpx.cc @@ -0,0 +1,323 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx.h" + +#include +#include +#include +#include +#include + + +#include +#include + +#include +#include + +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_listen_handler.h" + +namespace shrpx { + +namespace { +std::pair next_proto; +unsigned char proto_list[23]; +} // namespace + +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) +{ + std::pair *next_proto = + reinterpret_cast* >(arg); + *data = next_proto->first; + *len = next_proto->second; + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +namespace { +int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + // We don't verify the client certificate. Just request it for the + // testing purpose. + return 1; +} +} // namespace + +namespace { +SSL_CTX* create_ssl_ctx() +{ + // TODO lock function + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ssl_ctx) { + std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; + return NULL; + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + if(SSL_CTX_use_PrivateKey_file(ssl_ctx, + get_config()->private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; + return NULL; + } + if(SSL_CTX_use_certificate_file(ssl_ctx, get_config()->cert_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; + return NULL; + } + if(SSL_CTX_check_private_key(ssl_ctx) != 1) { + std::cerr << "SSL_CTX_check_private_key failed." << std::endl; + return NULL; + } + if(get_config()->verify_client) { + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + // We speaks "http/1.1", "spdy/2" and "spdy/3". + proto_list[0] = 6; + memcpy(&proto_list[1], "spdy/3", 6); + proto_list[7] = 6; + memcpy(&proto_list[8], "spdy/2", 6); + proto_list[14] = 8; + memcpy(&proto_list[15], "http/1.1", 8); + + next_proto.first = proto_list; + next_proto.second = sizeof(proto_list); + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); + return ssl_ctx; +} +} // namespace + +namespace { +void ssl_acceptcb(evconnlistener *listener, int fd, + sockaddr *addr, int addrlen, void *arg) +{ + ListenHandler *handler = reinterpret_cast(arg); + handler->accept_connection(fd, addr, addrlen); +} +} // namespace + +namespace { +int cache_downstream_host_address() +{ + addrinfo hints; + int rv; + char service[10]; + snprintf(service, sizeof(service), "%u", get_config()->downstream_port); + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + addrinfo *res, *rp; + rv = getaddrinfo(get_config()->downstream_host, service, &hints, &res); + if(rv != 0) { + LOG(ERROR) << "getaddrinfo: " << gai_strerror(rv); + return -1; + } + for(rp = res; rp; rp = rp->ai_next) { + int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(fd == -1) { + continue; + } + rv = connect(fd, rp->ai_addr, rp->ai_addrlen); + close(fd); + if(rv == -1) { + continue; + } + break; + } + if(rp == 0 && res) { + LOG(INFO) << "Using first returned address for downstream " + << get_config()->downstream_host + << ", port " + << get_config()->downstream_port; + rp = res; + } + if(rp != 0) { + memcpy(&mod_config()->downstream_addr, rp->ai_addr, rp->ai_addrlen); + mod_config()->downstream_addrlen = rp->ai_addrlen; + } + freeaddrinfo(res); + if(rp == 0) { + LOG(ERROR) << "No usable address found for downstream " + << get_config()->downstream_host + << ", port " + << get_config()->downstream_port; + return -1; + } else { + return 0; + } +} +} // namespace + +namespace { +evconnlistener* create_evlistener(ListenHandler *handler) +{ + // TODO Listen both IPv4 and IPv6 + addrinfo hints; + int fd = -1; + int r; + char service[10]; + snprintf(service, sizeof(service), "%u", get_config()->port); + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + addrinfo *res, *rp; + r = getaddrinfo(get_config()->host, service, &hints, &res); + if(r != 0) { + LOG(ERROR) << "getaddrinfo: " << gai_strerror(r); + return NULL; + } + for(rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(fd == -1) { + continue; + } + int val = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + close(fd); + continue; + } + evutil_make_socket_nonblocking(fd); + if(bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) { + break; + } + close(fd); + } + freeaddrinfo(res); + if(rp == 0) { + LOG(ERROR) << "No valid address returned for host " << get_config()->host + << ", port " << get_config()->port; + return 0; + } + evconnlistener *evlistener = evconnlistener_new + (handler->get_evbase(), + ssl_acceptcb, + handler, + LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, + 256, + fd); + return evlistener; +} +} // namespace + +namespace { +int event_loop() +{ + SSL_CTX *ssl_ctx = create_ssl_ctx(); + if(ssl_ctx == NULL) { + return -1; + } + event_base *evbase = event_base_new(); + ListenHandler *listener_handler = new ListenHandler(evbase, ssl_ctx); + evconnlistener *evlistener = create_evlistener(listener_handler); + if(evlistener == NULL) { + return -1; + } + if(ENABLE_LOG) { + LOG(INFO) << "Entering event loop"; + } + event_base_loop(evbase, 0); + + evconnlistener_free(evlistener); + SSL_CTX_free(ssl_ctx); + return 0; +} +} // namespace + +int main(int argc, char **argv) +{ + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_library_init(); + + create_config(); + mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; + mod_config()->port = 3000; + mod_config()->private_key_file = "server.key"; + mod_config()->cert_file = "server.crt"; + + mod_config()->upstream_read_timeout.tv_sec = 30; + mod_config()->upstream_read_timeout.tv_usec = 0; + mod_config()->upstream_write_timeout.tv_sec = 30; + mod_config()->upstream_write_timeout.tv_usec = 0; + + mod_config()->spdy_upstream_read_timeout.tv_sec = 600; + mod_config()->spdy_upstream_read_timeout.tv_usec = 0; + mod_config()->spdy_upstream_write_timeout.tv_sec = 30; + mod_config()->spdy_upstream_write_timeout.tv_usec = 0; + + mod_config()->downstream_read_timeout.tv_sec = 30; + mod_config()->downstream_read_timeout.tv_usec = 0; + mod_config()->downstream_write_timeout.tv_sec = 30; + mod_config()->downstream_write_timeout.tv_usec = 0; + + mod_config()->downstream_host = "localhost"; + mod_config()->downstream_port = 80; + char hostport[256]; + if(get_config()->downstream_port == 80) { + mod_config()->downstream_hostport = get_config()->downstream_host; + } else { + snprintf(hostport, sizeof(hostport), "%s:%u", + get_config()->downstream_host, get_config()->downstream_port); + mod_config()->downstream_hostport = hostport; + } + if(cache_downstream_host_address() == -1) { + exit(EXIT_FAILURE); + } + event_loop(); + return 0; +} + +} // namespace shrpx + +int main(int argc, char **argv) +{ + return shrpx::main(argc, argv); +} diff --git a/examples/shrpx.h b/examples/shrpx.h new file mode 100644 index 0000000..2258b43 --- /dev/null +++ b/examples/shrpx.h @@ -0,0 +1,37 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_H +#define SHRPX_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +#include "shrpx_log.h" + +#define DIE() \ + assert(0); + +#endif // SHRPX_H diff --git a/examples/shrpx_client_handler.cc b/examples/shrpx_client_handler.cc new file mode 100644 index 0000000..4fceaae --- /dev/null +++ b/examples/shrpx_client_handler.cc @@ -0,0 +1,213 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_client_handler.h" + +#include "shrpx_upstream.h" +#include "shrpx_spdy_upstream.h" +#include "shrpx_https_upstream.h" +#include "shrpx_config.h" + +namespace shrpx { + +namespace { +void upstream_readcb(bufferevent *bev, void *arg) +{ + ClientHandler *handler = reinterpret_cast(arg); + int rv = handler->on_read(); + if(rv != 0) { + if(ENABLE_LOG) { + LOG(INFO) << " Read operation (application level) failure"; + } + delete handler; + } +} +} // namespace + +namespace { +void upstream_writecb(bufferevent *bev, void *arg) +{ + if(ENABLE_LOG) { + LOG(INFO) << " upstream_writecb"; + } + ClientHandler *handler = reinterpret_cast(arg); + if(handler->get_should_close_after_write()) { + delete handler; + } +} +} // namespace + +namespace { +void upstream_eventcb(bufferevent *bev, short events, void *arg) +{ + ClientHandler *handler = reinterpret_cast(arg); + bool finish = false; + if(events & BEV_EVENT_EOF) { + if(ENABLE_LOG) { + LOG(INFO) << " SSL/TLS handshake EOF"; + } + finish = true; + } + if(events & BEV_EVENT_ERROR) { + if(ENABLE_LOG) { + LOG(INFO) << " SSL/TLS network error"; + } + finish = true; + } + if(events & BEV_EVENT_TIMEOUT) { + if(ENABLE_LOG) { + LOG(INFO) << "SPDY upstream SSL/TLS time out"; + } + finish = true; + } + if(finish) { + delete handler; + } else { + if(events & BEV_EVENT_CONNECTED) { + if(ENABLE_LOG) { + LOG(INFO) << "Connected Handler " + << handler; + } + handler->set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); + handler->validate_next_proto(); + // At this point, input buffer is already filled with some + // bytes. The read callback is not called until new data + // come. So consume input buffer here. + handler->get_upstream()->on_read(); + } + } +} +} // namespace + +ClientHandler::ClientHandler(bufferevent *bev, SSL *ssl, const char *ipaddr) + : bev_(bev), + ssl_(ssl), + upstream_(0), + ipaddr_(ipaddr), + should_close_after_write_(false) +{ + bufferevent_enable(bev_, EV_READ | EV_WRITE); + set_upstream_timeouts(&get_config()->upstream_read_timeout, + &get_config()->upstream_write_timeout); + set_bev_cb(0, upstream_writecb, upstream_eventcb); +} + +ClientHandler::~ClientHandler() +{ + if(ENABLE_LOG) { + LOG(INFO) << "Deleting ClientHandler " << this; + } + int fd = SSL_get_fd(ssl_); + SSL_shutdown(ssl_); + bufferevent_disable(bev_, EV_READ | EV_WRITE); + bufferevent_free(bev_); + SSL_free(ssl_); + shutdown(fd, SHUT_WR); + close(fd); + delete upstream_; + if(ENABLE_LOG) { + LOG(INFO) << "Deleted"; + } +} + +Upstream* ClientHandler::get_upstream() +{ + return upstream_; +} + +bufferevent* ClientHandler::get_bev() const +{ + return bev_; +} + +event_base* ClientHandler::get_evbase() const +{ + return bufferevent_get_base(bev_); +} + +void ClientHandler::set_bev_cb +(bufferevent_data_cb readcb, bufferevent_data_cb writecb, + bufferevent_event_cb eventcb) +{ + bufferevent_setcb(bev_, readcb, writecb, eventcb, this); +} + +void ClientHandler::set_upstream_timeouts(const timeval *read_timeout, + const timeval *write_timeout) +{ + bufferevent_set_timeouts(bev_, read_timeout, write_timeout); +} + +int ClientHandler::validate_next_proto() +{ + const unsigned char *next_proto = 0; + unsigned int next_proto_len; + SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len); + if(next_proto) { + std::string proto(next_proto, next_proto+next_proto_len); + if(ENABLE_LOG) { + LOG(INFO) << " The negotiated next protocol: " << proto; + } + uint16_t version = spdylay_npn_get_version(next_proto, next_proto_len); + if(version) { + SpdyUpstream *spdy_upstream = new SpdyUpstream(version, this); + upstream_ = spdy_upstream; + return 0; + } + } else { + if(ENABLE_LOG) { + LOG(INFO) << " No proto negotiated"; + } + } + HttpsUpstream *https_upstream = new HttpsUpstream(this); + upstream_ = https_upstream; + return 0; +} + +int ClientHandler::on_read() +{ + return upstream_->on_read(); +} + +int ClientHandler::on_event() +{ + return upstream_->on_event(); +} + +const std::string& ClientHandler::get_ipaddr() const +{ + return ipaddr_; +} + +bool ClientHandler::get_should_close_after_write() const +{ + return should_close_after_write_; +} + +void ClientHandler::set_should_close_after_write(bool f) +{ + should_close_after_write_ = f; +} + +} // namespace shrpx diff --git a/examples/shrpx_client_handler.h b/examples/shrpx_client_handler.h new file mode 100644 index 0000000..0392f76 --- /dev/null +++ b/examples/shrpx_client_handler.h @@ -0,0 +1,64 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CLIENT_HANDLER_H +#define SHRPX_CLIENT_HANDLER_H + +#include "shrpx.h" + +#include +#include + +namespace shrpx { + +class Upstream; + +class ClientHandler { +public: + ClientHandler(bufferevent *bev, SSL *ssl, const char *ipaddr); + ~ClientHandler(); + int on_read(); + int on_event(); + bufferevent* get_bev() const; + event_base* get_evbase() const; + void set_bev_cb(bufferevent_data_cb readcb, bufferevent_data_cb writecb, + bufferevent_event_cb eventcb); + void set_upstream_timeouts(const timeval *read_timeout, + const timeval *write_timeout); + int validate_next_proto(); + const std::string& get_ipaddr() const; + bool get_should_close_after_write() const; + void set_should_close_after_write(bool f); + Upstream* get_upstream(); +private: + bufferevent *bev_; + SSL *ssl_; + Upstream *upstream_; + std::string ipaddr_; + bool should_close_after_write_; +}; + +} // namespace shrpx + +#endif // SHRPX_CLIENT_HANDLER_H diff --git a/examples/shrpx_config.cc b/examples/shrpx_config.cc new file mode 100644 index 0000000..234f7eb --- /dev/null +++ b/examples/shrpx_config.cc @@ -0,0 +1,63 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_config.h" + +namespace shrpx { + +Config::Config() + : verbose(false), + daemon(false), + host(0), + port(0), + private_key_file(0), + cert_file(0), + verify_client(false), + server_name(0), + downstream_host(0), + downstream_port(0), + downstream_hostport(0), + downstream_addrlen(0) +{} + +namespace { +Config *config = 0; +} // namespace + +const Config* get_config() +{ + return config; +} + +Config* mod_config() +{ + return config; +} + +void create_config() +{ + config = new Config(); +} + +} // namespace shrpx diff --git a/examples/shrpx_config.h b/examples/shrpx_config.h new file mode 100644 index 0000000..9b95588 --- /dev/null +++ b/examples/shrpx_config.h @@ -0,0 +1,74 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONFIG_H +#define SHRPX_CONFIG_H + +#include +#include +#include +#include +#include + +#include + +namespace shrpx { + +union sockaddr_union { + sockaddr sa; + sockaddr_storage storage; + sockaddr_in6 in6; + sockaddr_in in; +}; + +struct Config { + bool verbose; + bool daemon; + const char *host; + uint16_t port; + const char *private_key_file; + const char *cert_file; + bool verify_client; + const char *server_name; + const char *downstream_host; + uint16_t downstream_port; + const char *downstream_hostport; + sockaddr_union downstream_addr; + size_t downstream_addrlen; + timeval upstream_read_timeout; + timeval upstream_write_timeout; + timeval spdy_upstream_read_timeout; + timeval spdy_upstream_write_timeout; + timeval downstream_read_timeout; + timeval downstream_write_timeout; + Config(); +}; + +const Config* get_config(); +Config* mod_config(); +void create_config(); + +} // namespace shrpx + +#endif // SHRPX_CONFIG_H diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc new file mode 100644 index 0000000..b8755b8 --- /dev/null +++ b/examples/shrpx_downstream.cc @@ -0,0 +1,412 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream.h" + +#include + +#include "shrpx_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "util.h" + +using namespace spdylay; + +namespace shrpx { + +Downstream::Downstream(Upstream *upstream, int stream_id, int priority) + : upstream_(upstream), + bev_(0), + stream_id_(stream_id), + priority_(priority), + request_state_(INITIAL), + chunked_request_(false), + request_connection_close_(false), + response_state_(INITIAL), + response_http_status_(0), + chunked_response_(false), + response_htp_(htparser_new()), + response_body_buf_(0) +{ + htparser_init(response_htp_, htp_type_response); + htparser_set_userdata(response_htp_, this); + event_base *evbase = upstream_->get_client_handler()->get_evbase(); + bev_ = bufferevent_socket_new + (evbase, -1, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); +} + +Downstream::~Downstream() +{ + if(ENABLE_LOG) { + LOG(INFO) << "Deleting downstream" << this; + } + if(response_body_buf_) { + // Passing NULL to evbuffer_free() causes segmentation fault. + evbuffer_free(response_body_buf_); + } + bufferevent_disable(bev_, EV_READ | EV_WRITE); + bufferevent_free(bev_); + free(response_htp_); + if(ENABLE_LOG) { + LOG(INFO) << "Deleted"; + } +} + +namespace { +void check_transfer_encoding_chunked(bool *chunked, + const Headers::value_type &item) +{ + if(util::strieq(item.first.c_str(), "transfer-encoding")) { + if(util::strifind(item.second.c_str(), "chunked")) { + *chunked = true; + } + } +} +} // namespace + +namespace { +void check_request_connection(bool *connection_close, + const Headers::value_type &item) +{ + if(util::strieq(item.first.c_str(), "connection")) { + if(util::strifind(item.second.c_str(), "close")) { + *connection_close = true; + } + } +} +} // namespace + +void Downstream::add_request_header(const std::string& name, + const std::string& value) +{ + request_headers_.push_back(std::make_pair(name, value)); +} + +void Downstream::set_last_request_header_value(const std::string& value) +{ + Headers::value_type &item = request_headers_.back(); + item.second = value; + check_transfer_encoding_chunked(&chunked_request_, item); + check_request_connection(&request_connection_close_, item); +} + +void Downstream::set_request_method(const std::string& method) +{ + request_method_ = method; +} + +void Downstream::set_request_path(const std::string& path) +{ + request_path_ = path; +} + +Upstream* Downstream::get_upstream() const +{ + return upstream_; +} + +int32_t Downstream::get_stream_id() const +{ + return stream_id_; +} + +int Downstream::start_connection() +{ + bufferevent_setcb(bev_, + upstream_->get_downstream_readcb(), + upstream_->get_downstream_writecb(), + upstream_->get_downstream_eventcb(), this); + bufferevent_enable(bev_, EV_READ | EV_WRITE); + bufferevent_set_timeouts(bev_, + &get_config()->downstream_read_timeout, + &get_config()->downstream_write_timeout); + bufferevent_socket_connect + (bev_, + // TODO maybe not thread-safe? + const_cast(&get_config()->downstream_addr.sa), + get_config()->downstream_addrlen); + return 0; +} + +void Downstream::set_request_state(int state) +{ + request_state_ = state; +} + +int Downstream::get_request_state() const +{ + return request_state_; +} + +bool Downstream::get_chunked_request() const +{ + return chunked_request_; +} + +bool Downstream::get_request_connection_close() const +{ + return request_connection_close_; +} + +void Downstream::set_request_connection_close(bool f) +{ + request_connection_close_ = f; +} + +int Downstream::push_request_headers() +{ + bool xff_found = false; + std::string hdrs = request_method_; + hdrs += " "; + hdrs += request_path_; + hdrs += " "; + hdrs += "HTTP/1.1\r\n"; + hdrs += "Host: "; + hdrs += get_config()->downstream_hostport; + hdrs += "\r\n"; + // TODO Rewrite user-agent? + for(Headers::const_iterator i = request_headers_.begin(); + i != request_headers_.end(); ++i) { + if(util::strieq((*i).first.c_str(), "X-Forwarded-Proto")) { + continue; + } + if(util::strieq((*i).first.c_str(), "user-agent")) { + hdrs += "User-Agent: "; + hdrs += get_config()->server_name; + } else { + hdrs += (*i).first; + hdrs += ": "; + hdrs += (*i).second; + if(!xff_found && util::strieq((*i).first.c_str(), "X-Forwarded-For")) { + xff_found = true; + hdrs += ", "; + hdrs += upstream_->get_client_handler()->get_ipaddr(); + } + } + hdrs += "\r\n"; + } + hdrs += "Connection: close\r\n"; + if(!xff_found) { + hdrs += "X-Forwarded-For: "; + hdrs += upstream_->get_client_handler()->get_ipaddr(); + hdrs += "\r\n"; + } + hdrs += "X-Forwarded-Proto: https\r\n"; + + hdrs += "\r\n"; + if(ENABLE_LOG) { + LOG(INFO) << " request headers\n" << hdrs; + } + evbuffer *output = bufferevent_get_output(bev_); + evbuffer_add(output, hdrs.c_str(), hdrs.size()); + return 0; +} + +int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen) +{ + // Assumes that request headers have already been pushed to output + // buffer using push_request_headers(). + ssize_t res = 0; + int rv; + evbuffer *output = bufferevent_get_output(bev_); + if(chunked_request_) { + char chunk_size_hex[16]; + rv = snprintf(chunk_size_hex, sizeof(chunk_size_hex), "%X\r\n", + static_cast(datalen)); + evbuffer_add(output, chunk_size_hex, rv); + res += rv; + } + rv = evbuffer_add(output, data, datalen); + if(rv == -1) { + return -1; + } + res += rv; + return res; +} + +int Downstream::end_upload_data() +{ + if(chunked_request_) { + evbuffer *output = bufferevent_get_output(bev_); + evbuffer_add(output, "0\r\n\r\n", 5); + } + return 0; +} + +const Headers& Downstream::get_response_headers() const +{ + return response_headers_; +} + +void Downstream::add_response_header(const std::string& name, + const std::string& value) +{ + response_headers_.push_back(std::make_pair(name, value)); +} + +void Downstream::set_last_response_header_value(const std::string& value) +{ + Headers::value_type &item = response_headers_.back(); + item.second = value; + check_transfer_encoding_chunked(&chunked_response_, item); +} + +unsigned int Downstream::get_response_http_status() const +{ + return response_http_status_; +} + +void Downstream::set_response_http_status(unsigned int status) +{ + response_http_status_ = status; +} + +bool Downstream::get_chunked_response() const +{ + return chunked_response_; +} + +namespace { +int htp_hdrs_completecb(htparser *htp) +{ + Downstream *downstream; + downstream = reinterpret_cast(htparser_get_userdata(htp)); + downstream->set_response_http_status(htparser_get_status(htp)); + downstream->set_response_state(Downstream::HEADER_COMPLETE); + downstream->get_upstream()->on_downstream_header_complete(downstream); + return 0; +} +} // namespace + +namespace { +int htp_hdr_keycb(htparser *htp, const char *data, size_t len) +{ + Downstream *downstream; + downstream = reinterpret_cast(htparser_get_userdata(htp)); + downstream->add_response_header(std::string(data, len), ""); + return 0; +} +} // namespace + +namespace { +int htp_hdr_valcb(htparser *htp, const char *data, size_t len) +{ + Downstream *downstream; + downstream = reinterpret_cast(htparser_get_userdata(htp)); + downstream->set_last_response_header_value(std::string(data, len)); + return 0; +} +} // namespace + +namespace { +int htp_bodycb(htparser *htp, const char *data, size_t len) +{ + Downstream *downstream; + downstream = reinterpret_cast(htparser_get_userdata(htp)); + downstream->get_upstream()->on_downstream_body + (downstream, reinterpret_cast(data), len); + return 0; +} +} // namespace + +namespace { +int htp_body_completecb(htparser *htp) +{ + Downstream *downstream; + downstream = reinterpret_cast(htparser_get_userdata(htp)); + downstream->set_response_state(Downstream::MSG_COMPLETE); + downstream->get_upstream()->on_downstream_body_complete(downstream); + return 0; +} +} // namespace + +namespace { +htparse_hooks htp_hooks = { + 0, /*htparse_hook on_msg_begin;*/ + 0, /*htparse_data_hook method;*/ + 0, /* htparse_data_hook scheme;*/ + 0, /* htparse_data_hook host; */ + 0, /* htparse_data_hook port; */ + 0, /* htparse_data_hook path; */ + 0, /* htparse_data_hook args; */ + 0, /* htparse_data_hook uri; */ + 0, /* htparse_hook on_hdrs_begin; */ + htp_hdr_keycb, /* htparse_data_hook hdr_key; */ + htp_hdr_valcb, /* htparse_data_hook hdr_val; */ + htp_hdrs_completecb, /* htparse_hook on_hdrs_complete; */ + 0, /*htparse_hook on_new_chunk;*/ + 0, /*htparse_hook on_chunk_complete;*/ + 0, /*htparse_hook on_chunks_complete;*/ + htp_bodycb, /* htparse_data_hook body; */ + htp_body_completecb /* htparse_hook on_msg_complete;*/ +}; +} // namespace + +int Downstream::parse_http_response() +{ + evbuffer *input = bufferevent_get_input(bev_); + unsigned char *mem = evbuffer_pullup(input, -1); + size_t nread = htparser_run(response_htp_, &htp_hooks, + reinterpret_cast(mem), + evbuffer_get_length(input)); + evbuffer_drain(input, nread); + if(htparser_get_error(response_htp_) == htparse_error_none) { + return 0; + } else { + if(ENABLE_LOG) { + LOG(INFO) << " http parser failure: " + << htparser_get_strerror(response_htp_); + } + return SHRPX_ERR_HTTP_PARSE; + } +} + +void Downstream::set_response_state(int state) +{ + response_state_ = state; +} + +int Downstream::get_response_state() const +{ + return response_state_; +} + +int Downstream::init_response_body_buf() +{ + assert(response_body_buf_ == 0); + response_body_buf_ = evbuffer_new(); + if(response_body_buf_ == 0) { + DIE(); + } + return 0; +} + +evbuffer* Downstream::get_response_body_buf() +{ + return response_body_buf_; +} + +} // namespace shrpx diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h new file mode 100644 index 0000000..d4b824d --- /dev/null +++ b/examples/shrpx_downstream.h @@ -0,0 +1,111 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_H +#define SHRPX_DOWNSTREAM_H + +#include "shrpx.h" + +#include + +#include +#include + +#include +#include + +extern "C" { +#include "htparse/htparse.h" +} + +namespace shrpx { + +class Upstream; + +typedef std::vector > Headers; + +class Downstream { +public: + Downstream(Upstream *upstream, int stream_id, int priority); + ~Downstream(); + int start_connection(); + Upstream* get_upstream() const; + int32_t get_stream_id() const; + // downstream request API + const Headers& get_request_headers() const; + void add_request_header(const std::string& name, const std::string& value); + void set_last_request_header_value(const std::string& value); + void set_request_method(const std::string& method); + void set_request_path(const std::string& path); + int push_request_headers(); + bool get_chunked_request() const; + bool get_request_connection_close() const; + void set_request_connection_close(bool f); + int push_upload_data_chunk(const uint8_t *data, size_t datalen); + int end_upload_data(); + enum { + INITIAL, + HEADER_COMPLETE, + MSG_COMPLETE, + STREAM_CLOSED + }; + void set_request_state(int state); + int get_request_state() const; + // downstream response API + const Headers& get_response_headers() const; + void add_response_header(const std::string& name, const std::string& value); + void set_last_response_header_value(const std::string& value); + unsigned int get_response_http_status() const; + void set_response_http_status(unsigned int status); + bool get_chunked_response() const; + int parse_http_response(); + void set_response_state(int state); + int get_response_state() const; + int init_response_body_buf(); + evbuffer* get_response_body_buf(); +private: + Upstream *upstream_; + bufferevent *bev_; + int32_t stream_id_; + int priority_; + + int request_state_; + std::string request_method_; + std::string request_path_; + bool chunked_request_; + bool request_connection_close_; + Headers request_headers_; + + int response_state_; + unsigned int response_http_status_; + bool chunked_response_; + Headers response_headers_; + htparser *response_htp_; + + evbuffer *response_body_buf_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_H diff --git a/examples/shrpx_downstream_queue.cc b/examples/shrpx_downstream_queue.cc new file mode 100644 index 0000000..8c61d83 --- /dev/null +++ b/examples/shrpx_downstream_queue.cc @@ -0,0 +1,67 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream_queue.h" + +#include "shrpx_downstream.h" + +namespace shrpx { + +DownstreamQueue::DownstreamQueue() +{} + +DownstreamQueue::~DownstreamQueue() +{ + for(std::map::iterator i = downstreams_.begin(); + i != downstreams_.end(); ++i) { + delete (*i).second; + } +} + +void DownstreamQueue::add(Downstream *downstream) +{ + downstreams_[downstream->get_stream_id()] = downstream; +} + +void DownstreamQueue::start(Downstream *downstream) +{ + downstream->start_connection(); +} + +void DownstreamQueue::remove(Downstream *downstream) +{ + downstreams_.erase(downstream->get_stream_id()); +} + +Downstream* DownstreamQueue::find(int32_t stream_id) +{ + std::map::iterator i = downstreams_.find(stream_id); + if(i == downstreams_.end()) { + return 0; + } else { + return (*i).second; + } +} + +} // namespace shrpx diff --git a/examples/shrpx_downstream_queue.h b/examples/shrpx_downstream_queue.h new file mode 100644 index 0000000..d8c76c8 --- /dev/null +++ b/examples/shrpx_downstream_queue.h @@ -0,0 +1,52 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_QUEUE_H +#define SHRPX_DOWNSTREAM_QUEUE_H + +#include "shrpx.h" + +#include + +#include + +namespace shrpx { + +class Downstream; + +class DownstreamQueue { +public: + DownstreamQueue(); + ~DownstreamQueue(); + void add(Downstream *downstream); + void start(Downstream *downstream); + void remove(Downstream *downstream); + Downstream* find(int32_t stream_id); +private: + std::map downstreams_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_QUEUE_H diff --git a/examples/shrpx_error.h b/examples/shrpx_error.h new file mode 100644 index 0000000..0c8dbd3 --- /dev/null +++ b/examples/shrpx_error.h @@ -0,0 +1,38 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_ERROR_H +#define SHRPX_ERROR_H + +namespace shrpx { + +enum ErrorCode { + SHRPX_ERR_SUCCESS = 0, + SHRPX_ERR_UNKNOWN = -1, + SHRPX_ERR_HTTP_PARSE = -2 +}; + +} // namespace shrpx + +#endif // SHRPX_ERROR_H diff --git a/examples/shrpx_http.cc b/examples/shrpx_http.cc new file mode 100644 index 0000000..8d775cf --- /dev/null +++ b/examples/shrpx_http.cc @@ -0,0 +1,98 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http.h" + +#include + +#include "shrpx_config.h" +namespace shrpx { + +namespace http { + +const char* get_status_string(int status_code) +{ + switch(status_code) { + case 100: return "100 Continue"; + case 101: return "101 Switching Protocols"; + case 200: return "200 OK"; + case 201: return "201 Created"; + case 202: return "202 Accepted"; + case 203: return "203 Non-Authoritative Information"; + case 204: return "204 No Content"; + case 205: return "205 Reset Content"; + case 206: return "206 Partial Content"; + case 300: return "300 Multiple Choices"; + case 301: return "301 Moved Permanently"; + case 302: return "302 Found"; + case 303: return "303 See Other"; + case 304: return "304 Not Modified"; + case 305: return "305 Use Proxy"; + // case 306: return "306 (Unused)"; + case 307: return "307 Temporary Redirect"; + case 400: return "400 Bad Request"; + case 401: return "401 Unauthorized"; + case 402: return "402 Payment Required"; + case 403: return "403 Forbidden"; + case 404: return "404 Not Found"; + case 405: return "405 Method Not Allowed"; + case 406: return "406 Not Acceptable"; + case 407: return "407 Proxy Authentication Required"; + case 408: return "408 Request Timeout"; + case 409: return "409 Conflict"; + case 410: return "410 Gone"; + case 411: return "411 Length Required"; + case 412: return "412 Precondition Failed"; + case 413: return "413 Request Entity Too Large"; + case 414: return "414 Request-URI Too Long"; + case 415: return "415 Unsupported Media Type"; + case 416: return "416 Requested Range Not Satisfiable"; + case 417: return "417 Expectation Failed"; + case 500: return "500 Internal Server Error"; + case 501: return "501 Not Implemented"; + case 502: return "502 Bad Gateway"; + case 503: return "503 Service Unavailable"; + case 504: return "504 Gateway Timeout"; + case 505: return "505 HTTP Version Not Supported"; + default: return ""; + } +} + +std::string create_error_html(int status_code) +{ + std::stringstream ss; + const char *status = http::get_status_string(status_code); + ss << "" << status << "" + << "

" << status << "

" + << "
" + << "
" << get_config()->server_name << " at port " + << get_config()->port + << "
" + << ""; + return ss.str(); +} + +} // namespace http + +} // namespace shrpx diff --git a/examples/shrpx_http.h b/examples/shrpx_http.h new file mode 100644 index 0000000..75b1b18 --- /dev/null +++ b/examples/shrpx_http.h @@ -0,0 +1,42 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP_H +#define SHRPX_HTTP_H + +#include + +namespace shrpx { + +namespace http { + +const char* get_status_string(int status_code); + +std::string create_error_html(int status_code); + +} // namespace http + +} // namespace shrpx + +#endif // SHRPX_HTTP_H diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc new file mode 100644 index 0000000..92e6e54 --- /dev/null +++ b/examples/shrpx_https_upstream.cc @@ -0,0 +1,464 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_https_upstream.h" + +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_http.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "util.h" + +using namespace spdylay; + +namespace shrpx { + +HttpsUpstream::HttpsUpstream(ClientHandler *handler) + : handler_(handler), + htp_(htparser_new()) +{ + if(ENABLE_LOG) { + LOG(INFO) << "HttpsUpstream ctor"; + } + htparser_init(htp_, htp_type_request); + htparser_set_userdata(htp_, this); +} + +HttpsUpstream::~HttpsUpstream() +{ + free(htp_); + for(std::deque::iterator i = downstream_queue_.begin(); + i != downstream_queue_.end(); ++i) { + delete *i; + } +} + +namespace { +int htp_msg_begin(htparser *htp) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: request start"; + } + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = new Downstream(upstream, 0, 0); + upstream->add_downstream(downstream); + return 0; +} +} // namespace + +namespace { +int htp_methodcb(htparser *htp, const char *data, size_t len) +{ + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->set_request_method(std::string(data, len)); + return 0; +} +} // namespace + +namespace { +int htp_uricb(htparser *htp, const char *data, size_t len) +{ + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->set_request_path(std::string(data, len)); + return 0; +} +} // namespace + +namespace { +int htp_hdrs_begincb(htparser *htp) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: request headers start"; + } + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + + int version = htparser_get_major(htp)*100 + htparser_get_minor(htp); + if(version < 101) { + downstream->set_request_connection_close(true); + } + return 0; +} +} // namespace + +namespace { +int htp_hdr_keycb(htparser *htp, const char *data, size_t len) +{ + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->add_request_header(std::string(data, len), ""); + return 0; +} +} // namespace + +namespace { +int htp_hdr_valcb(htparser *htp, const char *data, size_t len) +{ + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->set_last_request_header_value(std::string(data, len)); + return 0; +} +} // namespace + +namespace { +int htp_hdrs_completecb(htparser *htp) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: request headers complete"; + } + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + + downstream->push_request_headers(); + downstream->set_request_state(Downstream::HEADER_COMPLETE); + + downstream->start_connection(); + return 0; +} +} // namespace + +namespace { +int htp_bodycb(htparser *htp, const char *data, size_t len) +{ + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->push_upload_data_chunk(reinterpret_cast(data), + len); + return 0; +} +} // namespace + +namespace { +int htp_msg_completecb(htparser *htp) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: request complete"; + } + HttpsUpstream *upstream; + upstream = reinterpret_cast(htparser_get_userdata(htp)); + Downstream *downstream = upstream->get_last_downstream(); + downstream->end_upload_data(); + downstream->set_request_state(Downstream::MSG_COMPLETE); + // Stop further processing to complete this request + return 1; +} +} // namespace + +namespace { +htparse_hooks htp_hooks = { + htp_msg_begin, /*htparse_hook on_msg_begin;*/ + htp_methodcb, /*htparse_data_hook method;*/ + 0, /* htparse_data_hook scheme;*/ + 0, /* htparse_data_hook host; */ + 0, /* htparse_data_hook port; */ + 0, /* htparse_data_hook path; */ + 0, /* htparse_data_hook args; */ + htp_uricb, /* htparse_data_hook uri; */ + htp_hdrs_begincb, /* htparse_hook on_hdrs_begin; */ + htp_hdr_keycb, /* htparse_data_hook hdr_key; */ + htp_hdr_valcb, /* htparse_data_hook hdr_val; */ + htp_hdrs_completecb, /* htparse_hook on_hdrs_complete; */ + 0, /*htparse_hook on_new_chunk;*/ + 0, /*htparse_hook on_chunk_complete;*/ + 0, /*htparse_hook on_chunks_complete;*/ + htp_bodycb, /* htparse_data_hook body; */ + htp_msg_completecb /* htparse_hook on_msg_complete;*/ +}; +} // namespace + +std::set cache; + +// on_read() does not consume all available data in input buffer if +// one http request is fully received. +int HttpsUpstream::on_read() +{ + if(cache.count(this) == 0) { + LOG(INFO) << "HttpsUpstream::on_read"; + cache.insert(this); + } + bufferevent *bev = handler_->get_bev(); + evbuffer *input = bufferevent_get_input(bev); + unsigned char *mem = evbuffer_pullup(input, -1); + int nread = htparser_run(htp_, &htp_hooks, + reinterpret_cast(mem), + evbuffer_get_length(input)); + evbuffer_drain(input, nread); + htpparse_error htperr = htparser_get_error(htp_); + if(htperr == htparse_error_user) { + bufferevent_disable(bev, EV_READ); + if(ENABLE_LOG) { + LOG(INFO) << " remaining bytes " << evbuffer_get_length(input); + } + } else if(htperr != htparse_error_none) { + if(ENABLE_LOG) { + LOG(INFO) << " http parse failure: " + << htparser_get_strerror(htp_); + } + return SHRPX_ERR_HTTP_PARSE; + } + return 0; +} + +int HttpsUpstream::on_event() +{ + return 0; +} + +ClientHandler* HttpsUpstream::get_client_handler() const +{ + return handler_; +} + +void HttpsUpstream::resume_read() +{ + bufferevent_enable(handler_->get_bev(), EV_READ); + // Process remaining data in input buffer here. + on_read(); +} + +namespace { +void https_downstream_readcb(bufferevent *bev, void *ptr) +{ + Downstream *downstream = reinterpret_cast(ptr); + HttpsUpstream *upstream; + upstream = static_cast(downstream->get_upstream()); + int rv = downstream->parse_http_response(); + if(rv == 0) { + if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + assert(downstream == upstream->get_top_downstream()); + upstream->pop_downstream(); + delete downstream; + upstream->resume_read(); + } + } else { + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + delete upstream->get_client_handler(); + } else { + upstream->error_reply(downstream, 502); + assert(downstream == upstream->get_top_downstream()); + upstream->pop_downstream(); + delete downstream; + upstream->resume_read(); + } + } +} +} // namespace + +namespace { +void https_downstream_writecb(bufferevent *bev, void *ptr) +{ +} +} // namespace + +namespace { +void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) +{ + Downstream *downstream = reinterpret_cast(ptr); + HttpsUpstream *upstream; + upstream = static_cast(downstream->get_upstream()); + if(events & BEV_EVENT_CONNECTED) { + if(ENABLE_LOG) { + LOG(INFO) << " Connection established. " << downstream; + } + } + if(events & BEV_EVENT_EOF) { + if(ENABLE_LOG) { + LOG(INFO) << " EOF stream_id=" + << downstream->get_stream_id(); + } + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if(ENABLE_LOG) { + LOG(INFO) << " Assuming content-length is 0 byte"; + } + upstream->on_downstream_body_complete(downstream); + //downstream->set_response_state(Downstream::MSG_COMPLETE); + } else if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + // Nothing to do + } else { + // error + if(ENABLE_LOG) { + LOG(INFO) << " Treated as error"; + } + upstream->error_reply(downstream, 502); + } + upstream->pop_downstream(); + delete downstream; + upstream->resume_read(); + } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { + if(ENABLE_LOG) { + LOG(INFO) << " error/timeout. " << downstream; + } + if(downstream->get_response_state() == Downstream::INITIAL) { + int status; + if(events & BEV_EVENT_TIMEOUT) { + status = 504; + } else { + status = 502; + } + upstream->error_reply(downstream, status); + } + upstream->pop_downstream(); + delete downstream; + upstream->resume_read(); + } +} +} // namespace + +void HttpsUpstream::error_reply(Downstream *downstream, int status_code) +{ + std::string html = http::create_error_html(status_code); + std::stringstream ss; + ss << "HTTP/1.1 " << http::get_status_string(status_code) << "\r\n" + << "Server: " << get_config()->server_name << "\r\n" + << "Content-Length: " << html.size() << "\r\n" + << "Content-Type: " << "text/html; charset=UTF-8\r\n" + << "\r\n"; + std::string header = ss.str(); + evbuffer *output = bufferevent_get_output(handler_->get_bev()); + evbuffer_add(output, header.c_str(), header.size()); + evbuffer_add(output, html.c_str(), html.size()); + downstream->set_response_state(Downstream::MSG_COMPLETE); +} + +bufferevent_data_cb HttpsUpstream::get_downstream_readcb() +{ + return https_downstream_readcb; +} + +bufferevent_data_cb HttpsUpstream::get_downstream_writecb() +{ + return https_downstream_writecb; +} + +bufferevent_event_cb HttpsUpstream::get_downstream_eventcb() +{ + return https_downstream_eventcb; +} + +void HttpsUpstream::add_downstream(Downstream *downstream) +{ + downstream_queue_.push_back(downstream); +} + +void HttpsUpstream::pop_downstream() +{ + downstream_queue_.pop_front(); +} + +Downstream* HttpsUpstream::get_top_downstream() +{ + return downstream_queue_.front(); +} + +Downstream* HttpsUpstream::get_last_downstream() +{ + return downstream_queue_.back(); +} + +int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) +{ + if(ENABLE_LOG) { + LOG(INFO) << " on_downstream_header_complete"; + } + std::string hdrs = "HTTP/1.1 "; + hdrs += http::get_status_string(downstream->get_response_http_status()); + hdrs += "\r\n"; + for(Headers::const_iterator i = downstream->get_response_headers().begin(); + i != downstream->get_response_headers().end(); ++i) { + if(util::strieq((*i).first.c_str(), "keep-alive") || // HTTP/1.0? + util::strieq((*i).first.c_str(), "connection") || + util:: strieq((*i).first.c_str(), "proxy-connection")) { + // These are ignored + } else { + if(util::strieq((*i).first.c_str(), "server")) { + hdrs += "Server: "; + hdrs += get_config()->server_name; + } else { + hdrs += (*i).first; + hdrs += ": "; + hdrs += (*i).second; + } + hdrs += "\r\n"; + } + } + if(downstream->get_request_connection_close()) { + hdrs += "Connection: close\r\n"; + } + hdrs += "\r\n"; + if(ENABLE_LOG) { + LOG(INFO) << ":: Response headers\n" << hdrs; + } + evbuffer *output = bufferevent_get_output(handler_->get_bev()); + evbuffer_add(output, hdrs.c_str(), hdrs.size()); + return 0; +} + +int HttpsUpstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len) +{ + int rv; + evbuffer *output = bufferevent_get_output(handler_->get_bev()); + if(downstream->get_chunked_response()) { + char chunk_size_hex[16]; + rv = snprintf(chunk_size_hex, sizeof(chunk_size_hex), "%X\r\n", + static_cast(len)); + evbuffer_add(output, chunk_size_hex, rv); + } + evbuffer_add(output, data, len); + return 0; +} + +int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) +{ + if(downstream->get_chunked_response()) { + evbuffer *output = bufferevent_get_output(handler_->get_bev()); + evbuffer_add(output, "0\r\n\r\n", 5); + } + if(ENABLE_LOG) { + LOG(INFO) << " on_downstream_body_complete"; + } + if(downstream->get_request_connection_close()) { + ClientHandler *handler = downstream->get_upstream()->get_client_handler(); + handler->set_should_close_after_write(true); + } + return 0; +} + +} // namespace shrpx diff --git a/examples/shrpx_https_upstream.h b/examples/shrpx_https_upstream.h new file mode 100644 index 0000000..f05a48c --- /dev/null +++ b/examples/shrpx_https_upstream.h @@ -0,0 +1,76 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTPS_UPSTREAM_H +#define SHRPX_HTTPS_UPSTREAM_H + +#include "shrpx.h" + +#include + +#include + +extern "C" { +#include "htparse/htparse.h" +} + +#include "shrpx_upstream.h" + +namespace shrpx { + +class ClientHandler; + +class HttpsUpstream : public Upstream { +public: + HttpsUpstream(ClientHandler *handler); + virtual ~HttpsUpstream(); + virtual int on_read(); + virtual int on_event(); + //int send(); + virtual ClientHandler* get_client_handler() const; + virtual bufferevent_data_cb get_downstream_readcb(); + virtual bufferevent_data_cb get_downstream_writecb(); + virtual bufferevent_event_cb get_downstream_eventcb(); + void add_downstream(Downstream *downstream); + void pop_downstream(); + Downstream* get_top_downstream(); + Downstream* get_last_downstream(); + void error_reply(Downstream *downstream, int status_code); + + void resume_read(); + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len); + virtual int on_downstream_body_complete(Downstream *downstream); + +private: + ClientHandler *handler_; + htparser *htp_; + std::deque downstream_queue_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTPS_UPSTREAM_H diff --git a/examples/shrpx_listen_handler.cc b/examples/shrpx_listen_handler.cc new file mode 100644 index 0000000..8be62a1 --- /dev/null +++ b/examples/shrpx_listen_handler.cc @@ -0,0 +1,78 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_listen_handler.h" + +#include + +#include "shrpx_client_handler.h" + +namespace shrpx { + +ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx) + : evbase_(evbase), + ssl_ctx_(ssl_ctx) +{} + +ListenHandler::~ListenHandler() +{} + +int ListenHandler::accept_connection(evutil_socket_t fd, + sockaddr *addr, int addrlen) +{ + if(ENABLE_LOG) { + LOG(INFO) << " Accepted connection. fd=" << fd; + } + char host[NI_MAXHOST]; + int rv; + rv = getnameinfo(addr, addrlen, host, sizeof(host), 0, 0, NI_NUMERICHOST); + if(rv == 0) { + SSL *ssl = SSL_new(ssl_ctx_); + bufferevent *bev = bufferevent_openssl_socket_new + (evbase_, fd, ssl, + BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_DEFER_CALLBACKS); + if(bev == NULL) { + if(ENABLE_LOG) { + LOG(ERROR) << " bufferevent_openssl_socket_new failed"; + } + close(fd); + } else { + /*ClientHandler *client_handler =*/ new ClientHandler(bev, ssl, host); + } + } else { + if(ENABLE_LOG) { + LOG(INFO) << " getnameinfo failed"; + } + close(fd); + } + return 0; +} + +event_base* ListenHandler::get_evbase() const +{ + return evbase_; +} + +} // namespace shrpx diff --git a/examples/shrpx_listen_handler.h b/examples/shrpx_listen_handler.h new file mode 100644 index 0000000..4b3e31f --- /dev/null +++ b/examples/shrpx_listen_handler.h @@ -0,0 +1,51 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_LISTEN_HANDLER_H +#define SHRPX_LISTEN_HANDLER_H + +#include "shrpx.h" + +#include +#include + +#include +#include + +namespace shrpx { + +class ListenHandler { +public: + ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx); + ~ListenHandler(); + int accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen); + event_base* get_evbase() const; +private: + event_base *evbase_; + SSL_CTX *ssl_ctx_; +}; + +} // namespace shrpx + +#endif // SHRPX_LISTEN_HANDLER_H diff --git a/examples/shrpx_log.cc b/examples/shrpx_log.cc new file mode 100644 index 0000000..5f7469e --- /dev/null +++ b/examples/shrpx_log.cc @@ -0,0 +1,49 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_log.h" + +#include + +namespace shrpx { + +const char *SEVERITY_STR[] = { + "INFO", "WARN", "ERROR", "FATAL" +}; + +Log::Log(int severity, const char *filename, int linenum) + : severity_(severity), + filename_(filename), + linenum_(linenum) +{} + +Log::~Log() +{ + fprintf(stderr, "[%s] %s\n (%s, line %d)\n", + SEVERITY_STR[severity_], stream_.str().c_str(), + filename_, linenum_); + fflush(stderr); +} + +} // namespace shrpx diff --git a/examples/shrpx_log.h b/examples/shrpx_log.h new file mode 100644 index 0000000..9a1515a --- /dev/null +++ b/examples/shrpx_log.h @@ -0,0 +1,60 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_LOG_H +#define SHRPX_LOG_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +#define ENABLE_LOG 1 + +#define LOG(SEVERITY) Log(SEVERITY, __FILE__, __LINE__) + +enum SeverityLevel { + INFO, WARNING, ERROR, FATAL +}; + +class Log { +public: + Log(int severiy, const char *filename, int linenum); + ~Log(); + template Log& operator<<(Type s) + { + stream_ << s; + return *this; + } +private: + int severity_; + const char *filename_; + int linenum_; + std::stringstream stream_; +}; + +} // namespace shrpx + +#endif // SHRPX_LOG_H diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc new file mode 100644 index 0000000..f217dad --- /dev/null +++ b/examples/shrpx_spdy_upstream.cc @@ -0,0 +1,532 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_spdy_upstream.h" + +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_http.h" +#include "util.h" + +using namespace spdylay; + +namespace shrpx { + +namespace { +ssize_t send_callback(spdylay_session *session, + const uint8_t *data, size_t len, int flags, + void *user_data) +{ + int rv; + SpdyUpstream *upstream = reinterpret_cast(user_data); + ClientHandler *handler = upstream->get_client_handler(); + bufferevent *bev = handler->get_bev(); + evbuffer *output = bufferevent_get_output(bev); + rv = evbuffer_add(output, data, len); + if(rv == -1) { + return SPDYLAY_ERR_CALLBACK_FAILURE; + } else { + return len; + } +} +} // namespace + +namespace { +ssize_t recv_callback(spdylay_session *session, + uint8_t *data, size_t len, int flags, void *user_data) +{ + SpdyUpstream *upstream = reinterpret_cast(user_data); + ClientHandler *handler = upstream->get_client_handler(); + bufferevent *bev = handler->get_bev(); + evbuffer *input = bufferevent_get_input(bev); + int nread = evbuffer_remove(input, data, len); + if(nread == -1) { + return SPDYLAY_ERR_CALLBACK_FAILURE; + } else if(nread == 0) { + return SPDYLAY_ERR_WOULDBLOCK; + } else { + return nread; + } +} +} // namespace + +namespace { +void on_stream_close_callback +(spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, + void *user_data) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: Stream " << stream_id + << " is being closed"; + } + SpdyUpstream *upstream = reinterpret_cast(user_data); + Downstream *downstream = upstream->get_downstream_queue()->find(stream_id); + if(downstream) { + downstream->set_request_state(Downstream::STREAM_CLOSED); + if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + upstream->get_downstream_queue()->remove(downstream); + delete downstream; + } + } +} +} // namespace + +namespace { +void on_ctrl_recv_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + SpdyUpstream *upstream = reinterpret_cast(user_data); + switch(type) { + case SPDYLAY_SYN_STREAM: { + if(ENABLE_LOG) { + LOG(INFO) << ":: Received upstream SYN_STREAM stream_id=" + << frame->syn_stream.stream_id; + } + Downstream *downstream = new Downstream(upstream, + frame->syn_stream.stream_id, + frame->syn_stream.pri); + downstream->init_response_body_buf(); + + char **nv = frame->syn_stream.nv; + for(size_t i = 0; nv[i]; i += 2) { + if(strcmp(nv[i], ":path") == 0) { + downstream->set_request_path(nv[i+1]); + } else if(strcmp(nv[i], ":method") == 0) { + downstream->set_request_method(nv[i+1]); + } else if(nv[i][0] != ':') { + downstream->add_request_header(nv[i], nv[i+1]); + } + } + downstream->add_request_header("X-Forwarded-Spdy", "true"); + + if(ENABLE_LOG) { + std::stringstream ss; + for(size_t i = 0; nv[i]; i += 2) { + ss << nv[i] << ": " << nv[i+1] << "\n"; + } + LOG(INFO) << ":: Request headers:\n" << ss.str(); + } + + downstream->push_request_headers(); + downstream->set_request_state(Downstream::HEADER_COMPLETE); + if(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) { + if(ENABLE_LOG) { + LOG(INFO) << ":: " + << "Setting Downstream::MSG_COMPLETE for Downstream " + << downstream; + } + downstream->set_request_state(Downstream::MSG_COMPLETE); + } + upstream->add_downstream(downstream); + upstream->start_downstream(downstream); + break; + } + default: + break; + } +} +} // namespace + +namespace { +void on_data_chunk_recv_callback(spdylay_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) +{ + if(ENABLE_LOG) { + LOG(INFO) << ":: Received upstream DATA data stream_id=" + << stream_id; + } + SpdyUpstream *upstream = reinterpret_cast(user_data); + Downstream *downstream = upstream->get_downstream_queue()->find(stream_id); + if(downstream) { + downstream->push_upload_data_chunk(data, len); + if(flags & SPDYLAY_DATA_FLAG_FIN) { + if(ENABLE_LOG) { + LOG(INFO) << ":: " + << "Setting Downstream::MSG_COMPLETE for Downstream " + << downstream; + } + downstream->set_request_state(Downstream::MSG_COMPLETE); + } + } +} +} // namespace + +SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler) + : handler_(handler), + session_(0) +{ + //handler->set_bev_cb(spdy_readcb, 0, spdy_eventcb); + handler->set_upstream_timeouts(&get_config()->spdy_upstream_read_timeout, + &get_config()->spdy_upstream_write_timeout); + + spdylay_session_callbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = send_callback; + callbacks.recv_callback = recv_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + + int rv; + rv = spdylay_session_server_new(&session_, version, &callbacks, this); + assert(rv == 0); + // TODO Maybe call from outside? + spdylay_settings_entry entry; + entry.settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS; + entry.value = SPDYLAY_INITIAL_MAX_CONCURRENT_STREAMS; + entry.flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; + rv = spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE, + &entry, 1); + assert(rv == 0); + // TODO Maybe call from outside? + send(); +} + +SpdyUpstream::~SpdyUpstream() +{ + spdylay_session_del(session_); +} + +int SpdyUpstream::on_read() +{ + int rv; + if((rv = spdylay_session_recv(session_)) || + (rv = spdylay_session_send(session_))) { + if(rv != SPDYLAY_ERR_EOF) { + LOG(ERROR) << "spdylay error: " << spdylay_strerror(rv); + DIE(); + } + } + return 0; +} + +int SpdyUpstream::send() +{ + int rv; + if((rv = spdylay_session_send(session_))) { + LOG(ERROR) << "spdylay error: " << spdylay_strerror(rv); + DIE(); + } + return 0; +} + +int SpdyUpstream::on_event() +{ + return 0; +} + +ClientHandler* SpdyUpstream::get_client_handler() const +{ + return handler_; +} + +namespace { +void spdy_downstream_readcb(bufferevent *bev, void *ptr) +{ + if(ENABLE_LOG) { + LOG(INFO) << "spdy_downstream_readcb"; + } + Downstream *downstream = reinterpret_cast(ptr); + SpdyUpstream *upstream; + upstream = static_cast(downstream->get_upstream()); + int rv = downstream->parse_http_response(); + if(rv == 0) { + if(downstream->get_request_state() == Downstream::STREAM_CLOSED && + downstream->get_response_state() == Downstream::MSG_COMPLETE) { + upstream->get_downstream_queue()->remove(downstream); + delete downstream; + } else { + upstream->send(); + } + } else { + if(ENABLE_LOG) { + LOG(INFO) << " http parser failure"; + } + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); + } else { + upstream->error_reply(downstream, 502); + } + downstream->set_response_state(Downstream::MSG_COMPLETE); + upstream->send(); + // upstream->remove_downstream(downstream); + // delete downstream; + } +} +} // namespace + +namespace { +void spdy_downstream_writecb(bufferevent *bev, void *ptr) +{ +} +} // namespace + +namespace { +void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) +{ + Downstream *downstream = reinterpret_cast(ptr); + SpdyUpstream *upstream; + upstream = static_cast(downstream->get_upstream()); + if(events & BEV_EVENT_CONNECTED) { + if(ENABLE_LOG) { + LOG(INFO) << " Connection established. Downstream " + << downstream; + } + } + if(events & BEV_EVENT_EOF) { + if(ENABLE_LOG) { + LOG(INFO) << " EOF stream_id=" + << downstream->get_stream_id(); + } + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if(ENABLE_LOG) { + LOG(INFO) << " Assuming content-length is 0 byte"; + } + upstream->on_downstream_body_complete(downstream); + downstream->set_response_state(Downstream::MSG_COMPLETE); + // downstream wil be deleted in on_stream_close_callback. + } else if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + // nothing todo + } else { + // error + if(ENABLE_LOG) { + LOG(INFO) << " Treated as error"; + } + upstream->error_reply(downstream, 502); + upstream->send(); + upstream->remove_downstream(downstream); + delete downstream; + } + } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { + if(ENABLE_LOG) { + LOG(INFO) << " error/timeout. Downstream " << downstream; + } + // For Downstream::MSG_COMPLETE case, downstream will be deleted + // in on_stream_close_callback. + if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); + } else { + int status; + if(events & BEV_EVENT_TIMEOUT) { + status = 504; + } else { + status = 502; + } + upstream->error_reply(downstream, status); + } + upstream->send(); + upstream->remove_downstream(downstream); + delete downstream; + } + } +} +} // namespace + +int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) +{ + int rv; + rv = spdylay_submit_rst_stream(session_, downstream->get_stream_id(), + status_code); + if(rv < SPDYLAY_ERR_FATAL) { + DIE(); + } else { + return 0; + } +} + +namespace { +ssize_t spdy_data_read_callback(spdylay_session *session, + int32_t stream_id, + uint8_t *buf, size_t length, + int *eof, + spdylay_data_source *source, + void *user_data) +{ + Downstream *downstream = reinterpret_cast(source->ptr); + evbuffer *body = downstream->get_response_body_buf(); + assert(body); + int nread = evbuffer_remove(body, buf, length); + if(nread == 0 && + downstream->get_response_state() == Downstream::MSG_COMPLETE) { + *eof = 1; + } + if(nread == 0 && *eof != 1) { + return SPDYLAY_ERR_DEFERRED; + } + return nread; +} +} // namespace + +int SpdyUpstream::error_reply(Downstream *downstream, int status_code) +{ + int rv; + std::string html = http::create_error_html(status_code); + downstream->init_response_body_buf(); + evbuffer *body = downstream->get_response_body_buf(); + rv = evbuffer_add(body, html.c_str(), html.size()); + if(rv == -1) { + DIE(); + } + downstream->set_response_state(Downstream::MSG_COMPLETE); + + spdylay_data_provider data_prd; + data_prd.source.ptr = downstream; + data_prd.read_callback = spdy_data_read_callback; + + const char *nv[] = { + ":status", http::get_status_string(status_code), + ":version", "http/1.1", + "content-type", "text/html; charset=UTF-8", + "server", get_config()->server_name, + 0 + }; + + rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv, + &data_prd); + if(rv < SPDYLAY_ERR_FATAL) { + DIE(); + } else { + return 0; + } +} + +bufferevent_data_cb SpdyUpstream::get_downstream_readcb() +{ + return spdy_downstream_readcb; +} + +bufferevent_data_cb SpdyUpstream::get_downstream_writecb() +{ + return spdy_downstream_writecb; +} + +bufferevent_event_cb SpdyUpstream::get_downstream_eventcb() +{ + return spdy_downstream_eventcb; +} + +void SpdyUpstream::add_downstream(Downstream *downstream) +{ + downstream_queue_.add(downstream); +} + +void SpdyUpstream::start_downstream(Downstream *downstream) +{ + downstream_queue_.start(downstream); +} + +void SpdyUpstream::remove_downstream(Downstream *downstream) +{ + downstream_queue_.remove(downstream); +} + +spdylay_session* SpdyUpstream::get_spdy_session() +{ + return session_; +} + +int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) +{ + if(ENABLE_LOG) { + LOG(INFO) << " on_downstream_header_complete"; + } + size_t nheader = downstream->get_response_headers().size(); + const char **nv = new const char*[nheader * 2 + 4 + 1]; + size_t hdidx = 0; + for(Headers::const_iterator i = downstream->get_response_headers().begin(); + i != downstream->get_response_headers().end(); ++i) { + if(util::strieq((*i).first.c_str(), "transfer-encoding") || + util::strieq((*i).first.c_str(), "keep-alive") || // HTTP/1.0? + util::strieq((*i).first.c_str(), "connection") || + util:: strieq((*i).first.c_str(), "proxy-connection")) { + // These are ignored + } else if(util::strieq((*i).first.c_str(), "server")) { + nv[hdidx++] = "server"; + nv[hdidx++] = get_config()->server_name; + } else { + nv[hdidx++] = (*i).first.c_str(); + nv[hdidx++] = (*i).second.c_str(); + } + } + nv[hdidx++] = ":status"; + nv[hdidx++] = http::get_status_string(downstream->get_response_http_status()); + nv[hdidx++] = ":version"; + nv[hdidx++] = "HTTP/1.1"; + nv[hdidx++] = 0; + if(ENABLE_LOG) { + std::stringstream ss; + for(size_t i = 0; nv[i]; i += 2) { + ss << nv[i] << ": " << nv[i+1] << "\n"; + } + LOG(INFO) << ":: Response headers\n" << ss.str(); + } + spdylay_data_provider data_prd; + data_prd.source.ptr = downstream; + data_prd.read_callback = spdy_data_read_callback; + + spdylay_submit_response(session_, downstream->get_stream_id(), nv, + &data_prd); + delete [] nv; + //send(); + return 0; +} + +int SpdyUpstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len) +{ + if(ENABLE_LOG) { + LOG(INFO) << " on_downstream_body"; + } + evbuffer *body = downstream->get_response_body_buf(); + evbuffer_add(body, data, len); + spdylay_session_resume_data(session_, downstream->get_stream_id()); + //send(); + return 0; +} + +int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) +{ + if(ENABLE_LOG) { + LOG(INFO) << " on_downstream_body_complete"; + } + spdylay_session_resume_data(session_, downstream->get_stream_id()); + //send(); + return 0; +} + +DownstreamQueue* SpdyUpstream::get_downstream_queue() +{ + return &downstream_queue_; +} + +} // namespace shrpx diff --git a/examples/shrpx_spdy_upstream.h b/examples/shrpx_spdy_upstream.h new file mode 100644 index 0000000..c9db8bb --- /dev/null +++ b/examples/shrpx_spdy_upstream.h @@ -0,0 +1,71 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_SPDY_UPSTREAM_H +#define SHRPX_SPDY_UPSTREAM_H + +#include "shrpx.h" + +#include + +#include "shrpx_upstream.h" +#include "shrpx_downstream_queue.h" + +namespace shrpx { + +class ClientHandler; + +class SpdyUpstream : public Upstream { +public: + SpdyUpstream(uint16_t version, ClientHandler *handler); + virtual ~SpdyUpstream(); + virtual int on_read(); + virtual int on_event(); + int send(); + virtual ClientHandler* get_client_handler() const; + virtual bufferevent_data_cb get_downstream_readcb(); + virtual bufferevent_data_cb get_downstream_writecb(); + virtual bufferevent_event_cb get_downstream_eventcb(); + void add_downstream(Downstream *downstream); + void remove_downstream(Downstream *downstream); + void start_downstream(Downstream *downstream); + spdylay_session* get_spdy_session(); + DownstreamQueue* get_downstream_queue(); + + int rst_stream(Downstream *downstream, int status_code); + int error_reply(Downstream *downstream, int status_code); + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len); + virtual int on_downstream_body_complete(Downstream *downstream); +private: + ClientHandler *handler_; + spdylay_session *session_; + DownstreamQueue downstream_queue_; +}; + +} // namespace shrpx + +#endif // SHRPX_SPDY_UPSTREAM_H diff --git a/examples/shrpx_upstream.h b/examples/shrpx_upstream.h new file mode 100644 index 0000000..bb886ad --- /dev/null +++ b/examples/shrpx_upstream.h @@ -0,0 +1,55 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_UPSTREAM_H +#define SHRPX_UPSTREAM_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +class ClientHandler; +class Downstream; + +class Upstream { +public: + virtual ~Upstream() {} + virtual int on_read() = 0; + virtual int on_event() = 0; + virtual bufferevent_data_cb get_downstream_readcb() = 0; + virtual bufferevent_data_cb get_downstream_writecb() = 0; + virtual bufferevent_event_cb get_downstream_eventcb() = 0; + virtual ClientHandler* get_client_handler() const = 0; + + virtual int on_downstream_header_complete(Downstream *downstream) = 0; + virtual int on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len) = 0; + virtual int on_downstream_body_complete(Downstream *downstream) = 0; +}; + +} // namespace shrpx + +#endif // SHRPX_UPSTREAM_H diff --git a/examples/util.cc b/examples/util.cc index ec22dc8..c7b8e96 100644 --- a/examples/util.cc +++ b/examples/util.cc @@ -169,6 +169,21 @@ bool strieq(const char *a, const char *b) return !*a && !*b; } +bool strifind(const char *a, const char *b) +{ + if(!a || !b) { + return false; + } + for(size_t i = 0; a[i]; ++i) { + const char *ap = &a[i], *bp = b; + for(; *ap && *bp && lowcase(*ap) == lowcase(*bp); ++ap, ++bp); + if(!*bp) { + return true; + } + } + return false; +} + } // namespace util } // namespace spdylay diff --git a/examples/util.h b/examples/util.h index d997995..8c20873 100644 --- a/examples/util.h +++ b/examples/util.h @@ -235,6 +235,8 @@ bool endsWith(const std::string& a, const std::string& b); bool strieq(const char *a, const char *b); +bool strifind(const char *a, const char *b); + } // namespace util } // namespace spdylay From c04c09ff3e285bd73420d2a95fea392fab312caa Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 Jun 2012 03:11:43 +0900 Subject: [PATCH 03/21] Avoid too large buffering in upstream output. --- examples/Makefile.am | 1 + examples/shrpx.cc | 2 +- examples/shrpx_client_handler.cc | 6 +-- examples/shrpx_config.h | 2 + examples/shrpx_downstream.cc | 23 +++++++++ examples/shrpx_downstream.h | 6 ++- examples/shrpx_error.h | 2 + examples/shrpx_https_upstream.cc | 60 ++++++++++++++++----- examples/shrpx_https_upstream.h | 6 ++- examples/shrpx_io_control.cc | 61 ++++++++++++++++++++++ examples/shrpx_io_control.h | 58 +++++++++++++++++++++ examples/shrpx_spdy_upstream.cc | 89 ++++++++++++++++++++------------ examples/shrpx_spdy_upstream.h | 1 + examples/shrpx_upstream.h | 1 + 14 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 examples/shrpx_io_control.cc create mode 100644 examples/shrpx_io_control.h diff --git a/examples/Makefile.am b/examples/Makefile.am index 42d97a4..d62e8d6 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -77,6 +77,7 @@ shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_downstream.cc shrpx_downstream.h \ shrpx_log.cc shrpx_log.h \ shrpx_http.cc shrpx_http.h \ + shrpx_io_control.cc shrpx_io_control.h \ htparse/htparse.c htparse/htparse.h noinst_PROGRAMS = spdycli diff --git a/examples/shrpx.cc b/examples/shrpx.cc index ba57abb..3200f73 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -285,7 +285,7 @@ int main(int argc, char **argv) mod_config()->upstream_read_timeout.tv_sec = 30; mod_config()->upstream_read_timeout.tv_usec = 0; - mod_config()->upstream_write_timeout.tv_sec = 30; + mod_config()->upstream_write_timeout.tv_sec = 60; mod_config()->upstream_write_timeout.tv_usec = 0; mod_config()->spdy_upstream_read_timeout.tv_sec = 600; diff --git a/examples/shrpx_client_handler.cc b/examples/shrpx_client_handler.cc index 4fceaae..4139c3a 100644 --- a/examples/shrpx_client_handler.cc +++ b/examples/shrpx_client_handler.cc @@ -48,12 +48,12 @@ void upstream_readcb(bufferevent *bev, void *arg) namespace { void upstream_writecb(bufferevent *bev, void *arg) { - if(ENABLE_LOG) { - LOG(INFO) << " upstream_writecb"; - } ClientHandler *handler = reinterpret_cast(arg); if(handler->get_should_close_after_write()) { delete handler; + } else { + Upstream *upstream = handler->get_upstream(); + upstream->on_write(); } } } // namespace diff --git a/examples/shrpx_config.h b/examples/shrpx_config.h index 9b95588..7063081 100644 --- a/examples/shrpx_config.h +++ b/examples/shrpx_config.h @@ -25,6 +25,8 @@ #ifndef SHRPX_CONFIG_H #define SHRPX_CONFIG_H +#include "shrpx.h" + #include #include #include diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index b8755b8..08ab5fd 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -41,6 +41,7 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) bev_(0), stream_id_(stream_id), priority_(priority), + ioctrl_(0), request_state_(INITIAL), chunked_request_(false), request_connection_close_(false), @@ -56,6 +57,7 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) bev_ = bufferevent_socket_new (evbase, -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + ioctrl_.set_bev(bev_); } Downstream::~Downstream() @@ -75,6 +77,16 @@ Downstream::~Downstream() } } +void Downstream::pause_read(IOCtrlReason reason) +{ + ioctrl_.pause_read(reason); +} + +bool Downstream::resume_read(IOCtrlReason reason) +{ + return ioctrl_.resume_read(reason); +} + namespace { void check_transfer_encoding_chunked(bool *chunked, const Headers::value_type &item) @@ -394,6 +406,16 @@ int Downstream::get_response_state() const return response_state_; } +namespace { +void body_buf_cb(evbuffer *body, size_t oldlen, size_t newlen, void *arg) +{ + Downstream *downstream = reinterpret_cast(arg); + if(newlen == 0) { + downstream->resume_read(SHRPX_NO_BUFFER); + } +} +} // namespace + int Downstream::init_response_body_buf() { assert(response_body_buf_ == 0); @@ -401,6 +423,7 @@ int Downstream::init_response_body_buf() if(response_body_buf_ == 0) { DIE(); } + evbuffer_setcb(response_body_buf_, body_buf_cb, this); return 0; } diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h index d4b824d..ef5687b 100644 --- a/examples/shrpx_downstream.h +++ b/examples/shrpx_downstream.h @@ -39,6 +39,8 @@ extern "C" { #include "htparse/htparse.h" } +#include "shrpx_io_control.h" + namespace shrpx { class Upstream; @@ -52,6 +54,8 @@ public: int start_connection(); Upstream* get_upstream() const; int32_t get_stream_id() const; + void pause_read(IOCtrlReason reason); + bool resume_read(IOCtrlReason reason); // downstream request API const Headers& get_request_headers() const; void add_request_header(const std::string& name, const std::string& value); @@ -89,7 +93,7 @@ private: bufferevent *bev_; int32_t stream_id_; int priority_; - + IOControl ioctrl_; int request_state_; std::string request_method_; std::string request_path_; diff --git a/examples/shrpx_error.h b/examples/shrpx_error.h index 0c8dbd3..40a6850 100644 --- a/examples/shrpx_error.h +++ b/examples/shrpx_error.h @@ -25,6 +25,8 @@ #ifndef SHRPX_ERROR_H #define SHRPX_ERROR_H +#include "shrpx.h" + namespace shrpx { enum ErrorCode { diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc index 92e6e54..c33c6a9 100644 --- a/examples/shrpx_https_upstream.cc +++ b/examples/shrpx_https_upstream.cc @@ -38,9 +38,14 @@ using namespace spdylay; namespace shrpx { +namespace { +const size_t SHRPX_HTTPS_UPSTREAM_OUTPUT_UPPER_THRES = 512*1024; +} // namespace + HttpsUpstream::HttpsUpstream(ClientHandler *handler) : handler_(handler), - htp_(htparser_new()) + htp_(htparser_new()), + ioctrl_(handler->get_bev()) { if(ENABLE_LOG) { LOG(INFO) << "HttpsUpstream ctor"; @@ -221,7 +226,7 @@ int HttpsUpstream::on_read() evbuffer_drain(input, nread); htpparse_error htperr = htparser_get_error(htp_); if(htperr == htparse_error_user) { - bufferevent_disable(bev, EV_READ); + pause_read(SHRPX_MSG_BLOCK); if(ENABLE_LOG) { LOG(INFO) << " remaining bytes " << evbuffer_get_length(input); } @@ -235,6 +240,15 @@ int HttpsUpstream::on_read() return 0; } +int HttpsUpstream::on_write() +{ + Downstream *downstream = get_top_downstream(); + if(downstream) { + downstream->resume_read(SHRPX_NO_BUFFER); + } + return 0; +} + int HttpsUpstream::on_event() { return 0; @@ -245,11 +259,18 @@ ClientHandler* HttpsUpstream::get_client_handler() const return handler_; } -void HttpsUpstream::resume_read() +void HttpsUpstream::pause_read(IOCtrlReason reason) { - bufferevent_enable(handler_->get_bev(), EV_READ); - // Process remaining data in input buffer here. - on_read(); + ioctrl_.pause_read(reason); +} + +void HttpsUpstream::resume_read(IOCtrlReason reason) +{ + if(ioctrl_.resume_read(reason)) { + // Process remaining data in input buffer here because these bytes + // are not notified by readcb until new data arrive. + on_read(); + } } namespace { @@ -264,7 +285,14 @@ void https_downstream_readcb(bufferevent *bev, void *ptr) assert(downstream == upstream->get_top_downstream()); upstream->pop_downstream(); delete downstream; - upstream->resume_read(); + upstream->resume_read(SHRPX_MSG_BLOCK); + } else { + ClientHandler *handler = upstream->get_client_handler(); + bufferevent *bev = handler->get_bev(); + size_t outputlen = evbuffer_get_length(bufferevent_get_output(bev)); + if(outputlen > SHRPX_HTTPS_UPSTREAM_OUTPUT_UPPER_THRES) { + downstream->pause_read(SHRPX_NO_BUFFER); + } } } else { if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { @@ -274,7 +302,7 @@ void https_downstream_readcb(bufferevent *bev, void *ptr) assert(downstream == upstream->get_top_downstream()); upstream->pop_downstream(); delete downstream; - upstream->resume_read(); + upstream->resume_read(SHRPX_MSG_BLOCK); } } } @@ -320,7 +348,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) } upstream->pop_downstream(); delete downstream; - upstream->resume_read(); + upstream->resume_read(SHRPX_MSG_BLOCK); } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { if(ENABLE_LOG) { LOG(INFO) << " error/timeout. " << downstream; @@ -336,7 +364,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) } upstream->pop_downstream(); delete downstream; - upstream->resume_read(); + upstream->resume_read(SHRPX_MSG_BLOCK); } } } // namespace @@ -384,12 +412,20 @@ void HttpsUpstream::pop_downstream() Downstream* HttpsUpstream::get_top_downstream() { - return downstream_queue_.front(); + if(downstream_queue_.empty()) { + return 0; + } else { + return downstream_queue_.front(); + } } Downstream* HttpsUpstream::get_last_downstream() { - return downstream_queue_.back(); + if(downstream_queue_.empty()) { + return 0; + } else { + return downstream_queue_.back(); + } } int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) diff --git a/examples/shrpx_https_upstream.h b/examples/shrpx_https_upstream.h index f05a48c..3fa76f9 100644 --- a/examples/shrpx_https_upstream.h +++ b/examples/shrpx_https_upstream.h @@ -36,6 +36,7 @@ extern "C" { } #include "shrpx_upstream.h" +#include "shrpx_io_control.h" namespace shrpx { @@ -46,6 +47,7 @@ public: HttpsUpstream(ClientHandler *handler); virtual ~HttpsUpstream(); virtual int on_read(); + virtual int on_write(); virtual int on_event(); //int send(); virtual ClientHandler* get_client_handler() const; @@ -58,7 +60,8 @@ public: Downstream* get_last_downstream(); void error_reply(Downstream *downstream, int status_code); - void resume_read(); + void pause_read(IOCtrlReason reason); + void resume_read(IOCtrlReason reason); virtual int on_downstream_header_complete(Downstream *downstream); virtual int on_downstream_body(Downstream *downstream, @@ -69,6 +72,7 @@ private: ClientHandler *handler_; htparser *htp_; std::deque downstream_queue_; + IOControl ioctrl_; }; } // namespace shrpx diff --git a/examples/shrpx_io_control.cc b/examples/shrpx_io_control.cc new file mode 100644 index 0000000..76b957a --- /dev/null +++ b/examples/shrpx_io_control.cc @@ -0,0 +1,61 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_io_control.h" + +#include + +namespace shrpx { + +IOControl::IOControl(bufferevent *bev) + : bev_(bev), + ctrlv_(SHRPX_REASON_MAX) +{} + +IOControl::~IOControl() +{} + +void IOControl::set_bev(bufferevent *bev) +{ + bev_ = bev; +} + +void IOControl::pause_read(IOCtrlReason reason) +{ + ctrlv_[reason] = 1; + bufferevent_disable(bev_, EV_READ); +} + +bool IOControl::resume_read(IOCtrlReason reason) +{ + ctrlv_[reason] = 0; + if(std::find(ctrlv_.begin(), ctrlv_.end(), 1) == ctrlv_.end()) { + bufferevent_enable(bev_, EV_READ); + return true; + } else { + return false; + } +} + +} // namespace shrpx diff --git a/examples/shrpx_io_control.h b/examples/shrpx_io_control.h new file mode 100644 index 0000000..b73e36e --- /dev/null +++ b/examples/shrpx_io_control.h @@ -0,0 +1,58 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_IO_CONTROL_H +#define SHRPX_IO_CONTROL_H + +#include "shrpx.h" + +#include + +#include +#include + +namespace shrpx { + +enum IOCtrlReason { + SHRPX_NO_BUFFER = 0, + SHRPX_MSG_BLOCK, + SHRPX_REASON_MAX +}; + +class IOControl { +public: + IOControl(bufferevent *bev); + ~IOControl(); + void set_bev(bufferevent *bev); + void pause_read(IOCtrlReason reason); + // Returns true if read operation is enabled after this call + bool resume_read(IOCtrlReason reason); +private: + bufferevent *bev_; + std::vector ctrlv_; +}; + +} // namespace shrpx + +#endif // SHRPX_IO_CONTROL_H diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index f217dad..e1a09f4 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -37,6 +37,10 @@ using namespace spdylay; namespace shrpx { +namespace { +const size_t SHRPX_SPDY_UPSTREAM_OUTPUT_UPPER_THRES = 512*1024; +} // namespace + namespace { ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len, int flags, @@ -47,6 +51,11 @@ ssize_t send_callback(spdylay_session *session, ClientHandler *handler = upstream->get_client_handler(); bufferevent *bev = handler->get_bev(); evbuffer *output = bufferevent_get_output(bev); + // Check buffer length and return WOULDBLOCK if it is large enough. + if(evbuffer_get_length(output) > SHRPX_SPDY_UPSTREAM_OUTPUT_UPPER_THRES) { + return SPDYLAY_ERR_WOULDBLOCK; + } + rv = evbuffer_add(output, data, len); if(rv == -1) { return SPDYLAY_ERR_CALLBACK_FAILURE; @@ -228,6 +237,12 @@ int SpdyUpstream::on_read() return 0; } +int SpdyUpstream::on_write() +{ + send(); + return 0; +} + int SpdyUpstream::send() { int rv; @@ -306,47 +321,51 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) LOG(INFO) << " EOF stream_id=" << downstream->get_stream_id(); } - if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - // Server may indicate the end of the request by EOF - if(ENABLE_LOG) { - LOG(INFO) << " Assuming content-length is 0 byte"; - } - upstream->on_downstream_body_complete(downstream); - downstream->set_response_state(Downstream::MSG_COMPLETE); - // downstream wil be deleted in on_stream_close_callback. - } else if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { - // nothing todo - } else { - // error - if(ENABLE_LOG) { - LOG(INFO) << " Treated as error"; - } - upstream->error_reply(downstream, 502); - upstream->send(); + if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { + //If stream was closed already, we don't need to send reply at + // the first place. We can delete downstream. upstream->remove_downstream(downstream); delete downstream; + } else { + // downstream wil be deleted in on_stream_close_callback. + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if(ENABLE_LOG) { + LOG(INFO) << " Assuming content-length is 0 byte"; + } + downstream->set_response_state(Downstream::MSG_COMPLETE); + upstream->on_downstream_body_complete(downstream); + } else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { + // If stream was not closed, then we set MSG_COMPLETE and let + // on_stream_close_callback delete downstream. + upstream->error_reply(downstream, 502); + downstream->set_response_state(Downstream::MSG_COMPLETE); + upstream->send(); + } } } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { if(ENABLE_LOG) { LOG(INFO) << " error/timeout. Downstream " << downstream; } - // For Downstream::MSG_COMPLETE case, downstream will be deleted - // in on_stream_close_callback. - if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { - if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } else { - int status; - if(events & BEV_EVENT_TIMEOUT) { - status = 504; - } else { - status = 502; - } - upstream->error_reply(downstream, status); - } - upstream->send(); + if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { upstream->remove_downstream(downstream); delete downstream; + } else { + if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { + if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { + upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); + } else { + int status; + if(events & BEV_EVENT_TIMEOUT) { + status = 504; + } else { + status = 502; + } + upstream->error_reply(downstream, status); + } + downstream->set_response_state(Downstream::MSG_COMPLETE); + upstream->send(); + } } } } @@ -511,6 +530,12 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream, evbuffer_add(body, data, len); spdylay_session_resume_data(session_, downstream->get_stream_id()); //send(); + + size_t bodylen = evbuffer_get_length(body); + if(bodylen > SHRPX_SPDY_UPSTREAM_OUTPUT_UPPER_THRES) { + downstream->pause_read(SHRPX_NO_BUFFER); + } + return 0; } diff --git a/examples/shrpx_spdy_upstream.h b/examples/shrpx_spdy_upstream.h index c9db8bb..fc87898 100644 --- a/examples/shrpx_spdy_upstream.h +++ b/examples/shrpx_spdy_upstream.h @@ -41,6 +41,7 @@ public: SpdyUpstream(uint16_t version, ClientHandler *handler); virtual ~SpdyUpstream(); virtual int on_read(); + virtual int on_write(); virtual int on_event(); int send(); virtual ClientHandler* get_client_handler() const; diff --git a/examples/shrpx_upstream.h b/examples/shrpx_upstream.h index bb886ad..7c7593c 100644 --- a/examples/shrpx_upstream.h +++ b/examples/shrpx_upstream.h @@ -38,6 +38,7 @@ class Upstream { public: virtual ~Upstream() {} virtual int on_read() = 0; + virtual int on_write() = 0; virtual int on_event() = 0; virtual bufferevent_data_cb get_downstream_readcb() = 0; virtual bufferevent_data_cb get_downstream_writecb() = 0; From be1c6bb968d912404d7d97ca3b6658a0af74d8fe Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 Jun 2012 21:25:05 +0900 Subject: [PATCH 04/21] Removed commented send() --- examples/shrpx_spdy_upstream.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index e1a09f4..ba2e413 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -516,7 +516,6 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) spdylay_submit_response(session_, downstream->get_stream_id(), nv, &data_prd); delete [] nv; - //send(); return 0; } @@ -529,7 +528,6 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream, evbuffer *body = downstream->get_response_body_buf(); evbuffer_add(body, data, len); spdylay_session_resume_data(session_, downstream->get_stream_id()); - //send(); size_t bodylen = evbuffer_get_length(body); if(bodylen > SHRPX_SPDY_UPSTREAM_OUTPUT_UPPER_THRES) { @@ -545,7 +543,6 @@ int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) LOG(INFO) << " on_downstream_body_complete"; } spdylay_session_resume_data(session_, downstream->get_stream_id()); - //send(); return 0; } From e8cefa923879b2ff3509b176b322e3745358e66d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 Jun 2012 22:13:22 +0900 Subject: [PATCH 05/21] Handle too long upstream request headers --- examples/shrpx_http.cc | 2 +- examples/shrpx_https_upstream.cc | 50 ++++++++++++++++++-------------- examples/shrpx_https_upstream.h | 4 ++- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/examples/shrpx_http.cc b/examples/shrpx_http.cc index 8d775cf..4037677 100644 --- a/examples/shrpx_http.cc +++ b/examples/shrpx_http.cc @@ -89,7 +89,7 @@ std::string create_error_html(int status_code) << "
" << get_config()->server_name << " at port " << get_config()->port << "
" - << ""; + << "\n"; return ss.str(); } diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc index c33c6a9..707b82b 100644 --- a/examples/shrpx_https_upstream.cc +++ b/examples/shrpx_https_upstream.cc @@ -40,16 +40,15 @@ namespace shrpx { namespace { const size_t SHRPX_HTTPS_UPSTREAM_OUTPUT_UPPER_THRES = 512*1024; +const size_t SHRPX_HTTPS_MAX_HEADER_LENGTH = 64*1024; } // namespace HttpsUpstream::HttpsUpstream(ClientHandler *handler) : handler_(handler), htp_(htparser_new()), + current_header_length_(0), ioctrl_(handler->get_bev()) { - if(ENABLE_LOG) { - LOG(INFO) << "HttpsUpstream ctor"; - } htparser_init(htp_, htp_type_request); htparser_set_userdata(htp_, this); } @@ -63,6 +62,11 @@ HttpsUpstream::~HttpsUpstream() } } +void HttpsUpstream::reset_current_header_length() +{ + current_header_length_ = 0; +} + namespace { int htp_msg_begin(htparser *htp) { @@ -71,6 +75,7 @@ int htp_msg_begin(htparser *htp) } HttpsUpstream *upstream; upstream = reinterpret_cast(htparser_get_userdata(htp)); + upstream->reset_current_header_length(); Downstream *downstream = new Downstream(upstream, 0, 0); upstream->add_downstream(downstream); return 0; @@ -207,16 +212,10 @@ htparse_hooks htp_hooks = { }; } // namespace -std::set cache; - // on_read() does not consume all available data in input buffer if // one http request is fully received. int HttpsUpstream::on_read() { - if(cache.count(this) == 0) { - LOG(INFO) << "HttpsUpstream::on_read"; - cache.insert(this); - } bufferevent *bev = handler_->get_bev(); evbuffer *input = bufferevent_get_input(bev); unsigned char *mem = evbuffer_pullup(input, -1); @@ -224,18 +223,25 @@ int HttpsUpstream::on_read() reinterpret_cast(mem), evbuffer_get_length(input)); evbuffer_drain(input, nread); + // Well, actually header length + some body bytes + current_header_length_ += nread; htpparse_error htperr = htparser_get_error(htp_); if(htperr == htparse_error_user) { - pause_read(SHRPX_MSG_BLOCK); - if(ENABLE_LOG) { - LOG(INFO) << " remaining bytes " << evbuffer_get_length(input); + if(current_header_length_ > SHRPX_HTTPS_MAX_HEADER_LENGTH) { + LOG(WARNING) << "Request Header too long:" << current_header_length_ + << " bytes"; + get_client_handler()->set_should_close_after_write(true); + error_reply(400); + } else { + pause_read(SHRPX_MSG_BLOCK); } } else if(htperr != htparse_error_none) { if(ENABLE_LOG) { LOG(INFO) << " http parse failure: " << htparser_get_strerror(htp_); } - return SHRPX_ERR_HTTP_PARSE; + get_client_handler()->set_should_close_after_write(true); + error_reply(400); } return 0; } @@ -298,7 +304,7 @@ void https_downstream_readcb(bufferevent *bev, void *ptr) if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { delete upstream->get_client_handler(); } else { - upstream->error_reply(downstream, 502); + upstream->error_reply(502); assert(downstream == upstream->get_top_downstream()); upstream->pop_downstream(); delete downstream; @@ -344,7 +350,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) if(ENABLE_LOG) { LOG(INFO) << " Treated as error"; } - upstream->error_reply(downstream, 502); + upstream->error_reply(502); } upstream->pop_downstream(); delete downstream; @@ -360,7 +366,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) } else { status = 502; } - upstream->error_reply(downstream, status); + upstream->error_reply(status); } upstream->pop_downstream(); delete downstream; @@ -369,20 +375,22 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) } } // namespace -void HttpsUpstream::error_reply(Downstream *downstream, int status_code) +void HttpsUpstream::error_reply(int status_code) { std::string html = http::create_error_html(status_code); std::stringstream ss; ss << "HTTP/1.1 " << http::get_status_string(status_code) << "\r\n" << "Server: " << get_config()->server_name << "\r\n" << "Content-Length: " << html.size() << "\r\n" - << "Content-Type: " << "text/html; charset=UTF-8\r\n" - << "\r\n"; + << "Content-Type: " << "text/html; charset=UTF-8\r\n"; + if(get_client_handler()->get_should_close_after_write()) { + ss << "Connection: close\r\n"; + } + ss << "\r\n"; std::string header = ss.str(); evbuffer *output = bufferevent_get_output(handler_->get_bev()); evbuffer_add(output, header.c_str(), header.size()); evbuffer_add(output, html.c_str(), html.size()); - downstream->set_response_state(Downstream::MSG_COMPLETE); } bufferevent_data_cb HttpsUpstream::get_downstream_readcb() @@ -454,7 +462,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) hdrs += "\r\n"; } } - if(downstream->get_request_connection_close()) { + if(get_client_handler()->get_should_close_after_write()) { hdrs += "Connection: close\r\n"; } hdrs += "\r\n"; diff --git a/examples/shrpx_https_upstream.h b/examples/shrpx_https_upstream.h index 3fa76f9..ae679be 100644 --- a/examples/shrpx_https_upstream.h +++ b/examples/shrpx_https_upstream.h @@ -58,7 +58,7 @@ public: void pop_downstream(); Downstream* get_top_downstream(); Downstream* get_last_downstream(); - void error_reply(Downstream *downstream, int status_code); + void error_reply(int status_code); void pause_read(IOCtrlReason reason); void resume_read(IOCtrlReason reason); @@ -68,9 +68,11 @@ public: const uint8_t *data, size_t len); virtual int on_downstream_body_complete(Downstream *downstream); + void reset_current_header_length(); private: ClientHandler *handler_; htparser *htp_; + size_t current_header_length_; std::deque downstream_queue_; IOControl ioctrl_; }; From 67669bf3ca1af200cb808434f4b5e78b02d7ebf8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 Jun 2012 22:46:47 +0900 Subject: [PATCH 06/21] Delete downstream if SPDY stream has been already closed --- examples/shrpx_spdy_upstream.cc | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index ba2e413..7f5dd3c 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -272,18 +272,17 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr) Downstream *downstream = reinterpret_cast(ptr); SpdyUpstream *upstream; upstream = static_cast(downstream->get_upstream()); + // If upstream SPDY stream was closed, we just close downstream, + // because there is no consumer now. + if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { + upstream->remove_downstream(downstream); + delete downstream; + return; + } int rv = downstream->parse_http_response(); - if(rv == 0) { - if(downstream->get_request_state() == Downstream::STREAM_CLOSED && - downstream->get_response_state() == Downstream::MSG_COMPLETE) { - upstream->get_downstream_queue()->remove(downstream); - delete downstream; - } else { - upstream->send(); - } - } else { + if(rv != 0) { if(ENABLE_LOG) { - LOG(INFO) << " http parser failure"; + LOG(INFO) << "Downstream HTTP parser failure"; } if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); @@ -291,10 +290,8 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr) upstream->error_reply(downstream, 502); } downstream->set_response_state(Downstream::MSG_COMPLETE); - upstream->send(); - // upstream->remove_downstream(downstream); - // delete downstream; } + upstream->send(); } } // namespace From c0b564abe43107647170f2bdfa9717dec564f574 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 Jun 2012 22:55:19 +0900 Subject: [PATCH 07/21] Tidied up --- examples/shrpx_downstream.cc | 6 +++--- examples/shrpx_downstream.h | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index 08ab5fd..63e9872 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -63,7 +63,7 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) Downstream::~Downstream() { if(ENABLE_LOG) { - LOG(INFO) << "Deleting downstream" << this; + LOG(INFO) << "Deleting downstream " << this; } if(response_body_buf_) { // Passing NULL to evbuffer_free() causes segmentation fault. @@ -230,7 +230,7 @@ int Downstream::push_request_headers() hdrs += "\r\n"; if(ENABLE_LOG) { - LOG(INFO) << " request headers\n" << hdrs; + LOG(INFO) << "Downstream request headers\n" << hdrs; } evbuffer *output = bufferevent_get_output(bev_); evbuffer_add(output, hdrs.c_str(), hdrs.size()); @@ -389,7 +389,7 @@ int Downstream::parse_http_response() return 0; } else { if(ENABLE_LOG) { - LOG(INFO) << " http parser failure: " + LOG(INFO) << "Downstream HTTP parser failure: " << htparser_get_strerror(response_htp_); } return SHRPX_ERR_HTTP_PARSE; diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h index ef5687b..3bf67d9 100644 --- a/examples/shrpx_downstream.h +++ b/examples/shrpx_downstream.h @@ -106,7 +106,8 @@ private: bool chunked_response_; Headers response_headers_; htparser *response_htp_; - + // This buffer is used to temporarily store downstream response + // body. Spdylay reads data from this in the callback. evbuffer *response_body_buf_; }; From 8f1c49e75c78876cea701b27ee4822dd5df31817 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 01:26:04 +0900 Subject: [PATCH 08/21] Added multi thread support --- examples/Makefile.am | 3 + examples/shrpx.cc | 136 +++-------------------- examples/shrpx.h | 2 + examples/shrpx_client_handler.cc | 17 +-- examples/shrpx_config.cc | 3 +- examples/shrpx_config.h | 1 + examples/shrpx_https_upstream.cc | 27 ++--- examples/shrpx_listen_handler.cc | 80 ++++++++++---- examples/shrpx_listen_handler.h | 14 ++- examples/shrpx_spdy_upstream.cc | 31 +++--- examples/shrpx_ssl.cc | 141 ++++++++++++++++++++++++ examples/shrpx_ssl.h | 51 +++++++++ examples/shrpx_thread_event_receiver.cc | 69 ++++++++++++ examples/shrpx_thread_event_receiver.h | 55 +++++++++ examples/shrpx_worker.cc | 93 ++++++++++++++++ examples/shrpx_worker.h | 50 +++++++++ 16 files changed, 592 insertions(+), 181 deletions(-) create mode 100644 examples/shrpx_ssl.cc create mode 100644 examples/shrpx_ssl.h create mode 100644 examples/shrpx_thread_event_receiver.cc create mode 100644 examples/shrpx_thread_event_receiver.h create mode 100644 examples/shrpx_worker.cc create mode 100644 examples/shrpx_worker.h diff --git a/examples/Makefile.am b/examples/Makefile.am index d62e8d6..3b304be 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -78,6 +78,9 @@ shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_log.cc shrpx_log.h \ shrpx_http.cc shrpx_http.h \ shrpx_io_control.cc shrpx_io_control.h \ + shrpx_ssl.cc shrpx_ssl.h \ + shrpx_thread_event_receiver.cc shrpx_thread_event_receiver.h \ + shrpx_worker.cc shrpx_worker.h \ htparse/htparse.c htparse/htparse.h noinst_PROGRAMS = spdycli diff --git a/examples/shrpx.cc b/examples/shrpx.cc index 3200f73..f01a39f 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -30,9 +30,7 @@ #include #include - #include -#include #include #include @@ -46,82 +44,6 @@ namespace shrpx { -namespace { -std::pair next_proto; -unsigned char proto_list[23]; -} // namespace - -namespace { -int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, - void *arg) -{ - std::pair *next_proto = - reinterpret_cast* >(arg); - *data = next_proto->first; - *len = next_proto->second; - return SSL_TLSEXT_ERR_OK; -} -} // namespace - -namespace { -int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) -{ - // We don't verify the client certificate. Just request it for the - // testing purpose. - return 1; -} -} // namespace - -namespace { -SSL_CTX* create_ssl_ctx() -{ - // TODO lock function - SSL_CTX *ssl_ctx; - ssl_ctx = SSL_CTX_new(SSLv23_server_method()); - if(!ssl_ctx) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return NULL; - } - SSL_CTX_set_options(ssl_ctx, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); - if(SSL_CTX_use_PrivateKey_file(ssl_ctx, - get_config()->private_key_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; - return NULL; - } - if(SSL_CTX_use_certificate_file(ssl_ctx, get_config()->cert_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; - return NULL; - } - if(SSL_CTX_check_private_key(ssl_ctx) != 1) { - std::cerr << "SSL_CTX_check_private_key failed." << std::endl; - return NULL; - } - if(get_config()->verify_client) { - SSL_CTX_set_verify(ssl_ctx, - SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - verify_callback); - } - // We speaks "http/1.1", "spdy/2" and "spdy/3". - proto_list[0] = 6; - memcpy(&proto_list[1], "spdy/3", 6); - proto_list[7] = 6; - memcpy(&proto_list[8], "spdy/2", 6); - proto_list[14] = 8; - memcpy(&proto_list[15], "http/1.1", 8); - - next_proto.first = proto_list; - next_proto.second = sizeof(proto_list); - SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); - return ssl_ctx; -} -} // namespace - namespace { void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int addrlen, void *arg) @@ -144,45 +66,20 @@ int cache_downstream_host_address() #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG - addrinfo *res, *rp; + addrinfo *res; rv = getaddrinfo(get_config()->downstream_host, service, &hints, &res); if(rv != 0) { - LOG(ERROR) << "getaddrinfo: " << gai_strerror(rv); - return -1; - } - for(rp = res; rp; rp = rp->ai_next) { - int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if(fd == -1) { - continue; - } - rv = connect(fd, rp->ai_addr, rp->ai_addrlen); - close(fd); - if(rv == -1) { - continue; - } - break; - } - if(rp == 0 && res) { - LOG(INFO) << "Using first returned address for downstream " - << get_config()->downstream_host - << ", port " - << get_config()->downstream_port; - rp = res; - } - if(rp != 0) { - memcpy(&mod_config()->downstream_addr, rp->ai_addr, rp->ai_addrlen); - mod_config()->downstream_addrlen = rp->ai_addrlen; + LOG(FATAL) << "Unable to get downstream address: " << gai_strerror(rv); + DIE(); } + LOG(INFO) << "Using first returned address for downstream " + << get_config()->downstream_host + << ", port " + << get_config()->downstream_port; + memcpy(&mod_config()->downstream_addr, res->ai_addr, res->ai_addrlen); + mod_config()->downstream_addrlen = res->ai_addrlen; freeaddrinfo(res); - if(rp == 0) { - LOG(ERROR) << "No usable address found for downstream " - << get_config()->downstream_host - << ", port " - << get_config()->downstream_port; - return -1; - } else { - return 0; - } + return 0; } } // namespace @@ -245,12 +142,11 @@ evconnlistener* create_evlistener(ListenHandler *handler) namespace { int event_loop() { - SSL_CTX *ssl_ctx = create_ssl_ctx(); - if(ssl_ctx == NULL) { - return -1; - } event_base *evbase = event_base_new(); - ListenHandler *listener_handler = new ListenHandler(evbase, ssl_ctx); + ListenHandler *listener_handler = new ListenHandler(evbase); + if(get_config()->num_worker > 1) { + listener_handler->create_worker_thread(get_config()->num_worker); + } evconnlistener *evlistener = create_evlistener(listener_handler); if(evlistener == NULL) { return -1; @@ -261,7 +157,6 @@ int event_loop() event_base_loop(evbase, 0); evconnlistener_free(evlistener); - SSL_CTX_free(ssl_ctx); return 0; } } // namespace @@ -311,6 +206,9 @@ int main(int argc, char **argv) if(cache_downstream_host_address() == -1) { exit(EXIT_FAILURE); } + + mod_config()->num_worker = 4; + event_loop(); return 0; } diff --git a/examples/shrpx.h b/examples/shrpx.h index 2258b43..50e458e 100644 --- a/examples/shrpx.h +++ b/examples/shrpx.h @@ -29,6 +29,8 @@ # include #endif // HAVE_CONFIG_H +#include + #include "shrpx_log.h" #define DIE() \ diff --git a/examples/shrpx_client_handler.cc b/examples/shrpx_client_handler.cc index 4139c3a..b62716e 100644 --- a/examples/shrpx_client_handler.cc +++ b/examples/shrpx_client_handler.cc @@ -49,6 +49,7 @@ namespace { void upstream_writecb(bufferevent *bev, void *arg) { ClientHandler *handler = reinterpret_cast(arg); + // We actually depend on write low-warter mark == 0. if(handler->get_should_close_after_write()) { delete handler; } else { @@ -65,19 +66,19 @@ void upstream_eventcb(bufferevent *bev, short events, void *arg) bool finish = false; if(events & BEV_EVENT_EOF) { if(ENABLE_LOG) { - LOG(INFO) << " SSL/TLS handshake EOF"; + LOG(INFO) << "Upstream handshake EOF"; } finish = true; } if(events & BEV_EVENT_ERROR) { if(ENABLE_LOG) { - LOG(INFO) << " SSL/TLS network error"; + LOG(INFO) << "Upstream network error"; } finish = true; } if(events & BEV_EVENT_TIMEOUT) { if(ENABLE_LOG) { - LOG(INFO) << "SPDY upstream SSL/TLS time out"; + LOG(INFO) << "Upstream time out"; } finish = true; } @@ -86,8 +87,7 @@ void upstream_eventcb(bufferevent *bev, short events, void *arg) } else { if(events & BEV_EVENT_CONNECTED) { if(ENABLE_LOG) { - LOG(INFO) << "Connected Handler " - << handler; + LOG(INFO) << "Upstream connected. handler " << handler; } handler->set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); handler->validate_next_proto(); @@ -167,7 +167,7 @@ int ClientHandler::validate_next_proto() if(next_proto) { std::string proto(next_proto, next_proto+next_proto_len); if(ENABLE_LOG) { - LOG(INFO) << " The negotiated next protocol: " << proto; + LOG(INFO) << "Upstream negotiated next protocol: " << proto; } uint16_t version = spdylay_npn_get_version(next_proto, next_proto_len); if(version) { @@ -177,9 +177,12 @@ int ClientHandler::validate_next_proto() } } else { if(ENABLE_LOG) { - LOG(INFO) << " No proto negotiated"; + LOG(INFO) << "No proto negotiated."; } } + if(ENABLE_LOG) { + LOG(INFO) << "Use HTTP/1.1"; + } HttpsUpstream *https_upstream = new HttpsUpstream(this); upstream_ = https_upstream; return 0; diff --git a/examples/shrpx_config.cc b/examples/shrpx_config.cc index 234f7eb..1d801f7 100644 --- a/examples/shrpx_config.cc +++ b/examples/shrpx_config.cc @@ -38,7 +38,8 @@ Config::Config() downstream_host(0), downstream_port(0), downstream_hostport(0), - downstream_addrlen(0) + downstream_addrlen(0), + num_worker(0) {} namespace { diff --git a/examples/shrpx_config.h b/examples/shrpx_config.h index 7063081..22e3899 100644 --- a/examples/shrpx_config.h +++ b/examples/shrpx_config.h @@ -64,6 +64,7 @@ struct Config { timeval spdy_upstream_write_timeout; timeval downstream_read_timeout; timeval downstream_write_timeout; + size_t num_worker; Config(); }; diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc index 707b82b..276b81a 100644 --- a/examples/shrpx_https_upstream.cc +++ b/examples/shrpx_https_upstream.cc @@ -71,7 +71,7 @@ namespace { int htp_msg_begin(htparser *htp) { if(ENABLE_LOG) { - LOG(INFO) << ":: request start"; + LOG(INFO) << "Upstream https request start"; } HttpsUpstream *upstream; upstream = reinterpret_cast(htparser_get_userdata(htp)); @@ -108,7 +108,7 @@ namespace { int htp_hdrs_begincb(htparser *htp) { if(ENABLE_LOG) { - LOG(INFO) << ":: request headers start"; + LOG(INFO) << "Upstream https request headers start"; } HttpsUpstream *upstream; upstream = reinterpret_cast(htparser_get_userdata(htp)); @@ -148,7 +148,7 @@ namespace { int htp_hdrs_completecb(htparser *htp) { if(ENABLE_LOG) { - LOG(INFO) << ":: request headers complete"; + LOG(INFO) << "Upstream https request headers complete"; } HttpsUpstream *upstream; upstream = reinterpret_cast(htparser_get_userdata(htp)); @@ -178,7 +178,7 @@ namespace { int htp_msg_completecb(htparser *htp) { if(ENABLE_LOG) { - LOG(INFO) << ":: request complete"; + LOG(INFO) << "Upstream https request complete"; } HttpsUpstream *upstream; upstream = reinterpret_cast(htparser_get_userdata(htp)); @@ -237,7 +237,7 @@ int HttpsUpstream::on_read() } } else if(htperr != htparse_error_none) { if(ENABLE_LOG) { - LOG(INFO) << " http parse failure: " + LOG(INFO) << "Upstream http parse failure: " << htparser_get_strerror(htp_); } get_client_handler()->set_should_close_after_write(true); @@ -328,18 +328,19 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) upstream = static_cast(downstream->get_upstream()); if(events & BEV_EVENT_CONNECTED) { if(ENABLE_LOG) { - LOG(INFO) << " Connection established. " << downstream; + LOG(INFO) << "Downstream connection established. downstream " + << downstream; } } if(events & BEV_EVENT_EOF) { if(ENABLE_LOG) { - LOG(INFO) << " EOF stream_id=" + LOG(INFO) << "Downstream EOF. stream_id=" << downstream->get_stream_id(); } if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { // Server may indicate the end of the request by EOF if(ENABLE_LOG) { - LOG(INFO) << " Assuming content-length is 0 byte"; + LOG(INFO) << "Assuming downstream content-length is 0 byte"; } upstream->on_downstream_body_complete(downstream); //downstream->set_response_state(Downstream::MSG_COMPLETE); @@ -348,7 +349,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) } else { // error if(ENABLE_LOG) { - LOG(INFO) << " Treated as error"; + LOG(INFO) << "Treated as downstream error"; } upstream->error_reply(502); } @@ -357,7 +358,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) upstream->resume_read(SHRPX_MSG_BLOCK); } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { if(ENABLE_LOG) { - LOG(INFO) << " error/timeout. " << downstream; + LOG(INFO) << "Downstream error/timeout. " << downstream; } if(downstream->get_response_state() == Downstream::INITIAL) { int status; @@ -439,7 +440,7 @@ Downstream* HttpsUpstream::get_last_downstream() int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { if(ENABLE_LOG) { - LOG(INFO) << " on_downstream_header_complete"; + LOG(INFO) << "Downstream on_downstream_header_complete"; } std::string hdrs = "HTTP/1.1 "; hdrs += http::get_status_string(downstream->get_response_http_status()); @@ -467,7 +468,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) } hdrs += "\r\n"; if(ENABLE_LOG) { - LOG(INFO) << ":: Response headers\n" << hdrs; + LOG(INFO) << "Upstream https response headers\n" << hdrs; } evbuffer *output = bufferevent_get_output(handler_->get_bev()); evbuffer_add(output, hdrs.c_str(), hdrs.size()); @@ -496,7 +497,7 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) evbuffer_add(output, "0\r\n\r\n", 5); } if(ENABLE_LOG) { - LOG(INFO) << " on_downstream_body_complete"; + LOG(INFO) << "Downstream on_downstream_body_complete"; } if(downstream->get_request_connection_close()) { ClientHandler *handler = downstream->get_upstream()->get_client_handler(); diff --git a/examples/shrpx_listen_handler.cc b/examples/shrpx_listen_handler.cc index 8be62a1..685321e 100644 --- a/examples/shrpx_listen_handler.cc +++ b/examples/shrpx_listen_handler.cc @@ -24,48 +24,82 @@ */ #include "shrpx_listen_handler.h" +#include + +#include + #include #include "shrpx_client_handler.h" +#include "shrpx_thread_event_receiver.h" +#include "shrpx_ssl.h" +#include "shrpx_worker.h" namespace shrpx { -ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx) +ListenHandler::ListenHandler(event_base *evbase) : evbase_(evbase), - ssl_ctx_(ssl_ctx) + ssl_ctx_(ssl::create_ssl_context()), + worker_round_robin_cnt_(0), + workers_(0), + num_worker_(0) {} ListenHandler::~ListenHandler() {} +void ListenHandler::create_worker_thread(size_t num) +{ + workers_ = new WorkerInfo[num]; + num_worker_ = 0; + for(size_t i = 0; i < num; ++i) { + int rv; + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + WorkerInfo *info = &workers_[num_worker_]; + rv = socketpair(AF_UNIX, SOCK_STREAM, 0, info->sv); + if(rv == -1) { + LOG(ERROR) << "socketpair() failed: " << strerror(errno); + continue; + } + rv = pthread_create(&thread, &attr, start_threaded_worker, &info->sv[1]); + if(rv != 0) { + LOG(ERROR) << "pthread_create() failed: " << strerror(rv); + for(size_t j = 0; j < 2; ++j) { + close(info->sv[j]); + } + continue; + } + bufferevent *bev = bufferevent_socket_new(evbase_, info->sv[0], + BEV_OPT_DEFER_CALLBACKS); + info->bev = bev; + if(ENABLE_LOG) { + LOG(INFO) << "Created thread#" << num_worker_; + } + ++num_worker_; + } +} + int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen) { if(ENABLE_LOG) { LOG(INFO) << " Accepted connection. fd=" << fd; } - char host[NI_MAXHOST]; - int rv; - rv = getnameinfo(addr, addrlen, host, sizeof(host), 0, 0, NI_NUMERICHOST); - if(rv == 0) { - SSL *ssl = SSL_new(ssl_ctx_); - bufferevent *bev = bufferevent_openssl_socket_new - (evbase_, fd, ssl, - BUFFEREVENT_SSL_ACCEPTING, - BEV_OPT_DEFER_CALLBACKS); - if(bev == NULL) { - if(ENABLE_LOG) { - LOG(ERROR) << " bufferevent_openssl_socket_new failed"; - } - close(fd); - } else { - /*ClientHandler *client_handler =*/ new ClientHandler(bev, ssl, host); - } + if(num_worker_ == 0) { + /*ClientHandler* client = */ + ssl::accept_ssl_connection(evbase_, ssl_ctx_, fd, addr, addrlen); } else { - if(ENABLE_LOG) { - LOG(INFO) << " getnameinfo failed"; - } - close(fd); + size_t idx = worker_round_robin_cnt_ % num_worker_; + ++worker_round_robin_cnt_; + WorkerEvent wev; + wev.client_fd = fd; + memcpy(&wev.client_addr, addr, addrlen); + wev.client_addrlen = addrlen; + evbuffer *output = bufferevent_get_output(workers_[idx].bev); + evbuffer_add(output, &wev, sizeof(wev)); } return 0; } diff --git a/examples/shrpx_listen_handler.h b/examples/shrpx_listen_handler.h index 4b3e31f..ccae81c 100644 --- a/examples/shrpx_listen_handler.h +++ b/examples/shrpx_listen_handler.h @@ -31,19 +31,29 @@ #include #include + #include namespace shrpx { +struct WorkerInfo { + int sv[2]; + bufferevent *bev; +}; + class ListenHandler { public: - ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx); + ListenHandler(event_base *evbase); ~ListenHandler(); int accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen); + void create_worker_thread(size_t num); event_base* get_evbase() const; private: event_base *evbase_; - SSL_CTX *ssl_ctx_; + SSL_CTX *ssl_ctx_; + unsigned int worker_round_robin_cnt_; + WorkerInfo *workers_; + size_t num_worker_; }; } // namespace shrpx diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index 7f5dd3c..8cc39d9 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -90,8 +90,7 @@ void on_stream_close_callback void *user_data) { if(ENABLE_LOG) { - LOG(INFO) << ":: Stream " << stream_id - << " is being closed"; + LOG(INFO) << "Upstream spdy Stream " << stream_id << " is being closed"; } SpdyUpstream *upstream = reinterpret_cast(user_data); Downstream *downstream = upstream->get_downstream_queue()->find(stream_id); @@ -114,7 +113,7 @@ void on_ctrl_recv_callback switch(type) { case SPDYLAY_SYN_STREAM: { if(ENABLE_LOG) { - LOG(INFO) << ":: Received upstream SYN_STREAM stream_id=" + LOG(INFO) << "Upstream spdy received upstream SYN_STREAM stream_id=" << frame->syn_stream.stream_id; } Downstream *downstream = new Downstream(upstream, @@ -139,14 +138,14 @@ void on_ctrl_recv_callback for(size_t i = 0; nv[i]; i += 2) { ss << nv[i] << ": " << nv[i+1] << "\n"; } - LOG(INFO) << ":: Request headers:\n" << ss.str(); + LOG(INFO) << "Upstream spdy request headers:\n" << ss.str(); } downstream->push_request_headers(); downstream->set_request_state(Downstream::HEADER_COMPLETE); if(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) { if(ENABLE_LOG) { - LOG(INFO) << ":: " + LOG(INFO) << "Upstream spdy " << "Setting Downstream::MSG_COMPLETE for Downstream " << downstream; } @@ -169,7 +168,7 @@ void on_data_chunk_recv_callback(spdylay_session *session, void *user_data) { if(ENABLE_LOG) { - LOG(INFO) << ":: Received upstream DATA data stream_id=" + LOG(INFO) << "Upstream spdy received upstream DATA data stream_id=" << stream_id; } SpdyUpstream *upstream = reinterpret_cast(user_data); @@ -178,8 +177,8 @@ void on_data_chunk_recv_callback(spdylay_session *session, downstream->push_upload_data_chunk(data, len); if(flags & SPDYLAY_DATA_FLAG_FIN) { if(ENABLE_LOG) { - LOG(INFO) << ":: " - << "Setting Downstream::MSG_COMPLETE for Downstream " + LOG(INFO) << "Upstream spdy " + << "setting Downstream::MSG_COMPLETE for Downstream " << downstream; } downstream->set_request_state(Downstream::MSG_COMPLETE); @@ -309,13 +308,13 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) upstream = static_cast(downstream->get_upstream()); if(events & BEV_EVENT_CONNECTED) { if(ENABLE_LOG) { - LOG(INFO) << " Connection established. Downstream " + LOG(INFO) << "Downstream connection established. Downstream " << downstream; } } if(events & BEV_EVENT_EOF) { if(ENABLE_LOG) { - LOG(INFO) << " EOF stream_id=" + LOG(INFO) << "Downstream EOF stream_id=" << downstream->get_stream_id(); } if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { @@ -328,7 +327,7 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { // Server may indicate the end of the request by EOF if(ENABLE_LOG) { - LOG(INFO) << " Assuming content-length is 0 byte"; + LOG(INFO) << "Assuming downstream content-length is 0 byte"; } downstream->set_response_state(Downstream::MSG_COMPLETE); upstream->on_downstream_body_complete(downstream); @@ -342,7 +341,7 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) } } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { if(ENABLE_LOG) { - LOG(INFO) << " error/timeout. Downstream " << downstream; + LOG(INFO) << "Downstream error/timeout. Downstream " << downstream; } if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { upstream->remove_downstream(downstream); @@ -474,7 +473,7 @@ spdylay_session* SpdyUpstream::get_spdy_session() int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { if(ENABLE_LOG) { - LOG(INFO) << " on_downstream_header_complete"; + LOG(INFO) << "Downstream on_downstream_header_complete"; } size_t nheader = downstream->get_response_headers().size(); const char **nv = new const char*[nheader * 2 + 4 + 1]; @@ -504,7 +503,7 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) for(size_t i = 0; nv[i]; i += 2) { ss << nv[i] << ": " << nv[i+1] << "\n"; } - LOG(INFO) << ":: Response headers\n" << ss.str(); + LOG(INFO) << "Upstream spdy response headers\n" << ss.str(); } spdylay_data_provider data_prd; data_prd.source.ptr = downstream; @@ -520,7 +519,7 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream, const uint8_t *data, size_t len) { if(ENABLE_LOG) { - LOG(INFO) << " on_downstream_body"; + LOG(INFO) << "Downstream on_downstream_body"; } evbuffer *body = downstream->get_response_body_buf(); evbuffer_add(body, data, len); @@ -537,7 +536,7 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream, int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) { if(ENABLE_LOG) { - LOG(INFO) << " on_downstream_body_complete"; + LOG(INFO) << "Downstream on_downstream_body_complete"; } spdylay_session_resume_data(session_, downstream->get_stream_id()); return 0; diff --git a/examples/shrpx_ssl.cc b/examples/shrpx_ssl.cc new file mode 100644 index 0000000..a278daa --- /dev/null +++ b/examples/shrpx_ssl.cc @@ -0,0 +1,141 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_ssl.h" + +#include +#include + +#include +#include + +#include "shrpx_log.h" +#include "shrpx_client_handler.h" +#include "shrpx_config.h" + +namespace shrpx { + +namespace ssl { + +namespace { +std::pair next_proto; +unsigned char proto_list[23]; +} // namespace + +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) +{ + std::pair *next_proto = + reinterpret_cast* >(arg); + *data = next_proto->first; + *len = next_proto->second; + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +namespace { +int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + // We don't verify the client certificate. Just request it for the + // testing purpose. + return 1; +} +} // namespace + +SSL_CTX* create_ssl_context() +{ + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), 0); + DIE(); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + if(SSL_CTX_use_PrivateKey_file(ssl_ctx, + get_config()->private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed."; + DIE(); + } + if(SSL_CTX_use_certificate_file(ssl_ctx, get_config()->cert_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed."; + DIE(); + } + if(SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed."; + DIE(); + } + if(get_config()->verify_client) { + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + // We speaks "http/1.1", "spdy/2" and "spdy/3". + proto_list[0] = 6; + memcpy(&proto_list[1], "spdy/3", 6); + proto_list[7] = 6; + memcpy(&proto_list[8], "spdy/2", 6); + proto_list[14] = 8; + memcpy(&proto_list[15], "http/1.1", 8); + + next_proto.first = proto_list; + next_proto.second = sizeof(proto_list); + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); + return ssl_ctx; +} + +ClientHandler* accept_ssl_connection(event_base *evbase, SSL_CTX *ssl_ctx, + evutil_socket_t fd, + sockaddr *addr, int addrlen) +{ + char host[NI_MAXHOST]; + int rv; + rv = getnameinfo(addr, addrlen, host, sizeof(host), 0, 0, NI_NUMERICHOST); + if(rv == 0) { + SSL *ssl = SSL_new(ssl_ctx); + if(!ssl) { + LOG(ERROR) << "SSL_new() failed"; + return 0; + } + bufferevent *bev = bufferevent_openssl_socket_new + (evbase, fd, ssl, + BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS); + + ClientHandler *client_handler = new ClientHandler(bev, ssl, host); + return client_handler; + } else { + LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv); + return 0; + } +} + +} // namespace ssl + +} // namespace shrpx diff --git a/examples/shrpx_ssl.h b/examples/shrpx_ssl.h new file mode 100644 index 0000000..1f3e362 --- /dev/null +++ b/examples/shrpx_ssl.h @@ -0,0 +1,51 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_SSL_H +#define SHRPX_SSL_H + +#include "shrpx.h" + +#include +#include + +#include + +namespace shrpx { + +class ClientHandler; + +namespace ssl { + +SSL_CTX* create_ssl_context(); + +ClientHandler* accept_ssl_connection(event_base *evbase, SSL_CTX *ssl_ctx, + evutil_socket_t fd, + sockaddr *addr, int addrlen); + +} // namespace ssl + +} // namespace shrpx + +#endif // SHRPX_SSL_H diff --git a/examples/shrpx_thread_event_receiver.cc b/examples/shrpx_thread_event_receiver.cc new file mode 100644 index 0000000..009589d --- /dev/null +++ b/examples/shrpx_thread_event_receiver.cc @@ -0,0 +1,69 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_thread_event_receiver.h" + +#include "shrpx_ssl.h" +#include "shrpx_log.h" +#include "shrpx_client_handler.h" + +namespace shrpx { + +ThreadEventReceiver::ThreadEventReceiver(SSL_CTX *ssl_ctx) + : ssl_ctx_(ssl_ctx) +{} + +ThreadEventReceiver::~ThreadEventReceiver() +{} + +void ThreadEventReceiver::on_read(bufferevent *bev) +{ + evbuffer *input = bufferevent_get_input(bev); + while(evbuffer_get_length(input) >= sizeof(WorkerEvent)) { + WorkerEvent wev; + evbuffer_remove(input, &wev, sizeof(WorkerEvent)); + if(ENABLE_LOG) { + LOG(INFO) << "WorkerEvent: client_fd=" << wev.client_fd + << ", addrlen=" << wev.client_addrlen; + } + event_base *evbase = bufferevent_get_base(bev); + ClientHandler *client_handler; + client_handler = ssl::accept_ssl_connection(evbase, ssl_ctx_, + wev.client_fd, + &wev.client_addr.sa, + wev.client_addrlen); + if(client_handler) { + if(ENABLE_LOG) { + LOG(INFO) << "ClientHandler " << client_handler << " created"; + } + } else { + if(ENABLE_LOG) { + LOG(ERROR) << "ClientHandler creation failed"; + } + close(wev.client_fd); + } + } +} + +} // namespace shrpx diff --git a/examples/shrpx_thread_event_receiver.h b/examples/shrpx_thread_event_receiver.h new file mode 100644 index 0000000..70116ac --- /dev/null +++ b/examples/shrpx_thread_event_receiver.h @@ -0,0 +1,55 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_THREAD_EVENT_RECEIVER_H +#define SHRPX_THREAD_EVENT_RECEIVER_H + +#include "shrpx.h" + +#include + +#include + +#include "shrpx_config.h" + +namespace shrpx { + +struct WorkerEvent { + evutil_socket_t client_fd; + sockaddr_union client_addr; + size_t client_addrlen; +}; + +class ThreadEventReceiver { +public: + ThreadEventReceiver(SSL_CTX *ssl_ctx); + ~ThreadEventReceiver(); + void on_read(bufferevent *bev); +private: + SSL_CTX *ssl_ctx_; +}; + +} // namespace shrpx + +#endif // SHRPX_THREAD_EVENT_RECEIVER_H diff --git a/examples/shrpx_worker.cc b/examples/shrpx_worker.cc new file mode 100644 index 0000000..c0543aa --- /dev/null +++ b/examples/shrpx_worker.cc @@ -0,0 +1,93 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_worker.h" + +#include +#include + +#include +#include + +#include "shrpx_ssl.h" +#include "shrpx_thread_event_receiver.h" +#include "shrpx_log.h" + +namespace shrpx { + +Worker::Worker(int fd) + : fd_(fd), + ssl_ctx_(ssl::create_ssl_context()) +{} + +Worker::~Worker() +{ + SSL_CTX_free(ssl_ctx_); + shutdown(fd_, SHUT_WR); + close(fd_); +} + +namespace { +void readcb(bufferevent *bev, void *arg) +{ + ThreadEventReceiver *receiver = reinterpret_cast(arg); + receiver->on_read(bev); +} +} // namespace + +namespace { +void eventcb(bufferevent *bev, short events, void *arg) +{ + if(events & BEV_EVENT_EOF) { + LOG(ERROR) << "Connection to main thread lost: eof"; + } + if(events & BEV_EVENT_ERROR) { + LOG(ERROR) << "Connection to main thread lost: network error"; + } +} +} // namespace + +void Worker::run() +{ + event_base *evbase = event_base_new(); + bufferevent *bev = bufferevent_socket_new(evbase, fd_, + BEV_OPT_DEFER_CALLBACKS); + ThreadEventReceiver *receiver = new ThreadEventReceiver(ssl_ctx_); + bufferevent_enable(bev, EV_READ); + bufferevent_setcb(bev, readcb, 0, eventcb, receiver); + + event_base_loop(evbase, 0); + + delete receiver; +} + +void* start_threaded_worker(void *arg) +{ + int fd = *reinterpret_cast(arg); + Worker worker(fd); + worker.run(); + return 0; +} + +} // namespace shrpx diff --git a/examples/shrpx_worker.h b/examples/shrpx_worker.h new file mode 100644 index 0000000..2a03065 --- /dev/null +++ b/examples/shrpx_worker.h @@ -0,0 +1,50 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_WORKER_H +#define SHRPX_WORKER_H + +#include "shrpx.h" + +#include +#include + +namespace shrpx { + +class Worker { +public: + Worker(int fd); + ~Worker(); + void run(); +private: + // Channel to the main thread + int fd_; + SSL_CTX *ssl_ctx_; +}; + +void* start_threaded_worker(void *arg); + +} // namespace shrpx + +#endif // SHRPX_WORKER_H From 71a3a70c026a49e5a62798cfe8b30b442f18a446 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 02:23:07 +0900 Subject: [PATCH 09/21] Handle error when fd runs out The default log level is now WARNING. --- examples/shrpx.cc | 12 +++++++++++- examples/shrpx_downstream.cc | 8 ++++++-- examples/shrpx_downstream.h | 3 ++- examples/shrpx_downstream_queue.cc | 4 ++-- examples/shrpx_downstream_queue.h | 2 +- examples/shrpx_error.h | 3 ++- examples/shrpx_https_upstream.cc | 14 ++++++++++++-- examples/shrpx_log.cc | 17 +++++++++++++---- examples/shrpx_log.h | 4 +++- examples/shrpx_spdy_upstream.cc | 19 ++++++++++++++----- examples/shrpx_spdy_upstream.h | 2 +- 11 files changed, 67 insertions(+), 21 deletions(-) diff --git a/examples/shrpx.cc b/examples/shrpx.cc index f01a39f..552d41a 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -83,6 +83,13 @@ int cache_downstream_host_address() } } // namespace +namespace { +void evlistener_errorcb(evconnlistener *listener, void *ptr) +{ + sleep(1); +} +} // namespace + namespace { evconnlistener* create_evlistener(ListenHandler *handler) { @@ -133,8 +140,9 @@ evconnlistener* create_evlistener(ListenHandler *handler) ssl_acceptcb, handler, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, - 256, + 1024, fd); + evconnlistener_set_error_cb(evlistener, evlistener_errorcb); return evlistener; } } // namespace @@ -172,6 +180,8 @@ int main(int argc, char **argv) SSL_load_error_strings(); SSL_library_init(); + Log::set_severity_level(WARNING); + create_config(); mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; mod_config()->port = 3000; diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index 63e9872..16e5fbd 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -155,12 +155,16 @@ int Downstream::start_connection() bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout, &get_config()->downstream_write_timeout); - bufferevent_socket_connect + int rv = bufferevent_socket_connect (bev_, // TODO maybe not thread-safe? const_cast(&get_config()->downstream_addr.sa), get_config()->downstream_addrlen); - return 0; + if(rv == 0) { + return 0; + } else { + return SHRPX_ERR_NETWORK; + } } void Downstream::set_request_state(int state) diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h index 3bf67d9..e7df104 100644 --- a/examples/shrpx_downstream.h +++ b/examples/shrpx_downstream.h @@ -72,7 +72,8 @@ public: INITIAL, HEADER_COMPLETE, MSG_COMPLETE, - STREAM_CLOSED + STREAM_CLOSED, + CONNECT_FAIL }; void set_request_state(int state); int get_request_state() const; diff --git a/examples/shrpx_downstream_queue.cc b/examples/shrpx_downstream_queue.cc index 8c61d83..6d943eb 100644 --- a/examples/shrpx_downstream_queue.cc +++ b/examples/shrpx_downstream_queue.cc @@ -44,9 +44,9 @@ void DownstreamQueue::add(Downstream *downstream) downstreams_[downstream->get_stream_id()] = downstream; } -void DownstreamQueue::start(Downstream *downstream) +int DownstreamQueue::start(Downstream *downstream) { - downstream->start_connection(); + return downstream->start_connection(); } void DownstreamQueue::remove(Downstream *downstream) diff --git a/examples/shrpx_downstream_queue.h b/examples/shrpx_downstream_queue.h index d8c76c8..b4e100a 100644 --- a/examples/shrpx_downstream_queue.h +++ b/examples/shrpx_downstream_queue.h @@ -40,7 +40,7 @@ public: DownstreamQueue(); ~DownstreamQueue(); void add(Downstream *downstream); - void start(Downstream *downstream); + int start(Downstream *downstream); void remove(Downstream *downstream); Downstream* find(int32_t stream_id); private: diff --git a/examples/shrpx_error.h b/examples/shrpx_error.h index 40a6850..581b1d6 100644 --- a/examples/shrpx_error.h +++ b/examples/shrpx_error.h @@ -32,7 +32,8 @@ namespace shrpx { enum ErrorCode { SHRPX_ERR_SUCCESS = 0, SHRPX_ERR_UNKNOWN = -1, - SHRPX_ERR_HTTP_PARSE = -2 + SHRPX_ERR_HTTP_PARSE = -2, + SHRPX_ERR_NETWORK = -3 }; } // namespace shrpx diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc index 276b81a..8bf3623 100644 --- a/examples/shrpx_https_upstream.cc +++ b/examples/shrpx_https_upstream.cc @@ -157,7 +157,12 @@ int htp_hdrs_completecb(htparser *htp) downstream->push_request_headers(); downstream->set_request_state(Downstream::HEADER_COMPLETE); - downstream->start_connection(); + int rv = downstream->start_connection(); + if(rv != 0) { + LOG(ERROR) << "Upstream connection failed"; + downstream->set_request_state(Downstream::CONNECT_FAIL); + return 1; + } return 0; } } // namespace @@ -227,7 +232,12 @@ int HttpsUpstream::on_read() current_header_length_ += nread; htpparse_error htperr = htparser_get_error(htp_); if(htperr == htparse_error_user) { - if(current_header_length_ > SHRPX_HTTPS_MAX_HEADER_LENGTH) { + Downstream *downstream = get_top_downstream(); + if(downstream && + downstream->get_request_state() == Downstream::CONNECT_FAIL) { + get_client_handler()->set_should_close_after_write(true); + error_reply(503); + } else if(current_header_length_ > SHRPX_HTTPS_MAX_HEADER_LENGTH) { LOG(WARNING) << "Request Header too long:" << current_header_length_ << " bytes"; get_client_handler()->set_should_close_after_write(true); diff --git a/examples/shrpx_log.cc b/examples/shrpx_log.cc index 5f7469e..5869f6c 100644 --- a/examples/shrpx_log.cc +++ b/examples/shrpx_log.cc @@ -32,6 +32,13 @@ const char *SEVERITY_STR[] = { "INFO", "WARN", "ERROR", "FATAL" }; +int Log::severity_thres_ = WARNING; + +void Log::set_severity_level(int severity) +{ + severity_thres_ = severity; +} + Log::Log(int severity, const char *filename, int linenum) : severity_(severity), filename_(filename), @@ -40,10 +47,12 @@ Log::Log(int severity, const char *filename, int linenum) Log::~Log() { - fprintf(stderr, "[%s] %s\n (%s, line %d)\n", - SEVERITY_STR[severity_], stream_.str().c_str(), - filename_, linenum_); - fflush(stderr); + if(severity_ >= severity_thres_) { + fprintf(stderr, "[%s] %s\n (%s, line %d)\n", + SEVERITY_STR[severity_], stream_.str().c_str(), + filename_, linenum_); + fflush(stderr); + } } } // namespace shrpx diff --git a/examples/shrpx_log.h b/examples/shrpx_log.h index 9a1515a..bbda7ef 100644 --- a/examples/shrpx_log.h +++ b/examples/shrpx_log.h @@ -41,18 +41,20 @@ enum SeverityLevel { class Log { public: - Log(int severiy, const char *filename, int linenum); + Log(int severity, const char *filename, int linenum); ~Log(); template Log& operator<<(Type s) { stream_ << s; return *this; } + static void set_severity_level(int severity); private: int severity_; const char *filename_; int linenum_; std::stringstream stream_; + static int severity_thres_; }; } // namespace shrpx diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index 8cc39d9..a78356d 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -95,10 +95,15 @@ void on_stream_close_callback SpdyUpstream *upstream = reinterpret_cast(user_data); Downstream *downstream = upstream->get_downstream_queue()->find(stream_id); if(downstream) { - downstream->set_request_state(Downstream::STREAM_CLOSED); - if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { upstream->get_downstream_queue()->remove(downstream); delete downstream; + } else { + downstream->set_request_state(Downstream::STREAM_CLOSED); + if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { + upstream->get_downstream_queue()->remove(downstream); + delete downstream; + } } } } @@ -152,7 +157,11 @@ void on_ctrl_recv_callback downstream->set_request_state(Downstream::MSG_COMPLETE); } upstream->add_downstream(downstream); - upstream->start_downstream(downstream); + if(upstream->start_downstream(downstream) != 0) { + // If downstream connection fails, issue RST_STREAM. + upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); + downstream->set_request_state(Downstream::CONNECT_FAIL); + } break; } default: @@ -455,9 +464,9 @@ void SpdyUpstream::add_downstream(Downstream *downstream) downstream_queue_.add(downstream); } -void SpdyUpstream::start_downstream(Downstream *downstream) +int SpdyUpstream::start_downstream(Downstream *downstream) { - downstream_queue_.start(downstream); + return downstream_queue_.start(downstream); } void SpdyUpstream::remove_downstream(Downstream *downstream) diff --git a/examples/shrpx_spdy_upstream.h b/examples/shrpx_spdy_upstream.h index fc87898..6a0c2bf 100644 --- a/examples/shrpx_spdy_upstream.h +++ b/examples/shrpx_spdy_upstream.h @@ -50,7 +50,7 @@ public: virtual bufferevent_event_cb get_downstream_eventcb(); void add_downstream(Downstream *downstream); void remove_downstream(Downstream *downstream); - void start_downstream(Downstream *downstream); + int start_downstream(Downstream *downstream); spdylay_session* get_spdy_session(); DownstreamQueue* get_downstream_queue(); From 117a39d35b6c0d73f63defb86c711ccbc0c4b948 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 02:30:20 +0900 Subject: [PATCH 10/21] Zeroed wev --- examples/shrpx_listen_handler.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/shrpx_listen_handler.cc b/examples/shrpx_listen_handler.cc index 685321e..a49036c 100644 --- a/examples/shrpx_listen_handler.cc +++ b/examples/shrpx_listen_handler.cc @@ -95,6 +95,7 @@ int ListenHandler::accept_connection(evutil_socket_t fd, size_t idx = worker_round_robin_cnt_ % num_worker_; ++worker_round_robin_cnt_; WorkerEvent wev; + memset(&wev, 0, sizeof(wev)); wev.client_fd = fd; memcpy(&wev.client_addr, addr, addrlen); wev.client_addrlen = addrlen; From ac84b68189449894a9573a5df1e83164dad4c29d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 21:10:13 +0900 Subject: [PATCH 11/21] Enable SSL partial write --- examples/shrpx_ssl.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/shrpx_ssl.cc b/examples/shrpx_ssl.cc index a278daa..ea8cd0e 100644 --- a/examples/shrpx_ssl.cc +++ b/examples/shrpx_ssl.cc @@ -74,6 +74,7 @@ SSL_CTX* create_ssl_context() } SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); if(SSL_CTX_use_PrivateKey_file(ssl_ctx, From 4ac689526b719c7251cabc9d3c873394ebcf4881 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 21:10:44 +0900 Subject: [PATCH 12/21] Don't modify user-agent --- examples/shrpx_downstream.cc | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index 16e5fbd..725dc56 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -203,24 +203,18 @@ int Downstream::push_request_headers() hdrs += "Host: "; hdrs += get_config()->downstream_hostport; hdrs += "\r\n"; - // TODO Rewrite user-agent? for(Headers::const_iterator i = request_headers_.begin(); i != request_headers_.end(); ++i) { if(util::strieq((*i).first.c_str(), "X-Forwarded-Proto")) { continue; } - if(util::strieq((*i).first.c_str(), "user-agent")) { - hdrs += "User-Agent: "; - hdrs += get_config()->server_name; - } else { - hdrs += (*i).first; - hdrs += ": "; - hdrs += (*i).second; - if(!xff_found && util::strieq((*i).first.c_str(), "X-Forwarded-For")) { - xff_found = true; - hdrs += ", "; - hdrs += upstream_->get_client_handler()->get_ipaddr(); - } + hdrs += (*i).first; + hdrs += ": "; + hdrs += (*i).second; + if(!xff_found && util::strieq((*i).first.c_str(), "X-Forwarded-For")) { + xff_found = true; + hdrs += ", "; + hdrs += upstream_->get_client_handler()->get_ipaddr(); } hdrs += "\r\n"; } From faee23a9258629eff91094bf50792b0ac10fcda5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 21:39:55 +0900 Subject: [PATCH 13/21] Fixed assertion failure. Resume downstream read on SPDY stream close. --- examples/shrpx_downstream.cc | 16 +++++++++++----- examples/shrpx_downstream.h | 1 + examples/shrpx_io_control.cc | 6 ++++++ examples/shrpx_io_control.h | 2 ++ examples/shrpx_spdy_upstream.cc | 5 +++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index 725dc56..e121638 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -87,6 +87,11 @@ bool Downstream::resume_read(IOCtrlReason reason) return ioctrl_.resume_read(reason); } +void Downstream::force_resume_read() +{ + ioctrl_.force_resume_read(); +} + namespace { void check_transfer_encoding_chunked(bool *chunked, const Headers::value_type &item) @@ -416,12 +421,13 @@ void body_buf_cb(evbuffer *body, size_t oldlen, size_t newlen, void *arg) int Downstream::init_response_body_buf() { - assert(response_body_buf_ == 0); - response_body_buf_ = evbuffer_new(); - if(response_body_buf_ == 0) { - DIE(); + if(!response_body_buf_) { + response_body_buf_ = evbuffer_new(); + if(response_body_buf_ == 0) { + DIE(); + } + evbuffer_setcb(response_body_buf_, body_buf_cb, this); } - evbuffer_setcb(response_body_buf_, body_buf_cb, this); return 0; } diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h index e7df104..d4c8d98 100644 --- a/examples/shrpx_downstream.h +++ b/examples/shrpx_downstream.h @@ -56,6 +56,7 @@ public: int32_t get_stream_id() const; void pause_read(IOCtrlReason reason); bool resume_read(IOCtrlReason reason); + void force_resume_read(); // downstream request API const Headers& get_request_headers() const; void add_request_header(const std::string& name, const std::string& value); diff --git a/examples/shrpx_io_control.cc b/examples/shrpx_io_control.cc index 76b957a..007c39d 100644 --- a/examples/shrpx_io_control.cc +++ b/examples/shrpx_io_control.cc @@ -58,4 +58,10 @@ bool IOControl::resume_read(IOCtrlReason reason) } } +void IOControl::force_resume_read() +{ + std::fill(ctrlv_.begin(), ctrlv_.end(), 0); + bufferevent_enable(bev_, EV_READ); +} + } // namespace shrpx diff --git a/examples/shrpx_io_control.h b/examples/shrpx_io_control.h index b73e36e..800c4e7 100644 --- a/examples/shrpx_io_control.h +++ b/examples/shrpx_io_control.h @@ -48,6 +48,8 @@ public: void pause_read(IOCtrlReason reason); // Returns true if read operation is enabled after this call bool resume_read(IOCtrlReason reason); + // Clear all pause flags and enable read + void force_resume_read(); private: bufferevent *bev_; std::vector ctrlv_; diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index a78356d..7ac4f27 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -103,6 +103,11 @@ void on_stream_close_callback if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { upstream->get_downstream_queue()->remove(downstream); delete downstream; + } else { + // At this point, downstream read may be paused. To reclaim + // file descriptor, enable read here and catch read + // notification. And delete downstream there. + downstream->force_resume_read(); } } } From 28ed887463a60305ee3bc95fbf1bfab5a0e3f136 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 21:44:08 +0900 Subject: [PATCH 14/21] Don't sleep. Just log the error --- examples/shrpx.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shrpx.cc b/examples/shrpx.cc index 552d41a..547f483 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -86,7 +86,7 @@ int cache_downstream_host_address() namespace { void evlistener_errorcb(evconnlistener *listener, void *ptr) { - sleep(1); + LOG(ERROR) << "Accepting incoming connection failed"; } } // namespace From 8da4938031815554f9ffce8c08d1586ab897c45d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 22:43:35 +0900 Subject: [PATCH 15/21] Listen both on IPv6 and IPv4 if possible --- examples/shrpx.cc | 79 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/examples/shrpx.cc b/examples/shrpx.cc index 547f483..cf19cc4 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include @@ -72,10 +74,19 @@ int cache_downstream_host_address() LOG(FATAL) << "Unable to get downstream address: " << gai_strerror(rv); DIE(); } - LOG(INFO) << "Using first returned address for downstream " - << get_config()->downstream_host - << ", port " - << get_config()->downstream_port; + + char host[NI_MAXHOST]; + rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), + 0, 0, NI_NUMERICHOST); + if(rv == 0) { + LOG(INFO) << "Using first returned address for downstream " + << host + << ", port " + << get_config()->downstream_port; + } else { + LOG(FATAL) << gai_strerror(rv); + DIE(); + } memcpy(&mod_config()->downstream_addr, res->ai_addr, res->ai_addrlen); mod_config()->downstream_addrlen = res->ai_addrlen; freeaddrinfo(res); @@ -91,7 +102,7 @@ void evlistener_errorcb(evconnlistener *listener, void *ptr) } // namespace namespace { -evconnlistener* create_evlistener(ListenHandler *handler) +evconnlistener* create_evlistener(ListenHandler *handler, int family) { // TODO Listen both IPv4 and IPv6 addrinfo hints; @@ -100,12 +111,13 @@ evconnlistener* create_evlistener(ListenHandler *handler) char service[10]; snprintf(service, sizeof(service), "%u", get_config()->port); memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = AF_UNSPEC; + hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG + addrinfo *res, *rp; r = getaddrinfo(get_config()->host, service, &hints, &res); if(r != 0) { @@ -124,23 +136,46 @@ evconnlistener* create_evlistener(ListenHandler *handler) continue; } evutil_make_socket_nonblocking(fd); +#ifdef IPV6_V6ONLY + if(family == AF_INET6) { + if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast(sizeof(val))) == -1) { + close(fd); + continue; + } + } +#endif // IPV6_V6ONLY if(bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) { break; } close(fd); } + if(rp) { + char host[NI_MAXHOST]; + r = getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), + 0, 0, NI_NUMERICHOST); + if(r == 0) { + LOG(INFO) << "Listening on " << host << ", port " << get_config()->port; + } else { + LOG(FATAL) << gai_strerror(r); + DIE(); + } + } freeaddrinfo(res); if(rp == 0) { - LOG(ERROR) << "No valid address returned for host " << get_config()->host - << ", port " << get_config()->port; + if(ENABLE_LOG) { + LOG(INFO) << "Listening " << (family == AF_INET ? "IPv4" : "IPv6") + << " socket failed"; + } return 0; } + evconnlistener *evlistener = evconnlistener_new (handler->get_evbase(), ssl_acceptcb, handler, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, - 1024, + 512, fd); evconnlistener_set_error_cb(evlistener, evlistener_errorcb); return evlistener; @@ -151,20 +186,32 @@ namespace { int event_loop() { event_base *evbase = event_base_new(); + ListenHandler *listener_handler = new ListenHandler(evbase); + + evconnlistener *evlistener6, *evlistener4; + evlistener6 = create_evlistener(listener_handler, AF_INET6); + evlistener4 = create_evlistener(listener_handler, AF_INET); + if(!evlistener6 && !evlistener4) { + LOG(FATAL) << "Failed to listen on address " + << get_config()->host << ", port " << get_config()->port; + DIE(); + } + if(get_config()->num_worker > 1) { listener_handler->create_worker_thread(get_config()->num_worker); } - evconnlistener *evlistener = create_evlistener(listener_handler); - if(evlistener == NULL) { - return -1; - } + if(ENABLE_LOG) { LOG(INFO) << "Entering event loop"; } event_base_loop(evbase, 0); - - evconnlistener_free(evlistener); + if(evlistener4) { + evconnlistener_free(evlistener4); + } + if(evlistener6) { + evconnlistener_free(evlistener6); + } return 0; } } // namespace @@ -180,7 +227,7 @@ int main(int argc, char **argv) SSL_load_error_strings(); SSL_library_init(); - Log::set_severity_level(WARNING); + Log::set_severity_level(INFO); create_config(); mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; From 8e0a1c91a3a0029c684e62cf4a2fa89796becd8a Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Jun 2012 23:58:19 +0900 Subject: [PATCH 16/21] Added command-line options --- examples/shrpx.cc | 192 +++++++++++++++++++++++++++++++++++---- examples/shrpx_config.cc | 3 +- examples/shrpx_config.h | 1 + examples/shrpx_log.cc | 12 +++ examples/shrpx_log.h | 1 + 5 files changed, 191 insertions(+), 18 deletions(-) diff --git a/examples/shrpx.cc b/examples/shrpx.cc index cf19cc4..df026ac 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -31,8 +31,12 @@ #include #include #include +#include +#include +#include #include +#include #include #include @@ -61,14 +65,17 @@ int cache_downstream_host_address() addrinfo hints; int rv; char service[10]; + snprintf(service, sizeof(service), "%u", get_config()->downstream_port); memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG addrinfo *res; + rv = getaddrinfo(get_config()->downstream_host, service, &hints, &res); if(rv != 0) { LOG(FATAL) << "Unable to get downstream address: " << gai_strerror(rv); @@ -121,7 +128,8 @@ evconnlistener* create_evlistener(ListenHandler *handler, int family) addrinfo *res, *rp; r = getaddrinfo(get_config()->host, service, &hints, &res); if(r != 0) { - LOG(ERROR) << "getaddrinfo: " << gai_strerror(r); + LOG(ERROR) << "Unable to get address for " << get_config()->host << ": " + << gai_strerror(r); return NULL; } for(rp = res; rp; rp = rp->ai_next) { @@ -195,7 +203,7 @@ int event_loop() if(!evlistener6 && !evlistener4) { LOG(FATAL) << "Failed to listen on address " << get_config()->host << ", port " << get_config()->port; - DIE(); + exit(EXIT_FAILURE); } if(get_config()->num_worker > 1) { @@ -216,21 +224,11 @@ int event_loop() } } // namespace -int main(int argc, char **argv) +namespace { +void fill_default_config() { - struct sigaction act; - memset(&act, 0, sizeof(struct sigaction)); - act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, 0); - - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - SSL_library_init(); - - Log::set_severity_level(INFO); - - create_config(); mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; + mod_config()->host = "localhost"; mod_config()->port = 3000; mod_config()->private_key_file = "server.key"; mod_config()->cert_file = "server.crt"; @@ -252,7 +250,159 @@ int main(int argc, char **argv) mod_config()->downstream_host = "localhost"; mod_config()->downstream_port = 80; - char hostport[256]; + + mod_config()->num_worker = 4; + + mod_config()->spdy_max_concurrent_streams = + SPDYLAY_INITIAL_MAX_CONCURRENT_STREAMS; +} +} // namespace + +namespace { +int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, + const char *hostport) +{ + // host and port in |hostport| is separated by single ','. + const char *p = strchr(hostport, ','); + if(!p) { + std::cerr << "Invalid host, port: " << hostport << std::endl; + return -1; + } + size_t len = p-hostport; + if(hostlen < len+1) { + std::cerr << "Hostname too long: " << hostport << std::endl; + return -1; + } + memcpy(host, hostport, len); + host[len] = '\0'; + + errno = 0; + unsigned long d = strtoul(p+1, 0, 10); + if(errno == 0 && 1 <= d && d <= std::numeric_limits::max()) { + *port_ptr = d; + return 0; + } else { + std::cerr << "Port is invalid: " << p+1 << std::endl; + return -1; + } +} +} // namespace + +namespace { +void print_usage(std::ostream& out) +{ + out << "Usage: shrpx [-Dh] [-b ] [-f ] [-n ]\n" + << " [-c ] [-L ] \n" + << "\n" + << "A reverse proxy for SPDY/HTTPS.\n" + << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream& out) +{ + fill_default_config(); + print_usage(out); + out << "\n" + << "OPTIONS:\n" + << " -b, --backend=\n" + << " Set backend host and port.\n" + << " Default: '" + << get_config()->downstream_host << "," + << get_config()->downstream_port << "'\n" + << " -f, --frontend=\n" + << " Set frontend host and port.\n" + << " Default: '" + << get_config()->host << "," << get_config()->port << "'\n" + << " -n, --workers=\n" + << " Set the number of worker threads.\n" + << " -c, --spdy-max-concurrent-streams=\n" + << " Set the maximum number of the concurrent\n" + << " streams in one SPDY session.\n" + << " -L, --log-level=\n" + << " Set the severity level of log output.\n" + << " INFO, WARNING, ERROR and FATAL\n" + << " -D, --daemon Run in a background. If -D is used, the\n" + << " current working directory is changed to '/'.\n" + << " -h, --help Print this help.\n" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) +{ + Log::set_severity_level(WARNING); + create_config(); + fill_default_config(); + + char frontend_host[NI_MAXHOST]; + uint16_t frontend_port; + char backend_host[NI_MAXHOST]; + uint16_t backend_port; + + while(1) { + static option long_options[] = { + {"backend", required_argument, 0, 'b' }, + {"frontend", required_argument, 0, 'f' }, + {"workers", required_argument, 0, 'n' }, + {"spdy-max-concurrent-streams", required_argument, 0, 'c' }, + {"log-level", required_argument, 0, 'L' }, + {"daemon", no_argument, 0, 'D' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + int option_index = 0; + int c = getopt_long(argc, argv, "DL:b:c:f:n:h", long_options, + &option_index); + if(c == -1) { + break; + } + switch(c) { + case 'D': + mod_config()->daemon = true; + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'L': + if(Log::set_severity_level_by_name(optarg) == -1) { + std::cerr << "Invalid severity level: " << optarg << std::endl; + exit(EXIT_SUCCESS); + } + break; + case 'b': + if(split_host_port(backend_host, sizeof(backend_host), + &backend_port, optarg) == -1) { + exit(EXIT_FAILURE); + } else { + mod_config()->downstream_host = backend_host; + mod_config()->downstream_port = backend_port; + } + break; + case 'f': + if(split_host_port(frontend_host, sizeof(frontend_host), + &frontend_port, optarg) == -1) { + exit(EXIT_FAILURE); + } else { + mod_config()->host = frontend_host; + mod_config()->port = frontend_port; + } + break; + case 'n': + mod_config()->num_worker = strtol(optarg, 0, 10); + break; + case 'c': + mod_config()->spdy_max_concurrent_streams = strtol(optarg, 0, 10); + break; + case '?': + exit(EXIT_FAILURE); + default: + break; + } + } + + char hostport[NI_MAXHOST]; if(get_config()->downstream_port == 80) { mod_config()->downstream_hostport = get_config()->downstream_host; } else { @@ -260,11 +410,19 @@ int main(int argc, char **argv) get_config()->downstream_host, get_config()->downstream_port); mod_config()->downstream_hostport = hostport; } + if(cache_downstream_host_address() == -1) { exit(EXIT_FAILURE); } - mod_config()->num_worker = 4; + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_library_init(); event_loop(); return 0; diff --git a/examples/shrpx_config.cc b/examples/shrpx_config.cc index 1d801f7..4c741fc 100644 --- a/examples/shrpx_config.cc +++ b/examples/shrpx_config.cc @@ -39,7 +39,8 @@ Config::Config() downstream_port(0), downstream_hostport(0), downstream_addrlen(0), - num_worker(0) + num_worker(0), + spdy_max_concurrent_streams(0) {} namespace { diff --git a/examples/shrpx_config.h b/examples/shrpx_config.h index 22e3899..9c2bdc8 100644 --- a/examples/shrpx_config.h +++ b/examples/shrpx_config.h @@ -65,6 +65,7 @@ struct Config { timeval downstream_read_timeout; timeval downstream_write_timeout; size_t num_worker; + size_t spdy_max_concurrent_streams; Config(); }; diff --git a/examples/shrpx_log.cc b/examples/shrpx_log.cc index 5869f6c..3163309 100644 --- a/examples/shrpx_log.cc +++ b/examples/shrpx_log.cc @@ -25,6 +25,7 @@ #include "shrpx_log.h" #include +#include namespace shrpx { @@ -39,6 +40,17 @@ void Log::set_severity_level(int severity) severity_thres_ = severity; } +int Log::set_severity_level_by_name(const char *name) +{ + for(size_t i = 0, max = sizeof(SEVERITY_STR)/sizeof(char*); i < max; ++i) { + if(strcmp(SEVERITY_STR[i], name) == 0) { + severity_thres_ = i; + return 0; + } + } + return -1; +} + Log::Log(int severity, const char *filename, int linenum) : severity_(severity), filename_(filename), diff --git a/examples/shrpx_log.h b/examples/shrpx_log.h index bbda7ef..a9d2976 100644 --- a/examples/shrpx_log.h +++ b/examples/shrpx_log.h @@ -49,6 +49,7 @@ public: return *this; } static void set_severity_level(int severity); + static int set_severity_level_by_name(const char *name); private: int severity_; const char *filename_; From 908ec2e69553afe3a566defa4ae3187243b2b7cf Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 Jun 2012 00:03:05 +0900 Subject: [PATCH 17/21] Made -D option work. Get private key and certificate file from cmd-line --- examples/shrpx.cc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/examples/shrpx.cc b/examples/shrpx.cc index df026ac..096030b 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -227,11 +227,13 @@ int event_loop() namespace { void fill_default_config() { + mod_config()->daemon = false; + mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; mod_config()->host = "localhost"; mod_config()->port = 3000; - mod_config()->private_key_file = "server.key"; - mod_config()->cert_file = "server.crt"; + mod_config()->private_key_file = 0; + mod_config()->cert_file = 0; mod_config()->upstream_read_timeout.tv_sec = 30; mod_config()->upstream_read_timeout.tv_usec = 0; @@ -402,6 +404,15 @@ int main(int argc, char **argv) } } + if(argc-optind < 2) { + print_usage(std::cerr); + std::cerr << "Too few arguments" << std::endl; + exit(EXIT_FAILURE); + } + + mod_config()->private_key_file = argv[optind++]; + mod_config()->cert_file = argv[optind++]; + char hostport[NI_MAXHOST]; if(get_config()->downstream_port == 80) { mod_config()->downstream_hostport = get_config()->downstream_host; @@ -415,6 +426,13 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } + if(get_config()->daemon) { + if(daemon(0, 0) == -1) { + perror("daemon"); + exit(EXIT_FAILURE); + } + } + struct sigaction act; memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = SIG_IGN; From e37ec7b76515504eef92bc015d1c867f7cecbc1b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 Jun 2012 00:43:18 +0900 Subject: [PATCH 18/21] Append Via header field. Don't modify Server header field. --- examples/shrpx_downstream.cc | 50 ++++++++++++++++++++++++++++++++ examples/shrpx_downstream.h | 10 +++++++ examples/shrpx_http.cc | 10 +++++++ examples/shrpx_http.h | 2 ++ examples/shrpx_https_upstream.cc | 25 +++++++++++----- examples/shrpx_spdy_upstream.cc | 3 -- 6 files changed, 89 insertions(+), 11 deletions(-) diff --git a/examples/shrpx_downstream.cc b/examples/shrpx_downstream.cc index e121638..b7a8d74 100644 --- a/examples/shrpx_downstream.cc +++ b/examples/shrpx_downstream.cc @@ -30,6 +30,7 @@ #include "shrpx_client_handler.h" #include "shrpx_config.h" #include "shrpx_error.h" +#include "shrpx_http.h" #include "util.h" using namespace spdylay; @@ -43,10 +44,14 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) priority_(priority), ioctrl_(0), request_state_(INITIAL), + request_major_(1), + request_minor_(1), chunked_request_(false), request_connection_close_(false), response_state_(INITIAL), response_http_status_(0), + response_major_(1), + response_minor_(1), chunked_response_(false), response_htp_(htparser_new()), response_body_buf_(0) @@ -140,6 +145,16 @@ void Downstream::set_request_path(const std::string& path) request_path_ = path; } +void Downstream::set_request_major(int major) +{ + request_major_ = major; +} + +void Downstream::set_request_minor(int minor) +{ + request_minor_ = minor; +} + Upstream* Downstream::get_upstream() const { return upstream_; @@ -208,11 +223,16 @@ int Downstream::push_request_headers() hdrs += "Host: "; hdrs += get_config()->downstream_hostport; hdrs += "\r\n"; + std::string via_value; for(Headers::const_iterator i = request_headers_.begin(); i != request_headers_.end(); ++i) { if(util::strieq((*i).first.c_str(), "X-Forwarded-Proto")) { continue; } + if(util::strieq((*i).first.c_str(), "via")) { + via_value = (*i).second; + continue; + } hdrs += (*i).first; hdrs += ": "; hdrs += (*i).second; @@ -231,6 +251,14 @@ int Downstream::push_request_headers() } hdrs += "X-Forwarded-Proto: https\r\n"; + hdrs += "Via: "; + hdrs += via_value; + if(!via_value.empty()) { + hdrs += ", "; + } + hdrs += http::create_via_header_value(request_major_, request_minor_); + hdrs += "\r\n"; + hdrs += "\r\n"; if(ENABLE_LOG) { LOG(INFO) << "Downstream request headers\n" << hdrs; @@ -299,6 +327,26 @@ void Downstream::set_response_http_status(unsigned int status) response_http_status_ = status; } +void Downstream::set_response_major(int major) +{ + response_major_ = major; +} + +void Downstream::set_response_minor(int minor) +{ + response_minor_ = minor; +} + +int Downstream::get_response_major() const +{ + return response_major_; +} + +int Downstream::get_response_minor() const +{ + return response_minor_; +} + bool Downstream::get_chunked_response() const { return chunked_response_; @@ -310,6 +358,8 @@ int htp_hdrs_completecb(htparser *htp) Downstream *downstream; downstream = reinterpret_cast(htparser_get_userdata(htp)); downstream->set_response_http_status(htparser_get_status(htp)); + downstream->set_response_major(htparser_get_major(htp)); + downstream->set_response_minor(htparser_get_minor(htp)); downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->get_upstream()->on_downstream_header_complete(downstream); return 0; diff --git a/examples/shrpx_downstream.h b/examples/shrpx_downstream.h index d4c8d98..dfafa5d 100644 --- a/examples/shrpx_downstream.h +++ b/examples/shrpx_downstream.h @@ -63,6 +63,8 @@ public: void set_last_request_header_value(const std::string& value); void set_request_method(const std::string& method); void set_request_path(const std::string& path); + void set_request_major(int major); + void set_request_minor(int minor); int push_request_headers(); bool get_chunked_request() const; bool get_request_connection_close() const; @@ -84,6 +86,10 @@ public: void set_last_response_header_value(const std::string& value); unsigned int get_response_http_status() const; void set_response_http_status(unsigned int status); + void set_response_major(int major); + void set_response_minor(int minor); + int get_response_major() const; + int get_response_minor() const; bool get_chunked_response() const; int parse_http_response(); void set_response_state(int state); @@ -99,12 +105,16 @@ private: int request_state_; std::string request_method_; std::string request_path_; + int request_major_; + int request_minor_; bool chunked_request_; bool request_connection_close_; Headers request_headers_; int response_state_; unsigned int response_http_status_; + int response_major_; + int response_minor_; bool chunked_response_; Headers response_headers_; htparser *response_htp_; diff --git a/examples/shrpx_http.cc b/examples/shrpx_http.cc index 4037677..357da78 100644 --- a/examples/shrpx_http.cc +++ b/examples/shrpx_http.cc @@ -93,6 +93,16 @@ std::string create_error_html(int status_code) return ss.str(); } +std::string create_via_header_value(int major, int minor) +{ + std::string hdrs; + hdrs += static_cast(major+'0'); + hdrs += "."; + hdrs += static_cast(minor+'0'); + hdrs += " shrpx"; + return hdrs; +} + } // namespace http } // namespace shrpx diff --git a/examples/shrpx_http.h b/examples/shrpx_http.h index 75b1b18..4b7cf36 100644 --- a/examples/shrpx_http.h +++ b/examples/shrpx_http.h @@ -35,6 +35,8 @@ const char* get_status_string(int status_code); std::string create_error_html(int status_code); +std::string create_via_header_value(int major, int minor); + } // namespace http } // namespace shrpx diff --git a/examples/shrpx_https_upstream.cc b/examples/shrpx_https_upstream.cc index 8bf3623..779de53 100644 --- a/examples/shrpx_https_upstream.cc +++ b/examples/shrpx_https_upstream.cc @@ -154,6 +154,9 @@ int htp_hdrs_completecb(htparser *htp) upstream = reinterpret_cast(htparser_get_userdata(htp)); Downstream *downstream = upstream->get_last_downstream(); + downstream->set_request_major(htparser_get_major(htp)); + downstream->set_request_minor(htparser_get_minor(htp)); + downstream->push_request_headers(); downstream->set_request_state(Downstream::HEADER_COMPLETE); @@ -452,6 +455,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) if(ENABLE_LOG) { LOG(INFO) << "Downstream on_downstream_header_complete"; } + std::string via_value; std::string hdrs = "HTTP/1.1 "; hdrs += http::get_status_string(downstream->get_response_http_status()); hdrs += "\r\n"; @@ -461,21 +465,26 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) util::strieq((*i).first.c_str(), "connection") || util:: strieq((*i).first.c_str(), "proxy-connection")) { // These are ignored + } else if(util::strieq((*i).first.c_str(), "via")) { + via_value = (*i).second; } else { - if(util::strieq((*i).first.c_str(), "server")) { - hdrs += "Server: "; - hdrs += get_config()->server_name; - } else { - hdrs += (*i).first; - hdrs += ": "; - hdrs += (*i).second; - } + hdrs += (*i).first; + hdrs += ": "; + hdrs += (*i).second; hdrs += "\r\n"; } } if(get_client_handler()->get_should_close_after_write()) { hdrs += "Connection: close\r\n"; } + hdrs += "Via: "; + hdrs += via_value; + if(!via_value.empty()) { + hdrs += ", "; + } + hdrs += http::create_via_header_value + (downstream->get_response_major(), downstream->get_response_minor()); + hdrs += "\r\n"; hdrs += "\r\n"; if(ENABLE_LOG) { LOG(INFO) << "Upstream https response headers\n" << hdrs; diff --git a/examples/shrpx_spdy_upstream.cc b/examples/shrpx_spdy_upstream.cc index 7ac4f27..5d6e865 100644 --- a/examples/shrpx_spdy_upstream.cc +++ b/examples/shrpx_spdy_upstream.cc @@ -499,9 +499,6 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) util::strieq((*i).first.c_str(), "connection") || util:: strieq((*i).first.c_str(), "proxy-connection")) { // These are ignored - } else if(util::strieq((*i).first.c_str(), "server")) { - nv[hdidx++] = "server"; - nv[hdidx++] = get_config()->server_name; } else { nv[hdidx++] = (*i).first.c_str(); nv[hdidx++] = (*i).second.c_str(); From b189e291a99c73b14256491c8fd2ff41e9daf7bb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 Jun 2012 01:25:43 +0900 Subject: [PATCH 19/21] Added htparse library htparse is written by Mark Ellzey and part of libevhtp. https://github.com/ellzey/libevhtp The included code are modified by me for bugfixes. See my fork: https://github.com/tatsuhiro-t/libevhtp/tree/master/htparse --- examples/htparse/LICENSE | 34 + examples/htparse/Makefile | 23 + examples/htparse/htparse.c | 1656 ++++++++++++++++++++++++++++++++++++ examples/htparse/htparse.h | 108 +++ examples/htparse/test.c | 250 ++++++ 5 files changed, 2071 insertions(+) create mode 100644 examples/htparse/LICENSE create mode 100644 examples/htparse/Makefile create mode 100644 examples/htparse/htparse.c create mode 100644 examples/htparse/htparse.h create mode 100644 examples/htparse/test.c diff --git a/examples/htparse/LICENSE b/examples/htparse/LICENSE new file mode 100644 index 0000000..cf4e8a2 --- /dev/null +++ b/examples/htparse/LICENSE @@ -0,0 +1,34 @@ +Libevhtp is available for use under the following license, commonly known +as the 3-clause (or "modified") BSD license: + +============================== +Copyright (c) 2010-2011 Mark Ellzey + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +============================== + +Portions of Libevhtp are based on works by others, also made available by them +under the three-clause BSD license above. The functions include: + +evhtp.c: _evhtp_glob_match(): + Copyright (c) 2006-2009, Salvatore Sanfilippo diff --git a/examples/htparse/Makefile b/examples/htparse/Makefile new file mode 100644 index 0000000..8028cae --- /dev/null +++ b/examples/htparse/Makefile @@ -0,0 +1,23 @@ +SRC = htparse.c +OUT = libhtparse.a +OBJ = $(SRC:.c=.o) +INCLUDES = -I. +CFLAGS += -ggdb -Wall -Wextra +LDFLAGS += +CC = gcc + +.SUFFIXES: .c + +default: $(OUT) + +.c.o: + $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@ + +$(OUT): $(OBJ) + ar rcs $(OUT) $(OBJ) + +test: $(OUT) test.c + $(CC) $(INCLUDES) $(CFLAGS) test.c -o test $(OUT) + +clean: + rm -f $(OBJ) $(OUT) test diff --git a/examples/htparse/htparse.c b/examples/htparse/htparse.c new file mode 100644 index 0000000..3e5d6c6 --- /dev/null +++ b/examples/htparse/htparse.c @@ -0,0 +1,1656 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "htparse.h" + +#ifdef PARSER_DEBUG +#define __QUOTE(x) # x +#define _QUOTE(x) __QUOTE(x) +#define htparse_debug_strlen(x) strlen(x) + +#define htparse_log_debug(fmt, ...) do { \ + time_t t = time(NULL); \ + struct tm * dm = localtime(&t); \ + \ + fprintf(stdout, "[%02d:%02d:%02d] htparse.c:[" _QUOTE(__LINE__) "]\t %-26s: " \ + fmt "\n", dm->tm_hour, dm->tm_min, dm->tm_sec, __func__, ## __VA_ARGS__); \ + fflush(stdout); \ +} while (0) + +#else +#define htparse_debug_strlen(x) 0 +#define htparse_log_debug(fmt, ...) do {} while (0) +#endif + +#if '\n' != '\x0a' || 'A' != 65 +#error "You have somehow found a non-ASCII host. We can't build here." +#endif + +#define PARSER_STACK_MAX 8192 +#define LF (unsigned char)10 +#define CR (unsigned char)13 +#define CRLF "\x0d\x0a" + +typedef enum eval_hdr_val eval_hdr_val; +typedef enum parser_flags parser_flags; +typedef enum parser_state parser_state; + +enum eval_hdr_val { + eval_hdr_val_none = 0, + eval_hdr_val_connection, + eval_hdr_val_proxy_connection, + eval_hdr_val_content_length, + eval_hdr_val_transfer_encoding +}; + +enum parser_flags { + parser_flag_chunked = 1 << 0, + parser_flag_connection_keep_alive = 1 << 1, + parser_flag_connection_close = 1 << 2, + parser_flag_trailing = 1 << 3, +}; + +enum parser_state { + s_start = 0, + s_method, + s_spaces_before_uri, + s_schema, + s_schema_slash, + s_schema_slash_slash, + s_host, + s_port, + s_after_slash_in_uri, + s_check_uri, + s_uri, + s_http_09, + s_http_H, + s_http_HT, + s_http_HTT, + s_http_HTTP, + s_first_major_digit, + s_major_digit, + s_first_minor_digit, + s_minor_digit, + s_spaces_after_digit, + s_almost_done, + s_done, + s_hdrline_start, + s_hdrline_hdr_almost_done, + s_hdrline_hdr_done, + s_hdrline_hdr_key, + s_hdrline_hdr_space_before_val, + s_hdrline_hdr_val, + s_hdrline_almost_done, + s_hdrline_done, + s_body_read, + s_chunk_size_start, + s_chunk_size, + s_chunk_size_almost_done, + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done, + s_status, + s_space_after_status, + s_status_text +}; + +struct htparser { + htpparse_error error; + parser_state state; + parser_flags flags; + eval_hdr_val heval; + + htp_type type; + htp_scheme scheme; + htp_method method; + + unsigned char major; + unsigned char minor; + uint64_t content_len; + uint64_t bytes_read; + uint64_t total_bytes_read; + unsigned int status; /* only for responses */ + unsigned int status_count; /* only for responses */ + + char buf[PARSER_STACK_MAX]; + unsigned int buf_idx; + + char * scheme_offset; + char * host_offset; + char * port_offset; + char * path_offset; + char * args_offset; + + void * userdata; +}; + +static uint32_t usual[] = { + 0xffffdbfe, + 0x7fff37d6, + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff +}; + +static int8_t unhex[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char * errstr_map[] = { + "htparse_error_none", + "htparse_error_too_big", + "htparse_error_invalid_method", + "htparse_error_invalid_requestline", + "htparse_error_invalid_schema", + "htparse_error_invalid_protocol", + "htparse_error_invalid_version", + "htparse_error_invalid_header", + "htparse_error_invalid_chunk_size", + "htparse_error_invalid_chunk", + "htparse_error_invalid_state", + "htparse_error_user", + "htparse_error_unknown" +}; + +static const char * method_strmap[] = { + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "MKCOL", + "COPY", + "MOVE", + "OPTIONS", + "PROPFIND", + "PROPATCH", + "LOCK", + "UNLOCK", + "TRACE" +}; + +static inline uint32_t to_uint32(char *m) { + union { + uint32_t i; + char data[4]; + } u; + memcpy(u.data, m, 4); + return u.i; +} + +#define _MIN_READ(a, b) ((a) < (b) ? (a) : (b)) + +#define _str3_cmp(m, c0, c1, c2, c3) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) + +#define _str3Ocmp(m, c0, c1, c2, c3) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) + +#define _str4cmp(m, c0, c1, c2, c3) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) + +#define _str5cmp(m, c0, c1, c2, c3, c4) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ + && m[4] == c4 + +#define _str6cmp(m, c0, c1, c2, c3, c4, c5) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ + && (to_uint32(m+4) & 0xffff) == ((c5 << 8) | c4) + +#define _str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ + && to_uint32(m+4) == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) + +#define _str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ + && to_uint32(m) == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) + +#define _str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ + to_uint32(m) == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ + && to_uint32(m) == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) \ + && m[8] == c8 + +#define __HTPARSE_GENHOOK(__n) \ + static inline int hook_ ## __n ## _run(htparser * p, htparse_hooks * hooks) { \ + htparse_log_debug("enter"); \ + if (hooks && (hooks)->__n) { \ + return (hooks)->__n(p); \ + } \ + \ + return 0; \ + } + +#define __HTPARSE_GENDHOOK(__n) \ + static inline int hook_ ## __n ## _run(htparser * p, htparse_hooks * hooks, const char * s, size_t l) { \ + htparse_log_debug("enter"); \ + if (hooks && (hooks)->__n) { \ + return (hooks)->__n(p, s, l); \ + } \ + \ + return 0; \ + } + +__HTPARSE_GENHOOK(on_msg_begin); +__HTPARSE_GENHOOK(on_hdrs_begin); +__HTPARSE_GENHOOK(on_hdrs_complete); +__HTPARSE_GENHOOK(on_new_chunk); +__HTPARSE_GENHOOK(on_chunk_complete); +__HTPARSE_GENHOOK(on_chunks_complete); +__HTPARSE_GENHOOK(on_msg_complete); + +__HTPARSE_GENDHOOK(method); +__HTPARSE_GENDHOOK(scheme); +__HTPARSE_GENDHOOK(host); +__HTPARSE_GENDHOOK(port); +__HTPARSE_GENDHOOK(path); +__HTPARSE_GENDHOOK(args); +__HTPARSE_GENDHOOK(uri); +__HTPARSE_GENDHOOK(hdr_key); +__HTPARSE_GENDHOOK(hdr_val); +__HTPARSE_GENDHOOK(body); + + +static inline uint64_t +str_to_uint64(char * str, size_t n, int * err) { + uint64_t value; + + if (n > 20) { + /* 18446744073709551615 is 20 bytes */ + *err = 1; + return 0; + } + + for (value = 0; n--; str++) { + uint64_t check; + + if (*str < '0' || *str > '9') { + *err = 1; + return 0; + } + + check = value * 10 + (*str - '0'); + + if ((value && check <= value) || check > UINT64_MAX) { + *err = 1; + return 0; + } + + value = check; + } + + return value; +} + +static inline ssize_t +_str_to_ssize_t(char * str, size_t n) { + ssize_t value; + + if (n == 0) { + return -1; + } + + for (value = 0; n--; str++) { + if (*str < '0' || *str > '9') { + return -1; + } + + value = value * 10 + (*str - '0'); + +#if 0 + if (value > INTMAX_MAX) { + return -1; + } +#endif + } + + return value; +} + +htpparse_error +htparser_get_error(htparser * p) { + return p->error; +} + +const char * +htparser_get_strerror(htparser * p) { + htpparse_error e = htparser_get_error(p); + + if (e > htparse_error_generic) { + return "htparse_no_such_error"; + } + + return errstr_map[e]; +} + +unsigned int +htparser_get_status(htparser * p) { + return p->status; +} + +int +htparser_should_keep_alive(htparser * p) { + if (p->major > 0 && p->minor > 0) { + if (p->flags & parser_flag_connection_close) { + return 0; + } else { + return 1; + } + } else { + if (p->flags & parser_flag_connection_keep_alive) { + return 1; + } else { + return 0; + } + } + + return 0; +} + +htp_scheme +htparser_get_scheme(htparser * p) { + return p->scheme; +} + +htp_method +htparser_get_method(htparser * p) { + return p->method; +} + +const char * +htparser_get_methodstr(htparser * p) { + if (p->method >= htp_method_UNKNOWN) { + return NULL; + } + + return method_strmap[p->method]; +} + +void +htparser_set_major(htparser * p, unsigned char major) { + p->major = major; +} + +void +htparser_set_minor(htparser * p, unsigned char minor) { + p->minor = minor; +} + +unsigned char +htparser_get_major(htparser * p) { + return p->major; +} + +unsigned char +htparser_get_minor(htparser * p) { + return p->minor; +} + +void * +htparser_get_userdata(htparser * p) { + return p->userdata; +} + +void +htparser_set_userdata(htparser * p, void * ud) { + p->userdata = ud; +} + +uint64_t +htparser_get_content_length(htparser * p) { + return p->content_len; +} + +uint64_t +htparser_get_bytes_read(htparser * p) { + return p->bytes_read; +} + +uint64_t +htparser_get_total_bytes_read(htparser * p) { + return p->total_bytes_read; +} + +void +htparser_init(htparser * p, htp_type type) { + memset(p, 0, sizeof(htparser)); + p->error = htparse_error_none; + p->type = type; +} + +htparser * +htparser_new(void) { + return malloc(sizeof(htparser)); +} + +size_t +htparser_run(htparser * p, htparse_hooks * hooks, const char * data, size_t len) { + unsigned char ch; + char c; + size_t i; + + htparse_log_debug("enter"); + htparse_log_debug("p == %p", p); + + p->error = htparse_error_none; + p->bytes_read = 0; + + for (i = 0; i < len; i++) { + int res; + int err; + + ch = data[i]; + + htparse_log_debug("[%p] data[%d] = %c (%x)", p, i, isprint(ch) ? ch : ' ', ch); + + if (p->buf_idx >= sizeof(p->buf)) { + p->error = htparse_error_too_big; + return i + 1; + } + + p->total_bytes_read += 1; + p->bytes_read += 1; + + switch (p->state) { + case s_start: + htparse_log_debug("[%p] s_start", p); + + p->flags = 0; + + if (ch == CR || ch == LF) { + break; + } + + if ((ch < 'A' || ch > 'Z') && ch != '_') { + p->error = htparse_error_inval_reqline; + return i + 1; + } + + res = hook_on_msg_begin_run(p, hooks); + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + if (p->type == htp_type_request) { + p->state = s_method; + } else if (p->type == htp_type_response && ch == 'H') { + p->state = s_http_H; + } else { + p->error = htparse_error_inval_reqline; + return i + 1; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_method: + htparse_log_debug("[%p] s_method", p); + + if (ch == ' ') { + char * m = p->buf; + + switch (p->buf_idx) { + case 3: + if (_str3_cmp(m, 'G', 'E', 'T', '\0')) { + p->method = htp_method_GET; + break; + } + + if (_str3_cmp(m, 'P', 'U', 'T', '\0')) { + p->method = htp_method_PUT; + break; + } + + break; + case 4: + if (m[1] == 'O') { + if (_str3Ocmp(m, 'P', 'O', 'S', 'T')) { + p->method = htp_method_POST; + break; + } + + if (_str3Ocmp(m, 'C', 'O', 'P', 'Y')) { + p->method = htp_method_COPY; + break; + } + + if (_str3Ocmp(m, 'M', 'O', 'V', 'E')) { + p->method = htp_method_MOVE; + break; + } + + if (_str3Ocmp(m, 'L', 'O', 'C', 'K')) { + p->method = htp_method_LOCK; + break; + } + } else { + if (_str4cmp(m, 'H', 'E', 'A', 'D')) { + p->method = htp_method_HEAD; + break; + } + } + break; + case 5: + if (_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) { + p->method = htp_method_MKCOL; + break; + } + + if (_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) { + p->method = htp_method_TRACE; + break; + } + break; + case 6: + if (_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) { + p->method = htp_method_DELETE; + break; + } + + if (_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) { + p->method = htp_method_UNLOCK; + break; + } + break; + case 7: + if (_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', '\0')) { + p->method = htp_method_OPTIONS; + } + + break; + case 8: + if (_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D')) { + p->method = htp_method_PROPFIND; + } + + break; + + case 9: + if (_str9cmp(m, 'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H')) { + p->method = htp_method_PROPPATCH; + } + break; + } /* switch */ + + res = hook_method_run(p, hooks, p->buf, p->buf_idx); + p->buf_idx = 0; + p->state = s_spaces_before_uri; + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + } + + if ((ch < 'A' || ch > 'Z') && ch != '_') { + p->error = htparse_error_inval_method; + return i + 1; + } + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + break; + case s_spaces_before_uri: + htparse_log_debug("[%p] s_spaces_before_uri", p); + + switch (ch) { + case ' ': + break; + case '/': + p->path_offset = &p->buf[p->buf_idx]; + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_after_slash_in_uri; + break; + default: + c = (unsigned char)(ch | 0x20); + + if (c >= 'a' && c <= 'z') { + p->scheme_offset = &p->buf[p->buf_idx]; + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_schema; + break; + } + + p->error = htparse_error_inval_reqline; + return i + 1; + } /* switch */ + + break; + case s_schema: + htparse_log_debug("[%p] s_schema", p); + + c = (unsigned char)(ch | 0x20); + + if (c >= 'a' && c <= 'z') { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + switch (ch) { + case ':': + p->scheme = htp_scheme_unknown; + + switch (p->buf_idx) { + case 3: + if (_str3_cmp(p->scheme_offset, 'f', 't', 'p', ' ')) { + p->scheme = htp_scheme_ftp; + break; + } + + if (_str3_cmp(p->scheme_offset, 'n', 'f', 's', ' ')) { + p->scheme = htp_scheme_nfs; + break; + } + + break; + case 4: + if (_str4cmp(p->scheme_offset, 'h', 't', 't', 'p')) { + p->scheme = htp_scheme_http; + break; + } + break; + case 5: + if (_str5cmp(p->scheme_offset, 'h', 't', 't', 'p', 's')) { + p->scheme = htp_scheme_https; + break; + } + break; + } /* switch */ + + res = hook_scheme_run(p, hooks, p->scheme_offset, p->buf_idx); + +#if 0 + p->buf_idx = 0; + p->buf[0] = '\0'; +#endif + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_schema_slash; + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + default: + p->error = htparse_error_inval_schema; + return i + 1; + } /* switch */ + + break; + case s_schema_slash: + htparse_log_debug("[%p] s_schema_slash", p); + + switch (ch) { + case '/': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_schema_slash_slash; + break; + default: + p->error = htparse_error_inval_schema; + return i + 1; + } + break; + case s_schema_slash_slash: + htparse_log_debug("[%p] s_schema_slash_slash", p); + + switch (ch) { + case '/': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->host_offset = &p->buf[p->buf_idx]; + + p->state = s_host; + break; + default: + p->error = htparse_error_inval_schema; + return i + 1; + } + break; + case s_host: + c = (unsigned char)(ch | 0x20); + + if (c >= 'a' && c <= 'z') { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + res = hook_host_run(p, hooks, p->host_offset, (&p->buf[p->buf_idx] - p->host_offset)); + + switch (ch) { + case ':': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->port_offset = &p->buf[p->buf_idx]; + p->state = s_port; + break; + case '/': + p->path_offset = &p->buf[p->buf_idx]; + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_after_slash_in_uri; + break; + case ' ': + /* p->buf should contain the whole uri */ + p->state = s_http_09; + break; + default: + p->error = htparse_error_inval_schema; + return i + 1; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_port: + res = 0; + + if (ch >= '0' && ch <= '9') { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + res = hook_port_run(p, hooks, p->buf, p->buf_idx); + + switch (ch) { + case '/': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->path_offset = &p->buf[p->buf_idx - 1]; + + p->state = s_after_slash_in_uri; + break; + case ' ': + p->state = s_http_09; + p->buf_idx = 0; + break; + default: + p->error = htparse_error_inval_reqline; + return i + 1; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_after_slash_in_uri: + htparse_log_debug("[%p] s_after_slash_in_uri", p); + + res = 0; + + if (usual[ch >> 5] & (1 << (ch & 0x1f))) { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_check_uri; + break; + } + + switch (ch) { + case ' ': + { + int r1 = hook_path_run(p, hooks, p->path_offset, p->buf_idx); + int r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx); + + p->state = s_http_09; + p->buf_idx = 0; + + if (r1 || r2) { + res = 1; + } + } + + break; + case CR: + p->minor = 9; + p->state = s_almost_done; + break; + case LF: + p->minor = 9; + p->state = s_hdrline_start; + break; + case '.': + case '%': + case '/': + case '#': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_uri; + break; + case '?': + res = hook_path_run(p, hooks, p->buf, p->buf_idx); + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->args_offset = &p->buf[p->buf_idx]; + p->state = s_uri; + + break; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_check_uri; + break; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_check_uri: + htparse_log_debug("[%p] s_check_uri", p); + + res = 0; + + if (usual[ch >> 5] & (1 << (ch & 0x1f))) { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + switch (ch) { + case ' ': + { + int r1 = 0; + int r2 = 0; + + if (p->args_offset) { + r1 = hook_args_run(p, hooks, p->args_offset, p->buf_idx); + } else { + r1 = hook_path_run(p, hooks, p->buf, p->buf_idx); + } + + r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx); + p->buf_idx = 0; + p->state = s_http_09; + + if (r1 || r2) { + res = 1; + } + } + break; + case '/': + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_after_slash_in_uri; + break; + case CR: + p->minor = 9; + p->buf_idx = 0; + p->state = s_almost_done; + break; + case LF: + p->minor = 9; + p->buf_idx = 0; + + p->state = s_hdrline_start; + break; + default: + if (ch == '?') { + res = hook_path_run(p, hooks, p->path_offset, (&p->buf[p->buf_idx] - p->path_offset)); + p->args_offset = &p->buf[p->buf_idx]; + } + + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_uri; + + break; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_uri: + htparse_log_debug("[%p] s_uri", p); + + res = 0; + + if (usual[ch >> 5] & (1 << (ch & 0x1f))) { + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } + + switch (ch) { + case ' ': + { + int r1 = 0; + int r2 = 0; + + if (p->args_offset) { + r1 = hook_args_run(p, hooks, p->args_offset, + (&p->buf[p->buf_idx] - p->args_offset)); + } else { + r1 = hook_path_run(p, hooks, p->path_offset, + (&p->buf[p->buf_idx] - p->path_offset)); + } + + r2 = hook_uri_run(p, hooks, p->path_offset, + (&p->buf[p->buf_idx] - p->path_offset)); + + p->buf_idx = 0; + p->state = s_http_09; + + if (r1 || r2) { + res = 1; + } + } + break; + case CR: + p->minor = 9; + p->buf_idx = 0; + p->state = s_almost_done; + break; + case LF: + p->minor = 9; + p->buf_idx = 0; + p->state = s_hdrline_start; + break; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_http_09: + htparse_log_debug("[%p] s_http_09", p); + + switch (ch) { + case ' ': + break; + case CR: + p->minor = 9; + p->buf_idx = 0; + p->state = s_almost_done; + break; + case LF: + p->minor = 9; + p->buf_idx = 0; + p->state = s_hdrline_start; + break; + case 'H': + p->buf_idx = 0; + p->state = s_http_H; + break; + default: + p->error = htparse_error_inval_proto; + return i + 1; + } /* switch */ + + break; + case s_http_H: + htparse_log_debug("[%p] s_http_H", p); + + switch (ch) { + case 'T': + p->state = s_http_HT; + break; + default: + p->error = htparse_error_inval_proto; + return i + 1; + } + break; + case s_http_HT: + switch (ch) { + case 'T': + p->state = s_http_HTT; + break; + default: + p->error = htparse_error_inval_proto; + return i + 1; + } + break; + case s_http_HTT: + switch (ch) { + case 'P': + p->state = s_http_HTTP; + break; + default: + p->error = htparse_error_inval_proto; + return i + 1; + } + break; + case s_http_HTTP: + switch (ch) { + case '/': + p->state = s_first_major_digit; + break; + default: + p->error = htparse_error_inval_proto; + return i + 1; + } + break; + case s_first_major_digit: + if (ch < '1' || ch > '9') { + p->error = htparse_error_inval_ver; + return i + 1; + } + + p->major = ch - '0'; + p->state = s_major_digit; + break; + case s_major_digit: + if (ch == '.') { + p->state = s_first_minor_digit; + break; + } + + if (ch < '0' || ch > '9') { + p->error = htparse_error_inval_ver; + return i + 1; + } + + p->major = p->major * 10 + ch - '0'; + break; + case s_first_minor_digit: + if (ch < '0' || ch > '9') { + p->error = htparse_error_inval_ver; + return i + 1; + } + + p->minor = ch - '0'; + p->state = s_minor_digit; + break; + case s_minor_digit: + switch (ch) { + case ' ': + if (p->type == htp_type_request) { + p->state = s_spaces_after_digit; + } else if (p->type == htp_type_response) { + p->state = s_status; + } + + break; + case CR: + p->state = s_almost_done; + break; + case LF: + p->state = s_hdrline_start; + break; + default: + if (ch < '0' || ch > '9') { + p->error = htparse_error_inval_ver; + return i + 1; + } + + p->minor = p->minor * 10 + ch - '0'; + break; + } /* switch */ + break; + case s_status: + /* http response status code */ + if (ch == ' ') { + if (p->status) { + p->state = s_status_text; + } + break; + } + + if (ch < '0' || ch > '9') { + p->error = htparse_error_status; + return i + 1; + } + + p->status = p->status * 10 + ch - '0'; + + if (++p->status_count == 3) { + p->state = s_space_after_status; + } + + break; + case s_space_after_status: + switch (ch) { + case ' ': + p->state = s_status_text; + break; + case CR: + p->state = s_almost_done; + break; + case LF: + p->state = s_hdrline_start; + break; + default: + p->error = htparse_error_generic; + return i + 1; + } + break; + case s_status_text: + switch (ch) { + case CR: + p->state = s_almost_done; + break; + case LF: + p->state = s_hdrline_start; + break; + default: + break; + } + break; + case s_spaces_after_digit: + switch (ch) { + case ' ': + break; + case CR: + p->state = s_almost_done; + break; + case LF: + p->state = s_hdrline_start; + break; + default: + p->error = htparse_error_inval_ver; + return i + 1; + } + break; + + case s_almost_done: + switch (ch) { + case LF: + if (p->type == htp_type_response && p->status >= 100 && p->status < 200) { + p->status = 0; + p->status_count = 0; + p->state = s_start; + break; + } + + p->state = s_done; + res = hook_on_hdrs_begin_run(p, hooks); + break; + default: + p->error = htparse_error_inval_reqline; + return i + 1; + } + break; + case s_done: + switch (ch) { + case CR: + p->state = s_hdrline_almost_done; + break; + case LF: + return i + 1; + default: + goto hdrline_start; + } + break; +hdrline_start: + case s_hdrline_start: + htparse_log_debug("[%p] s_hdrline_start", p); + + p->buf_idx = 0; + + switch (ch) { + case CR: + p->state = s_hdrline_hdr_almost_done; + break; + case LF: + p->state = s_hdrline_hdr_done; + break; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_hdrline_hdr_key; + break; + } + + break; + case s_hdrline_hdr_key: + htparse_log_debug("[%p] s_hdrline_hdr_key", p); + + res = 0; + switch (ch) { + case ':': + res = hook_hdr_key_run(p, hooks, p->buf, p->buf_idx); + + /* figure out if the value of this header is valueable */ + p->heval = eval_hdr_val_none; + + switch (p->buf_idx + 1) { + case 11: + if (!strcasecmp(p->buf, "connection")) { + p->heval = eval_hdr_val_connection; + } + break; + case 15: + if (!strcasecmp(p->buf, "content-length")) { + p->heval = eval_hdr_val_content_length; + } + break; + case 17: + if (!strcasecmp(p->buf, "proxy-connection")) { + p->heval = eval_hdr_val_proxy_connection; + } + break; + case 18: + if (!strcasecmp(p->buf, "transfer-encoding")) { + p->heval = eval_hdr_val_transfer_encoding; + } + break; + } /* switch */ + + p->buf_idx = 0; + p->state = s_hdrline_hdr_space_before_val; + + break; + case CR: + p->state = s_hdrline_hdr_almost_done; + break; + case LF: + p->state = s_hdrline_hdr_done; + break; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_hdrline_hdr_space_before_val: + htparse_log_debug("[%p] s_hdrline_hdr_space_before_val", p); + + switch (ch) { + case ' ': + break; + case CR: + case LF: + /* empty header value, is this legal? */ + p->error = htparse_error_inval_hdr; + return i + 1; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + p->state = s_hdrline_hdr_val; + break; + } + break; + case s_hdrline_hdr_val: + htparse_log_debug("[%p] s_hdrline_hdr_val", p); + err = 0; + res = 0; + + switch (ch) { + case CR: + res = hook_hdr_val_run(p, hooks, p->buf, p->buf_idx); + + switch (p->heval) { + case eval_hdr_val_none: + break; + case eval_hdr_val_content_length: + p->content_len = str_to_uint64(p->buf, p->buf_idx, &err); + + if (err == 1) { + p->error = htparse_error_too_big; + return i + 1; + } + + break; + case eval_hdr_val_connection: + switch (p->buf[0]) { + case 'K': + case 'k': + if (_str9cmp((p->buf + 1), + 'e', 'e', 'p', '-', 'A', 'l', 'i', 'v', 'e')) { + p->flags |= parser_flag_connection_keep_alive; + } + break; + case 'c': + if (_str5cmp(p->buf, 'c', 'l', 'o', 's', 'e')) { + p->flags |= parser_flag_connection_close; + } + break; + } + break; + case eval_hdr_val_transfer_encoding: + if (_str7_cmp(p->buf, 'c', 'h', 'u', 'n', 'k', 'e', 'd', '\0')) { + p->flags |= parser_flag_chunked; + } + + break; + default: + break; + } /* switch */ + + p->state = s_hdrline_hdr_almost_done; + p->buf_idx = 0; + + break; + case LF: + p->state = s_hdrline_hdr_done; + break; + default: + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + break; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_hdrline_hdr_almost_done: + htparse_log_debug("[%p] s_hdrline_hdr_almost_done", p); + + res = 0; + switch (ch) { + case LF: + if (p->flags & parser_flag_trailing) { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + break; + } + + p->state = s_hdrline_hdr_done; + break; + default: + p->error = htparse_error_inval_hdr; + return i + 1; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_hdrline_hdr_done: + htparse_log_debug("[%p] s_hdrline_hdr_done", p); + + switch (ch) { + case CR: + p->state = s_hdrline_almost_done; + break; + case LF: + /* got LFLF? is this valid? */ + return i + 1; + default: + p->buf_idx = 0; + p->buf[p->buf_idx++] = ch; + p->buf[p->buf_idx] = '\0'; + + p->state = s_hdrline_hdr_key; + break; + } + break; + case s_hdrline_almost_done: + htparse_log_debug("[%p] s_hdrline_almost_done", p); + + res = 0; + + switch (ch) { + case LF: + p->buf_idx = 0; + htparse_log_debug("[%p] HERE", p); + + res = hook_on_hdrs_complete_run(p, hooks); + + if (!res) { + if (p->flags & parser_flag_trailing) { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + } else if (p->flags & parser_flag_chunked) { + p->state = s_chunk_size_start; + } else if (p->content_len > 0) { + p->state = s_body_read; + } else if (p->content_len == 0) { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + } + } else { + p->state = s_hdrline_done; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + break; + default: + p->error = htparse_error_inval_hdr; + return i + 1; + } /* switch */ + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + case s_hdrline_done: + htparse_log_debug("[%p] s_hdrline_done", p); + res = 0; + if (p->flags & parser_flag_trailing) { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + break; + } else if (p->flags & parser_flag_chunked) { + p->state = s_chunk_size_start; + i--; + } else if (p->content_len > 0) { + p->state = s_body_read; + i--; + } else if (p->content_len == 0) { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + } + if (res) { + p->error = htparse_error_user; + return i + 1; + } + break; + + case s_chunk_size_start: + c = unhex[(unsigned char)ch]; + + if (c == -1) { + p->error = htparse_error_inval_chunk_sz; + return i + 1; + } + + p->content_len = c; + p->state = s_chunk_size; + break; + case s_chunk_size: + if (ch == CR) { + p->state = s_chunk_size_almost_done; + break; + } + + c = unhex[(unsigned char)ch]; + + if (c == -1) { + p->error = htparse_error_inval_chunk_sz; + return i + 1; + } + + p->content_len *= 16; + p->content_len += c; + break; + + case s_chunk_size_almost_done: + res = 0; + + if (ch != LF) { + p->error = htparse_error_inval_chunk_sz; + return i + 1; + } + + if (p->content_len == 0) { + res = hook_on_chunks_complete_run(p, hooks); + + p->flags |= parser_flag_trailing; + p->state = s_hdrline_start; + } else { + res = hook_on_new_chunk_run(p, hooks); + + p->state = s_chunk_data; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_chunk_data: + res = 0; + { + const char * pp = &data[i]; + const char * pe = (const char *)(data + len); + size_t to_read = _MIN_READ(pe - pp, p->content_len); + + if (to_read > 0) { + res = hook_body_run(p, hooks, pp, to_read); + + i += to_read - 1; + } + + if (to_read == p->content_len) { + p->state = s_chunk_data_almost_done; + } + + p->content_len -= to_read; + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_chunk_data_almost_done: + if (ch != CR) { + p->error = htparse_error_inval_chunk; + return i + 1; + } + + p->state = s_chunk_data_done; + break; + + case s_chunk_data_done: + if (ch != LF) { + p->error = htparse_error_inval_chunk; + return i + 1; + } + + p->state = s_chunk_size_start; + + if (hook_on_chunk_complete_run(p, hooks)) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + case s_body_read: + res = 0; + + { + const char * pp = &data[i]; + const char * pe = (const char *)(data + len); + size_t to_read = _MIN_READ(pe - pp, p->content_len); + + htparse_log_debug("[%p] s_body_read %zu", p, to_read); + + if (to_read > 0) { + res = hook_body_run(p, hooks, pp, to_read); + + i += to_read - 1; + p->content_len -= to_read; + + if (p->content_len == 0) { + res = hook_on_msg_complete_run(p, hooks); + + p->state = s_start; + } + } else { + res = hook_on_msg_complete_run(p, hooks); + p->state = s_start; + } + } + + if (res) { + p->error = htparse_error_user; + return i + 1; + } + + break; + + default: + htparse_log_debug("[%p] This is a silly state....", p); + p->error = htparse_error_inval_state; + return i + 1; + } /* switch */ + } + + return i; +} /* htparser_run */ + diff --git a/examples/htparse/htparse.h b/examples/htparse/htparse.h new file mode 100644 index 0000000..685015a --- /dev/null +++ b/examples/htparse/htparse.h @@ -0,0 +1,108 @@ +#ifndef __HTPARSE_H__ +#define __HTPARSE_H__ + +struct htparser; + +enum htp_type { + htp_type_request = 0, + htp_type_response +}; + +enum htp_scheme { + htp_scheme_none = 0, + htp_scheme_ftp, + htp_scheme_http, + htp_scheme_https, + htp_scheme_nfs, + htp_scheme_unknown +}; + +enum htp_method { + htp_method_GET = 0, + htp_method_HEAD, + htp_method_POST, + htp_method_PUT, + htp_method_DELETE, + htp_method_MKCOL, + htp_method_COPY, + htp_method_MOVE, + htp_method_OPTIONS, + htp_method_PROPFIND, + htp_method_PROPPATCH, + htp_method_LOCK, + htp_method_UNLOCK, + htp_method_TRACE, + htp_method_UNKNOWN +}; + +enum htpparse_error { + htparse_error_none = 0, + htparse_error_too_big, + htparse_error_inval_method, + htparse_error_inval_reqline, + htparse_error_inval_schema, + htparse_error_inval_proto, + htparse_error_inval_ver, + htparse_error_inval_hdr, + htparse_error_inval_chunk_sz, + htparse_error_inval_chunk, + htparse_error_inval_state, + htparse_error_user, + htparse_error_status, + htparse_error_generic +}; + +typedef struct htparser htparser; +typedef struct htparse_hooks htparse_hooks; + +typedef enum htp_scheme htp_scheme; +typedef enum htp_method htp_method; +typedef enum htp_type htp_type; +typedef enum htpparse_error htpparse_error; + +typedef int (*htparse_hook)(htparser *); +typedef int (*htparse_data_hook)(htparser *, const char *, size_t); + + +struct htparse_hooks { + htparse_hook on_msg_begin; + htparse_data_hook method; + htparse_data_hook scheme; /* called if scheme is found */ + htparse_data_hook host; /* called if a host was in the request scheme */ + htparse_data_hook port; /* called if a port was in the request scheme */ + htparse_data_hook path; /* only the path of the uri */ + htparse_data_hook args; /* only the arguments of the uri */ + htparse_data_hook uri; /* the entire uri including path/args */ + htparse_hook on_hdrs_begin; + htparse_data_hook hdr_key; + htparse_data_hook hdr_val; + htparse_hook on_hdrs_complete; + htparse_hook on_new_chunk; /* called after parsed chunk octet */ + htparse_hook on_chunk_complete; /* called after single parsed chunk */ + htparse_hook on_chunks_complete; /* called after all parsed chunks processed */ + htparse_data_hook body; + htparse_hook on_msg_complete; +}; + + +size_t htparser_run(htparser *, htparse_hooks *, const char *, size_t); +int htparser_should_keep_alive(htparser * p); +htp_scheme htparser_get_scheme(htparser *); +htp_method htparser_get_method(htparser *); +const char * htparser_get_methodstr(htparser *); +void htparser_set_major(htparser *, unsigned char); +void htparser_set_minor(htparser *, unsigned char); +unsigned char htparser_get_major(htparser *); +unsigned char htparser_get_minor(htparser *); +unsigned int htparser_get_status(htparser *); +uint64_t htparser_get_content_length(htparser *); +uint64_t htparser_get_total_bytes_read(htparser *); +htpparse_error htparser_get_error(htparser *); +const char * htparser_get_strerror(htparser *); +void * htparser_get_userdata(htparser *); +void htparser_set_userdata(htparser *, void *); +void htparser_init(htparser *, htp_type); +htparser * htparser_new(void); + +#endif + diff --git a/examples/htparse/test.c b/examples/htparse/test.c new file mode 100644 index 0000000..77c7076 --- /dev/null +++ b/examples/htparse/test.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include +#include + +#include "htparse.h" + +static int +_on_msg_start(htparser * p) { + printf("START {\n"); + return 0; +} + +static int +_on_msg_end(htparser * p) { + printf("}\n"); + return 0; +} + +static int +_path(htparser * p, const char * data, size_t len) { + printf("\tpath = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_method(htparser * p, const char * data, size_t len) { + printf("\tmethod = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_uri(htparser * p, const char * data, size_t len) { + printf("\turi = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_args(htparser * p, const char * data, size_t len) { + printf("\targs = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_hdrs_end(htparser * p) { + printf("\t}\n"); + return 0; +} + +static int +_hdrs_start(htparser * p) { + printf("\thdrs {\n"); + return 0; +} + +static int +_hdr_key(htparser * p, const char * data, size_t len) { + printf("\t\thdr_key = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_hdr_val(htparser * p, const char * data, size_t len) { + printf("\t\thdr_val = '%.*s'\n", (int)len, data); + return 0; +} + +static int +_read_body(htparser * p, const char * data, size_t len) { + printf("\t'%.*s'\n", (int)len, data); + return 0; +} + +static int +_on_new_chunk(htparser * p) { + printf("\t--chunk payload (%zu)--\n", htparser_get_content_length(p)); + /* printf("..chunk..\n"); */ + return 0; +} + +static void +_test(htparser * p, htparse_hooks * hooks, const char * l, htp_type type) { + printf("---- test ----\n"); + printf("%zu, %s\n", strlen(l), l); + + htparser_init(p, type); + printf("%zu == %zu\n", htparser_run(p, hooks, l, strlen(l)), strlen(l)); + + if (htparser_get_error(p)) { + printf("ERROR: %s\n", htparser_get_strerror(p)); + } + + printf("\n"); +} + +static void +_test_fragments(htparser * p, htparse_hooks * hooks, const char ** fragments, + htp_type type) { + int i = 0; + + printf("---- test fragment ----\n"); + htparser_init(p, type); + + while (1) { + const char * l = fragments[i++]; + + if (l == NULL) { + break; + } + + htparser_run(p, hooks, l, strlen(l)); + + if (htparser_get_error(p)) { + printf("ERROR: %s\n", htparser_get_strerror(p)); + } + } + + printf("\n"); +} + +static const char * test_fragment_1[] = { + "GET \0", + " /fjdksf\0", + "jfkdslfds H\0", + "TTP/1.\0", + "1\r\0", + "\n\0", + "\r\0", + "\n\0", + NULL +}; + +static const char * test_fragment_2[] = { + "POST /\0", + "h?a=b HTTP/1.0\r\n\0", + "Content-Len\0", + "gth\0", + ": 1\0", + "0\r\n\0", + "\r\n\0", + "12345\0", + "67890\0", + NULL +}; + +static const char * test_chunk_fragment_1[] = { + "POST /stupid HTTP/1.1\r\n", + "Transfer-Encoding: chunked\r\n", + "\r\n", + "25\r\n", + "This is the data in the first chunk\r\n", + "\r\n", + "1C\r\n", + "and this is the second one\r\n", + "\r\n", + "3\r\n", + "con\r\n", + "8\r\n", + "sequence\r\n", + "0\r\n", + "\r\n", + NULL +}; + +static const char * test_chunk_fragment_2[] = { + "POST /stupid HTTP/1.1\r\n", + "Transfer-Encoding: chunked\r\n", + "\r\n", + "25\r\n", + "This is the data in the first chunk\r\n", + "\r\n", + "1C\r\n", + "and this is the second one\r\n", + "\r\n", + "3\r\n", + "c", + "on\r\n", + "8\r\n", + "sequence\r\n", + "0\r\n", + "\r\n", + "GET /foo?bar/baz? HTTP/1.0\r\n", + "Host: stupid.com\r\n", + "\r\n", + NULL +}; +int +main(int argc, char ** argv) { + htparser * p = htparser_new(); + htparse_hooks hooks = { + .on_msg_begin = _on_msg_start, + .method = _method, + .scheme = NULL, + .host = NULL, + .port = NULL, + .path = _path, + .args = _args, + .uri = _uri, + .on_hdrs_begin = _hdrs_start, + .hdr_key = _hdr_key, + .hdr_val = _hdr_val, + .on_hdrs_complete = _hdrs_end, + .on_new_chunk = _on_new_chunk, + .on_chunk_complete = NULL, + .on_chunks_complete = NULL, + .body = _read_body, + .on_msg_complete = _on_msg_end + }; + + const char * test_1 = "GET / HTTP/1.0\r\n\r\n"; + const char * test_2 = "GET /hi?a=b&c=d HTTP/1.1\r\n\r\n"; + const char * test_3 = "GET /hi/die/?a=b&c=d HTTP/1.1\r\n\r\n"; + const char * test_4 = "POST /fjdls HTTP/1.0\r\n" + "Content-Length: 4\r\n" + "\r\n" + "abcd"; + const char * test_7 = "POST /derp HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "1e\r\nall your base are belong to us\r\n" + "0\r\n" + "\r\n\0"; + const char * test_8 = "GET /DIE HTTP/1.1\r\n" + "HERP: DE\r\n" + "\tRP\r\nthings:stuff\r\n\r\n"; + const char * test_9 = "GET /big_content_len HTTP/1.1\r\n" + "Content-Length: 18446744073709551615\r\n\r\n"; + + const char * test_fail = "GET /JF HfD]\r\n\r\n"; + const char * test_resp_1 = "HTTP/1.0 200 OK\r\n" + "Stuff: junk\r\n\r\n"; + + _test(p, &hooks, test_resp_1, htp_type_response); + _test(p, &hooks, test_1, htp_type_request); + _test(p, &hooks, test_2, htp_type_request); + _test(p, &hooks, test_3, htp_type_request); + _test(p, &hooks, test_4, htp_type_request); + _test(p, &hooks, test_7, htp_type_request); + _test(p, &hooks, test_8, htp_type_request); + _test(p, &hooks, test_9, htp_type_request); + _test(p, &hooks, test_fail, htp_type_request); + + _test_fragments(p, &hooks, test_fragment_1, htp_type_request); + _test_fragments(p, &hooks, test_fragment_2, htp_type_request); + _test_fragments(p, &hooks, test_chunk_fragment_1, htp_type_request); + _test_fragments(p, &hooks, test_chunk_fragment_2, htp_type_request); + + return 0; +} /* main */ + From 18f557f743f3d9d16f16723268f69777bde16607 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 Jun 2012 01:36:49 +0900 Subject: [PATCH 20/21] Conditional compilation with libevent-openssl Require Libevent-openssl 2.0.8 because we use evconnlistener_set_error_cb(). --- configure.ac | 6 +++++- examples/Makefile.am | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 4d6a04a..37ee633 100644 --- a/configure.ac +++ b/configure.ac @@ -96,12 +96,15 @@ if test "x${have_openssl}" = "xno"; then fi # libevent_openssl -PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.6], +# 2.0.8 is required because we use evconnlistener_set_error_cb() +PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8], [have_libevent_openssl=yes], [have_libevent_openssl=no]) if test "x${have_libevent_openssl}" = "xno"; then AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS) AC_MSG_NOTICE([Shrpx example program will not be built.]) fi +AM_CONDITIONAL([HAVE_LIBEVENT_OPENSSL], + [ test "x${have_libevent_openssl}" = "xyes" ]) # libxml2 (for examples/spdycat) AM_PATH_XML2(2.7.7, [have_libxml2=yes], [have_libxml2=no]) @@ -186,5 +189,6 @@ AC_MSG_NOTICE([summary of build options: CUnit: ${have_cunit} OpenSSL: ${have_openssl} Libxml2: ${have_libxml2} + Libevent(SSL): ${have_libevent_openssl} Examples: ${enable_examples} ]) diff --git a/examples/Makefile.am b/examples/Makefile.am index 3b304be..cd34dc6 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -29,7 +29,11 @@ AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \ AM_LDFLAGS = @OPENSSL_LIBS@ @XML_LIBS@ @LIBEVENT_OPENSSL_LIBS@ LDADD = $(top_builddir)/lib/libspdylay.la -bin_PROGRAMS = spdycat spdyd shrpx +bin_PROGRAMS = spdycat spdyd + +if HAVE_LIBEVENT_OPENSSL +bin_PROGRAMS += shrpx +endif # HAVE_LIBEVENT_OPENSSL HELPER_OBJECTS = uri.cc util.cc spdylay_ssl.cc HELPER_HFILES = uri.h util.h spdylay_ssl.h @@ -65,6 +69,7 @@ spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_HFILES} \ spdyd.cc +if HAVE_LIBEVENT_OPENSSL shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_config.cc shrpx_config.h \ shrpx.cc \ @@ -82,6 +87,7 @@ shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_thread_event_receiver.cc shrpx_thread_event_receiver.h \ shrpx_worker.cc shrpx_worker.h \ htparse/htparse.c htparse/htparse.h +endif # HAVE_LIBEVENT_OPENSSL noinst_PROGRAMS = spdycli spdycli_SOURCES = spdycli.c From 6b5beeac14357df69b444487535a98ff152c1087 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 Jun 2012 01:58:36 +0900 Subject: [PATCH 21/21] Updated README.rst --- README.rst | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9c4f7e0..9a71b34 100644 --- a/README.rst +++ b/README.rst @@ -41,11 +41,16 @@ needed: * OpenSSL >= 1.0.1 To enable ``-a`` option (getting linked assets from the downloaded -resouce) in spdycat (one of the example program), the following +resouce) in ``spdycat`` (one of the example program), the following packages are needed: * libxml2 >= 2.7.7 +To build SPDY/HTTPS to HTTP reverse proxy ``shrpx`` (one of the +example program), the following packages are needed: + +* libevent-openssl >= 2.0.8 + Build from git -------------- @@ -88,6 +93,9 @@ purposes. Please note that OpenSSL with `NPN required in order to build and run these programs. At the time of this writing, the OpenSSL 1.0.1 supports NPN. +Spdycat - SPDY client ++++++++++++++++++++++ + The SPDY client is called ``spdycat``. It is a dead simple downloader like wget/curl. It connects to SPDY server and gets resources given in the command-line:: @@ -154,6 +162,9 @@ the command-line:: [ 0.077] send GOAWAY frame (last_good_stream_id=0) +Spdyd - SPDY server ++++++++++++++++++++ + SPDY server is called ``spdyd`` and serves static files. It is single threaded and multiplexes connections using non-blocking socket. The static files are read using blocking I/O system call, read(2). It @@ -193,6 +204,45 @@ speaks SPDY/2 and SPDY/3:: Currently, ``spdyd`` needs ``epoll`` or ``kqueue``. +Shrpx - A reverse proxy for SPDY/HTTPS +++++++++++++++++++++++++++++++++++++++ + +The ``shrpx`` is a multi-threaded reverse proxy for SPDY/HTTPS. It +converts SPDY/HTTPS traffic to plain HTTP. + +Here is the command-line options:: + + Usage: shrpx [-Dh] [-b ] [-f ] [-n ] + [-c ] [-L ] + + A reverse proxy for SPDY/HTTPS. + + + OPTIONS: + -b, --backend= + Set backend host and port. + Default: 'localhost,80' + -f, --frontend= + Set frontend host and port. + Default: 'localhost,3000' + -n, --workers= + Set the number of worker threads. + -c, --spdy-max-concurrent-streams= + Set the maximum number of the concurrent + streams in one SPDY session. + -L, --log-level= + Set the severity level of log output. + INFO, WARNING, ERROR and FATAL + -D, --daemon Run in a background. If -D is used, the + current working directory is changed to '/'. + -h, --help Print this help. + +For those of you who are curious, ``shrpx`` is an abbreviation of +"Spdy/https to Http Reverse ProXy". + +Other examples +++++++++++++++ + There is another SPDY server called ``spdynative``, which is `node.native `_ style simple SPDY server::