From 1508c50a45b5321c7ba902c18369be8376ae3308 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 2 Sep 2015 00:19:32 +0900 Subject: [PATCH] nghttpx: Add basic infrastructure for mruby support --- gennghttpxfun.py | 1 + src/Makefile.am | 6 +- src/shrpx.cc | 17 ++- src/shrpx_config.cc | 10 ++ src/shrpx_config.h | 3 + src/shrpx_connection_handler.cc | 20 +++- src/shrpx_connection_handler.h | 4 +- src/shrpx_downstream.cc | 15 ++- src/shrpx_downstream.h | 5 + src/shrpx_http2_upstream.cc | 8 ++ src/shrpx_https_upstream.cc | 7 ++ src/shrpx_mruby.cc | 163 +++++++++++++++++++++++++++ src/shrpx_mruby.h | 63 +++++++++++ src/shrpx_mruby_module.cc | 193 ++++++++++++++++++++++++++++++++ src/shrpx_mruby_module.h | 44 ++++++++ src/shrpx_worker.cc | 14 +++ src/shrpx_worker.h | 11 ++ 17 files changed, 573 insertions(+), 11 deletions(-) create mode 100644 src/shrpx_mruby.cc create mode 100644 src/shrpx_mruby.h create mode 100644 src/shrpx_mruby_module.cc create mode 100644 src/shrpx_mruby_module.h diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 4e99155c..3b1a3ae0 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -98,6 +98,7 @@ OPTIONS = [ "tls-ticket-key-memcached-interval", "tls-ticket-key-memcached-max-retry", "tls-ticket-key-memcached-max-fail", + "on-request-mruby-file", "conf", ] diff --git a/src/Makefile.am b/src/Makefile.am index 304b6c09..bc3a6ce7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -124,6 +124,8 @@ NGHTTPX_SRCS = \ shrpx_memcached_connection.cc shrpx_memcached_connection.h \ shrpx_memcached_request.h \ shrpx_memcached_result.h \ + shrpx_mruby.cc shrpx_mruby.h \ + shrpx_mruby_module.cc shrpx_mruby_module.h \ buffer.h memchunk.h template.h if HAVE_SPDYLAY @@ -134,7 +136,7 @@ noinst_LIBRARIES = libnghttpx.a libnghttpx_a_SOURCES = ${NGHTTPX_SRCS} nghttpx_SOURCES = shrpx.cc shrpx.h -nghttpx_LDADD = libnghttpx.a ${LDADD} +nghttpx_LDADD = libnghttpx.a ${LDADD} -lmruby if HAVE_CUNIT check_PROGRAMS += nghttpx-unittest @@ -150,7 +152,7 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \ memchunk_test.cc memchunk_test.h nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\ -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\" -nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@ +nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} -lmruby @CUNIT_LIBS@ @TESTLDADD@ TESTS += nghttpx-unittest endif # HAVE_CUNIT diff --git a/src/shrpx.cc b/src/shrpx.cc index 17c01f80..51c2cf9d 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -926,9 +926,13 @@ int event_loop() { #endif // !NOTHREADS if (get_config()->num_worker == 1) { - conn_handler->create_single_worker(); + rv = conn_handler->create_single_worker(); } else { - conn_handler->create_worker_thread(get_config()->num_worker); + rv = conn_handler->create_worker_thread(get_config()->num_worker); + } + + if (rv != 0) { + return -1; } #ifndef NOTHREADS @@ -1899,6 +1903,7 @@ int main(int argc, char **argv) { 89}, {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, required_argument, &flag, 90}, + {SHRPX_OPT_ON_REQUEST_MRUBY_FILE, required_argument, &flag, 91}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -2296,6 +2301,10 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, optarg); break; + case 91: + // --on-request-mruby-file + cmdcfgs.emplace_back(SHRPX_OPT_ON_REQUEST_MRUBY_FILE, optarg); + break; default: break; } @@ -2613,7 +2622,9 @@ int main(int argc, char **argv) { act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, nullptr); - event_loop(); + if (event_loop() != 0) { + return -1; + } LOG(NOTICE) << "Shutdown momentarily"; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 45db8daf..1d4b480c 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -690,6 +690,7 @@ enum { SHRPX_OPTID_NO_VIA, SHRPX_OPTID_NPN_LIST, SHRPX_OPTID_OCSP_UPDATE_INTERVAL, + SHRPX_OPTID_ON_REQUEST_MRUBY_FILE, SHRPX_OPTID_PADDING, SHRPX_OPTID_PID_FILE, SHRPX_OPTID_PRIVATE_KEY_FILE, @@ -1089,6 +1090,11 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD; } break; + case 'e': + if (util::strieq_l("on-request-mruby-fil", name, 20)) { + return SHRPX_OPTID_ON_REQUEST_MRUBY_FILE; + } + break; case 'r': if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; @@ -1938,6 +1944,10 @@ int parse_config(const char *opt, const char *optarg, case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL: return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt, optarg); + case SHRPX_OPTID_ON_REQUEST_MRUBY_FILE: + mod_config()->on_request_mruby_file = strcopy(optarg); + + return 0; case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index e75a859c..d7b59c84 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -183,6 +183,7 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY[] = "tls-ticket-key-memcached-max-retry"; constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] = "tls-ticket-key-memcached-max-fail"; +constexpr char SHRPX_OPT_ON_REQUEST_MRUBY_FILE[] = "on-request-mruby-file"; union sockaddr_union { sockaddr_storage storage; @@ -314,6 +315,8 @@ struct Config { std::unique_ptr user; std::unique_ptr session_cache_memcached_host; std::unique_ptr tls_ticket_key_memcached_host; + std::unique_ptr on_request_mruby_file; + std::unique_ptr on_response_mruby_file; FILE *http2_upstream_dump_request_header; FILE *http2_upstream_dump_response_header; nghttp2_session_callbacks *http2_upstream_callbacks; diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index 8b493a55..0dc6dcb3 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -149,7 +149,7 @@ void ConnectionHandler::worker_reopen_log_files() { } } -void ConnectionHandler::create_single_worker() { +int ConnectionHandler::create_single_worker() { auto cert_tree = ssl::create_cert_lookup_tree(); auto sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree); auto cl_ssl_ctx = ssl::setup_client_ssl_context(); @@ -160,9 +160,14 @@ void ConnectionHandler::create_single_worker() { single_worker_ = make_unique(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree, ticket_keys_); + if (single_worker_->create_mruby_context() != 0) { + return -1; + } + + return 0; } -void ConnectionHandler::create_worker_thread(size_t num) { +int ConnectionHandler::create_worker_thread(size_t num) { #ifndef NOTHREADS assert(workers_.size() == 0); @@ -179,14 +184,23 @@ void ConnectionHandler::create_worker_thread(size_t num) { auto worker = make_unique(loop, sv_ssl_ctx, cl_ssl_ctx, cert_tree, ticket_keys_); - worker->run_async(); + if (worker->create_mruby_context() != 0) { + return -1; + } + workers_.push_back(std::move(worker)); if (LOG_ENABLED(INFO)) { LLOG(INFO, this) << "Created thread #" << workers_.size() - 1; } } + + for (auto &worker : workers_) { + worker->run_async(); + } #endif // NOTHREADS + + return 0; } void ConnectionHandler::join_worker() { diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h index d46f059c..dc33eb61 100644 --- a/src/shrpx_connection_handler.h +++ b/src/shrpx_connection_handler.h @@ -73,10 +73,10 @@ public: ~ConnectionHandler(); int handle_connection(int fd, sockaddr *addr, int addrlen); // Creates Worker object for single threaded configuration. - void create_single_worker(); + int create_single_worker(); // Creates |num| Worker objects for multi threaded configuration. // The |num| must be strictly more than 1. - void create_worker_thread(size_t num); + int create_worker_thread(size_t num); void set_ticket_keys_to_worker(const std::shared_ptr &ticket_keys); void worker_reopen_log_files(); diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index a37c7f2f..2f55e5d6 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -127,7 +127,7 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, request_http2_expect_body_(false), chunked_response_(false), response_connection_close_(false), response_header_key_prev_(false), response_trailer_key_prev_(false), expect_final_response_(false), - request_pending_(false) { + request_pending_(false), request_headers_dirty_(false) { ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., get_config()->stream_read_timeout); @@ -240,6 +240,8 @@ const Headers &Downstream::get_request_headers() const { return request_headers_; } +Headers &Downstream::get_request_headers() { return request_headers_; } + void Downstream::assemble_request_cookie() { std::string &cookie = assembled_request_cookie_; cookie = ""; @@ -336,6 +338,9 @@ void set_last_header_value(bool &key_prev, size_t &sum, Headers &headers, namespace { int index_headers(http2::HeaderIndex &hdidx, Headers &headers, int64_t &content_length) { + http2::init_hdidx(hdidx); + content_length = -1; + for (size_t i = 0; i < headers.size(); ++i) { auto &kv = headers[i]; util::inp_strlower(kv.name); @@ -1213,4 +1218,12 @@ bool Downstream::can_detach_downstream_connection() const { !response_connection_close_; } +void Downstream::set_request_headers_dirty(bool f) { + request_headers_dirty_ = f; +} + +bool Downstream::get_request_headers_dirty() const { + return request_headers_dirty_; +} + } // namespace shrpx diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index c64aba4f..a12359fa 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -96,6 +96,7 @@ public: const std::string &get_http2_settings() const; // downstream request API const Headers &get_request_headers() const; + Headers &get_request_headers(); // Crumbles (split cookie by ";") in request_headers_ and returns // them. Headers::no_index is inherited. Headers crumble_request_cookie(); @@ -126,6 +127,8 @@ public: void append_last_request_header_value(const char *data, size_t len); // Empties request headers. void clear_request_headers(); + void set_request_headers_dirty(bool f); + bool get_request_headers_dirty() const; size_t get_request_headers_sum() const; @@ -454,6 +457,8 @@ private: // has not been established or should be checked before use; // currently used only with HTTP/2 connection. bool request_pending_; + // true if we need to execute index_request_headers() + bool request_headers_dirty_; }; } // namespace shrpx diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 9659596c..59dc5299 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -37,6 +37,7 @@ #include "shrpx_http.h" #include "shrpx_worker.h" #include "shrpx_http2_session.h" +#include "shrpx_mruby.h" #include "http2.h" #include "util.h" #include "base64.h" @@ -306,6 +307,13 @@ int Http2Upstream::on_request_headers(Downstream *downstream, downstream->set_request_http2_expect_body(true); } + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + mruby_ctx->run_on_request_proc(downstream); + downstream->inspect_http2_request(); downstream->set_request_state(Downstream::HEADER_COMPLETE); diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 40a29593..4da4fed0 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -37,6 +37,7 @@ #include "shrpx_log_config.h" #include "shrpx_worker.h" #include "shrpx_http2_session.h" +#include "shrpx_mruby.h" #include "http2.h" #include "util.h" #include "template.h" @@ -272,6 +273,12 @@ int htp_hdrs_completecb(http_parser *htp) { return -1; } + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + mruby_ctx->run_on_request_proc(downstream); + downstream->inspect_http1_request(); if (downstream->get_request_method() != HTTP_CONNECT) { diff --git a/src/shrpx_mruby.cc b/src/shrpx_mruby.cc new file mode 100644 index 00000000..aa3135dd --- /dev/null +++ b/src/shrpx_mruby.cc @@ -0,0 +1,163 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 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 "shrpx_mruby.h" + +#include +#include + +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_mruby_module.h" +#include "template.h" + +namespace shrpx { + +namespace mruby { + +MRubyContext::MRubyContext(mrb_state *mrb, RProc *on_request_proc, + RProc *on_response_proc) + : mrb_(mrb), on_request_proc_(on_request_proc), + on_response_proc_(on_response_proc) {} + +MRubyContext::~MRubyContext() { mrb_close(mrb_); } + +int MRubyContext::run_on_request_proc(Downstream *downstream) { + if (!on_request_proc_) { + return 0; + } + + mrb_->ud = downstream; + + int rv = 0; + auto ai = mrb_gc_arena_save(mrb_); + + auto res = mrb_run(mrb_, on_request_proc_, mrb_top_self(mrb_)); + (void)res; + + if (mrb_->exc) { + rv = -1; + auto error = + mrb_str_ptr(mrb_funcall(mrb_, mrb_obj_value(mrb_->exc), "inspect", 0)); + + LOG(ERROR) << "Exception caught while executing mruby code: " + << error->as.heap.ptr; + mrb_->exc = 0; + } + + mrb_->ud = nullptr; + + mrb_gc_arena_restore(mrb_, ai); + + if (downstream->get_request_headers_dirty()) { + downstream->set_request_headers_dirty(false); + downstream->index_request_headers(); + } + + return rv; +} + +int run_on_response_proc(Downstream *downstream) { + // TODO not implemented yet + return 0; +} + +// Based on +// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is +// very hard to write these kind of code because mruby has almost no +// documentation aobut compiling or generating code, at least at the +// time of this writing. +RProc *compile(mrb_state *mrb, const char *filename) { + if (filename == nullptr) { + return nullptr; + } + + auto infile = fopen(filename, "rb"); + if (infile == nullptr) { + return nullptr; + } + auto infile_d = defer(fclose, infile); + + auto mrbc = mrbc_context_new(mrb); + if (mrbc == nullptr) { + LOG(ERROR) << "mrb_context_new failed"; + return nullptr; + } + auto mrbc_d = defer(mrbc_context_free, mrb, mrbc); + + auto parser = mrb_parse_file(mrb, infile, nullptr); + if (parser == nullptr) { + LOG(ERROR) << "mrb_parse_nstring failed"; + return nullptr; + } + auto parser_d = defer(mrb_parser_free, parser); + + if (parser->nerr != 0) { + LOG(ERROR) << "mruby parser detected parse error"; + return nullptr; + } + + auto proc = mrb_generate_code(mrb, parser); + if (proc == nullptr) { + LOG(ERROR) << "mrb_generate_code failed"; + return nullptr; + } + + return proc; +} + +std::unique_ptr create_mruby_context() { + auto mrb = mrb_open(); + if (mrb == nullptr) { + LOG(ERROR) << "mrb_open failed"; + return nullptr; + } + + init_module(mrb); + + auto req_file = get_config()->on_request_mruby_file.get(); + auto res_file = get_config()->on_response_mruby_file.get(); + + auto req_proc = compile(mrb, req_file); + + if (req_file && !req_proc) { + LOG(ERROR) << "Could not compile mruby code " << req_file; + mrb_close(mrb); + return nullptr; + } + + auto res_proc = compile(mrb, res_file); + + if (res_file && !res_proc) { + LOG(ERROR) << "Could not compile mruby code " << res_file; + mrb_close(mrb); + return nullptr; + } + + return make_unique(mrb, req_proc, res_proc); +} + +} // namespace mruby + +} // namespace shrpx diff --git a/src/shrpx_mruby.h b/src/shrpx_mruby.h new file mode 100644 index 00000000..6e0bc57e --- /dev/null +++ b/src/shrpx_mruby.h @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 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 SHRPX_MRUBY_H +#define SHRPX_MRUBY_H + +#include "shrpx.h" + +#include + +#include +#include + +using namespace nghttp2; + +namespace shrpx { + +namespace mruby { + +class MRubyContext { +public: + MRubyContext(mrb_state *mrb, RProc *on_request_proc, RProc *on_response_proc); + ~MRubyContext(); + + int run_on_request_proc(Downstream *downstream); + int run_on_response_proc(Downstream *downstream); + +private: + mrb_state *mrb_; + RProc *on_request_proc_; + RProc *on_response_proc_; +}; + +RProc *compile(mrb_state *mrb, const char *filename); + +std::unique_ptr create_mruby_context(); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_H diff --git a/src/shrpx_mruby_module.cc b/src/shrpx_mruby_module.cc new file mode 100644 index 00000000..5b45a9d3 --- /dev/null +++ b/src/shrpx_mruby_module.cc @@ -0,0 +1,193 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 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 "shrpx_mruby_module.h" + +#include +#include + +#include "shrpx_downstream.h" +#include "util.h" + +namespace shrpx { + +namespace mruby { + +namespace { +mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; } +} // namespace + +namespace { +mrb_value request_get_path(mrb_state *mrb, mrb_value self) { + auto downstream = static_cast(mrb->ud); + auto &path = downstream->get_request_path(); + + return mrb_str_new_static(mrb, path.c_str(), path.size()); +} +} // namespace + +namespace { +mrb_value request_set_path(mrb_state *mrb, mrb_value self) { + auto downstream = static_cast(mrb->ud); + + const char *path; + mrb_int pathlen; + mrb_get_args(mrb, "s", &path, &pathlen); + if (pathlen == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "path must not be empty string"); + } + + downstream->set_request_path(std::string(path, pathlen)); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_headers(mrb_state *mrb, mrb_value self) { + auto headers = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "RequestHeaders")); + if (mrb_nil_p(headers)) { + auto module = mrb_module_get(mrb, "Nghttpx"); + auto headers_class = mrb_class_get_under(mrb, module, "RequestHeaders"); + headers = mrb_obj_new(mrb, headers_class, 0, nullptr); + mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "RequestHeaders"), headers); + } + return headers; +} +} // namespace + +namespace { +mrb_value headers_init(mrb_state *mrb, mrb_value self) { return self; } +} // namespace + +namespace { +mrb_value request_headers_get(mrb_state *mrb, mrb_value self) { + auto downstream = static_cast(mrb->ud); + + mrb_value key; + mrb_get_args(mrb, "o", &key); + + key = mrb_funcall(mrb, key, "downcase", 0); + + if (RSTRING_LEN(key) == 0) { + return key; + } + + auto hd = downstream->get_request_header( + std::string(RSTRING_PTR(key), RSTRING_LEN(key))); + + if (hd == nullptr) { + return mrb_nil_value(); + } + + return mrb_str_new_static(mrb, hd->value.c_str(), hd->value.size()); +} +} // namespace + +namespace { +mrb_value request_headers_set(mrb_state *mrb, mrb_value self, bool repl) { + auto downstream = static_cast(mrb->ud); + + mrb_value key, value; + mrb_get_args(mrb, "oo", &key, &value); + + key = mrb_funcall(mrb, key, "downcase", 0); + + if (RSTRING_LEN(key) == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed"); + } + + if (repl) { + for (auto &hd : downstream->get_request_headers()) { + if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key), + RSTRING_LEN(key))) { + hd.name = ""; + } + } + } + + downstream->add_request_header( + std::string(RSTRING_PTR(key), RSTRING_LEN(key)), + std::string(RSTRING_PTR(value), RSTRING_LEN(value))); + + downstream->set_request_headers_dirty(true); + + return key; +} +} // namespace + +namespace { +mrb_value request_headers_set(mrb_state *mrb, mrb_value self) { + return request_headers_set(mrb, self, true); +} +} // namespace + +namespace { +mrb_value request_headers_add(mrb_state *mrb, mrb_value self) { + return request_headers_set(mrb, self, false); +} +} // namespace + +namespace { +void init_headers_class(mrb_state *mrb, RClass *module, const char *name, + mrb_func_t get, mrb_func_t set, mrb_func_t add) { + auto headers_class = + mrb_define_class_under(mrb, module, name, mrb->object_class); + + mrb_define_method(mrb, headers_class, "initialize", headers_init, + MRB_ARGS_NONE()); + mrb_define_method(mrb, headers_class, "get", get, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, headers_class, "set", set, MRB_ARGS_REQ(2)); + mrb_define_method(mrb, headers_class, "add", set, MRB_ARGS_REQ(2)); +} +} // namespace + +namespace { +void init_request_class(mrb_state *mrb, RClass *module) { + auto request_class = + mrb_define_class_under(mrb, module, "Request", mrb->object_class); + + mrb_define_method(mrb, request_class, "initialize", request_init, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "path", request_get_path, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "path=", request_set_path, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, request_class, "headers", request_get_headers, + MRB_ARGS_NONE()); + + init_headers_class(mrb, module, "RequestHeaders", request_headers_get, + request_headers_set, request_headers_add); +} +} // namespace + +void init_module(mrb_state *mrb) { + auto module = mrb_define_module(mrb, "Nghttpx"); + + init_request_class(mrb, module); +} + +} // namespace mruby + +} // namespace shrpx diff --git a/src/shrpx_mruby_module.h b/src/shrpx_mruby_module.h new file mode 100644 index 00000000..bec51934 --- /dev/null +++ b/src/shrpx_mruby_module.h @@ -0,0 +1,44 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 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 SHRPX_MRUBY_MODULE_H +#define SHRPX_MRUBY_MODULE_H + +#include "shrpx.h" + +#include + +using namespace nghttp2; + +namespace shrpx { + +namespace mruby { + +void init_module(mrb_state *mrb); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_H diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 2241f979..006e7e3a 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -37,6 +37,7 @@ #include "shrpx_log_config.h" #include "shrpx_connect_blocker.h" #include "shrpx_memcached_dispatcher.h" +#include "shrpx_mruby.h" #include "util.h" #include "template.h" @@ -265,4 +266,17 @@ MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() { return session_cache_memcached_dispatcher_.get(); } +int Worker::create_mruby_context() { + mruby_ctx_ = mruby::create_mruby_context(); + if (!mruby_ctx_) { + return -1; + } + + return 0; +} + +mruby::MRubyContext *Worker::get_mruby_context() const { + return mruby_ctx_.get(); +} + } // namespace shrpx diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 3613b0d5..38868aab 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -51,6 +51,12 @@ class Http2Session; class ConnectBlocker; class MemcachedDispatcher; +namespace mruby { + +class MRubyContext; + +} // namespace mruby + namespace ssl { class CertLookupTree; } // namespace ssl @@ -124,6 +130,10 @@ public: MemcachedDispatcher *get_session_cache_memcached_dispatcher(); + int create_mruby_context(); + + mruby::MRubyContext *get_mruby_context() const; + private: #ifndef NOTHREADS std::future fut_; @@ -137,6 +147,7 @@ private: WorkerStat worker_stat_; std::vector dgrps_; std::unique_ptr session_cache_memcached_dispatcher_; + std::unique_ptr mruby_ctx_; struct ev_loop *loop_; // Following fields are shared across threads if