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
MRubyContext(mrb_state * mrb,mrb_value app,mrb_value env)40 MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
41 : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
42
~MRubyContext()43 MRubyContext::~MRubyContext() {
44 if (mrb_) {
45 mrb_close(mrb_);
46 }
47 }
48
run_app(Downstream * downstream,int phase)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
run_on_request_proc(Downstream * downstream)102 int MRubyContext::run_on_request_proc(Downstream *downstream) {
103 return run_app(downstream, PHASE_REQUEST);
104 }
105
run_on_response_proc(Downstream * downstream)106 int MRubyContext::run_on_response_proc(Downstream *downstream) {
107 return run_app(downstream, PHASE_RESPONSE);
108 }
109
delete_downstream(Downstream * downstream)110 void MRubyContext::delete_downstream(Downstream *downstream) {
111 if (!mrb_) {
112 return;
113 }
114 delete_downstream_from_module(mrb_, downstream);
115 }
116
117 namespace {
instantiate_app(mrb_state * mrb,RProc * proc)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.
compile(mrb_state * mrb,const StringRef & filename)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
create_mruby_context(const StringRef & filename)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
intern_ptr(mrb_state * mrb,void * ptr)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
check_phase(mrb_state * mrb,int phase,int phase_mask)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
239