• 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   }
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