2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2015 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 #include "shrpx_mruby.h"
27 #include <mruby/compile.h>
28 #include <mruby/string.h>
30 #include "shrpx_downstream.h"
31 #include "shrpx_config.h"
32 #include "shrpx_mruby_module.h"
33 #include "shrpx_downstream_connection.h"
34 #include "shrpx_log.h"
40 MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
41 : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
43 MRubyContext::~MRubyContext() {
49 int MRubyContext::run_app(Downstream *downstream, int phase) {
54 MRubyAssocData data{downstream, phase};
59 auto ai = mrb_gc_arena_save(mrb_);
60 auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); });
65 if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) {
71 if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) {
81 auto res = mrb_funcall(mrb_, app_, method, 1, env_);
85 // If response has been committed, ignore error
86 if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
90 auto exc = mrb_obj_value(mrb_->exc);
91 auto inspect = mrb_inspect(mrb_, exc);
93 LOG(ERROR) << "Exception caught while executing mruby code: "
94 << mrb_str_to_cstr(mrb_, inspect);
102 int MRubyContext::run_on_request_proc(Downstream *downstream) {
103 return run_app(downstream, PHASE_REQUEST);
106 int MRubyContext::run_on_response_proc(Downstream *downstream) {
107 return run_app(downstream, PHASE_RESPONSE);
110 void MRubyContext::delete_downstream(Downstream *downstream) {
114 delete_downstream_from_module(mrb_, downstream);
118 mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
121 auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
124 auto exc = mrb_obj_value(mrb->exc);
125 auto inspect = mrb_inspect(mrb, exc);
127 LOG(ERROR) << "Exception caught while executing mruby code: "
128 << mrb_str_to_cstr(mrb, inspect);
130 return mrb_nil_value();
138 // https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
139 // very hard to write these kind of code because mruby has almost no
140 // documentation about compiling or generating code, at least at the
141 // time of this writing.
142 RProc *compile(mrb_state *mrb, const StringRef &filename) {
143 if (filename.empty()) {
147 auto infile = fopen(filename.c_str(), "rb");
148 if (infile == nullptr) {
149 LOG(ERROR) << "Could not open mruby file " << filename;
152 auto infile_d = defer(fclose, infile);
154 auto mrbc = mrbc_context_new(mrb);
155 if (mrbc == nullptr) {
156 LOG(ERROR) << "mrb_context_new failed";
159 auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
161 auto parser = mrb_parse_file(mrb, infile, nullptr);
162 if (parser == nullptr) {
163 LOG(ERROR) << "mrb_parse_nstring failed";
166 auto parser_d = defer(mrb_parser_free, parser);
168 if (parser->nerr != 0) {
169 LOG(ERROR) << "mruby parser detected parse error";
173 auto proc = mrb_generate_code(mrb, parser);
174 if (proc == nullptr) {
175 LOG(ERROR) << "mrb_generate_code failed";
182 std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename) {
183 if (filename.empty()) {
184 return std::make_unique<MRubyContext>(nullptr, mrb_nil_value(),
188 auto mrb = mrb_open();
189 if (mrb == nullptr) {
190 LOG(ERROR) << "mrb_open failed";
194 auto ai = mrb_gc_arena_save(mrb);
196 auto req_proc = compile(mrb, filename);
199 mrb_gc_arena_restore(mrb, ai);
200 LOG(ERROR) << "Could not compile mruby code " << filename;
205 auto env = init_module(mrb);
207 auto app = instantiate_app(mrb, req_proc);
208 if (mrb_nil_p(app)) {
209 mrb_gc_arena_restore(mrb, ai);
210 LOG(ERROR) << "Could not instantiate mruby app from " << filename;
215 mrb_gc_arena_restore(mrb, ai);
217 // TODO These are not necessary, because we retain app and env?
218 mrb_gc_protect(mrb, env);
219 mrb_gc_protect(mrb, app);
221 return std::make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
224 mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
225 auto p = reinterpret_cast<uintptr_t>(ptr);
227 return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
230 void check_phase(mrb_state *mrb, int phase, int phase_mask) {
231 if ((phase & phase_mask) == 0) {
232 mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase");