From dfce262fe5984753efba7ea433ff70c86ecb87f8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 8 Feb 2012 01:50:58 +0900 Subject: [PATCH] Added non-blocking SPDY server spdyd. It only handles static contents. --- examples/Makefile.am | 4 +- examples/spdyd.cc | 946 ++++++++++++++++++++++++++++++++++++++++ examples/spdylay_ssl.cc | 92 +++- examples/spdylay_ssl.h | 14 +- examples/util.cc | 27 ++ examples/util.h | 28 ++ 6 files changed, 1098 insertions(+), 13 deletions(-) create mode 100644 examples/spdyd.cc diff --git a/examples/Makefile.am b/examples/Makefile.am index fd71088d..2017f8d6 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -26,6 +26,8 @@ AM_CPPFLAGS = -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes `pkg-con AM_LDFLAGS = `pkg-config --libs libssl libcrypto` LDADD = $(top_builddir)/lib/libspdylay.la -bin_PROGRAMS = spdycat +bin_PROGRAMS = spdycat spdyd spdycat_SOURCES = uri.cc spdylay_ssl.cc util.cc spdycat.cc + +spdyd_SOURCES = uri.cc spdylay_ssl.cc util.cc spdyd.cc diff --git a/examples/spdyd.cc b/examples/spdyd.cc new file mode 100644 index 00000000..114e7da1 --- /dev/null +++ b/examples/spdyd.cc @@ -0,0 +1,946 @@ +/* + * 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 +#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 "spdylay_ssl.h" +#include "uri.h" +#include "util.h" + +namespace spdylay { + +struct Config { + std::string htdocs; + bool verbose; + uint16_t port; + std::string private_key_file; + std::string cert_file; + Config(): verbose(false), port(0) {} +}; + +extern bool ssl_debug; + +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 spdylay/"SPDYLAY_VERSION; +} // namespace + +struct Request { + int32_t stream_id; + std::vector > headers; + int file; + Request(int32_t stream_id) + : stream_id(stream_id), file(-1) {} + ~Request() + { + if(file != -1) { + close(file); + } + } +}; + +class Sessions; + +class EventHandler { +public: + 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; +}; + +class Sessions { +public: + Sessions(int epfd, SSL_CTX *ssl_ctx) : epfd_(epfd), ssl_ctx_(ssl_ctx) + {} + ~Sessions() + { + 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, EPOLL_CTL_ADD); + } + int mod_poll(EventHandler *handler) + { + return update_poll_internal(handler, EPOLL_CTL_MOD); + } +private: + int update_poll_internal(EventHandler *handler, int op) + { + epoll_event ev; + ev.events = 0; + if(handler->want_read()) { + ev.events |= EPOLLIN; + } + if(handler->want_write()) { + ev.events |= EPOLLOUT; + } + ev.data.ptr = handler; + int r = epoll_ctl(epfd_, op, handler->fd(), &ev); + return r; + } + + std::set handlers_; + SSL_CTX *ssl_ctx_; + int epfd_; +}; + +namespace { +void print_session_id(int64_t id) +{ + std::cout << "[id=" << id << "] "; +} +} // namespace + +namespace { +void on_session_closed(int64_t session_id) +{ + if(config.verbose) { + print_session_id(session_id); + print_timer(); + std::cout << " closed" << std::endl; + } +} +} // namespace + +class SpdyEventHandler : public EventHandler { +public: + SpdyEventHandler(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks, + int64_t session_id) + : fd_(fd), ssl_(ssl), session_id_(session_id), want_write_(false) + { + spdylay_session_server_new(&session_, callbacks, this); + } + + virtual ~SpdyEventHandler() + { + on_session_closed(session_id_); + spdylay_session_del(session_); + for(std::map::iterator i = id2req_.begin(), + eoi = id2req_.end(); i != eoi; ++i) { + delete (*i).second; + } + SSL_shutdown(ssl_); + SSL_free(ssl_); + shutdown(fd_, SHUT_WR); + close(fd_); + } + virtual int execute(Sessions *sessions) + { + int r; + r = spdylay_session_recv(session_); + if(r == 0) { + r = spdylay_session_send(session_); + } + return r; + } + virtual bool want_read() + { + return spdylay_session_want_read(session_); + } + virtual bool want_write() + { + return spdylay_session_want_write(session_) || want_write_; + } + virtual int fd() const + { + return fd_; + } + virtual bool finish() + { + return !want_read() && !want_write(); + } + ssize_t send_data(const uint8_t *data, size_t len, int flags) + { + ssize_t r; + r = SSL_write(ssl_, data, len); + return r; + } + + ssize_t recv_data(uint8_t *data, size_t len, int flags) + { + ssize_t r; + want_write_ = false; + r = SSL_read(ssl_, data, len); + if(r < 0) { + if(SSL_get_error(ssl_, r) == SSL_ERROR_WANT_WRITE) { + want_write_ = true; + } + } + return r; + } + bool would_block(int r) + { + int e = SSL_get_error(ssl_, r); + return e == SSL_ERROR_WANT_WRITE || e == SSL_ERROR_WANT_READ; + } + int submit_file_response(const std::string& status, + int32_t stream_id, + time_t last_modified, + off_t file_length, + spdylay_data_provider *data_prd) + { + const char *nv[] = { + "status", status.c_str(), + "version", "HTTP/1.1", + "server", SPDYD_SERVER.c_str(), + "content-length", util::to_str(file_length).c_str(), + "cache-control", "max-age=3600", + "date", util::http_date(time(0)).c_str(), + 0, 0, + 0 + }; + if(last_modified != 0) { + nv[12] = "last-modified"; + nv[13] = util::http_date(last_modified).c_str(); + } + return spdylay_submit_response(session_, stream_id, nv, data_prd); + } + int submit_response(const std::string& status, + int32_t stream_id, + spdylay_data_provider *data_prd) + { + const char *nv[] = { + "status", status.c_str(), + "version", "HTTP/1.1", + "server", SPDYD_SERVER.c_str(), + 0 + }; + return spdylay_submit_response(session_, stream_id, nv, data_prd); + } + void add_stream(int32_t stream_id, Request *req) + { + id2req_[stream_id] = req; + } + void remove_stream(int32_t stream_id) + { + Request *req = id2req_[stream_id]; + id2req_.erase(stream_id); + delete req; + } + Request* get_stream(int32_t stream_id) + { + return id2req_[stream_id]; + } + int64_t session_id() const + { + return session_id_; + } +private: + spdylay_session *session_; + int fd_; + SSL* ssl_; + int64_t session_id_; + bool want_write_; + std::map id2req_; +}; + +namespace { +ssize_t hd_send_callback(spdylay_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)) { + r = SPDYLAY_ERR_WOULDBLOCK; + } else { + r = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + return r; +} +} // namespace + +namespace { +ssize_t hd_recv_callback(spdylay_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)) { + r = SPDYLAY_ERR_WOULDBLOCK; + } else { + r = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } else if(r == 0) { + r = SPDYLAY_ERR_CALLBACK_FAILURE; + } + return r; +} +} // namespace + +namespace { +ssize_t file_read_callback +(spdylay_session *session, uint8_t *buf, size_t length, int *eof, + spdylay_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 SPDYLAY_ERR_CALLBACK_FAILURE; + } else { + if(r == 0) { + *eof = 1; + } + return r; + } +} +} // namespace + +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(pipe(pipefd) == -1) { + hd->submit_response(status, req->stream_id, 0); + } else { + std::stringstream ss; + ss << "" << status << "" + << "

" << status << "

" + << "
" + << "
" << SPDYD_SERVER << " at port " << config.port + << "
" + << ""; + std::string body = ss.str(); + write(pipefd[1], body.c_str(), body.size()); + close(pipefd[1]); + + req->file = pipefd[0]; + spdylay_data_provider data_prd; + data_prd.source.fd = pipefd[0]; + data_prd.read_callback = file_read_callback; + hd->submit_file_response(status, req->stream_id, 0, body.size(), &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 version_found = false; + time_t last_mod; + 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 == "url") { + url_found = true; + url = value; + } else if(field == "method") { + method_found = true; + } else if(field == "scheme") { + scheme_found = true; + } else if(field == "version") { + version_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 || !version_found) { + prepare_status_response(req, hd, STATUS_400); + return; + } + std::string::size_type query_pos = url.find("?"); + if(query_pos != std::string::npos) { + 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 = config.htdocs+url; + if(path[path.size()-1] == '/') { + path += DEFAULT_HTML; + } + int file = open(path.c_str(), O_RDONLY); + 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; + spdylay_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, char **nv) +{ + for(int i = 0; nv[i]; i += 2) { + req->headers.push_back(std::make_pair(nv[i], nv[i+1])); + } +} +} // namespace + +namespace { +void hd_on_ctrl_recv_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + if(config.verbose) { + print_session_id(hd->session_id()); + on_ctrl_recv_callback(session, type, frame, user_data); + } + switch(type) { + case SPDYLAY_SYN_STREAM: { + int32_t stream_id = frame->syn_stream.stream_id; + Request *req = new Request(stream_id); + append_nv(req, frame->syn_stream.nv); + hd->add_stream(stream_id, req); + break; + } + case SPDYLAY_HEADERS: { + int32_t stream_id = frame->headers.stream_id; + Request *req = hd->get_stream(stream_id); + append_nv(req, frame->headers.nv); + break; + } + default: + break; + } +} +} // namespace + +namespace { +void hd_on_request_recv_callback +(spdylay_session *session, int32_t stream_id, void *user_data) +{ + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + prepare_response(hd->get_stream(stream_id), hd); +} +} // namespace + +namespace { +void hd_on_ctrl_send_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + if(config.verbose) { + print_session_id(hd->session_id()); + on_ctrl_send_callback(session, type, frame, user_data); + } +} +} // 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) +{ + // TODO Handle POST +} +} // namespace + +namespace { +void hd_on_data_recv_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, + void *user_data) +{ + // TODO Handle POST + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + if(config.verbose) { + print_session_id(hd->session_id()); + on_data_recv_callback(session, flags, stream_id, length, user_data); + } +} +} // namespace + +namespace { +void hd_on_data_send_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, + void *user_data) +{ + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + if(config.verbose) { + print_session_id(hd->session_id()); + on_data_send_callback(session, flags, stream_id, length, user_data); + } +} +} // namespace + +namespace { +void on_stream_close_callback +(spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, + void *user_data) +{ + SpdyEventHandler *hd = (SpdyEventHandler*)user_data; + hd->remove_stream(stream_id); + if(config.verbose) { + print_session_id(hd->session_id()); + print_timer(); + printf(" stream_id=%d closed\n", stream_id); + fflush(stdout); + } +} +} // namespace + +class SSLAcceptEventHandler : public EventHandler { +public: + SSLAcceptEventHandler(int fd, SSL *ssl, int64_t session_id) + : fd_(fd), ssl_(ssl), fail_(false), + want_read_(true), want_write_(true), finish_(false), + session_id_(session_id) + {} + virtual ~SSLAcceptEventHandler() + { + if(fail_) { + on_session_closed(session_id_); + SSL_shutdown(ssl_); + SSL_free(ssl_); + shutdown(fd_, SHUT_WR); + close(fd_); + } + } + virtual int execute(Sessions *sessions) + { + want_read_ = want_write_ = false; + 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 == "spdy/2") { + add_next_handler(sessions); + } else { + fail_ = true; + } + } else { + fail_ = true; + } + } else if(r == 0) { + int e = SSL_get_error(ssl_, r); + if(e == SSL_ERROR_SSL) { + if(config.verbose) { + std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; + } + } + finish_ = true; + fail_ = true; + } else { + int d = SSL_get_error(ssl_, r); + if(d == SSL_ERROR_WANT_READ) { + want_read_ = true; + } else if(d == SSL_ERROR_WANT_WRITE) { + want_write_ = true; + } else { + finish_ = true; + fail_ = true; + } + } + return 0; + } + virtual bool want_read() + { + return want_read_; + } + virtual bool want_write() + { + return want_write_; + } + virtual int fd() const + { + return fd_; + } + virtual bool finish() + { + return finish_; + } +private: + void add_next_handler(Sessions *sessions) + { + spdylay_session_callbacks callbacks; + memset(&callbacks, 0, sizeof(spdylay_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_ctrl_recv_callback = hd_on_ctrl_recv_callback; + callbacks.on_ctrl_send_callback = hd_on_ctrl_send_callback; + callbacks.on_data_recv_callback = hd_on_data_recv_callback; + callbacks.on_data_send_callback = hd_on_data_send_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + callbacks.on_request_recv_callback = hd_on_request_recv_callback; + SpdyEventHandler *hd = new SpdyEventHandler(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_; + bool want_read_, want_write_; + int64_t session_id_; +}; + +class ListenEventHandler : public EventHandler { +public: + ListenEventHandler(int fd) : fd_(fd), session_id_seed_(0) {} + virtual ~ListenEventHandler() + { + close(fd_); + } + 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) + { + SSL *ssl = sessions->ssl_session_new(cfd); + if(ssl == 0) { + close(cfd); + return; + } + SSLAcceptEventHandler *hd = new SSLAcceptEventHandler(cfd, ssl, + ++session_id_seed_); + 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_; +}; + +namespace { +void on_close(Sessions &sessions, EventHandler *hd) +{ + sessions.remove_handler(hd); + delete hd; +} +} // namespace + +namespace { +unsigned char *proto_list; +unsigned int proto_list_len; +} // namespace + +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) +{ + *data = proto_list; + *len = proto_list_len; + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +namespace { +int reactor() +{ + 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 -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_file(ssl_ctx, config.cert_file.c_str(), + SSL_FILETYPE_PEM) != 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; + } + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, 0); + int sfd = make_listen_socket(config.port); + if(sfd == -1) { + std::cerr << "Could not listen on port " << config.port << std::endl; + return -1; + } + make_non_block(sfd); + + int epfd = epoll_create(1); + if(epfd == -1) { + perror("epoll_create"); + return -1; + } + Sessions sessions(epfd, ssl_ctx); + ListenEventHandler *listen_hd = new ListenEventHandler(sfd); + if(sessions.add_poll(listen_hd) == -1) { + std::cerr << "Adding listening socket to poll failed." << std::endl; + return -1; + } + sessions.add_handler(listen_hd); + const int MAX_EVENTS = 256; + epoll_event events[MAX_EVENTS]; + while(1) { + int n = epoll_wait(epfd, events, MAX_EVENTS, -1); + if(n == -1) { + perror("epoll_wait"); + } else { + for(int i = 0; i < n; ++i) { + EventHandler *hd = reinterpret_cast(events[i].data.ptr); + int r = 0; + if((events[i].events & EPOLLIN) || (events[i].events & EPOLLOUT)) { + r = hd->execute(&sessions); + } else if(events[i].events & (EPOLLERR | EPOLLHUP)) { + on_close(sessions, hd); + } + if(r != 0) { + on_close(sessions, hd); + } else { + if(hd->finish()) { + on_close(sessions, hd); + } else { + sessions.mod_poll(hd); + } + } + } + } + } + on_close(sessions, listen_hd); + close(epfd); + return 0; +} +} // namespace + +namespace { +void print_usage(std::ostream& out) +{ + out << "Usage: spdyd [-hv] PORT PRIVATE_KEY CERT" << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream& out) +{ + print_usage(out); + out << "\n" + << "OPTIONS:\n" + << " -d, --htdocs=PATH Specify document root. If this option is not\n" + << " specified, the document root is the current\n" + << " working directory.\n" + << "\n" + << " -v, --verbose Print debug information such as reception/\n" + << " transmission of frames and name/value pairs.\n" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) +{ + config.htdocs = "./"; + while(1) { + static option long_options[] = { + {"verbose", no_argument, 0, 'v' }, + {"htdocs", required_argument, 0, 'd' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + int option_index = 0; + int c = getopt_long(argc, argv, "dhv", long_options, &option_index); + if(c == -1) { + break; + } + switch(c) { + 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); + default: + break; + } + } + if(argc-optind < 3) { + print_usage(std::cerr); + std::cerr << "Too few arguments" << std::endl; + exit(EXIT_FAILURE); + } + 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.port = strtol(argv[optind++], 0, 10); + config.private_key_file = argv[optind++]; + config.cert_file = argv[optind++]; + ssl_debug = config.verbose; + // We only speak "spdy/2". + proto_list_len = 7; + proto_list = new unsigned char[proto_list_len]; + proto_list[0] = 6; + memcpy(&proto_list[1], "spdy/2", 6); + reactor(); + return 0; +} + +} // namespace spdylay + +int main(int argc, char **argv) +{ + return spdylay::main(argc, argv); +} diff --git a/examples/spdylay_ssl.cc b/examples/spdylay_ssl.cc index f6c1b06b..74703afc 100644 --- a/examples/spdylay_ssl.cc +++ b/examples/spdylay_ssl.cc @@ -159,6 +159,52 @@ int connect_to(const std::string& host, uint16_t port) return fd; } +int make_listen_socket(uint16_t port) +{ + addrinfo hints; + int fd = -1; + int r; + char service[10]; + snprintf(service, sizeof(service), "%u", port); + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; + addrinfo *res, *rp; + r = getaddrinfo(0, 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; + } + 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; @@ -173,6 +219,12 @@ int make_non_block(int fd) 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(spdylay_session *session, const uint8_t *data, size_t len, int flags, void *user_data) @@ -250,6 +302,7 @@ void print_ctrl_hd(const spdylay_ctrl_hd& hd) } } // namespace +namespace { void print_frame(spdylay_frame_type type, spdylay_frame *frame) { printf("%s frame ", ctrl_names[type-1]); @@ -300,6 +353,7 @@ void print_frame(spdylay_frame_type type, spdylay_frame *frame) break; } } +} // namespace void on_ctrl_recv_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, @@ -311,16 +365,6 @@ void on_ctrl_recv_callback fflush(stdout); } -void on_data_recv_callback -(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, - void *user_data) -{ - print_timer(); - printf(" recv DATA frame (stream_id=%d, flags=%d, length=%d)\n", - stream_id, flags, length); - fflush(stdout); -} - void on_ctrl_send_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data) @@ -331,6 +375,34 @@ void on_ctrl_send_callback fflush(stdout); } +namespace { +void print_data_frame(uint8_t flags, int32_t stream_id, int32_t length) +{ + printf("DATA frame (stream_id=%d, flags=%d, length=%d)\n", + stream_id, flags, length); +} +} // namespace + +void on_data_recv_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, + void *user_data) +{ + print_timer(); + printf(" recv "); + print_data_frame(flags, stream_id, length); + fflush(stdout); +} + +void on_data_send_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, + void *user_data) +{ + print_timer(); + printf(" send "); + print_data_frame(flags, stream_id, length); + fflush(stdout); +} + void ctl_poll(pollfd *pollfd, Spdylay *sc) { pollfd->events = 0; diff --git a/examples/spdylay_ssl.h b/examples/spdylay_ssl.h index bc9d5693..95ced793 100644 --- a/examples/spdylay_ssl.h +++ b/examples/spdylay_ssl.h @@ -62,8 +62,12 @@ private: int connect_to(const std::string& host, uint16_t port); +int make_listen_socket(uint16_t port); + int make_non_block(int fd); +int set_tcp_nodelay(int fd); + ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len, int flags, void *user_data); @@ -77,12 +81,16 @@ void on_ctrl_recv_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data); +void on_ctrl_send_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data); + void on_data_recv_callback (spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, void *user_data); -void on_ctrl_send_callback -(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, +void on_data_send_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, void *user_data); void ctl_poll(pollfd *pollfd, Spdylay *sc); @@ -100,6 +108,8 @@ void reset_timer(); void get_timer(timeval *tv); +void print_timer(); + } // namespace spdylay #endif // SPDYLAY_SSL_H diff --git a/examples/util.cc b/examples/util.cc index 189a5aa5..a2cda706 100644 --- a/examples/util.cc +++ b/examples/util.cc @@ -24,7 +24,10 @@ */ #include "util.h" +#include + #include +#include namespace spdylay { @@ -97,6 +100,30 @@ std::string percentDecode return result; } +std::string http_date(time_t t) +{ + char buf[32]; + tm* tms = gmtime(&t); // returned struct is statically allocated. + size_t r = strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tms); + return std::string(&buf[0], &buf[r]); +} + +time_t parse_http_date(const std::string& s) +{ + tm tm; + memset(&tm, 0, sizeof(tm)); + char* r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm); + if(r == 0) { + return 0; + } + return timegm(&tm); +} + +bool endsWith(const std::string& a, const std::string& b) +{ + return endsWith(a.begin(), a.end(), b.begin(), b.end()); +} + } // namespace util } // namespace spdylay diff --git a/examples/util.h b/examples/util.h index 2ee7d533..f2f549ab 100644 --- a/examples/util.h +++ b/examples/util.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace spdylay { @@ -161,6 +162,33 @@ std::string percentEncode(const std::string& target); std::string percentDecode (std::string::const_iterator first, std::string::const_iterator last); +std::string http_date(time_t t); + +time_t parse_http_date(const std::string& s); + +template +std::string to_str(T value) +{ + std::stringstream ss; + ss << value; + return ss.str(); +} + +template +bool endsWith +(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2) +{ + if(last1-first1 < last2-first2) { + return false; + } + return std::equal(first2, last2, last1-(last2-first2)); +} + +bool endsWith(const std::string& a, const std::string& b); + } // namespace util } // namespace spdylay