• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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