Imported Upstream version 1.46.0
[platform/upstream/nghttp2.git] / src / shrpx_mruby.cc
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2015 Tatsuhiro Tsujikawa
5  *
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:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
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.
24  */
25 #include "shrpx_mruby.h"
26
27 #include <mruby/compile.h>
28 #include <mruby/string.h>
29
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"
35
36 namespace shrpx {
37
38 namespace mruby {
39
40 MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
41     : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
42
43 MRubyContext::~MRubyContext() {
44   if (mrb_) {
45     mrb_close(mrb_);
46   }
47 }
48
49 int MRubyContext::run_app(Downstream *downstream, int phase) {
50   if (!mrb_) {
51     return 0;
52   }
53
54   MRubyAssocData data{downstream, phase};
55
56   mrb_->ud = &data;
57
58   int rv = 0;
59   auto ai = mrb_gc_arena_save(mrb_);
60   auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); });
61
62   const char *method;
63   switch (phase) {
64   case PHASE_REQUEST:
65     if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) {
66       return 0;
67     }
68     method = "on_req";
69     break;
70   case PHASE_RESPONSE:
71     if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) {
72       return 0;
73     }
74     method = "on_resp";
75     break;
76   default:
77     assert(0);
78     abort();
79   }
80
81   auto res = mrb_funcall(mrb_, app_, method, 1, env_);
82   (void)res;
83
84   if (mrb_->exc) {
85     // If response has been committed, ignore error
86     if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
87       rv = -1;
88     }
89
90     auto exc = mrb_obj_value(mrb_->exc);
91     auto inspect = mrb_inspect(mrb_, exc);
92
93     LOG(ERROR) << "Exception caught while executing mruby code: "
94                << mrb_str_to_cstr(mrb_, inspect);
95   }
96
97   mrb_->ud = nullptr;
98
99   return rv;
100 }
101
102 int MRubyContext::run_on_request_proc(Downstream *downstream) {
103   return run_app(downstream, PHASE_REQUEST);
104 }
105
106 int MRubyContext::run_on_response_proc(Downstream *downstream) {
107   return run_app(downstream, PHASE_RESPONSE);
108 }
109
110 void MRubyContext::delete_downstream(Downstream *downstream) {
111   if (!mrb_) {
112     return;
113   }
114   delete_downstream_from_module(mrb_, downstream);
115 }
116
117 namespace {
118 mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
119   mrb->ud = nullptr;
120
121   auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
122
123   if (mrb->exc) {
124     auto exc = mrb_obj_value(mrb->exc);
125     auto inspect = mrb_inspect(mrb, exc);
126
127     LOG(ERROR) << "Exception caught while executing mruby code: "
128                << mrb_str_to_cstr(mrb, inspect);
129
130     return mrb_nil_value();
131   }
132
133   return res;
134 }
135 } // namespace
136
137 // Based on
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()) {
144     return nullptr;
145   }
146
147   auto infile = fopen(filename.c_str(), "rb");
148   if (infile == nullptr) {
149     LOG(ERROR) << "Could not open mruby file " << filename;
150     return nullptr;
151   }
152   auto infile_d = defer(fclose, infile);
153
154   auto mrbc = mrbc_context_new(mrb);
155   if (mrbc == nullptr) {
156     LOG(ERROR) << "mrb_context_new failed";
157     return nullptr;
158   }
159   auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
160
161   auto parser = mrb_parse_file(mrb, infile, nullptr);
162   if (parser == nullptr) {
163     LOG(ERROR) << "mrb_parse_nstring failed";
164     return nullptr;
165   }
166   auto parser_d = defer(mrb_parser_free, parser);
167
168   if (parser->nerr != 0) {
169     LOG(ERROR) << "mruby parser detected parse error";
170     return nullptr;
171   }
172
173   auto proc = mrb_generate_code(mrb, parser);
174   if (proc == nullptr) {
175     LOG(ERROR) << "mrb_generate_code failed";
176     return nullptr;
177   }
178
179   return proc;
180 }
181
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(),
185                                           mrb_nil_value());
186   }
187
188   auto mrb = mrb_open();
189   if (mrb == nullptr) {
190     LOG(ERROR) << "mrb_open failed";
191     return nullptr;
192   }
193
194   auto ai = mrb_gc_arena_save(mrb);
195
196   auto req_proc = compile(mrb, filename);
197
198   if (!req_proc) {
199     mrb_gc_arena_restore(mrb, ai);
200     LOG(ERROR) << "Could not compile mruby code " << filename;
201     mrb_close(mrb);
202     return nullptr;
203   }
204
205   auto env = init_module(mrb);
206
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;
211     mrb_close(mrb);
212     return nullptr;
213   }
214
215   mrb_gc_arena_restore(mrb, ai);
216
217   // TODO These are not necessary, because we retain app and env?
218   mrb_gc_protect(mrb, env);
219   mrb_gc_protect(mrb, app);
220
221   return std::make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
222 }
223
224 mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
225   auto p = reinterpret_cast<uintptr_t>(ptr);
226
227   return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
228 }
229
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");
233   }
234 }
235
236 } // namespace mruby
237
238 } // namespace shrpx