diff --git a/examples/Makefile.am b/examples/Makefile.am index 93cd666..e78728d 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -32,19 +32,25 @@ bin_PROGRAMS = spdycat spdyd HELPER_OBJECTS = uri.cc util.cc spdylay_ssl.cc HELPER_HFILES = uri.h util.h spdylay_ssl.h -spdycat_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} spdycat.cc - -spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} spdyd.cc EventPoll.h \ - EventPollEvent.h +EVENT_OBJECTS = +EVENT_HFILES = EventPoll.h EventPollEvent.h if HAVE_EPOLL - -spdyd_SOURCES += EventPoll_epoll.cc EventPoll_epoll.h - +EVENT_OBJECTS += EventPoll_epoll.cc +EVENT_HFILES += EventPoll_epoll.h endif # HAVE_EPOLL if HAVE_KQUEUE - -spdyd_SOURCES += EventPoll_kqueue.cc EventPoll_kqueue.h - +EVENT_OBJECTS += EventPoll_kqueue.cc +EVENT_HFILES += EventPoll_kqueue.h endif # HAVE_KQUEUE + +SPDY_SERVER_OBJECTS = SpdyServer.cc +SPDY_SERVER_HFILES = SpdyServer.h + +spdycat_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} spdycat.cc + +spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ + ${EVENT_OBJECTS} ${EVENT_HFILES} \ + ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_HFILES}\ + spdyd.cc diff --git a/examples/SpdyServer.cc b/examples/SpdyServer.cc new file mode 100644 index 0000000..240d65f --- /dev/null +++ b/examples/SpdyServer.cc @@ -0,0 +1,935 @@ +/* + * 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 "SpdyServer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "spdylay_ssl.h" +#include "uri.h" +#include "util.h" +#include "EventPoll.h" + +namespace spdylay { + +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 + +Config::Config(): verbose(false), daemon(false), port(0), data_ptr(0) +{} + +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(std::set::iterator i = handlers_.begin(), + eoi = handlers_.end(); i != eoi; ++i) { + on_close(*this, *i); + delete *i; + } + 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 spdylay_session_callbacks *callbacks, + int64_t session_id) + : EventHandler(config), + fd_(fd), ssl_(ssl), session_id_(session_id), want_write_(false) +{ + spdylay_session_server_new(&session_, callbacks, this); +} + +SpdyEventHandler::~SpdyEventHandler() +{ + on_session_closed(this, 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_); +} + +int SpdyEventHandler::execute(Sessions *sessions) +{ + int r; + r = spdylay_session_recv(session_); + if(r == 0) { + r = spdylay_session_send(session_); + } + return r; +} + +bool SpdyEventHandler::want_read() +{ + return spdylay_session_want_read(session_); +} + +bool SpdyEventHandler::want_write() +{ + return spdylay_session_want_write(session_) || want_write_; +} + +int SpdyEventHandler::fd() const +{ + return fd_; +} + +bool SpdyEventHandler::finish() +{ + return !want_read() && !want_write(); +} + +ssize_t SpdyEventHandler::send_data(const uint8_t *data, size_t len, int flags) +{ + ssize_t r; + r = SSL_write(ssl_, data, len); + return r; +} + +ssize_t SpdyEventHandler::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 SpdyEventHandler::would_block(int r) +{ + int e = SSL_get_error(ssl_, r); + return e == SSL_ERROR_WANT_WRITE || e == SSL_ERROR_WANT_READ; +} + +int SpdyEventHandler::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 SpdyEventHandler::submit_response +(const std::string& status, + int32_t stream_id, + const std::vector >& headers, + spdylay_data_provider *data_prd) +{ + const char **nv = new const char*[8+headers.size()*2+1]; + nv[0] = "status"; + nv[1] = status.c_str(); + nv[2] = "version"; + nv[3] = "HTTP/1.1"; + nv[4] = "server"; + nv[5] = SPDYD_SERVER.c_str(); + nv[6] = "data"; + nv[7] = util::http_date(time(0)).c_str(); + for(int i = 0; i < (int)headers.size(); ++i) { + nv[8+i*2] = headers[i].first.c_str(); + nv[8+i*2+1] = headers[i].second.c_str(); + } + nv[8+headers.size()*2] = 0; + int r = spdylay_submit_response(session_, stream_id, nv, data_prd); + delete [] nv; + return r; +} + +int SpdyEventHandler::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 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(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 + +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 { +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 " << hd->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 = hd->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(hd->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 + +void htdocs_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 { +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(hd->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(hd->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(hd->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(hd->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(const Config *config, + int fd, SSL *ssl, int64_t session_id) + : EventHandler(config), + 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(this, 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 = config()->on_request_recv_callback; + 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_; + bool want_read_, want_write_; + 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) + { + SSL *ssl = sessions->ssl_session_new(cfd); + if(ssl == 0) { + close(cfd); + return; + } + SSLAcceptEventHandler *hd = new SSLAcceptEventHandler + (config(), cfd, ssl, ++(*session_id_seed_ptr_)); + 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_->port, families[i]); + if(sfd == -1) { + std::cerr << ipv << ": Could not listen on port " << config_->port + << std::endl; + } + 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 + +int SpdyServer::run() +{ + 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; + } + + // We only speak "spdy/2". + std::pair next_proto; + unsigned char proto_list[7]; + proto_list[0] = 6; + memcpy(&proto_list[1], "spdy/2", 6); + next_proto.first = proto_list; + next_proto.second = sizeof(proto_list); + + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); + + 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"); + ListenEventHandler *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; + } + 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) { + EventHandler *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); + } + } + } + for(std::vector::iterator i = del_list.begin(), + eoi = del_list.end(); i != eoi; ++i) { + on_close(sessions, *i); + } + del_list.clear(); + } + } + return 0; +} + +} // namespace spdylay diff --git a/examples/SpdyServer.h b/examples/SpdyServer.h new file mode 100644 index 0000000..7e79913 --- /dev/null +++ b/examples/SpdyServer.h @@ -0,0 +1,158 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SPDY_SERVER_H +#define SPDY_SERVER_H + +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +namespace spdylay { + +struct Config { + std::string htdocs; + bool verbose; + bool daemon; + uint16_t port; + std::string private_key_file; + std::string cert_file; + spdylay_on_request_recv_callback on_request_recv_callback; + void *data_ptr; + 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; + Request(int32_t stream_id); + ~Request(); +}; + +class SpdyEventHandler : public EventHandler { +public: + SpdyEventHandler(const Config* config, + int fd, SSL *ssl, const spdylay_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(int r); + + 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); + + int submit_response(const std::string& status, + int32_t stream_id, + spdylay_data_provider *data_prd); + + int submit_response + (const std::string& status, + int32_t stream_id, + const std::vector >& headers, + spdylay_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: + spdylay_session *session_; + int fd_; + SSL* ssl_; + int64_t session_id_; + bool want_write_; + 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 +(spdylay_session *session, int32_t stream_id, void *user_data); + +ssize_t file_read_callback +(spdylay_session *session, uint8_t *buf, size_t length, int *eof, + spdylay_data_source *source, void *user_data); + +} // namespace spdylay + +#endif // SPDY_SERVER_H diff --git a/examples/spdyd.cc b/examples/spdyd.cc index 2e4b40e..314387d 100644 --- a/examples/spdyd.cc +++ b/examples/spdyd.cc @@ -22,895 +22,27 @@ * 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 "spdylay_ssl.h" -#include "uri.h" -#include "util.h" -#include "EventPoll.h" +#include "SpdyServer.h" namespace spdylay { -struct Config { - std::string htdocs; - bool verbose; - bool daemon; - uint16_t port; - std::string private_key_file; - std::string cert_file; - Config(): verbose(false), daemon(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: - EventHandler() : mark_del_(false) {} - 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; - bool mark_del() - { - return mark_del_; - } - void mark_del(bool d) - { - mark_del_ = d; - } -private: - bool mark_del_; -}; - -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(std::set::iterator i = handlers_.begin(), - eoi = handlers_.end(); i != eoi; ++i) { - on_close(*this, *i); - delete *i; - } - 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(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_; -}; - -// Global -int64_t session_id_seed = 0; - -class ListenEventHandler : public EventHandler { -public: - ListenEventHandler(int fd) : fd_(fd) {} - 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_; -}; - -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); - - const size_t MAX_EVENTS = 256; - Sessions sessions(MAX_EVENTS, ssl_ctx); - - 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.port, families[i]); - if(sfd == -1) { - std::cerr << ipv << ": Could not listen on port " << config.port - << std::endl; - } - make_non_block(sfd); - - ListenEventHandler *listen_hd = new ListenEventHandler(sfd); - if(sessions.add_poll(listen_hd) == -1) { - std::cerr << ipv << ": Adding listening socket to poll failed." - << std::endl; - delete listen_hd; - } - sessions.add_handler(listen_hd); - if(config.verbose) { - std::cout << ipv << ": listen on port " << config.port << std::endl; - } - 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) { - EventHandler *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); - } - } - } - for(std::vector::iterator i = del_list.begin(), - eoi = del_list.end(); i != eoi; ++i) { - on_close(sessions, *i); - } - del_list.clear(); - } - } - return 0; -} -} // namespace - namespace { void print_usage(std::ostream& out) { @@ -943,6 +75,7 @@ void print_help(std::ostream& out) int main(int argc, char **argv) { + Config config; while(1) { static option long_options[] = { {"daemon", no_argument, 0, 'D' }, @@ -1005,14 +138,13 @@ int main(int argc, char **argv) config.port = strtol(argv[optind++], 0, 10); config.private_key_file = argv[optind++]; config.cert_file = argv[optind++]; + config.on_request_recv_callback = htdocs_on_request_recv_callback; 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(); - delete [] proto_list; + + SpdyServer server(&config); + if(server.listen() == 0) { + server.run(); + } return 0; }