diff --git a/README.rst b/README.rst index 3ca4838f..5b5ae863 100644 --- a/README.rst +++ b/README.rst @@ -100,3 +100,29 @@ serves static contents. It only speaks ``spdy/2``:: [id=1] [ 1.634] closed Currently, ``spdyd`` needs ``epoll`` or ``kqueue``. + +There is another SPDY server called ``spdynative``, which is +`node.native `_ style simple SPDY +server:: + + #include + + #include "spdy.h" + + int main() + { + spdy server; + if(!server.listen("localhost", 8080, "server.key", "server.crt", + [](request& req, response& res) { + res.set_status(200); + res.set_header("content-type", "text/plain"); + res.end("C++ FTW\n"); + })) + return EXIT_FAILURE; + + std::cout << "Server running at http://localhost:8080/" << std::endl; + return reactor::run(server); + } + +Don't expect much from ``spdynative``. It is just an example and does +not support asynchronous I/O at all. diff --git a/configure.ac b/configure.ac index 61bd8d59..5f060cc8 100644 --- a/configure.ac +++ b/configure.ac @@ -43,6 +43,10 @@ AC_PROG_LN_S AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG([0.20]) +AC_COMPILE_STDCXX_11 +AM_CONDITIONAL([HAVE_STDCXX_11], + [ test "x$ac_cv_cxx_compile_cxx11_cxx" = "xyes" ]) + # Checks for libraries. # zlib @@ -66,16 +70,15 @@ if test "x${have_cunit}" = "xno"; then fi fi if test "x${have_cunit}" = "xyes"; then - # Check whether the installed cunit requires ncurses. This is - # needed because cunit in Mac OS X requires it. - LIBS_TEMP=${LIBS} - LIBS=${CUNIT_LIBS} - CFLAGS_TEMP=${CFLAGS} - CFLAGS=${CUNIT_CFLAGS} - AC_RUN_IFELSE(AC_LANG_PROGRAM([[#include "CUnit/Basic.h"]], [CU_initialize_registry()]), [], - [CUNIT_LIBS="${LIBS} -lncurses"]) - LIBS=${LIBS_TEMP} - CFLAGS=${CFLAGS_TEMP} + # cunit in Mac OS X requires ncurses. Note that in Mac OS X, test + # program can be built without -lncurses, but it emits runtime + # error. + case "${build}" in + *-apple-darwin*) + CUNIT_LIBS="$CUNIT_LIBS -lncurses" + AC_SUBST([CUNIT_LIBS]) + ;; + esac fi AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) diff --git a/examples/.gitignore b/examples/.gitignore index 4bc65f96..5c505521 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,2 +1,3 @@ spdycat spdyd +spdynative diff --git a/examples/Makefile.am b/examples/Makefile.am index 93cd6667..f064548e 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -22,7 +22,7 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AM_CFLAGS = -Wall -AM_CPPFLAGS = -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \ +AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \ @OPENSSL_CFLAGS@ AM_LDFLAGS = @OPENSSL_LIBS@ LDADD = $(top_builddir)/lib/libspdylay.la @@ -32,19 +32,34 @@ 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 + +if HAVE_STDCXX_11 +bin_PROGRAMS += spdynative +spdynative_CXXFLAGS = -std=c++0x +spdynative_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ + ${EVENT_OBJECTS} ${EVENT_HFILES} \ + ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_HFILES} \ + spdy.h spdynative.cc +endif # HAVE_STDCXX_11 diff --git a/examples/SpdyServer.cc b/examples/SpdyServer.cc new file mode 100644 index 00000000..82f6ad4b --- /dev/null +++ b/examples/SpdyServer.cc @@ -0,0 +1,936 @@ +/* + * 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] = "date"; + 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 = 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 == "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), finish_(false), + want_read_(true), want_write_(true), + 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_->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 + +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 00000000..1512c35e --- /dev/null +++ b/examples/SpdyServer.h @@ -0,0 +1,160 @@ +/* + * 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; + std::string host; + 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; + std::pair response_body; + 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/spdy.h b/examples/spdy.h new file mode 100644 index 00000000..ea883d74 --- /dev/null +++ b/examples/spdy.h @@ -0,0 +1,232 @@ +/* + * 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 "spdylay_ssl.h" +#include "uri.h" +#include "util.h" +#include "SpdyServer.h" + +using namespace spdylay; + +namespace spdylay { + +class request { +public: + request(const std::vector>& headers) + : headers_(headers) + {} + + const std::vector>& headers() + { + return headers_; + } +private: + std::vector> headers_; +}; + +class response { +public: + response() + : status_code_(200) + {} + + void set_status(int status_code) + { + status_code_ = status_code; + } + + const char* get_status_string() const + { + switch(status_code_) { + case 100: return "100 Continue"; + case 101: return "101 Switching Protocols"; + case 200: return "200 OK"; + case 201: return "201 Created"; + case 202: return "202 Accepted"; + case 203: return "203 Non-Authoritative Information"; + case 204: return "204 No Content"; + case 205: return "205 Reset Content"; + case 206: return "206 Partial Content"; + case 300: return "300 Multiple Choices"; + case 301: return "301 Moved Permanently"; + case 302: return "302 Found"; + case 303: return "303 See Other"; + case 304: return "304 Not Modified"; + case 305: return "305 Use Proxy"; + // case 306: return "306 (Unused)"; + case 307: return "307 Temporary Redirect"; + case 400: return "400 Bad Request"; + case 401: return "401 Unauthorized"; + case 402: return "402 Payment Required"; + case 403: return "403 Forbidden"; + case 404: return "404 Not Found"; + case 405: return "405 Method Not Allowed"; + case 406: return "406 Not Acceptable"; + case 407: return "407 Proxy Authentication Required"; + case 408: return "408 Request Timeout"; + case 409: return "409 Conflict"; + case 410: return "410 Gone"; + case 411: return "411 Length Required"; + case 412: return "412 Precondition Failed"; + case 413: return "413 Request Entity Too Large"; + case 414: return "414 Request-URI Too Long"; + case 415: return "415 Unsupported Media Type"; + case 416: return "416 Requested Range Not Satisfiable"; + case 417: return "417 Expectation Failed"; + case 500: return "500 Internal Server Error"; + case 501: return "501 Not Implemented"; + case 502: return "502 Bad Gateway"; + case 503: return "503 Service Unavailable"; + case 504: return "504 Gateway Timeout"; + case 505: return "505 HTTP Version Not Supported"; + default: return ""; + } + } + + void set_header(const std::string& key, const std::string& value) + { + headers_.push_back(std::make_pair(key, value)); + } + + const std::vector>& get_headers() + { + return headers_; + } + + void end(const std::string& body) + { + body_ = body; + } + + const std::string& get_body() const + { + return body_; + } +private: + int status_code_; + std::string body_; + std::vector> headers_; +}; + +ssize_t string_read_callback +(spdylay_session *session, uint8_t *buf, size_t length, int *eof, + spdylay_data_source *source, void *user_data) +{ + std::pair& body_pair = + *reinterpret_cast*>(source->ptr); + const std::string& body = body_pair.first; + size_t off = body_pair.second; + ssize_t readlen = std::min(body.size()-off, length); + memcpy(buf, body.c_str()+off, readlen); + off += readlen; + if(off == body.size()) { + *eof = 1; + } + return readlen; +} + +void on_request_recv_callback +(spdylay_session *session, int32_t stream_id, void *user_data) +{ + SpdyEventHandler *hd = reinterpret_cast(user_data); + Request *req = hd->get_stream(stream_id); + request request_obj(req->headers); + response response_obj; + (*reinterpret_cast*> + (hd->config()->data_ptr))(request_obj, response_obj); + size_t body_length = response_obj.get_body().size(); + response_obj.set_header("content-length", util::to_str(body_length)); + req->response_body = std::make_pair(response_obj.get_body(), 0); + + spdylay_data_provider data_prd; + data_prd.source.ptr = &req->response_body; + data_prd.read_callback = string_read_callback; + hd->submit_response(response_obj.get_status_string(), stream_id, + response_obj.get_headers(), &data_prd); +} + +class spdy { +public: + spdy() : server_(0) {} + ~spdy() + { + delete server_; + } + bool listen(const std::string& host, uint16_t port, + const std::string& private_key_file, const std::string& cert_file, + std::function callback, + bool verbose = false) + { + delete server_; + callback_ = callback; + config_.verbose = verbose; + config_.host = host; + config_.port = port; + config_.private_key_file = private_key_file; + config_.cert_file = cert_file; + config_.on_request_recv_callback = on_request_recv_callback; + config_.data_ptr = &callback_; + server_ = new SpdyServer(&config_); + return server_->listen() == 0; + } + + int run() + { + return server_->run(); + } +private: + Config config_; + std::function callback_; + SpdyServer *server_; +}; + +namespace reactor { + +template +int run(Server& server) +{ + 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(); + int r = server.run(); + if(r == 0) { + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + +} // namespace reactor + +} // namespace spdylay diff --git a/examples/spdycat.cc b/examples/spdycat.cc index cde82197..b433958e 100644 --- a/examples/spdycat.cc +++ b/examples/spdycat.cc @@ -161,7 +161,14 @@ int communicate(const std::string& host, uint16_t port, pollfd pollfds[1]; std::stringstream ss; - ss << host << ":" << port; + if(reqvec[0].us.ipv6LiteralAddress) { + ss << "["; + } + ss << host; + if(reqvec[0].us.ipv6LiteralAddress) { + ss << "]"; + } + ss << ":" << port; std::string hostport = ss.str(); for(int i = 0, n = reqvec.size(); i < n; ++i) { diff --git a/examples/spdyd.cc b/examples/spdyd.cc index 959ceb7c..314387d6 100644 --- a/examples/spdyd.cc +++ b/examples/spdyd.cc @@ -22,893 +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_; -}; - -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); - - 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) { @@ -941,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' }, @@ -1003,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; } diff --git a/examples/spdylay_ssl.cc b/examples/spdylay_ssl.cc index 37c04d89..f6964228 100644 --- a/examples/spdylay_ssl.cc +++ b/examples/spdylay_ssl.cc @@ -159,7 +159,7 @@ int connect_to(const std::string& host, uint16_t port) return fd; } -int make_listen_socket(uint16_t port, int family) +int make_listen_socket(const std::string& host, uint16_t port, int family) { addrinfo hints; int fd = -1; @@ -174,7 +174,13 @@ int make_listen_socket(uint16_t port, int family) hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG addrinfo *res, *rp; - r = getaddrinfo(0, service, &hints, &res); + 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; diff --git a/examples/spdylay_ssl.h b/examples/spdylay_ssl.h index 2cde92d2..51e5b9a0 100644 --- a/examples/spdylay_ssl.h +++ b/examples/spdylay_ssl.h @@ -62,7 +62,7 @@ private: int connect_to(const std::string& host, uint16_t port); -int make_listen_socket(uint16_t port, int family); +int make_listen_socket(const std::string& host, uint16_t port, int family); int make_non_block(int fd); diff --git a/examples/spdynative.cc b/examples/spdynative.cc new file mode 100644 index 00000000..3e64bd15 --- /dev/null +++ b/examples/spdynative.cc @@ -0,0 +1,42 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include + +#include "spdy.h" + +int main() +{ + spdy server; + if(!server.listen("localhost", 8080, "server.key", "server.crt", + [](request& req, response& res) { + res.set_status(200); + res.set_header("content-type", "text/plain"); + res.end("C++ FTW\n"); + })) + return EXIT_FAILURE; + + std::cout << "Server running at http://localhost:8080/" << std::endl; + return reactor::run(server); +} diff --git a/lib/Makefile.am b/lib/Makefile.am index 24ea437a..3a22be20 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -33,12 +33,14 @@ lib_LTLIBRARIES = libspdylay.la OBJECTS = spdylay_pq.c spdylay_map.c spdylay_queue.c \ spdylay_buffer.c spdylay_frame.c spdylay_zlib.c \ - spdylay_session.c spdylay_helper.c spdylay_stream.c spdylay_npn.c + spdylay_session.c spdylay_helper.c spdylay_stream.c spdylay_npn.c \ + spdylay_submit.c HFILES = spdylay_pq.h spdylay_int.h spdylay_map.h spdylay_queue.h \ spdylay_buffer.h spdylay_frame.h spdylay_zlib.h \ spdylay_session.h spdylay_helper.h spdylay_stream.h spdylay_int.h \ - spdylay_npn.h + spdylay_npn.h \ + spdylay_submit.h libspdylay_la_SOURCES = $(HFILES) $(OBJECTS) libspdylay_la_LDFLAGS = -no-undefined \ diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 26f5122f..01f2026d 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -400,9 +400,11 @@ void* spdylay_session_get_stream_user_data(spdylay_session *session, * * "method": HTTP method (e.g., "GET" or "POST") * "scheme": URI scheme (e.g., "https") - * "url": Abosolute path of this request (e.g., "/foo") + * "url": Absolute path of this request (e.g., "/foo") * "version": HTTP version (e.g., "HTTP/1.1") * + * "host" name/value pair is also required by some hosts. + * * This function creates copies of all name/value pairs in |nv|. * * If |data_prd| is not NULL, it provides data which will be sent in diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index b5ce4b7b..1983fb73 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -60,7 +60,7 @@ spdylay_stream* spdylay_session_get_stream(spdylay_session *session, return (spdylay_stream*)spdylay_map_find(&session->streams, stream_id); } -int spdylay_outbound_item_compar(const void *lhsx, const void *rhsx) +static int spdylay_outbound_item_compar(const void *lhsx, const void *rhsx) { const spdylay_outbound_item *lhs, *rhs; lhs = (const spdylay_outbound_item*)lhsx; @@ -437,7 +437,16 @@ static int spdylay_session_is_data_allowed(spdylay_session *session, return 0; } if(spdylay_session_is_my_stream_id(session, stream_id)) { - return (stream->shut_flags & SPDYLAY_SHUT_WR) == 0; + /* If stream->state is SPDYLAY_STREAM_CLOSING, RST_STREAM was + queued but not yet sent. In this case, we won't send DATA + frames. This is because in the current architecture, DATA and + RST_STREAM in the same stream have same priority and DATA is + small seq number. So RST_STREAM will not be sent until all DATA + frames are sent. This is not desirable situation; we want to + close stream as soon as possible. To achieve this, we remove + DATA frame before RST_STREAM. */ + return stream->state != SPDYLAY_STREAM_CLOSING && + (stream->shut_flags & SPDYLAY_SHUT_WR) == 0; } else { return stream->state == SPDYLAY_STREAM_OPENED && (stream->shut_flags & SPDYLAY_SHUT_WR) == 0; @@ -654,7 +663,7 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) if(session->callbacks.on_data_send_callback) { session->callbacks.on_data_send_callback (session, frame->data.flags, frame->data.stream_id, - session->aob.framebuflen, session->user_data); + session->aob.framebuflen-SPDYLAY_HEAD_LEN, session->user_data); } } else { if(session->callbacks.on_ctrl_send_callback) { @@ -756,7 +765,10 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) }; if(type == SPDYLAY_DATA) { int r; - if(frame->data.flags & SPDYLAY_FLAG_FIN) { + /* If session is closed or RST_STREAM was queued, we won't send + further data. */ + if((frame->data.flags & SPDYLAY_FLAG_FIN) || + !spdylay_session_is_data_allowed(session, frame->data.stream_id)) { spdylay_active_outbound_item_reset(&session->aob); } else { spdylay_outbound_item* item = spdylay_session_get_next_ob_item(session); @@ -1001,19 +1013,20 @@ static int spdylay_session_handle_invalid_stream int spdylay_session_on_syn_stream_received(spdylay_session *session, spdylay_frame *frame) { - int r; + int r = 0; + int status_code; if(session->goaway_flags) { /* We don't accept SYN_STREAM after GOAWAY is sent or received. */ return 0; } - r = spdylay_session_validate_syn_stream(session, &frame->syn_stream); - if(r == 0) { + status_code = spdylay_session_validate_syn_stream(session, + &frame->syn_stream); + if(status_code == 0) { uint8_t flags = frame->syn_stream.hd.flags; if((flags & SPDYLAY_FLAG_FIN) && (flags & SPDYLAY_FLAG_UNIDIRECTIONAL)) { /* If the stream is UNIDIRECTIONAL and FIN bit set, we can close stream upon receiving SYN_STREAM. So, the stream needs not to be opened. */ - r = 0; } else { spdylay_stream *stream; stream = spdylay_session_open_stream(session, frame->syn_stream.stream_id, @@ -1033,18 +1046,26 @@ int spdylay_session_on_syn_stream_received(spdylay_session *session, SPDYLAY_FLAG_UNIDIRECTIONAL is not set here. */ } } - if(r == 0) { - session->last_recv_stream_id = frame->syn_stream.stream_id; - spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SYN_STREAM, - frame); - if(flags & SPDYLAY_FLAG_FIN) { - spdylay_session_call_on_request_recv(session, - frame->syn_stream.stream_id); + session->last_recv_stream_id = frame->syn_stream.stream_id; + spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SYN_STREAM, + frame); + if(flags & SPDYLAY_FLAG_FIN) { + spdylay_session_call_on_request_recv(session, + frame->syn_stream.stream_id); + if(flags & SPDYLAY_FLAG_UNIDIRECTIONAL) { + /* Note that we call on_stream_close_callback without opening + stream. */ + if(session->callbacks.on_stream_close_callback) { + session->callbacks.on_stream_close_callback + (session, frame->syn_stream.stream_id, SPDYLAY_OK, + session->user_data); + } } } } else { r = spdylay_session_handle_invalid_stream - (session, frame->syn_stream.stream_id, SPDYLAY_SYN_STREAM, frame, r); + (session, frame->syn_stream.stream_id, SPDYLAY_SYN_STREAM, frame, + status_code); } return r; } @@ -1533,141 +1554,6 @@ int spdylay_session_add_goaway(spdylay_session *session, return r; } -int spdylay_submit_ping(spdylay_session *session) -{ - return spdylay_session_add_ping(session, - spdylay_session_get_next_unique_id(session)); -} - -int spdylay_submit_cancel(spdylay_session *session, int32_t stream_id, - uint32_t status_code) -{ - return spdylay_session_add_rst_stream(session, stream_id, status_code); -} - -int spdylay_submit_goaway(spdylay_session *session) -{ - return spdylay_session_add_goaway(session, session->last_recv_stream_id); -} - -int spdylay_submit_response(spdylay_session *session, - int32_t stream_id, const char **nv, - spdylay_data_provider *data_prd) -{ - int r; - spdylay_frame *frame; - char **nv_copy; - uint8_t flags = 0; - spdylay_data_provider *data_prd_copy = NULL; - if(data_prd) { - data_prd_copy = malloc(sizeof(spdylay_data_provider)); - if(data_prd_copy == NULL) { - return SPDYLAY_ERR_NOMEM; - } - *data_prd_copy = *data_prd; - } - frame = malloc(sizeof(spdylay_frame)); - if(frame == NULL) { - free(data_prd_copy); - return SPDYLAY_ERR_NOMEM; - } - nv_copy = spdylay_frame_nv_copy(nv); - if(nv_copy == NULL) { - free(frame); - free(data_prd_copy); - return SPDYLAY_ERR_NOMEM; - } - spdylay_frame_nv_sort(nv_copy); - if(data_prd == NULL) { - flags |= SPDYLAY_FLAG_FIN; - } - spdylay_frame_syn_reply_init(&frame->syn_reply, flags, stream_id, - nv_copy); - r = spdylay_session_add_frame(session, SPDYLAY_SYN_REPLY, frame, - data_prd_copy); - if(r != 0) { - spdylay_frame_syn_reply_free(&frame->syn_reply); - free(frame); - free(data_prd_copy); - } - return r; -} - -int spdylay_submit_data(spdylay_session *session, int32_t stream_id, - spdylay_data_provider *data_prd) -{ - int r; - spdylay_frame *frame; - frame = malloc(sizeof(spdylay_frame)); - if(frame == NULL) { - return SPDYLAY_ERR_NOMEM; - } - spdylay_frame_data_init(&frame->data, stream_id, data_prd); - r = spdylay_session_add_frame(session, SPDYLAY_DATA, frame, NULL); - if(r != 0) { - spdylay_frame_data_free(&frame->data); - free(frame); - } - return r; -} - -int spdylay_submit_request(spdylay_session *session, uint8_t pri, - const char **nv, spdylay_data_provider *data_prd, - void *stream_user_data) -{ - int r; - spdylay_frame *frame; - char **nv_copy; - uint8_t flags = 0; - spdylay_data_provider *data_prd_copy = NULL; - spdylay_syn_stream_aux_data *aux_data; - if(pri > 3) { - return SPDYLAY_ERR_INVALID_ARGUMENT; - } - if(data_prd) { - data_prd_copy = malloc(sizeof(spdylay_data_provider)); - if(data_prd_copy == NULL) { - return SPDYLAY_ERR_NOMEM; - } - *data_prd_copy = *data_prd; - } - aux_data = malloc(sizeof(spdylay_syn_stream_aux_data)); - if(aux_data == NULL) { - free(data_prd_copy); - return SPDYLAY_ERR_NOMEM; - } - aux_data->data_prd = data_prd_copy; - aux_data->stream_user_data = stream_user_data; - - frame = malloc(sizeof(spdylay_frame)); - if(frame == NULL) { - free(aux_data); - free(data_prd_copy); - return SPDYLAY_ERR_NOMEM; - } - nv_copy = spdylay_frame_nv_copy(nv); - if(nv_copy == NULL) { - free(frame); - free(aux_data); - free(data_prd_copy); - return SPDYLAY_ERR_NOMEM; - } - spdylay_frame_nv_sort(nv_copy); - if(data_prd == NULL) { - flags |= SPDYLAY_FLAG_FIN; - } - spdylay_frame_syn_stream_init(&frame->syn_stream, flags, 0, 0, pri, nv_copy); - r = spdylay_session_add_frame(session, SPDYLAY_SYN_STREAM, frame, - aux_data); - if(r != 0) { - spdylay_frame_syn_stream_free(&frame->syn_stream); - free(frame); - free(aux_data); - free(data_prd_copy); - } - return r; -} - ssize_t spdylay_session_pack_data(spdylay_session *session, uint8_t **buf_ptr, spdylay_data *frame) { diff --git a/lib/spdylay_submit.c b/lib/spdylay_submit.c new file mode 100644 index 00000000..1daaba20 --- /dev/null +++ b/lib/spdylay_submit.c @@ -0,0 +1,163 @@ +/* + * 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 "spdylay_submit.h" + +#include "spdylay_session.h" +#include "spdylay_frame.h" + +int spdylay_submit_ping(spdylay_session *session) +{ + return spdylay_session_add_ping(session, + spdylay_session_get_next_unique_id(session)); +} + +int spdylay_submit_cancel(spdylay_session *session, int32_t stream_id, + uint32_t status_code) +{ + return spdylay_session_add_rst_stream(session, stream_id, status_code); +} + +int spdylay_submit_goaway(spdylay_session *session) +{ + return spdylay_session_add_goaway(session, session->last_recv_stream_id); +} + +int spdylay_submit_response(spdylay_session *session, + int32_t stream_id, const char **nv, + spdylay_data_provider *data_prd) +{ + int r; + spdylay_frame *frame; + char **nv_copy; + uint8_t flags = 0; + spdylay_data_provider *data_prd_copy = NULL; + if(data_prd) { + data_prd_copy = malloc(sizeof(spdylay_data_provider)); + if(data_prd_copy == NULL) { + return SPDYLAY_ERR_NOMEM; + } + *data_prd_copy = *data_prd; + } + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + free(data_prd_copy); + return SPDYLAY_ERR_NOMEM; + } + nv_copy = spdylay_frame_nv_copy(nv); + if(nv_copy == NULL) { + free(frame); + free(data_prd_copy); + return SPDYLAY_ERR_NOMEM; + } + spdylay_frame_nv_sort(nv_copy); + if(data_prd == NULL) { + flags |= SPDYLAY_FLAG_FIN; + } + spdylay_frame_syn_reply_init(&frame->syn_reply, flags, stream_id, + nv_copy); + r = spdylay_session_add_frame(session, SPDYLAY_SYN_REPLY, frame, + data_prd_copy); + if(r != 0) { + spdylay_frame_syn_reply_free(&frame->syn_reply); + free(frame); + free(data_prd_copy); + } + return r; +} + +int spdylay_submit_data(spdylay_session *session, int32_t stream_id, + spdylay_data_provider *data_prd) +{ + int r; + spdylay_frame *frame; + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + return SPDYLAY_ERR_NOMEM; + } + spdylay_frame_data_init(&frame->data, stream_id, data_prd); + r = spdylay_session_add_frame(session, SPDYLAY_DATA, frame, NULL); + if(r != 0) { + spdylay_frame_data_free(&frame->data); + free(frame); + } + return r; +} + +int spdylay_submit_request(spdylay_session *session, uint8_t pri, + const char **nv, spdylay_data_provider *data_prd, + void *stream_user_data) +{ + int r; + spdylay_frame *frame; + char **nv_copy; + uint8_t flags = 0; + spdylay_data_provider *data_prd_copy = NULL; + spdylay_syn_stream_aux_data *aux_data; + if(pri > 3) { + return SPDYLAY_ERR_INVALID_ARGUMENT; + } + if(data_prd) { + data_prd_copy = malloc(sizeof(spdylay_data_provider)); + if(data_prd_copy == NULL) { + return SPDYLAY_ERR_NOMEM; + } + *data_prd_copy = *data_prd; + } + aux_data = malloc(sizeof(spdylay_syn_stream_aux_data)); + if(aux_data == NULL) { + free(data_prd_copy); + return SPDYLAY_ERR_NOMEM; + } + aux_data->data_prd = data_prd_copy; + aux_data->stream_user_data = stream_user_data; + + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + free(aux_data); + free(data_prd_copy); + return SPDYLAY_ERR_NOMEM; + } + nv_copy = spdylay_frame_nv_copy(nv); + if(nv_copy == NULL) { + free(frame); + free(aux_data); + free(data_prd_copy); + return SPDYLAY_ERR_NOMEM; + } + spdylay_frame_nv_sort(nv_copy); + if(data_prd == NULL) { + flags |= SPDYLAY_FLAG_FIN; + } + spdylay_frame_syn_stream_init(&frame->syn_stream, flags, 0, 0, pri, nv_copy); + r = spdylay_session_add_frame(session, SPDYLAY_SYN_STREAM, frame, + aux_data); + if(r != 0) { + spdylay_frame_syn_stream_free(&frame->syn_stream); + free(frame); + free(aux_data); + free(data_prd_copy); + } + return r; +} diff --git a/lib/spdylay_submit.h b/lib/spdylay_submit.h new file mode 100644 index 00000000..b7bad410 --- /dev/null +++ b/lib/spdylay_submit.h @@ -0,0 +1,34 @@ +/* + * 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 SPDYLAY_SUBMIT_H +#define SPDYLAY_SUBMIT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* SPDYLAY_SUBMIT_H */ diff --git a/m4/ac_compile_stdcxx_11.m4 b/m4/ac_compile_stdcxx_11.m4 new file mode 100644 index 00000000..746c44ed --- /dev/null +++ b/m4/ac_compile_stdcxx_11.m4 @@ -0,0 +1,22 @@ +# AC_COMPILE_STDCXX_11 +AC_DEFUN([AC_COMPILE_STDCXX_11], [ + + AC_CACHE_CHECK(if g++ supports C++11 features with -std=c++0x, + ac_cv_cxx_compile_cxx11_cxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=gnu++0x" + AC_TRY_COMPILE([ + #include + std::function func; + ],, + ac_cv_cxx_compile_cxx11_cxx=yes, ac_cv_cxx_compile_cxx11_cxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + if test "$ac_cv_cxx_compile_cxx11_cxx" = yes; then + AC_DEFINE(HAVE_STDCXX_11,,[Define if g++ supports C++11 features. ]) + fi +]) diff --git a/tests/main.c b/tests/main.c index 95fa3214..1a1892d7 100644 --- a/tests/main.c +++ b/tests/main.c @@ -113,6 +113,12 @@ int main(int argc, char* argv[]) test_spdylay_session_on_stream_close) || !CU_add_test(pSuite, "session_max_concurrent_streams", test_spdylay_session_max_concurrent_streams) || + !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame", + test_spdylay_session_data_backoff_by_high_pri_frame) || + !CU_add_test(pSuite, "session_stop_data_with_rst_stream", + test_spdylay_session_stop_data_with_rst_stream) || + !CU_add_test(pSuite, "session_stream_close_on_syn_stream", + test_spdylay_session_stream_close_on_syn_stream) || !CU_add_test(pSuite, "frame_unpack_nv", test_spdylay_frame_unpack_nv) || !CU_add_test(pSuite, "frame_count_nv_space", test_spdylay_frame_count_nv_space) || diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index 5abd3644..b725ae73 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -50,9 +50,12 @@ typedef struct { accumulator *acc; scripted_data_feed *df; int ctrl_recv_cb_called, invalid_ctrl_recv_cb_called; + int ctrl_send_cb_called; + spdylay_frame_type sent_frame_type; int stream_close_cb_called; size_t data_source_length; int32_t stream_id; + size_t block_count; } my_user_data; static void scripted_data_feed_init(scripted_data_feed *df, @@ -124,6 +127,16 @@ static void on_invalid_ctrl_recv_callback(spdylay_session *session, ++ud->invalid_ctrl_recv_cb_called; } +static void on_ctrl_send_callback(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + ++ud->ctrl_send_cb_called; + ud->sent_frame_type = type; +} + static ssize_t fixed_length_data_source_read_callback (spdylay_session *session, uint8_t *buf, size_t len, int *eof, spdylay_data_source *source, void *user_data) @@ -150,6 +163,16 @@ static void on_request_recv_callback(spdylay_session *session, ud->stream_id = stream_id; } +static void no_stream_user_data_stream_close_callback +(spdylay_session *session, + int32_t stream_id, + spdylay_status_code status_code, + void *user_data) +{ + my_user_data* my_data = (my_user_data*)user_data; + ++my_data->stream_close_cb_called; +} + static char** dup_nv(const char **src) { return spdylay_frame_nv_copy(src); @@ -947,8 +970,9 @@ void test_spdylay_session_on_request_recv_callback() spdylay_session_del(session); } -void stream_close_callback(spdylay_session *session, int32_t stream_id, - spdylay_status_code status_code, void *user_data) +static void stream_close_callback(spdylay_session *session, int32_t stream_id, + spdylay_status_code status_code, + void *user_data) { my_user_data* my_data = (my_user_data*)user_data; void *stream_data = spdylay_session_get_stream_user_data(session, stream_id); @@ -1004,3 +1028,140 @@ void test_spdylay_session_max_concurrent_streams() spdylay_session_del(session); } + +static ssize_t block_count_send_callback(spdylay_session* session, + const uint8_t *data, size_t len, + int flags, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + int r; + if(ud->block_count == 0) { + r = SPDYLAY_ERR_WOULDBLOCK; + } else { + --ud->block_count; + r = len; + } + return r; +} + +void test_spdylay_session_data_backoff_by_high_pri_frame() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { NULL }; + my_user_data ud; + spdylay_data_provider data_prd; + spdylay_stream *stream; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_ctrl_send_callback = on_ctrl_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.ctrl_send_cb_called = 0; + ud.data_source_length = 16*1024; + + spdylay_session_client_new(&session, &callbacks, &ud); + spdylay_submit_request(session, 3, nv, &data_prd, NULL); + + ud.block_count = 2; + /* Sends SYN_STREAM + DATA[0] */ + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(SPDYLAY_SYN_STREAM == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == 8*1024); + + spdylay_submit_ping(session); + ud.block_count = 2; + /* Sends DATA[1] + PING, PING is interleaved in DATA sequence */ + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(SPDYLAY_PING == ud.sent_frame_type); + /* data for DATA[2] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == 4*1024); + + ud.block_count = 2; + /* Sends DATA[2..3] */ + CU_ASSERT(0 == spdylay_session_send(session)); + + stream = spdylay_session_get_stream(session, 1); + CU_ASSERT(stream->shut_flags & SPDYLAY_SHUT_WR); + + spdylay_session_del(session); +} + +void test_spdylay_session_stop_data_with_rst_stream() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { NULL }; + my_user_data ud; + spdylay_data_provider data_prd; + spdylay_frame frame; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.on_ctrl_send_callback = on_ctrl_send_callback; + callbacks.send_callback = block_count_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.ctrl_send_cb_called = 0; + ud.data_source_length = 16*1024; + + spdylay_session_server_new(&session, &callbacks, &ud); + spdylay_session_open_stream(session, 1, SPDYLAY_FLAG_NONE, 3, + SPDYLAY_STREAM_OPENING, NULL); + spdylay_submit_response(session, 1, nv, &data_prd); + + ud.block_count = 2; + /* Sends SYN_REPLY + DATA[0] */ + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(SPDYLAY_SYN_REPLY == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == 8*1024); + + spdylay_frame_rst_stream_init(&frame.rst_stream, 1, SPDYLAY_CANCEL); + CU_ASSERT(0 == spdylay_session_on_rst_stream_received(session, &frame)); + spdylay_frame_rst_stream_free(&frame.rst_stream); + + /* Big enough number to send all DATA frames potentially. */ + ud.block_count = 100; + /* Nothing will be sent in the following call. */ + CU_ASSERT(0 == spdylay_session_send(session)); + /* With RST_STREAM, stream is canceled and further DATA on that + stream are not sent. */ + CU_ASSERT(ud.data_source_length == 8*1024); + + CU_ASSERT(NULL == spdylay_session_get_stream(session, 1)); + + spdylay_session_del(session); +} + +/* + * Check that on_stream_close_callback is called when server pushed + * SYN_STREAM have SPDYLAY_FLAG_FIN. + */ +void test_spdylay_session_stream_close_on_syn_stream() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { NULL }; + my_user_data ud; + spdylay_frame frame; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.on_stream_close_callback = + no_stream_user_data_stream_close_callback; + ud.stream_close_cb_called = 0; + + spdylay_session_client_new(&session, &callbacks, &ud); + spdylay_session_open_stream(session, 1, SPDYLAY_FLAG_NONE, 3, + SPDYLAY_STREAM_OPENING, NULL); + spdylay_frame_syn_stream_init(&frame.syn_stream, + SPDYLAY_FLAG_FIN | SPDYLAY_FLAG_UNIDIRECTIONAL, + 2, 1, 3, dup_nv(nv)); + + CU_ASSERT(0 == spdylay_session_on_syn_stream_received(session, &frame)); + + spdylay_frame_syn_stream_free(&frame.syn_stream); + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index 02ede6b6..37ba779a 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -48,5 +48,8 @@ void test_spdylay_session_pop_next_ob_item(); void test_spdylay_session_on_request_recv_callback(); void test_spdylay_session_on_stream_close(); void test_spdylay_session_max_concurrent_streams(); +void test_spdylay_session_data_backoff_by_high_pri_frame(); +void test_spdylay_session_stop_data_with_rst_stream(); +void test_spdylay_session_stream_close_on_syn_stream(); #endif // SPDYLAY_SESSION_TEST_H