From 551ae72f3ae347744ae0e88f7b1aad45f48dee57 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 22 Jul 2013 22:06:31 +0900 Subject: [PATCH] Remove spdyd and spdycat --- src/HttpServer.cc | 1 + src/Makefile.am | 20 +- src/SpdyServer.cc | 1035 -------------------------------------------- src/SpdyServer.h | 166 ------- src/nghttp2_ssl.cc | 548 +---------------------- src/nghttp2_ssl.h | 73 +--- src/spdycat.cc | 1033 ------------------------------------------- src/spdyd.cc | 185 -------- 8 files changed, 28 insertions(+), 3033 deletions(-) delete mode 100644 src/SpdyServer.cc delete mode 100644 src/SpdyServer.h delete mode 100644 src/spdycat.cc delete mode 100644 src/spdyd.cc diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 71479fc9..a4410476 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -979,6 +979,7 @@ int HttpServer::run() SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); if(SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(), SSL_FILETYPE_PEM) != 1) { diff --git a/src/Makefile.am b/src/Makefile.am index 22c82158..e0d7cd55 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,11 +35,7 @@ AM_CXXFLAGS = -std=c++11 LDADD = $(top_builddir)/lib/libnghttp2.la -bin_PROGRAMS += spdycat nghttp nghttpd - -if ENABLE_SPDYD -bin_PROGRAMS += spdyd -endif # ENABLE_SPDYD +bin_PROGRAMS += nghttp nghttpd if HAVE_LIBEVENT_OPENSSL # bin_PROGRAMS += shrpx @@ -68,10 +64,6 @@ if HAVE_LIBXML2 HTML_PARSER_OBJECTS += HtmlParser.cc endif # HAVE_LIBXML2 -spdycat_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} spdycat.cc \ - ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \ - http-parser/http_parser.c http-parser/http_parser.h - nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \ ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \ http-parser/http_parser.c http-parser/http_parser.h @@ -79,16 +71,6 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \ nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \ HttpServer.cc HttpServer.h -if ENABLE_SPDYD -SPDY_SERVER_OBJECTS = SpdyServer.cc -SPDY_SERVER_HFILES = SpdyServer.h - -spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ - ${EVENT_OBJECTS} ${EVENT_HFILES} \ - ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_HFILES} \ - spdyd.cc -endif # ENABLE_SPDYD - if HAVE_LIBEVENT_OPENSSL # SHRPX_SRCS = \ # util.cc util.h timegm.c timegm.h base64.h \ diff --git a/src/SpdyServer.cc b/src/SpdyServer.cc deleted file mode 100644 index 63de92ee..00000000 --- a/src/SpdyServer.cc +++ /dev/null @@ -1,1035 +0,0 @@ -/* - * nghttp2 - HTTP/2.0 C 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 "SpdyServer.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "nghttp2_ssl.h" -#include "util.h" -#include "EventPoll.h" - -#ifndef O_BINARY -# define O_BINARY (0) -#endif // O_BINARY - -namespace nghttp2 { - -namespace { -Config config; -const std::string STATUS_200 = "200 OK"; -const std::string STATUS_304 = "304 Not Modified"; -const std::string STATUS_400 = "400 Bad Request"; -const std::string STATUS_404 = "404 Not Found"; -const std::string DEFAULT_HTML = "index.html"; -const std::string SPDYD_SERVER = "spdyd nghttp2/" NGHTTP2_VERSION; -} // namespace - -Config::Config(): verbose(false), daemon(false), port(0), - on_request_recv_callback(0), data_ptr(0), - verify_client(false), no_tls(false) -{} - -Request::Request(int32_t stream_id) - : stream_id(stream_id), - file(-1) -{} - -Request::~Request() -{ - if(file != -1) { - close(file); - } -} - -EventHandler::EventHandler(const Config *config) - : config_(config), - mark_del_(false) -{} - -namespace { -void on_close(Sessions &sessions, EventHandler *hd); -} // namespace - -class Sessions { -public: - Sessions(int max_events, SSL_CTX *ssl_ctx) - : eventPoll_(max_events), - ssl_ctx_(ssl_ctx) - {} - ~Sessions() - { - for(auto handler : handlers_) { - on_close(*this, handler); - delete handler; - } - SSL_CTX_free(ssl_ctx_); - } - void add_handler(EventHandler *handler) - { - handlers_.insert(handler); - } - void remove_handler(EventHandler *handler) - { - handlers_.erase(handler); - } - SSL* ssl_session_new(int fd) - { - SSL *ssl = SSL_new(ssl_ctx_); - if(SSL_set_fd(ssl, fd) == 0) { - SSL_free(ssl); - return 0; - } - return ssl; - } - int add_poll(EventHandler *handler) - { - return update_poll_internal(handler, EP_ADD); - } - int mod_poll(EventHandler *handler) - { - return update_poll_internal(handler, EP_MOD); - } - int poll(int timeout) - { - return eventPoll_.poll(timeout); - } - void* get_user_data(int p) - { - return eventPoll_.get_user_data(p); - } - int get_events(int p) - { - return eventPoll_.get_events(p); - } -private: - int update_poll_internal(EventHandler *handler, int op) - { - int events = 0; - if(handler->want_read()) { - events |= EP_POLLIN; - } - if(handler->want_write()) { - events |= EP_POLLOUT; - } - return eventPoll_.ctl_event(op, handler->fd(), events, handler); - } - - std::set handlers_; - EventPoll eventPoll_; - SSL_CTX *ssl_ctx_; -}; - -namespace { -void print_session_id(int64_t id) -{ - std::cout << "[id=" << id << "] "; -} -} // namespace - -namespace { -void on_session_closed(EventHandler *hd, int64_t session_id) -{ - if(hd->config()->verbose) { - print_session_id(session_id); - print_timer(); - std::cout << " closed" << std::endl; - } -} -} // namespace - -SpdyEventHandler::SpdyEventHandler(const Config* config, - int fd, SSL *ssl, - const nghttp2_session_callbacks *callbacks, - int64_t session_id) - : EventHandler(config), - fd_(fd), ssl_(ssl), session_id_(session_id), - io_flags_(0) -{ - int r; - r = nghttp2_session_server_new(&session_, callbacks, this); - assert(r == 0); - nghttp2_settings_entry entry; - entry.settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; - entry.value = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS; - r = nghttp2_submit_settings(session_, &entry, 1); - assert(r == 0); -} - -SpdyEventHandler::~SpdyEventHandler() -{ - on_session_closed(this, session_id_); - nghttp2_session_del(session_); - for(auto& item : id2req_) { - delete item.second; - } - if(ssl_) { - SSL_shutdown(ssl_); - SSL_free(ssl_); - } - shutdown(fd_, SHUT_WR); - close(fd_); -} - -int SpdyEventHandler::execute(Sessions *sessions) -{ - int r; - r = nghttp2_session_recv(session_); - if(r == 0) { - r = nghttp2_session_send(session_); - } - return r; -} - -bool SpdyEventHandler::want_read() -{ - return nghttp2_session_want_read(session_) || (io_flags_ & WANT_READ); -} - -bool SpdyEventHandler::want_write() -{ - return nghttp2_session_want_write(session_) || (io_flags_ & WANT_WRITE); -} - -int SpdyEventHandler::fd() const -{ - return fd_; -} - -bool SpdyEventHandler::finish() -{ - return !nghttp2_session_want_read(session_) && - !nghttp2_session_want_write(session_); -} - -ssize_t SpdyEventHandler::send_data(const uint8_t *data, size_t len, int flags) -{ - ssize_t r; - io_flags_ = 0; - if(ssl_) { - ERR_clear_error(); - r = SSL_write(ssl_, data, len); - if(r < 0) { - io_flags_ = get_ssl_io_demand(ssl_, r); - } - } else { - while((r = ::send(fd_, data, len, 0)) == -1 && errno == EINTR); - if(r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - io_flags_ |= WANT_WRITE; - } - } - return r; -} - -ssize_t SpdyEventHandler::recv_data(uint8_t *data, size_t len, int flags) -{ - ssize_t r; - io_flags_ = 0; - if(ssl_) { - ERR_clear_error(); - r = SSL_read(ssl_, data, len); - if(r < 0) { - io_flags_ = get_ssl_io_demand(ssl_, r); - } - } else { - while((r = ::recv(fd_, data, len, 0)) == -1 && errno == EINTR); - if(r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - io_flags_ |= WANT_READ; - } - } - return r; -} - -bool SpdyEventHandler::would_block() const -{ - return io_flags_; -} - -int SpdyEventHandler::submit_file_response(const std::string& status, - int32_t stream_id, - time_t last_modified, - off_t file_length, - nghttp2_data_provider *data_prd) -{ - std::string date_str = util::http_date(time(0)); - std::string content_length = util::to_str(file_length); - std::string last_modified_str; - const char *nv[] = { - ":status", status.c_str(), - "server", SPDYD_SERVER.c_str(), - "content-length", content_length.c_str(), - "cache-control", "max-age=3600", - "date", date_str.c_str(), - nullptr, nullptr, - nullptr - }; - if(last_modified != 0) { - last_modified_str = util::http_date(last_modified); - nv[10] = "last-modified"; - nv[11] = last_modified_str.c_str(); - } - return nghttp2_submit_response(session_, stream_id, nv, data_prd); -} - -int SpdyEventHandler::submit_response -(const std::string& status, - int32_t stream_id, - const std::vector >& headers, - nghttp2_data_provider *data_prd) -{ - std::string date_str = util::http_date(time(0)); - const char **nv = new const char*[6+headers.size()*2+1]; - nv[0] = ":status"; - nv[1] = status.c_str(); - nv[2] = "server"; - nv[3] = SPDYD_SERVER.c_str(); - nv[4] = "date"; - nv[5] = date_str.c_str(); - for(int i = 0; i < (int)headers.size(); ++i) { - nv[6+i*2] = headers[i].first.c_str(); - nv[6+i*2+1] = headers[i].second.c_str(); - } - nv[6+headers.size()*2] = nullptr; - int r = nghttp2_submit_response(session_, stream_id, nv, data_prd); - delete [] nv; - return r; -} - -int SpdyEventHandler::submit_response(const std::string& status, - int32_t stream_id, - nghttp2_data_provider *data_prd) -{ - const char *nv[] = { - ":status", status.c_str(), - "server", SPDYD_SERVER.c_str(), - nullptr - }; - return nghttp2_submit_response(session_, stream_id, nv, data_prd); -} - -void SpdyEventHandler::add_stream(int32_t stream_id, Request *req) -{ - id2req_[stream_id] = req; -} - -void SpdyEventHandler::remove_stream(int32_t stream_id) -{ - Request *req = id2req_[stream_id]; - id2req_.erase(stream_id); - delete req; -} - -Request* SpdyEventHandler::get_stream(int32_t stream_id) -{ - return id2req_[stream_id]; -} - -int64_t SpdyEventHandler::session_id() const -{ - return session_id_; -} - -namespace { -ssize_t hd_send_callback(nghttp2_session *session, - const uint8_t *data, size_t len, int flags, - void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - ssize_t r = hd->send_data(data, len, flags); - if(r < 0) { - if(hd->would_block()) { - r = NGHTTP2_ERR_WOULDBLOCK; - } else { - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else if(r == 0) { - // In OpenSSL, r == 0 means EOF because SSL_write may do read. - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - return r; -} -} // namespace - -namespace { -ssize_t hd_recv_callback(nghttp2_session *session, - uint8_t *data, size_t len, int flags, void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - ssize_t r = hd->recv_data(data, len, flags); - if(r < 0) { - if(hd->would_block()) { - r = NGHTTP2_ERR_WOULDBLOCK; - } else { - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else if(r == 0) { - r = NGHTTP2_ERR_EOF; - } - return r; -} -} // namespace - -ssize_t file_read_callback -(nghttp2_session *session, int32_t stream_id, - uint8_t *buf, size_t length, int *eof, - nghttp2_data_source *source, void *user_data) -{ - int fd = source->fd; - ssize_t r; - while((r = read(fd, buf, length)) == -1 && errno == EINTR); - if(r == -1) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } else { - if(r == 0) { - *eof = 1; - } - return r; - } -} - -namespace { -bool check_url(const std::string& url) -{ - // We don't like '\' in url. - return !url.empty() && url[0] == '/' && - url.find('\\') == std::string::npos && - url.find("/../") == std::string::npos && - url.find("/./") == std::string::npos && - !util::endsWith(url, "/..") && !util::endsWith(url, "/."); -} -} // namespace - -namespace { -void prepare_status_response(Request *req, SpdyEventHandler *hd, - const std::string& status) -{ - int pipefd[2]; - if(status == STATUS_304 || pipe(pipefd) == -1) { - hd->submit_response(status, req->stream_id, 0); - } else { - std::stringstream ss; - ss << "" << status << "" - << "

" << status << "

" - << "
" - << "
" << SPDYD_SERVER << " at port " << hd->config()->port - << "
" - << ""; - std::string body = ss.str(); - gzFile write_fd = gzdopen(pipefd[1], "w"); - gzwrite(write_fd, body.c_str(), body.size()); - gzclose(write_fd); - close(pipefd[1]); - - req->file = pipefd[0]; - nghttp2_data_provider data_prd; - data_prd.source.fd = pipefd[0]; - data_prd.read_callback = file_read_callback; - std::vector > headers; - headers.push_back(std::make_pair("content-encoding", "gzip")); - headers.push_back(std::make_pair("content-type", - "text/html; charset=UTF-8")); - hd->submit_response(status, req->stream_id, headers, &data_prd); - } -} -} // namespace - -namespace { -void prepare_response(Request *req, SpdyEventHandler *hd) -{ - std::string url; - bool url_found = false; - bool method_found = false; - bool scheme_found = false; - bool host_found = false; - time_t last_mod = 0; - bool last_mod_found = false; - for(int i = 0; i < (int)req->headers.size(); ++i) { - const std::string &field = req->headers[i].first; - const std::string &value = req->headers[i].second; - if(!url_found && field == ":path") { - url_found = true; - url = value; - } else if(field == ":method") { - method_found = true; - } else if(field == ":scheme") { - scheme_found = true; - } else if(field == ":host") { - host_found = true; - } else if(!last_mod_found && field == "if-modified-since") { - last_mod_found = true; - last_mod = util::parse_http_date(value); - } - } - if(!url_found || !method_found || !scheme_found || !host_found) { - prepare_status_response(req, hd, STATUS_400); - return; - } - std::string::size_type query_pos = url.find("?"); - if(query_pos != std::string::npos) { - // Do not response to this request to allow clients to test timeouts. - if (url.find("spdyd_do_not_respond_to_req=yes", - query_pos) != std::string::npos) { - return; - } - url = url.substr(0, query_pos); - } - url = util::percentDecode(url.begin(), url.end()); - if(!check_url(url)) { - prepare_status_response(req, hd, STATUS_404); - return; - } - std::string path = hd->config()->htdocs+url; - if(path[path.size()-1] == '/') { - path += DEFAULT_HTML; - } - int file = open(path.c_str(), O_RDONLY | O_BINARY); - if(file == -1) { - prepare_status_response(req, hd, STATUS_404); - } else { - struct stat buf; - if(fstat(file, &buf) == -1) { - close(file); - prepare_status_response(req, hd, STATUS_404); - } else { - req->file = file; - nghttp2_data_provider data_prd; - data_prd.source.fd = file; - data_prd.read_callback = file_read_callback; - if(last_mod_found && buf.st_mtime <= last_mod) { - prepare_status_response(req, hd, STATUS_304); - } else { - hd->submit_file_response(STATUS_200, req->stream_id, buf.st_mtime, - buf.st_size, &data_prd); - } - } - } -} -} // namespace - -namespace { -void append_nv(Request *req, nghttp2_nv *nva, size_t nvlen) -{ - for(size_t i = 0; i < nvlen; ++i) { - req->headers.push_back({ - std::string(nva[i].name, nva[i].name + nva[i].namelen), - std::string(nva[i].value, nva[i].value + nva[i].valuelen) - }); - } -} -} // namespace - -namespace { -void hd_on_frame_recv_callback -(nghttp2_session *session, nghttp2_frame *frame, void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - if(hd->config()->verbose) { - print_session_id(hd->session_id()); - on_frame_recv_callback(session, frame, user_data); - } - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - switch(frame->headers.cat) { - case NGHTTP2_HCAT_START_STREAM: { - int32_t stream_id = frame->hd.stream_id; - auto req = new Request(stream_id); - append_nv(req, frame->headers.nva, frame->headers.nvlen); - hd->add_stream(stream_id, req); - break; - } - case NGHTTP2_HCAT_HEADERS: { - Request *req = hd->get_stream(frame->hd.stream_id); - append_nv(req, frame->headers.nva, frame->headers.nvlen); - break; - } - default: - break; - } - break; - default: - break; - } -} -} // namespace - -void htdocs_on_request_recv_callback -(nghttp2_session *session, int32_t stream_id, void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - prepare_response(hd->get_stream(stream_id), hd); -} - -namespace { -void hd_on_frame_send_callback -(nghttp2_session *session, nghttp2_frame *frame, - void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - if(hd->config()->verbose) { - print_session_id(hd->session_id()); - on_frame_send_callback(session, frame, user_data); - } -} -} // namespace - -namespace { -void on_data_chunk_recv_callback -(nghttp2_session *session, uint8_t flags, int32_t stream_id, - const uint8_t *data, size_t len, void *user_data) -{ - // TODO Handle POST -} -} // namespace - -namespace { -void hd_on_data_recv_callback -(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id, - void *user_data) -{ - // TODO Handle POST - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - if(hd->config()->verbose) { - print_session_id(hd->session_id()); - on_data_recv_callback(session, length, flags, stream_id, user_data); - } -} -} // namespace - -namespace { -void hd_on_data_send_callback -(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id, - void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - if(hd->config()->verbose) { - print_session_id(hd->session_id()); - on_data_send_callback(session, length, flags, stream_id, user_data); - } -} -} // namespace - -namespace { -void on_stream_close_callback -(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, - void *user_data) -{ - SpdyEventHandler *hd = (SpdyEventHandler*)user_data; - hd->remove_stream(stream_id); - if(hd->config()->verbose) { - print_session_id(hd->session_id()); - print_timer(); - printf(" stream_id=%d closed\n", stream_id); - fflush(stdout); - } -} -} // namespace - -namespace { -void fill_callback(nghttp2_session_callbacks& callbacks, const Config *config) -{ - memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); - callbacks.send_callback = hd_send_callback; - callbacks.recv_callback = hd_recv_callback; - callbacks.on_stream_close_callback = on_stream_close_callback; - callbacks.on_frame_recv_callback = hd_on_frame_recv_callback; - callbacks.on_frame_send_callback = hd_on_frame_send_callback; - callbacks.on_data_recv_callback = hd_on_data_recv_callback; - callbacks.on_data_send_callback = hd_on_data_send_callback; - if(config->verbose) { - callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; - callbacks.on_frame_recv_parse_error_callback = - on_frame_recv_parse_error_callback; - callbacks.on_unknown_frame_recv_callback = on_unknown_frame_recv_callback; - } - callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; - callbacks.on_request_recv_callback = config->on_request_recv_callback; -} -} // namespace - -class SSLAcceptEventHandler : public EventHandler { -public: - SSLAcceptEventHandler(const Config *config, - int fd, SSL *ssl, int64_t session_id) - : EventHandler(config), - fd_(fd), ssl_(ssl), fail_(false), finish_(false), - io_flags_(WANT_READ), - session_id_(session_id) - {} - virtual ~SSLAcceptEventHandler() - { - if(fail_) { - on_session_closed(this, session_id_); - SSL_shutdown(ssl_); - SSL_free(ssl_); - shutdown(fd_, SHUT_WR); - close(fd_); - } - } - virtual int execute(Sessions *sessions) - { - io_flags_ = 0; - ERR_clear_error(); - int r = SSL_accept(ssl_); - if(r == 1) { - finish_ = true; - 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(config()->verbose) { - std::cout << "The negotiated next protocol: " << proto << std::endl; - } - if(proto == "HTTP-draft-04/2.0") { - add_next_handler(sessions); - } else { - std::cerr << "The negotiated next protocol is not supported." - << std::endl; - fail_ = true; - } - } else { - fail_ = true; - } - } else if(r == 0) { - finish_ = true; - fail_ = true; - } else { - io_flags_ = get_ssl_io_demand(ssl_, r); - if(io_flags_ == 0) { - finish_ = true; - fail_ = true; - } - } - if(config()->verbose && fail_) { - std::cerr << "SSL/TLS handshake failed: " - << ERR_error_string(ERR_get_error(), 0) << std::endl; - } - return 0; - } - virtual bool want_read() - { - return io_flags_ & WANT_READ; - } - virtual bool want_write() - { - return io_flags_ & WANT_WRITE; - } - virtual int fd() const - { - return fd_; - } - virtual bool finish() - { - return finish_; - } -private: - void add_next_handler(Sessions *sessions) - { - nghttp2_session_callbacks callbacks; - fill_callback(callbacks, config()); - SpdyEventHandler *hd = new SpdyEventHandler(config(), - fd_, ssl_, &callbacks, - session_id_); - if(sessions->mod_poll(hd) == -1) { - // fd_, ssl_ are freed by ~SpdyEventHandler() - delete hd; - } else { - sessions->add_handler(hd); - } - } - - int fd_; - SSL *ssl_; - bool fail_, finish_; - uint8_t io_flags_; - int64_t session_id_; -}; - -class ListenEventHandler : public EventHandler { -public: - ListenEventHandler(const Config* config, - int fd, int64_t *session_id_seed_ptr) - : EventHandler(config), - fd_(fd), session_id_seed_ptr_(session_id_seed_ptr) {} - virtual ~ListenEventHandler() - {} - virtual int execute(Sessions *sessions) - { - int cfd; - while((cfd = accept(fd_, 0, 0)) == -1 && errno == EINTR); - if(cfd != -1) { - if(make_non_block(cfd) == -1 || - set_tcp_nodelay(cfd) == -1) { - close(cfd); - } else { - add_next_handler(sessions, cfd); - } - } - return 0; - } - virtual bool want_read() - { - return true; - } - virtual bool want_write() - { - return false; - } - virtual int fd() const - { - return fd_; - } - virtual bool finish() - { - return false; - } -private: - void add_next_handler(Sessions *sessions, int cfd) - { - int64_t session_id = ++(*session_id_seed_ptr_); - if(config()->no_tls) { - nghttp2_session_callbacks callbacks; - fill_callback(callbacks, config()); - auto hd = new SpdyEventHandler(config(), cfd, nullptr, &callbacks, - session_id); - if(sessions->add_poll(hd) == -1) { - delete hd; - } else { - sessions->add_handler(hd); - } - } else { - SSL *ssl = sessions->ssl_session_new(cfd); - if(ssl == 0) { - close(cfd); - return; - } - auto hd = new SSLAcceptEventHandler(config(), cfd, ssl, session_id); - if(sessions->add_poll(hd) == -1) { - delete hd; - SSL_free(ssl); - close(cfd); - } else { - sessions->add_handler(hd); - } - } - } - - int fd_; - int64_t *session_id_seed_ptr_; -}; - -namespace { -void on_close(Sessions &sessions, EventHandler *hd) -{ - sessions.remove_handler(hd); - delete hd; -} -} // namespace - -SpdyServer::SpdyServer(const Config *config) - : config_(config) -{ - memset(sfd_, -1, sizeof(sfd_)); -} - -SpdyServer::~SpdyServer() -{ - for(int i = 0; i < 2; ++i) { - if(sfd_[i] != -1) { - close(sfd_[i]); - } - } -} - -int SpdyServer::listen() -{ - int families[] = { AF_INET, AF_INET6 }; - bool bind_ok = false; - for(int i = 0; i < 2; ++i) { - const char* ipv = (families[i] == AF_INET ? "IPv4" : "IPv6"); - int sfd = make_listen_socket(config_->host, config_->port, families[i]); - if(sfd == -1) { - std::cerr << ipv << ": Could not listen on port " << config_->port - << std::endl; - continue; - } - make_non_block(sfd); - sfd_[i] = sfd; - if(config_->verbose) { - std::cout << ipv << ": listen on port " << config_->port << std::endl; - } - bind_ok = true; - } - if(!bind_ok) { - return -1; - } - return 0; -} - -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 - -int SpdyServer::run() -{ - SSL_CTX *ssl_ctx = 0; - std::pair next_proto; - unsigned char proto_list[255]; - if(!config_->no_tls) { - ssl_ctx = SSL_CTX_new(SSLv23_server_method()); - if(!ssl_ctx) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); - 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, - config_->private_key_file.c_str(), - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; - return -1; - } - if(SSL_CTX_use_certificate_chain_file(ssl_ctx, - config_->cert_file.c_str()) != 1) { - std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; - return -1; - } - if(SSL_CTX_check_private_key(ssl_ctx) != 1) { - std::cerr << "SSL_CTX_check_private_key failed." << std::endl; - return -1; - } - if(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); - } - - proto_list[0] = 17; - memcpy(&proto_list[1], "HTTP-draft-04/2.0", 17); - next_proto.first = proto_list; - next_proto.second = 18; - - SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); - } - - const size_t MAX_EVENTS = 256; - Sessions sessions(MAX_EVENTS, ssl_ctx); - - int64_t session_id_seed = 0; - int families[] = { AF_INET, AF_INET6 }; - bool bind_ok = false; - for(int i = 0; i < 2; ++i) { - const char* ipv = (families[i] == AF_INET ? "IPv4" : "IPv6"); - if(sfd_[i] == -1) { - continue; - } - auto listen_hd = new ListenEventHandler(config_, sfd_[i], - &session_id_seed); - if(sessions.add_poll(listen_hd) == -1) { - std::cerr << ipv << ": Adding listening socket to poll failed." - << std::endl; - delete listen_hd; - continue; - } - sessions.add_handler(listen_hd); - bind_ok = true; - } - if(!bind_ok) { - return -1; - } - - std::vector del_list; - while(1) { - int n = sessions.poll(-1); - if(n == -1) { - perror("EventPoll"); - } else { - for(int i = 0; i < n; ++i) { - auto hd = reinterpret_cast(sessions.get_user_data(i)); - int events = sessions.get_events(i); - int r = 0; - if(hd->mark_del()) { - continue; - } - if((events & EP_POLLIN) || (events & EP_POLLOUT)) { - r = hd->execute(&sessions); - } else if(events & (EP_POLLERR | EP_POLLHUP)) { - hd->mark_del(true); - } - if(r != 0) { - hd->mark_del(true); - } else { - if(hd->finish()) { - hd->mark_del(true); - } else { - sessions.mod_poll(hd); - } - } - if(hd->mark_del()) { - del_list.push_back(hd); - } - } - for(auto handler : del_list) { - on_close(sessions, handler); - sessions.remove_handler(handler); - } - del_list.clear(); - } - } - return 0; -} - -} // namespace nghttp2 diff --git a/src/SpdyServer.h b/src/SpdyServer.h deleted file mode 100644 index 0547453c..00000000 --- a/src/SpdyServer.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * nghttp2 - HTTP/2.0 C 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 SPDY_SERVER_H -#define SPDY_SERVER_H - -#include "nghttp2_config.h" - -#include -#include - -#include - -#include -#include -#include - -#include - -#include - -namespace nghttp2 { - -struct Config { - std::string htdocs; - bool verbose; - bool daemon; - std::string host; - uint16_t port; - std::string private_key_file; - std::string cert_file; - nghttp2_on_request_recv_callback on_request_recv_callback; - void *data_ptr; - bool verify_client; - bool no_tls; - Config(); -}; - -class Sessions; - -class EventHandler { -public: - EventHandler(const Config *config); - virtual ~EventHandler() {} - virtual int execute(Sessions *sessions) = 0; - virtual bool want_read() = 0; - virtual bool want_write() = 0; - virtual int fd() const = 0; - virtual bool finish() = 0; - const Config* config() const - { - return config_; - } - bool mark_del() - { - return mark_del_; - } - void mark_del(bool d) - { - mark_del_ = d; - } -private: - const Config *config_; - bool mark_del_; -}; - -struct Request { - int32_t stream_id; - std::vector> headers; - int file; - std::pair response_body; - Request(int32_t stream_id); - ~Request(); -}; - -class SpdyEventHandler : public EventHandler { -public: - SpdyEventHandler(const Config* config, - int fd, SSL *ssl, - const nghttp2_session_callbacks *callbacks, - int64_t session_id); - virtual ~SpdyEventHandler(); - virtual int execute(Sessions *sessions); - virtual bool want_read(); - virtual bool want_write(); - virtual int fd() const; - virtual bool finish(); - - ssize_t send_data(const uint8_t *data, size_t len, int flags); - - ssize_t recv_data(uint8_t *data, size_t len, int flags); - - bool would_block() const; - - int submit_file_response(const std::string& status, - int32_t stream_id, - time_t last_modified, - off_t file_length, - nghttp2_data_provider *data_prd); - - int submit_response(const std::string& status, - int32_t stream_id, - nghttp2_data_provider *data_prd); - - int submit_response - (const std::string& status, - int32_t stream_id, - const std::vector>& headers, - nghttp2_data_provider *data_prd); - - void add_stream(int32_t stream_id, Request *req); - void remove_stream(int32_t stream_id); - Request* get_stream(int32_t stream_id); - int64_t session_id() const; -private: - nghttp2_session *session_; - int fd_; - SSL* ssl_; - int64_t session_id_; - uint8_t io_flags_; - std::map id2req_; -}; - -class SpdyServer { -public: - SpdyServer(const Config* config); - ~SpdyServer(); - int listen(); - int run(); -private: - const Config *config_; - int sfd_[2]; -}; - -void htdocs_on_request_recv_callback -(nghttp2_session *session, int32_t stream_id, void *user_data); - -ssize_t file_read_callback -(nghttp2_session *session, int32_t stream_id, - uint8_t *buf, size_t length, int *eof, - nghttp2_data_source *source, void *user_data); - -} // namespace nghttp2 - -#endif // SPDY_SERVER_H diff --git a/src/nghttp2_ssl.cc b/src/nghttp2_ssl.cc index 9af339c0..51637e63 100644 --- a/src/nghttp2_ssl.cc +++ b/src/nghttp2_ssl.cc @@ -50,407 +50,33 @@ namespace nghttp2 { bool ssl_debug = false; -Spdylay::Spdylay(int fd, SSL *ssl, - const nghttp2_session_callbacks *callbacks, - void *user_data) - : fd_(fd), ssl_(ssl), user_data_(user_data), io_flags_(0) +int select_next_proto_cb(SSL* ssl, + unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) { - int r = nghttp2_session_client_new(&session_, callbacks, this); - assert(r == 0); -} - -Spdylay::~Spdylay() -{ - nghttp2_session_del(session_); -} - -int Spdylay::recv() -{ - return nghttp2_session_recv(session_); -} - -int Spdylay::send() -{ - return nghttp2_session_send(session_); -} - -ssize_t Spdylay::send_data(const uint8_t *data, size_t len, int flags) -{ - ssize_t r; - io_flags_ = 0; - if(ssl_) { - ERR_clear_error(); - r = SSL_write(ssl_, data, len); - if(r < 0) { - io_flags_ = get_ssl_io_demand(ssl_, r); - } - } else { - while((r = ::send(fd_, data, len, 0)) == -1 && errno == EINTR); - if(r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - io_flags_ |= WANT_WRITE; + if(ssl_debug) { + print_timer(); + std::cout << " NPN select next protocol: the remote server offers:" + << std::endl; + } + for(unsigned int i = 0; i < inlen; i += in[i]+1) { + if(ssl_debug) { + std::cout << " * "; + std::cout.write(reinterpret_cast(&in[i+1]), in[i]); + std::cout << std::endl; } } - return r; -} - -ssize_t Spdylay::recv_data(uint8_t *data, size_t len, int flags) -{ - ssize_t r; - io_flags_ = 0; - if(ssl_) { - ERR_clear_error(); - r = SSL_read(ssl_, data, len); - if(r < 0) { - io_flags_ = get_ssl_io_demand(ssl_, r); - } - } else { - while((r = ::recv(fd_, data, len, 0)) == -1 && errno == EINTR); - if(r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - io_flags_ |= WANT_READ; - } + if(nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + std::cerr << "Server did not advertise HTTP/2.0 protocol." + << std::endl; + abort(); } - return r; -} - -bool Spdylay::want_read() -{ - return nghttp2_session_want_read(session_) || (io_flags_ & WANT_READ); -} - -bool Spdylay::want_write() -{ - return nghttp2_session_want_write(session_) || (io_flags_ & WANT_WRITE); -} - -bool Spdylay::finish() -{ - return !nghttp2_session_want_read(session_) && - !nghttp2_session_want_write(session_); -} - -int Spdylay::fd() const -{ - return fd_; -} - -void* Spdylay::user_data() -{ - return user_data_; -} - -int Spdylay::submit_request(const std::string& scheme, - const std::string& hostport, - const std::string& path, - const std::map &headers, - int32_t pri, - const nghttp2_data_provider *data_prd, - int64_t data_length, - void *stream_user_data) -{ - enum eStaticHeaderPosition - { - POS_METHOD = 0, - POS_PATH, - POS_SCHEME, - POS_HOST, - POS_ACCEPT, - POS_ACCEPT_ENCODING, - POS_USERAGENT - }; - - const char *static_nv[] = { - ":method", data_prd ? "POST" : "GET", - ":path", path.c_str(), - ":scheme", scheme.c_str(), - ":host", hostport.c_str(), - "accept", "*/*", - "accept-encoding", "gzip, deflate", - "user-agent", "nghttp2/" NGHTTP2_VERSION - }; - - int hardcoded_entry_count = sizeof(static_nv) / sizeof(*static_nv); - int header_count = headers.size(); - int total_entry_count = hardcoded_entry_count + header_count * 2; - if(data_prd) { - ++total_entry_count; + if(ssl_debug) { + std::cout << " NPN selected the protocol: " + << std::string((const char*)*out, (size_t)*outlen) << std::endl; } - - const char **nv = new const char*[total_entry_count + 1]; - - memcpy(nv, static_nv, hardcoded_entry_count * sizeof(*static_nv)); - - auto i = std::begin(headers); - auto end = std::end(headers); - - int pos = hardcoded_entry_count; - - std::string content_length_str; - if(data_prd) { - std::stringstream ss; - ss << data_length; - content_length_str = ss.str(); - nv[pos++] = "content-length"; - nv[pos++] = content_length_str.c_str(); - } - while( i != end ) { - const char *key = (*i).first.c_str(); - const char *value = (*i).second.c_str(); - if ( util::strieq( key, "accept" ) ) { - nv[POS_ACCEPT*2+1] = value; - } - else if ( util::strieq( key, "user-agent" ) ) { - nv[POS_USERAGENT*2+1] = value; - } - else if ( util::strieq( key, "host" ) ) { - nv[POS_HOST*2+1] = value; - } - else { - nv[pos] = key; - nv[pos+1] = value; - pos += 2; - } - ++i; - } - nv[pos] = nullptr; - - int r = nghttp2_submit_request(session_, pri, nv, data_prd, - stream_user_data); - - delete [] nv; - - return r; -} - -int Spdylay::submit_settings(nghttp2_settings_entry *iv, size_t niv) -{ - return nghttp2_submit_settings(session_, iv, niv); -} - -bool Spdylay::would_block() -{ - return io_flags_; -} - -int connect_to(const std::string& host, uint16_t port) -{ - struct addrinfo hints; - int fd = -1; - int r; - char service[10]; - snprintf(service, sizeof(service), "%u", port); - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - struct addrinfo *res; - r = getaddrinfo(host.c_str(), service, &hints, &res); - if(r != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(r) << std::endl; - return -1; - } - for(struct addrinfo *rp = res; rp; rp = rp->ai_next) { - fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if(fd == -1) { - continue; - } - while((r = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && - errno == EINTR); - if(r == 0) { - break; - } - close(fd); - fd = -1; - } - freeaddrinfo(res); - return fd; -} - -int nonblock_connect_to(const std::string& host, uint16_t port, int timeout) -{ - struct addrinfo hints; - int fd = -1; - int r; - char service[10]; - snprintf(service, sizeof(service), "%u", port); - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - struct addrinfo *res; - r = getaddrinfo(host.c_str(), service, &hints, &res); - if(r != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(r) << std::endl; - return -1; - } - for(struct addrinfo *rp = res; rp; rp = rp->ai_next) { - fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if(fd == -1) { - continue; - } - if(make_non_block(fd) == -1) { - close(fd); - fd = -1; - continue; - } - while((r = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && - errno == EINTR); - if(r == 0) { - break; - } else if(errno == EINPROGRESS) { - struct timeval tv1, tv2; - struct pollfd pfd = {fd, POLLOUT, 0}; - if(timeout != -1) { - get_time(&tv1); - } - r = poll(&pfd, 1, timeout); - if(r == 0) { - return -2; - } else if(r == -1) { - return -1; - } else { - if(timeout != -1) { - get_time(&tv2); - timeout -= time_delta(tv2, tv1); - if(timeout <= 0) { - return -2; - } - } - int socket_error; - socklen_t optlen = sizeof(socket_error); - r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &optlen); - if(r == 0 && socket_error == 0) { - break; - } else { - close(fd); - fd = -1; - } - } - } else { - close(fd); - fd = -1; - } - } - freeaddrinfo(res); - return fd; -} - -int make_listen_socket(const std::string& host, uint16_t port, int family) -{ - addrinfo hints; - int fd = -1; - int r; - char service[10]; - snprintf(service, sizeof(service), "%u", port); - memset(&hints, 0, sizeof(addrinfo)); - 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; - const char* host_ptr; - if(host.empty()) { - host_ptr = 0; - } else { - host_ptr = host.c_str(); - } - r = getaddrinfo(host_ptr, service, &hints, &res); - if(r != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(r) << std::endl; - return -1; - } - 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; - } -#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); - } - freeaddrinfo(res); - if(rp == 0) { - return -1; - } else { - if(listen(fd, 16) == -1) { - close(fd); - return -1; - } else { - return fd; - } - } -} - -int make_non_block(int fd) -{ - int flags, r; - while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR); - if(flags == -1) { - return -1; - } - while((r = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR); - if(r == -1) { - return -1; - } - return 0; -} - -int set_tcp_nodelay(int fd) -{ - int val = 1; - return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); -} - -ssize_t send_callback(nghttp2_session *session, - const uint8_t *data, size_t len, int flags, - void *user_data) -{ - Spdylay *sc = (Spdylay*)user_data; - ssize_t r = sc->send_data(data, len, flags); - if(r < 0) { - if(sc->would_block()) { - r = NGHTTP2_ERR_WOULDBLOCK; - } else { - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else if(r == 0) { - // In OpenSSL, r == 0 means EOF because SSL_write may do read. - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - return r; -} - -ssize_t recv_callback(nghttp2_session *session, - uint8_t *data, size_t len, int flags, void *user_data) -{ - Spdylay *sc = (Spdylay*)user_data; - ssize_t r = sc->recv_data(data, len, flags); - if(r < 0) { - if(sc->would_block()) { - r = NGHTTP2_ERR_WOULDBLOCK; - } else { - r = NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else if(r == 0) { - r = NGHTTP2_ERR_EOF; - } - return r; + return SSL_TLSEXT_ERR_OK; } namespace { @@ -791,124 +417,6 @@ void on_data_send_callback fflush(stdout); } -void ctl_poll(pollfd *pollfd, Spdylay *sc) -{ - pollfd->events = 0; - if(sc->want_read()) { - pollfd->events |= POLLIN; - } - if(sc->want_write()) { - pollfd->events |= POLLOUT; - } -} - -int select_next_proto_cb(SSL* ssl, - unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - void *arg) -{ - if(ssl_debug) { - print_timer(); - std::cout << " NPN select next protocol: the remote server offers:" - << std::endl; - } - for(unsigned int i = 0; i < inlen; i += in[i]+1) { - if(ssl_debug) { - std::cout << " * "; - std::cout.write(reinterpret_cast(&in[i+1]), in[i]); - std::cout << std::endl; - } - } - if(nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { - std::cerr << "Server did not advertise HTTP/2.0 protocol." - << std::endl; - abort(); - } - if(ssl_debug) { - std::cout << " NPN selected the protocol: " - << std::string((const char*)*out, (size_t)*outlen) << std::endl; - } - return SSL_TLSEXT_ERR_OK; -} - -void setup_ssl_ctx(SSL_CTX *ssl_ctx, void *next_proto_select_cb_arg) -{ - /* Disable SSLv2 and enable all workarounds for buggy servers */ - SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); - SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, - next_proto_select_cb_arg); -} - -int ssl_handshake(SSL *ssl, int fd) -{ - if(SSL_set_fd(ssl, fd) == 0) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - ERR_clear_error(); - int r = SSL_connect(ssl); - if(r <= 0) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - return 0; -} - -int ssl_nonblock_handshake(SSL *ssl, int fd, int& timeout) -{ - if(SSL_set_fd(ssl, fd) == 0) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - ERR_clear_error(); - pollfd pfd; - pfd.fd = fd; - pfd.events = POLLOUT; - timeval tv1, tv2; - while(1) { - if(timeout != -1) { - get_time(&tv1); - } - int rv = poll(&pfd, 1, timeout); - if(rv == 0) { - return -2; - } else if(rv == -1) { - return -1; - } - ERR_clear_error(); - rv = SSL_connect(ssl); - if(rv == 0) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } else if(rv < 0) { - if(timeout != -1) { - get_time(&tv2); - timeout -= time_delta(tv2, tv1); - if(timeout <= 0) { - return -2; - } - } - switch(SSL_get_error(ssl, rv)) { - case SSL_ERROR_WANT_READ: - pfd.events = POLLIN; - break; - case SSL_ERROR_WANT_WRITE: - pfd.events = POLLOUT; - break; - default: - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - } else { - break; - } - } - return 0; -} - int64_t time_delta(const timeval& a, const timeval& b) { int64_t res = (a.tv_sec - b.tv_sec) * 1000; @@ -916,18 +424,6 @@ int64_t time_delta(const timeval& a, const timeval& b) return res; } -uint8_t get_ssl_io_demand(SSL *ssl, ssize_t r) -{ - switch(SSL_get_error(ssl, r)) { - case SSL_ERROR_WANT_WRITE: - return WANT_WRITE; - case SSL_ERROR_WANT_READ: - return WANT_READ; - default: - return 0; - } -} - namespace { timeval base_tv; } // namespace diff --git a/src/nghttp2_ssl.h b/src/nghttp2_ssl.h index 37467194..e31bef26 100644 --- a/src/nghttp2_ssl.h +++ b/src/nghttp2_ssl.h @@ -41,55 +41,10 @@ namespace nghttp2 { extern bool ssl_debug; -class Spdylay { -public: - Spdylay(int fd, SSL *ssl, - const nghttp2_session_callbacks *callbacks, - void *user_data); - ~Spdylay(); - int recv(); - int send(); - ssize_t send_data(const uint8_t *data, size_t len, int flags); - ssize_t recv_data(uint8_t *data, size_t len, int flags); - bool want_read(); - bool want_write(); - bool finish(); - int fd() const; - int submit_request(const std::string& scheme, - const std::string& hostport, const std::string& path, - const std::map& headers, - int32_t pri, - const nghttp2_data_provider *data_prd, - int64_t data_length, - void *stream_user_data); - int submit_settings(nghttp2_settings_entry *iv, size_t niv); - bool would_block(); - void* user_data(); -private: - int fd_; - SSL *ssl_; - nghttp2_session *session_; - void *user_data_; - uint8_t io_flags_; - bool debug_; -}; - -int connect_to(const std::string& host, uint16_t port); - -int nonblock_connect_to(const std::string& host, uint16_t port, int timeout); - -int make_listen_socket(const std::string& host, uint16_t port, int family); - -int make_non_block(int fd); - -int set_tcp_nodelay(int fd); - -ssize_t send_callback(nghttp2_session *session, - const uint8_t *data, size_t len, int flags, - void *user_data); - -ssize_t recv_callback(nghttp2_session *session, - uint8_t *data, size_t len, int flags, void *user_data); +int select_next_proto_cb(SSL* ssl, + unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg); void print_nv(char **nv); @@ -126,19 +81,6 @@ void on_data_send_callback (nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id, void *user_data); -void ctl_poll(pollfd *pollfd, Spdylay *sc); - -int select_next_proto_cb(SSL* ssl, - unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - void *arg); - -void setup_ssl_ctx(SSL_CTX *ssl_ctx, void *next_proto_select_cb_arg); - -int ssl_handshake(SSL *ssl, int fd); - -int ssl_nonblock_handshake(SSL *ssl, int fd, int& timeout); - // Returns difference between |a| and |b| in milliseconds, assuming // |a| is more recent than |b|. int64_t time_delta(const timeval& a, const timeval& b); @@ -151,13 +93,6 @@ int get_time(timeval *tv); void print_timer(); -enum { - WANT_READ = 1, - WANT_WRITE = 1 << 1 -}; - -uint8_t get_ssl_io_demand(SSL *ssl, ssize_t r); - // Setting true will print characters with ANSI color escape codes // when printing SPDY frames. This function changes a static variable. void set_color_output(bool f); diff --git a/src/spdycat.cc b/src/spdycat.cc deleted file mode 100644 index a3c794f0..00000000 --- a/src/spdycat.cc +++ /dev/null @@ -1,1033 +0,0 @@ -/* - * nghttp2 - HTTP/2.0 C 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 "nghttp2_config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "http-parser/http_parser.h" - -#include "nghttp2_ssl.h" -#include "HtmlParser.h" -#include "util.h" - -#ifndef O_BINARY -# define O_BINARY (0) -#endif // O_BINARY - -namespace nghttp2 { - -struct Config { - bool null_out; - bool remote_name; - bool verbose; - bool get_assets; - bool stat; - bool no_tls; - int multiply; - // milliseconds - int timeout; - std::string certfile; - std::string keyfile; - int window_bits; - std::map headers; - std::string datafile; - Config():null_out(false), remote_name(false), verbose(false), - get_assets(false), stat(false), no_tls(false), multiply(1), - timeout(-1), window_bits(-1) - {} -}; - -struct RequestStat { - timeval on_syn_stream_time; - timeval on_syn_reply_time; - timeval on_complete_time; - RequestStat() - { - on_syn_stream_time.tv_sec = -1; - on_syn_stream_time.tv_usec = -1; - on_syn_reply_time.tv_sec = -1; - on_syn_reply_time.tv_usec = -1; - on_complete_time.tv_sec = -1; - on_complete_time.tv_usec = -1; - } -}; - -void record_time(timeval *tv) -{ - get_time(tv); -} - -bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) -{ - return u.field_set & (1 << field); -} - -bool fieldeq(const char *uri1, const http_parser_url &u1, - const char *uri2, const http_parser_url &u2, - http_parser_url_fields field) -{ - if(!has_uri_field(u1, field)) { - if(!has_uri_field(u2, field)) { - return true; - } else { - return false; - } - } else if(!has_uri_field(u2, field)) { - return false; - } - if(u1.field_data[field].len != u2.field_data[field].len) { - return false; - } - return memcmp(uri1+u1.field_data[field].off, - uri2+u2.field_data[field].off, - u1.field_data[field].len) == 0; -} - -bool fieldeq(const char *uri, const http_parser_url &u, - http_parser_url_fields field, - const char *t) -{ - if(!has_uri_field(u, field)) { - if(!t[0]) { - return true; - } else { - return false; - } - } else if(!t[0]) { - return false; - } - int i, len = u.field_data[field].len; - const char *p = uri+u.field_data[field].off; - for(i = 0; i < len && t[i] && p[i] == t[i]; ++i); - return i == len && !t[i]; -} - -uint16_t get_default_port(const char *uri, const http_parser_url &u) -{ - if(fieldeq(uri, u, UF_SCHEMA, "https")) { - return 443; - } else if(fieldeq(uri, u, UF_SCHEMA, "http")) { - return 80; - } else { - return 443; - } -} - -std::string get_uri_field(const char *uri, const http_parser_url &u, - http_parser_url_fields field) -{ - if(has_uri_field(u, field)) { - return std::string(uri+u.field_data[field].off, - u.field_data[field].len); - } else { - return ""; - } -} - -bool porteq(const char *uri1, const http_parser_url &u1, - const char *uri2, const http_parser_url &u2) -{ - uint16_t port1, port2; - port1 = has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1); - port2 = has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2); - return port1 == port2; -} - -void write_uri_field(std::ostream& o, - const char *uri, const http_parser_url &u, - http_parser_url_fields field) -{ - if(has_uri_field(u, field)) { - o.write(uri+u.field_data[field].off, u.field_data[field].len); - } -} - -std::string strip_fragment(const char *raw_uri) -{ - const char *end; - for(end = raw_uri; *end && *end != '#'; ++end); - size_t len = end-raw_uri; - return std::string(raw_uri, len); -} - -struct Request { - // URI without fragment - std::string uri; - http_parser_url u; - nghttp2_gzip *inflater; - HtmlParser *html_parser; - const nghttp2_data_provider *data_prd; - int64_t data_length; - int64_t data_offset; - // Recursion level: 0: first entity, 1: entity linked from first entity - int level; - RequestStat stat; - std::string status; - Request(const std::string& uri, const http_parser_url &u, - const nghttp2_data_provider *data_prd, int64_t data_length, - int level = 0) - : uri(uri), u(u), - inflater(0), html_parser(0), data_prd(data_prd), - data_length(data_length),data_offset(0), - level(level) - {} - - ~Request() - { - nghttp2_gzip_inflate_del(inflater); - delete html_parser; - } - - void init_inflater() - { - int rv; - rv = nghttp2_gzip_inflate_new(&inflater); - assert(rv == 0); - } - - void init_html_parser() - { - html_parser = new HtmlParser(uri); - } - - int update_html_parser(const uint8_t *data, size_t len, int fin) - { - if(!html_parser) { - return 0; - } - int rv; - rv = html_parser->parse_chunk(reinterpret_cast(data), len, - fin); - return rv; - } - - std::string make_reqpath() const - { - std::string path = has_uri_field(u, UF_PATH) ? - get_uri_field(uri.c_str(), u, UF_PATH) : "/"; - if(has_uri_field(u, UF_QUERY)) { - path += "?"; - path.append(uri.c_str()+u.field_data[UF_QUERY].off, - u.field_data[UF_QUERY].len); - } - return path; - } - - bool is_ipv6_literal_addr() const - { - if(has_uri_field(u, UF_HOST)) { - return memchr(uri.c_str()+u.field_data[UF_HOST].off, ':', - u.field_data[UF_HOST].len); - } else { - return false; - } - } - - void record_syn_stream_time() - { - record_time(&stat.on_syn_stream_time); - } - - void record_syn_reply_time() - { - record_time(&stat.on_syn_reply_time); - } - - void record_complete_time() - { - record_time(&stat.on_complete_time); - } -}; - -struct SessionStat { - timeval on_handshake_time; - SessionStat() - { - on_handshake_time.tv_sec = -1; - on_handshake_time.tv_usec = -1; - } -}; - -Config config; - -struct SpdySession { - std::vector reqvec; - // Map from stream ID to Request object. - std::map streams; - // Insert path already added in reqvec to prevent multiple request - // for 1 resource. - std::set path_cache; - // The number of completed requests, including failed ones. - size_t complete; - std::string hostport; - Spdylay *sc; - SessionStat stat; - SpdySession():complete(0), sc(0) {} - ~SpdySession() - { - for(size_t i = 0; i < reqvec.size(); ++i) { - delete reqvec[i]; - } - } - bool all_requests_processed() const - { - return complete == reqvec.size(); - } - void update_hostport() - { - if(reqvec.empty()) { - return; - } - std::stringstream ss; - if(reqvec[0]->is_ipv6_literal_addr()) { - ss << "["; - write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); - ss << "]"; - } else { - write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); - } - if(has_uri_field(reqvec[0]->u, UF_PORT) && - reqvec[0]->u.port != get_default_port(reqvec[0]->uri.c_str(), - reqvec[0]->u)) { - ss << ":" << reqvec[0]->u.port; - } - hostport = ss.str(); - } - bool add_request(const std::string& uri, const http_parser_url& u, - const nghttp2_data_provider *data_prd, - int64_t data_length, - int level = 0) - { - if(path_cache.count(uri)) { - return false; - } else { - if(config.multiply == 1) { - path_cache.insert(uri); - } - reqvec.push_back(new Request(uri, u, data_prd, data_length, level)); - return true; - } - } - void record_handshake_time() - { - record_time(&stat.on_handshake_time); - } -}; - -extern bool ssl_debug; - -void submit_request(Spdylay& sc, const std::string& hostport, - const std::map &headers, - Request* req) -{ - std::string path = req->make_reqpath(); - int r = sc.submit_request(get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA), - hostport, path, headers, - NGHTTP2_PRI_DEFAULT, req->data_prd, - req->data_length, req); - assert(r == 0); -} - -void update_html_parser(SpdySession *spdySession, Request *req, - const uint8_t *data, size_t len, int fin) -{ - if(!req->html_parser) { - return; - } - req->update_html_parser(data, len, fin); - - for(size_t i = 0; i < req->html_parser->get_links().size(); ++i) { - const std::string& raw_uri = req->html_parser->get_links()[i]; - std::string uri = strip_fragment(raw_uri.c_str()); - http_parser_url u; - if(http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 && - fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) && - fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) && - porteq(uri.c_str(), u, req->uri.c_str(), req->u)) { - // No POST data for assets - if ( spdySession->add_request(uri, u, 0, 0, req->level+1) ) { - submit_request(*spdySession->sc, spdySession->hostport, config.headers, - spdySession->reqvec.back()); - } - } - } - req->html_parser->clear_links(); -} - -SpdySession* get_session(void *user_data) -{ - return reinterpret_cast - (reinterpret_cast(user_data)->user_data()); -} - -void on_data_chunk_recv_callback -(nghttp2_session *session, uint8_t flags, int32_t stream_id, - const uint8_t *data, size_t len, void *user_data) -{ - SpdySession *spdySession = get_session(user_data); - std::map::iterator itr = - spdySession->streams.find(stream_id); - if(itr != spdySession->streams.end()) { - Request *req = (*itr).second; - if(req->inflater) { - while(len > 0) { - const size_t MAX_OUTLEN = 4096; - uint8_t out[MAX_OUTLEN]; - size_t outlen = MAX_OUTLEN; - size_t tlen = len; - int rv = nghttp2_gzip_inflate(req->inflater, out, &outlen, data, &tlen); - if(rv != 0) { - nghttp2_submit_rst_stream(session, stream_id, NGHTTP2_INTERNAL_ERROR); - break; - } - if(!config.null_out) { - std::cout.write(reinterpret_cast(out), outlen); - } - update_html_parser(spdySession, req, out, outlen, 0); - data += tlen; - len -= tlen; - } - } else { - if(!config.null_out) { - std::cout.write(reinterpret_cast(data), len); - } - update_html_parser(spdySession, req, data, len, 0); - } - } -} - -void check_stream_id(nghttp2_session *session, nghttp2_frame *frame, - void *user_data) -{ - SpdySession *spdySession = get_session(user_data); - int32_t stream_id = frame->hd.stream_id; - Request *req = (Request*)nghttp2_session_get_stream_user_data(session, - stream_id); - spdySession->streams[stream_id] = req; - req->record_syn_stream_time(); -} - -void on_frame_send_callback2 -(nghttp2_session *session, nghttp2_frame *frame, void *user_data) -{ - if(frame->hd.type == NGHTTP2_HEADERS && - frame->headers.cat == NGHTTP2_HCAT_START_STREAM) { - check_stream_id(session, frame, user_data); - } - if(config.verbose) { - on_frame_send_callback(session, frame, user_data); - } -} - -void check_response_header -(nghttp2_session *session, nghttp2_frame *frame, void *user_data) -{ - if(frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REPLY) { - return; - } - auto req = (Request*)nghttp2_session_get_stream_user_data - (session, frame->hd.stream_id); - if(!req) { - // Server-pushed stream does not have stream user data - return; - } - bool gzip = false; - for(size_t i = 0; i < frame->headers.nvlen; ++i) { - auto nv = &frame->headers.nva[i]; - if(util::strieq("content-encoding", nv->name, nv->namelen)) { - gzip = util::strieq("gzip", nv->value, nv->valuelen) || - util::strieq("deflate", nv->value, nv->valuelen); - } else if(util::strieq(":status", nv->name, nv->namelen)) { - req->status.assign(nv->value, nv->value + nv->valuelen); - } - } - if(gzip) { - if(!req->inflater) { - req->init_inflater(); - } - } - if(config.get_assets && req->level == 0) { - if(!req->html_parser) { - req->init_html_parser(); - } - } -} - -void on_frame_recv_callback2 -(nghttp2_session *session, nghttp2_frame *frame, void *user_data) -{ - if(frame->hd.type == NGHTTP2_HEADERS && - frame->headers.cat == NGHTTP2_HCAT_REPLY) { - auto req = (Request*)nghttp2_session_get_stream_user_data - (session, frame->hd.stream_id); - assert(req); - req->record_syn_reply_time(); - } - check_response_header(session, frame, user_data); - if(config.verbose) { - on_frame_recv_callback(session, frame, user_data); - } -} - -void on_stream_close_callback -(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, - void *user_data) -{ - SpdySession *spdySession = get_session(user_data); - auto itr = spdySession->streams.find(stream_id); - if(itr != spdySession->streams.end()) { - update_html_parser(spdySession, (*itr).second, 0, 0, 1); - (*itr).second->record_complete_time(); - ++spdySession->complete; - if(spdySession->all_requests_processed()) { - nghttp2_submit_goaway(session, NGHTTP2_NO_ERROR, NULL, 0); - } - } -} - -void print_stats(const SpdySession& spdySession) -{ - std::cout << "***** Statistics *****" << std::endl; - for(size_t i = 0; i < spdySession.reqvec.size(); ++i) { - auto req = spdySession.reqvec[i]; - std::cout << "#" << i+1 << ": " << req->uri << std::endl; - std::cout << " Status: " << req->status << std::endl; - std::cout << " Delta (ms) from SSL/TLS handshake(SYN_STREAM):" - << std::endl; - if(req->stat.on_syn_reply_time.tv_sec >= 0) { - std::cout << " SYN_REPLY: " - << time_delta(req->stat.on_syn_reply_time, - spdySession.stat.on_handshake_time) - << "(" - << time_delta(req->stat.on_syn_reply_time, - req->stat.on_syn_stream_time) - << ")" - << std::endl; - } - if(req->stat.on_complete_time.tv_sec >= 0) { - std::cout << " Completed: " - << time_delta(req->stat.on_complete_time, - spdySession.stat.on_handshake_time) - << "(" - << time_delta(req->stat.on_complete_time, - req->stat.on_syn_stream_time) - << ")" - << std::endl; - } - std::cout << std::endl; - } -} - -int spdy_evloop(int fd, SSL *ssl, SpdySession& spdySession, - const nghttp2_session_callbacks *callbacks, int timeout) -{ - int result = 0; - int rv; - Spdylay sc(fd, ssl, callbacks, &spdySession); - spdySession.sc = ≻ - - nfds_t npollfds = 1; - pollfd pollfds[1]; - if(config.window_bits != -1) { - nghttp2_settings_entry iv[1]; - iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[0].value = 1 << config.window_bits; - rv = sc.submit_settings(iv, 1); - assert(rv == 0); - } - for(int i = 0, n = spdySession.reqvec.size(); i < n; ++i) { - submit_request(sc, spdySession.hostport, config.headers, - spdySession.reqvec[i]); - } - pollfds[0].fd = fd; - ctl_poll(pollfds, &sc); - - timeval tv1, tv2; - while(!sc.finish()) { - if(config.timeout != -1) { - get_time(&tv1); - } - int nfds = poll(pollfds, npollfds, timeout); - if(nfds == -1) { - perror("poll"); - result = -1; - break; - } - if(pollfds[0].revents & (POLLIN | POLLOUT)) { - int rv; - if((rv = sc.recv()) != 0 || (rv = sc.send()) != 0) { - if(rv != NGHTTP2_ERR_EOF || !spdySession.all_requests_processed()) { - std::cerr << "Fatal: " << nghttp2_strerror(rv) << "\n" - << "reqnum=" << spdySession.reqvec.size() - << ", completed=" << spdySession.complete << std::endl; - } - result = -1; - break; - } - } - if((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) { - std::cerr << "HUP" << std::endl; - result = -1; - break; - } - if(config.timeout != -1) { - get_time(&tv2); - timeout -= time_delta(tv2, tv1); - if (timeout <= 0) { - std::cerr << "Requests to " << spdySession.hostport << " timed out." - << std::endl; - result = -1; - break; - } - } - ctl_poll(pollfds, &sc); - } - if(!spdySession.all_requests_processed()) { - std::cerr << "Some requests were not processed. total=" - << spdySession.reqvec.size() - << ", processed=" << spdySession.complete << std::endl; - } - if(config.stat) { - print_stats(spdySession); - } - return result; -} - -int communicate(const std::string& host, uint16_t port, - SpdySession& spdySession, - const nghttp2_session_callbacks *callbacks) -{ - int result = 0; - int rv; - int timeout = config.timeout; - SSL_CTX *ssl_ctx = 0; - SSL *ssl = 0; - int fd = nonblock_connect_to(host, port, timeout); - if(fd == -1) { - std::cerr << "Could not connect to the host: " << spdySession.hostport - << std::endl; - result = -1; - goto fin; - } else if(fd == -2) { - std::cerr << "Request to " << spdySession.hostport << " timed out " - << "during establishing connection." - << std::endl; - result = -1; - goto fin; - } - if(set_tcp_nodelay(fd) == -1) { - std::cerr << "Setting TCP_NODELAY failed: " << strerror(errno) - << std::endl; - } - - if(!config.no_tls) { - ssl_ctx = SSL_CTX_new(TLSv1_client_method()); - if(!ssl_ctx) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - result = -1; - goto fin; - } - setup_ssl_ctx(ssl_ctx, nullptr); - if(!config.keyfile.empty()) { - if(SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(), - SSL_FILETYPE_PEM) != 1) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - result = -1; - goto fin; - } - } - if(!config.certfile.empty()) { - if(SSL_CTX_use_certificate_chain_file(ssl_ctx, - config.certfile.c_str()) != 1) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - result = -1; - goto fin; - } - } - ssl = SSL_new(ssl_ctx); - if(!ssl) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - result = -1; - goto fin; - } - { - // If the user overrode the host header, use that value for the - // SNI extension - const char *host_string = 0; - auto i = config.headers.find( "Host" ); - if ( i != config.headers.end() ) { - host_string = (*i).second.c_str(); - } - else { - host_string = host.c_str(); - } - - if (!SSL_set_tlsext_host_name(ssl, host_string)) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - result = -1; - goto fin; - } - } - rv = ssl_nonblock_handshake(ssl, fd, timeout); - if(rv == -1) { - result = -1; - goto fin; - } else if(rv == -2) { - std::cerr << "Request to " << spdySession.hostport - << " timed out in SSL/TLS handshake." - << std::endl; - result = -1; - goto fin; - } - } - - if ( config.verbose ) { - print_timer(); - std::cout << " Handshake complete" << std::endl; - } - - spdySession.record_handshake_time(); - result = spdy_evloop(fd, ssl, spdySession, callbacks, timeout); - fin: - if(ssl) { - SSL_shutdown(ssl); - SSL_free(ssl); - SSL_CTX_free(ssl_ctx); - } - if(fd != -1) { - shutdown(fd, SHUT_WR); - close(fd); - } - return result; -} - -ssize_t file_read_callback -(nghttp2_session *session, int32_t stream_id, - uint8_t *buf, size_t length, int *eof, - nghttp2_data_source *source, void *user_data) -{ - auto req = (Request*)nghttp2_session_get_stream_user_data - (session, stream_id); - int fd = source->fd; - ssize_t r; - while((r = pread(fd, buf, length, req->data_offset)) == -1 && - errno == EINTR); - if(r == -1) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } else { - if(r == 0) { - *eof = 1; - } else { - req->data_offset += r; - } - return r; - } -} - -int run(char **uris, int n) -{ - nghttp2_session_callbacks callbacks; - memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); - callbacks.send_callback = send_callback; - callbacks.recv_callback = recv_callback; - callbacks.on_stream_close_callback = on_stream_close_callback; - callbacks.on_frame_recv_callback = on_frame_recv_callback2; - callbacks.on_frame_send_callback = on_frame_send_callback2; - if(config.verbose) { - callbacks.on_data_recv_callback = on_data_recv_callback; - callbacks.on_data_send_callback = on_data_send_callback; - callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; - callbacks.on_frame_recv_parse_error_callback = - on_frame_recv_parse_error_callback; - callbacks.on_unknown_frame_recv_callback = on_unknown_frame_recv_callback; - } - callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; - ssl_debug = config.verbose; - std::string prev_host; - uint16_t prev_port = 0; - int failures = 0; - SpdySession spdySession; - int data_fd = -1; - nghttp2_data_provider data_prd; - struct stat data_stat; - - if(!config.datafile.empty()) { - data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY); - if(data_fd == -1) { - std::cerr << "Could not open file " << config.datafile << std::endl; - return 1; - } - if(fstat(data_fd, &data_stat) == -1) { - close(data_fd); - std::cerr << "Could not stat file " << config.datafile << std::endl; - return 1; - } - data_prd.source.fd = data_fd; - data_prd.read_callback = file_read_callback; - } - - for(int i = 0; i < n; ++i) { - http_parser_url u; - std::string uri = strip_fragment(uris[i]); - if(http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 && - has_uri_field(u, UF_SCHEMA)) { - uint16_t port = has_uri_field(u, UF_PORT) ? - u.port : get_default_port(uri.c_str(), u); - if(!fieldeq(uri.c_str(), u, UF_HOST, prev_host.c_str()) || - u.port != prev_port) { - if(!spdySession.reqvec.empty()) { - spdySession.update_hostport(); - if (communicate(prev_host, prev_port, spdySession, &callbacks) != 0) { - ++failures; - } - spdySession = SpdySession(); - } - prev_host = get_uri_field(uri.c_str(), u, UF_HOST); - prev_port = port; - } - for(int j = 0; j < config.multiply; ++j) { - spdySession.add_request(uri, u, data_fd == -1 ? 0 : &data_prd, - data_stat.st_size); - } - } - } - if(!spdySession.reqvec.empty()) { - spdySession.update_hostport(); - if (communicate(prev_host, prev_port, spdySession, &callbacks) != 0) { - ++failures; - } - } - return failures; -} - -void print_usage(std::ostream& out) -{ - out << "Usage: spdycat [-Oansv] [-t ] [-w ] [--cert=]\n" - << " [--key=] [--no-tls] [-d ] [-m ] ..." - << std::endl; -} - -void print_help(std::ostream& out) -{ - print_usage(out); - out << "\n" - << "OPTIONS:\n" - << " -v, --verbose Print debug information such as reception/\n" - << " transmission of frames and name/value pairs.\n" - << " -n, --null-out Discard downloaded data.\n" - << " -O, --remote-name Save download data in the current directory.\n" - << " The filename is dereived from URI. If URI\n" - << " ends with '/', 'index.html' is used as a\n" - << " filename. Not implemented yet.\n" - << " -t, --timeout= Timeout each request after seconds.\n" - << " -w, --window-bits=\n" - << " Sets the initial window size to 2**.\n" - << " -a, --get-assets Download assets such as stylesheets, images\n" - << " and script files linked from the downloaded\n" - << " resource. Only links whose origins are the\n" - << " same with the linking resource will be\n" - << " downloaded.\n" - << " -s, --stat Print statistics.\n" - << " -H, --header Add a header to the requests.\n" - << " --cert= Use the specified client certificate file.\n" - << " The file must be in PEM format.\n" - << " --key= Use the client private key file. The file\n" - << " must be in PEM format.\n" - << " --no-tls Disable SSL/TLS.\n" - << " -d, --data= Post FILE to server. If - is given, data\n" - << " will be read from stdin.\n" - << " -m, --multiply= Request each URI times. By default, same\n" - << " URI is not requested twice. This option\n" - << " disables it too.\n" - << std::endl; -} - -int main(int argc, char **argv) -{ - while(1) { - int flag; - static option long_options[] = { - {"verbose", no_argument, 0, 'v' }, - {"null-out", no_argument, 0, 'n' }, - {"remote-name", no_argument, 0, 'O' }, - {"timeout", required_argument, 0, 't' }, - {"window-bits", required_argument, 0, 'w' }, - {"get-assets", no_argument, 0, 'a' }, - {"stat", no_argument, 0, 's' }, - {"cert", required_argument, &flag, 1 }, - {"key", required_argument, &flag, 2 }, - {"help", no_argument, 0, 'h' }, - {"header", required_argument, 0, 'H' }, - {"no-tls", no_argument, &flag, 3 }, - {"data", required_argument, 0, 'd' }, - {"multiply", required_argument, 0, 'm' }, - {0, 0, 0, 0 } - }; - int option_index = 0; - int c = getopt_long(argc, argv, "Oad:m:nhH:vst:w:", long_options, - &option_index); - if(c == -1) { - break; - } - switch(c) { - case 'O': - config.remote_name = true; - break; - case 'h': - print_help(std::cout); - exit(EXIT_SUCCESS); - case 'n': - config.null_out = true; - break; - case 'v': - config.verbose = true; - break; - case 't': - config.timeout = atoi(optarg) * 1000; - break; - case 'w': { - errno = 0; - unsigned long int n = strtoul(optarg, 0, 10); - if(errno == 0 && n < 31) { - config.window_bits = n; - } else { - std::cerr << "-w: specify the integer in the range [0, 30], inclusive" - << std::endl; - exit(EXIT_FAILURE); - } - break; - } - case 'H': { - char *header = optarg; - char *value = strchr( optarg, ':' ); - if ( ! value || header == value) { - std::cerr << "-H: invalid header: " << optarg - << std::endl; - exit(EXIT_FAILURE); - } - *value = 0; - value++; - while( isspace( *value ) ) { value++; } - if ( *value == 0 ) { - // This could also be a valid case for suppressing a header - // similar to curl - std::cerr << "-H: invalid header - value missing: " << optarg - << std::endl; - exit(EXIT_FAILURE); - } - // Note that there is no processing currently to handle multiple - // message-header fields with the same field name - config.headers.insert(std::pair(header, value)); - break; - } - case 'a': -#ifdef HAVE_LIBXML2 - config.get_assets = true; -#else // !HAVE_LIBXML2 - std::cerr << "Warning: -a, --get-assets option cannot be used because\n" - << "the binary was not compiled with libxml2." - << std::endl; -#endif // !HAVE_LIBXML2 - break; - case 's': - config.stat = true; - break; - case 'd': - config.datafile = strcmp("-", optarg) == 0 ? "/dev/stdin" : optarg; - break; - case 'm': - config.multiply = strtoul(optarg, 0, 10); - break; - case '?': - exit(EXIT_FAILURE); - case 0: - switch(flag) { - case 1: - // cert option - config.certfile = optarg; - break; - case 2: - // key option - config.keyfile = optarg; - break; - case 3: - // no-tls option - config.no_tls = true; - break; - } - break; - default: - break; - } - } - - set_color_output(isatty(fileno(stdout))); - - struct sigaction act; - memset(&act, 0, sizeof(struct sigaction)); - act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, 0); - SSL_load_error_strings(); - SSL_library_init(); - reset_timer(); - return run(argv+optind, argc-optind); -} - -} // namespace nghttp2 - -int main(int argc, char **argv) -{ - return nghttp2::main(argc, argv); -} diff --git a/src/spdyd.cc b/src/spdyd.cc deleted file mode 100644 index e48573a3..00000000 --- a/src/spdyd.cc +++ /dev/null @@ -1,185 +0,0 @@ -/* - * nghttp2 - HTTP/2.0 C 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 -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "nghttp2_ssl.h" -#include "SpdyServer.h" - -namespace nghttp2 { - -extern bool ssl_debug; - -namespace { -void print_usage(std::ostream& out) -{ - out << "Usage: spdyd [-DVhv] [-d ] [--no-tls] [ ]" - << std::endl; -} -} // namespace - -namespace { -void print_help(std::ostream& out) -{ - print_usage(out); - out << "\n" - << "OPTIONS:\n" - << " -D, --daemon Run in a background. If -D is used, the\n" - << " current working directory is changed to '/'.\n" - << " Therefore if this option is used, -d option\n" - << " must be specified.\n" - << " -V, --verify-client\n" - << " The server sends a client certificate\n" - << " request. If the client did not return a\n" - << " certificate, the handshake is terminated.\n" - << " Currently, this option just requests a\n" - << " client certificate and does not verify it.\n" - << " -d, --htdocs=\n" - << " Specify document root. If this option is\n" - << " not specified, the document root is the\n" - << " current working directory.\n" - << " -v, --verbose Print debug information such as reception/\n" - << " transmission of frames and name/value pairs.\n" - << " --no-tls Disable SSL/TLS.\n" - << " -h, --help Print this help.\n" - << std::endl; -} -} // namespace - -int main(int argc, char **argv) -{ - Config config; - while(1) { - int flag; - static option long_options[] = { - {"daemon", no_argument, 0, 'D' }, - {"htdocs", required_argument, 0, 'd' }, - {"help", no_argument, 0, 'h' }, - {"verbose", no_argument, 0, 'v' }, - {"verify-client", no_argument, 0, 'V' }, - {"no-tls", no_argument, &flag, 1 }, - {0, 0, 0, 0 } - }; - int option_index = 0; - int c = getopt_long(argc, argv, "DVd:hv", long_options, &option_index); - if(c == -1) { - break; - } - switch(c) { - case 'D': - config.daemon = true; - break; - case 'V': - config.verify_client = true; - break; - case 'd': - config.htdocs = optarg; - break; - case 'h': - print_help(std::cout); - exit(EXIT_SUCCESS); - case 'v': - config.verbose = true; - break; - case '?': - exit(EXIT_FAILURE); - case 0: - switch(flag) { - case 1: - // no-tls option - config.no_tls = true; - break; - } - break; - default: - break; - } - } - if(argc - optind < (config.no_tls ? 1 : 3)) { - print_usage(std::cerr); - std::cerr << "Too few arguments" << std::endl; - exit(EXIT_FAILURE); - } - - config.port = strtol(argv[optind++], 0, 10); - - if(!config.no_tls) { - config.private_key_file = argv[optind++]; - config.cert_file = argv[optind++]; - } - - if(config.daemon) { - if(config.htdocs.empty()) { - print_usage(std::cerr); - std::cerr << "-d option must be specified when -D is used." << std::endl; - exit(EXIT_FAILURE); - } - if(daemon(0, 0) == -1) { - perror("daemon"); - exit(EXIT_FAILURE); - } - } - if(config.htdocs.empty()) { - config.htdocs = "./"; - } - - set_color_output(isatty(fileno(stdout))); - - 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(); - reset_timer(); - config.on_request_recv_callback = htdocs_on_request_recv_callback; - ssl_debug = config.verbose; - - SpdyServer server(&config); - if(server.listen() == 0) { - server.run(); - } - return 0; -} - -} // namespace nghttp2 - -int main(int argc, char **argv) -{ - return nghttp2::main(argc, argv); -}