From 4dc3214222a25ec9d7854c6a761c27a57392e62e Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 14 Feb 2012 23:54:23 +0900 Subject: [PATCH] Added example of node.native style spdy server --- configure.ac | 4 + examples/.gitignore | 1 + examples/Makefile.am | 13 ++- examples/SpdyServer.cc | 5 +- examples/SpdyServer.h | 2 + examples/spdy.h | 232 +++++++++++++++++++++++++++++++++++++ examples/spdylay_ssl.cc | 10 +- examples/spdylay_ssl.h | 2 +- examples/spdynative.cc | 42 +++++++ m4/ac_compile_stdcxx_11.m4 | 22 ++++ 10 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 examples/spdy.h create mode 100644 examples/spdynative.cc create mode 100644 m4/ac_compile_stdcxx_11.m4 diff --git a/configure.ac b/configure.ac index 67c98ca4..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 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 e78728d9..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 @@ -52,5 +52,14 @@ spdycat_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} spdycat.cc spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ ${EVENT_OBJECTS} ${EVENT_HFILES} \ - ${SPDY_SERVER_OBJECTS} ${SPDY_SERVER_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 index cd7adfab..82f6ad4b 100644 --- a/examples/SpdyServer.cc +++ b/examples/SpdyServer.cc @@ -282,7 +282,7 @@ int SpdyEventHandler::submit_response nv[3] = "HTTP/1.1"; nv[4] = "server"; nv[5] = SPDYD_SERVER.c_str(); - nv[6] = "data"; + 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(); @@ -803,10 +803,11 @@ int SpdyServer::listen() 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]); + 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; diff --git a/examples/SpdyServer.h b/examples/SpdyServer.h index 7e799139..1512c35e 100644 --- a/examples/SpdyServer.h +++ b/examples/SpdyServer.h @@ -44,6 +44,7 @@ struct Config { std::string htdocs; bool verbose; bool daemon; + std::string host; uint16_t port; std::string private_key_file; std::string cert_file; @@ -84,6 +85,7 @@ struct Request { int32_t stream_id; std::vector > headers; int file; + std::pair response_body; Request(int32_t stream_id); ~Request(); }; 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/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/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 +])