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 }
79
80 auto res = mrb_funcall(mrb_, app_, method, 1, env_);
81 (void)res;
82
83 if (mrb_->exc) {
84 // If response has been committed, ignore error
85 if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
86 rv = -1;
87 }
88
89 auto exc = mrb_obj_value(mrb_->exc);
90 auto inspect = mrb_inspect(mrb_, exc);
91
92 LOG(ERROR) << "Exception caught while executing mruby code: "
93 << mrb_str_to_cstr(mrb_, inspect);
94 }
95
96 mrb_->ud = nullptr;
97
98 return rv;
99 }
100
run_on_request_proc(Downstream * downstream)101 int MRubyContext::run_on_request_proc(Downstream *downstream) {
102 return run_app(downstream, PHASE_REQUEST);
103 }
104
run_on_response_proc(Downstream * downstream)105 int MRubyContext::run_on_response_proc(Downstream *downstream) {
106 return run_app(downstream, PHASE_RESPONSE);
107 }
108
delete_downstream(Downstream * downstream)109 void MRubyContext::delete_downstream(Downstream *downstream) {
110 if (!mrb_) {
111 return;
112 }
113 delete_downstream_from_module(mrb_, downstream);
114 }
115
116 namespace {
instantiate_app(mrb_state * mrb,RProc * proc)117 mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
118 mrb->ud = nullptr;
119
120 auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
121
122 if (mrb->exc) {
123 auto exc = mrb_obj_value(mrb->exc);
124 auto inspect = mrb_inspect(mrb, exc);
125
126 LOG(ERROR) << "Exception caught while executing mruby code: "
127 << mrb_str_to_cstr(mrb, inspect);
128
129 return mrb_nil_value();
130 }
131
132 return res;
133 }
134 } // namespace
135
136 // Based on
137 // https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
138 // very hard to write these kind of code because mruby has almost no
139 // documentation about compiling or generating code, at least at the
140 // time of this writing.
compile(mrb_state * mrb,const StringRef & filename)141 RProc *compile(mrb_state *mrb, const StringRef &filename) {
142 if (filename.empty()) {
143 return nullptr;
144 }
145
146 auto infile = fopen(filename.c_str(), "rb");
147 if (infile == nullptr) {
148 LOG(ERROR) << "Could not open mruby file " << filename;
149 return nullptr;
150 }
151 auto infile_d = defer(fclose, infile);
152
153 auto mrbc = mrbc_context_new(mrb);
154 if (mrbc == nullptr) {
155 LOG(ERROR) << "mrb_context_new failed";
156 return nullptr;
157 }
158 auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
159
160 auto parser = mrb_parse_file(mrb, infile, nullptr);
161 if (parser == nullptr) {
162 LOG(ERROR) << "mrb_parse_nstring failed";
163 return nullptr;
164 }
165 auto parser_d = defer(mrb_parser_free, parser);
166
167 if (parser->nerr != 0) {
168 LOG(ERROR) << "mruby parser detected parse error";
169 return nullptr;
170 }
171
172 auto proc = mrb_generate_code(mrb, parser);
173 if (proc == nullptr) {
174 LOG(ERROR) << "mrb_generate_code failed";
175 return nullptr;
176 }
177
178 return proc;
179 }
180
create_mruby_context(const StringRef & filename)181 std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename) {
182 if (filename.empty()) {
183 return std::make_unique<MRubyContext>(nullptr, mrb_nil_value(),
184 mrb_nil_value());
185 }
186
187 auto mrb = mrb_open();
188 if (mrb == nullptr) {
189 LOG(ERROR) << "mrb_open failed";
190 return nullptr;
191 }
192
193 auto ai = mrb_gc_arena_save(mrb);
194
195 auto req_proc = compile(mrb, filename);
196
197 if (!req_proc) {
198 mrb_gc_arena_restore(mrb, ai);
199 LOG(ERROR) << "Could not compile mruby code " << filename;
200 mrb_close(mrb);
201 return nullptr;
202 }
203
204 auto env = init_module(mrb);
205
206 auto app = instantiate_app(mrb, req_proc);
207 if (mrb_nil_p(app)) {
208 mrb_gc_arena_restore(mrb, ai);
209 LOG(ERROR) << "Could not instantiate mruby app from " << filename;
210 mrb_close(mrb);
211 return nullptr;
212 }
213
214 mrb_gc_arena_restore(mrb, ai);
215
216 // TODO These are not necessary, because we retain app and env?
217 mrb_gc_protect(mrb, env);
218 mrb_gc_protect(mrb, app);
219
220 return std::make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
221 }
222
intern_ptr(mrb_state * mrb,void * ptr)223 mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
224 auto p = reinterpret_cast<uintptr_t>(ptr);
225
226 return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
227 }
228
check_phase(mrb_state * mrb,int phase,int phase_mask)229 void check_phase(mrb_state *mrb, int phase, int phase_mask) {
230 if ((phase & phase_mask) == 0) {
231 mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase");
232 }
233 }
234
235 } // namespace mruby
236
237 } // namespace shrpx
238