Merge branch 'nghttpx-mruby'
This commit is contained in:
commit
8e6b92bdd7
|
@ -185,23 +185,20 @@ server. These hooks allows users to modify header fields, or common
|
|||
HTTP variables, like authority or request path, and even return custom
|
||||
response without forwarding request to backend servers.
|
||||
|
||||
To set request phase hook, use :option:`--request-phase-file` option.
|
||||
To set response phase hook, use :option:`--response-phase-file`
|
||||
option.
|
||||
|
||||
For request and response phase hook, user calls :rb:meth:`Nghttpx.run`
|
||||
with block. The :rb:class:`Nghttpx::Env` is passed to the block.
|
||||
User can can access :rb:class:`Nghttpx::Request` and
|
||||
:rb:class:`Nghttpx::Response` objects via :rb:attr:`Nghttpx::Env#req`
|
||||
and :rb:attr:`Nghttpx::Env#resp` respectively.
|
||||
To specify mruby script file, use :option:`--mruby-file` option. The
|
||||
script will be evaluated once per thread on startup, and it must
|
||||
instantiate object and evaluate it as the return value (e.g.,
|
||||
``App.new``). This object is called app object. If app object
|
||||
defines ``on_req`` method, it is called with :rb:class:`Nghttpx::Env`
|
||||
object on request hook. Similarly, if app object defines ``on_resp``
|
||||
method, it is called with :rb:class:`Nghttpx::Env` object on response
|
||||
hook. For each method invocation, user can can access
|
||||
:rb:class:`Nghttpx::Request` and :rb:class:`Nghttpx::Response` objects
|
||||
via :rb:attr:`Nghttpx::Env#req` and :rb:attr:`Nghttpx::Env#resp`
|
||||
respectively.
|
||||
|
||||
.. rb:module:: Nghttpx
|
||||
|
||||
.. rb:classmethod:: run(&block)
|
||||
|
||||
Run request or response phase hook with given *block*.
|
||||
:rb:class:`Nghttpx::Env` object is passed to the given block.
|
||||
|
||||
.. rb:const:: REQUEST_PHASE
|
||||
|
||||
Constant to represent request phase.
|
||||
|
@ -379,19 +376,23 @@ Modify requet path:
|
|||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
class App
|
||||
def on_req(env)
|
||||
env.req.path = "/apps#{env.req.path}"
|
||||
end
|
||||
end
|
||||
|
||||
Note that the file containing the above script must be set with
|
||||
:option:`--request-phase-file` option since we modify request path.
|
||||
App.new
|
||||
|
||||
Don't forget to instantiate and evaluate object at the last line.
|
||||
|
||||
Restrict permission of viewing a content to a specific client
|
||||
addresses:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
class App
|
||||
def on_req(env)
|
||||
allowed_clients = ["127.0.0.1", "::1"]
|
||||
|
||||
if env.req.path.start_with?("/log/") &&
|
||||
|
@ -400,6 +401,9 @@ addresses:
|
|||
env.resp.return "permission denied"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
App.new
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
|
|
@ -98,8 +98,7 @@ OPTIONS = [
|
|||
"tls-ticket-key-memcached-interval",
|
||||
"tls-ticket-key-memcached-max-retry",
|
||||
"tls-ticket-key-memcached-max-fail",
|
||||
"request-phase-file",
|
||||
"response-phase-file",
|
||||
"mruby-file",
|
||||
"accept-proxy-protocol",
|
||||
"conf",
|
||||
"fastopen",
|
||||
|
|
|
@ -360,7 +360,7 @@ func TestH1H1Websocket(t *testing.T) {
|
|||
// TestH1H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestH1H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ func TestH1H1ReqPhaseSetHeader(t *testing.T) {
|
|||
// TestH1H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH1H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -401,7 +401,7 @@ func TestH1H1ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -410,7 +410,7 @@ func TestH1H1ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ func TestH1H1ReqPhaseReturn(t *testing.T) {
|
|||
// TestH1H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestH1H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
|
@ -440,7 +440,7 @@ func TestH1H1RespPhaseSetHeader(t *testing.T) {
|
|||
// TestH1H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH1H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
|
@ -457,7 +457,7 @@ func TestH1H1RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -466,7 +466,7 @@ func TestH1H1RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -667,7 +667,7 @@ func TestH1H2NoVia(t *testing.T) {
|
|||
// TestH1H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH1H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -686,7 +686,7 @@ func TestH1H2ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -695,7 +695,7 @@ func TestH1H2ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -703,7 +703,7 @@ func TestH1H2ReqPhaseReturn(t *testing.T) {
|
|||
// TestH1H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH1H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--http2-bridge", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
|
@ -720,7 +720,7 @@ func TestH1H2RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -729,7 +729,7 @@ func TestH1H2RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -644,7 +644,7 @@ func TestH2H1HeaderFields(t *testing.T) {
|
|||
// TestH2H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestH2H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
|
@ -666,7 +666,7 @@ func TestH2H1ReqPhaseSetHeader(t *testing.T) {
|
|||
// TestH2H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH2H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -685,7 +685,7 @@ func TestH2H1ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -694,7 +694,7 @@ func TestH2H1ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +702,7 @@ func TestH2H1ReqPhaseReturn(t *testing.T) {
|
|||
// TestH2H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestH2H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
|
@ -724,7 +724,7 @@ func TestH2H1RespPhaseSetHeader(t *testing.T) {
|
|||
// TestH2H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH2H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
|
@ -741,7 +741,7 @@ func TestH2H1RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -750,7 +750,7 @@ func TestH2H1RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -1351,7 +1351,7 @@ func TestH2H2TLSXfp(t *testing.T) {
|
|||
// TestH2H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH2H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -1370,7 +1370,7 @@ func TestH2H2ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -1379,7 +1379,7 @@ func TestH2H2ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -1387,7 +1387,7 @@ func TestH2H2ReqPhaseReturn(t *testing.T) {
|
|||
// TestH2H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH2H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTester([]string{"--http2-bridge", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
|
@ -1404,7 +1404,7 @@ func TestH2H2RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -1413,7 +1413,7 @@ func TestH2H2RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,7 +233,7 @@ func TestS3H1InvalidMethod(t *testing.T) {
|
|||
// TestS3H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestS3H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ func TestS3H1ReqPhaseSetHeader(t *testing.T) {
|
|||
// TestS3H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestS3H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -274,7 +274,7 @@ func TestS3H1ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -283,7 +283,7 @@ func TestS3H1ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ func TestS3H1ReqPhaseReturn(t *testing.T) {
|
|||
// TestS3H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestS3H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
|
@ -313,7 +313,7 @@ func TestS3H1RespPhaseSetHeader(t *testing.T) {
|
|||
// TestS3H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestS3H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
|
@ -330,7 +330,7 @@ func TestS3H1RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -339,7 +339,7 @@ func TestS3H1RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ func TestS3H2ConnectFailure(t *testing.T) {
|
|||
// TestS3H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestS3H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
@ -387,7 +387,7 @@ func TestS3H2ReqPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "20"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -396,7 +396,7 @@ func TestS3H2ReqPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from req"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ func TestS3H2ReqPhaseReturn(t *testing.T) {
|
|||
// TestS3H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestS3H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
|
@ -421,7 +421,7 @@ func TestS3H2RespPhaseReturn(t *testing.T) {
|
|||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"content-length", "21"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
|
@ -430,7 +430,7 @@ func TestS3H2RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
if got, want := string(res.body), "Hello World from resp"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class App
|
||||
def on_req(env)
|
||||
resp = env.resp
|
||||
|
||||
resp.clear_headers
|
||||
resp.status = 404
|
||||
resp.add_header "from", "mruby"
|
||||
resp.return "Hello World from req"
|
||||
end
|
||||
end
|
||||
|
||||
App.new
|
|
@ -1,3 +1,7 @@
|
|||
Nghttpx.run do |env|
|
||||
class App
|
||||
def on_req(env)
|
||||
env.req.set_header "User-Agent", "mruby"
|
||||
end
|
||||
end
|
||||
|
||||
App.new
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class App
|
||||
def on_resp(env)
|
||||
resp = env.resp
|
||||
|
||||
resp.clear_headers
|
||||
resp.status = 404
|
||||
resp.add_header "from", "mruby"
|
||||
resp.return "Hello World from resp"
|
||||
end
|
||||
end
|
||||
|
||||
App.new
|
|
@ -1,3 +1,7 @@
|
|||
Nghttpx.run do |env|
|
||||
class App
|
||||
def on_resp(env)
|
||||
env.resp.set_header "Alpha", "bravo"
|
||||
end
|
||||
end
|
||||
|
||||
App.new
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
Nghttpx.run do |env|
|
||||
resp = env.resp
|
||||
|
||||
resp.clear_headers
|
||||
resp.status = 404
|
||||
resp.add_header "from", "mruby"
|
||||
resp.return "Hello World"
|
||||
end
|
11
src/shrpx.cc
11
src/shrpx.cc
|
@ -1801,8 +1801,7 @@ int main(int argc, char **argv) {
|
|||
89},
|
||||
{SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, required_argument, &flag,
|
||||
90},
|
||||
{SHRPX_OPT_REQUEST_PHASE_FILE, required_argument, &flag, 91},
|
||||
{SHRPX_OPT_RESPONSE_PHASE_FILE, required_argument, &flag, 92},
|
||||
{SHRPX_OPT_MRUBY_FILE, required_argument, &flag, 91},
|
||||
{SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, no_argument, &flag, 93},
|
||||
{SHRPX_OPT_FASTOPEN, required_argument, &flag, 94},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
@ -2199,12 +2198,8 @@ int main(int argc, char **argv) {
|
|||
optarg);
|
||||
break;
|
||||
case 91:
|
||||
// --request-phase-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_PHASE_FILE, optarg);
|
||||
break;
|
||||
case 92:
|
||||
// --response-phase-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_PHASE_FILE, optarg);
|
||||
// --mruby-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, optarg);
|
||||
break;
|
||||
case 93:
|
||||
// --accept-proxy-protocol
|
||||
|
|
|
@ -672,6 +672,7 @@ enum {
|
|||
SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
|
||||
SHRPX_OPTID_LOG_LEVEL,
|
||||
SHRPX_OPTID_MAX_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MRUBY_FILE,
|
||||
SHRPX_OPTID_NO_HOST_REWRITE,
|
||||
SHRPX_OPTID_NO_LOCATION_REWRITE,
|
||||
SHRPX_OPTID_NO_OCSP,
|
||||
|
@ -685,8 +686,6 @@ enum {
|
|||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||
SHRPX_OPTID_READ_BURST,
|
||||
SHRPX_OPTID_READ_RATE,
|
||||
SHRPX_OPTID_REQUEST_PHASE_FILE,
|
||||
SHRPX_OPTID_RESPONSE_PHASE_FILE,
|
||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
||||
|
@ -844,6 +843,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
case 10:
|
||||
switch (name[9]) {
|
||||
case 'e':
|
||||
if (util::strieq_l("mruby-fil", name, 9)) {
|
||||
return SHRPX_OPTID_MRUBY_FILE;
|
||||
}
|
||||
if (util::strieq_l("write-rat", name, 9)) {
|
||||
return SHRPX_OPTID_WRITE_RATE;
|
||||
}
|
||||
|
@ -1013,11 +1015,6 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
break;
|
||||
case 18:
|
||||
switch (name[17]) {
|
||||
case 'e':
|
||||
if (util::strieq_l("request-phase-fil", name, 17)) {
|
||||
return SHRPX_OPTID_REQUEST_PHASE_FILE;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("add-request-heade", name, 17)) {
|
||||
return SHRPX_OPTID_ADD_REQUEST_HEADER;
|
||||
|
@ -1036,9 +1033,6 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
||||
return SHRPX_OPTID_NO_LOCATION_REWRITE;
|
||||
}
|
||||
if (util::strieq_l("response-phase-fil", name, 18)) {
|
||||
return SHRPX_OPTID_RESPONSE_PHASE_FILE;
|
||||
}
|
||||
if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
|
||||
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
||||
}
|
||||
|
@ -1967,17 +1961,9 @@ int parse_config(const char *opt, const char *optarg,
|
|||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
|
||||
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_REQUEST_PHASE_FILE:
|
||||
case SHRPX_OPTID_MRUBY_FILE:
|
||||
#ifdef HAVE_MRUBY
|
||||
mod_config()->request_phase_file = strcopy(optarg);
|
||||
#else // !HAVE_MRUBY
|
||||
LOG(WARN) << opt
|
||||
<< ": ignored because mruby support is disabled at build time.";
|
||||
#endif // !HAVE_MRUBY
|
||||
return 0;
|
||||
case SHRPX_OPTID_RESPONSE_PHASE_FILE:
|
||||
#ifdef HAVE_MRUBY
|
||||
mod_config()->response_phase_file = strcopy(optarg);
|
||||
mod_config()->mruby_file = strcopy(optarg);
|
||||
#else // !HAVE_MRUBY
|
||||
LOG(WARN) << opt
|
||||
<< ": ignored because mruby support is disabled at build time.";
|
||||
|
|
|
@ -184,8 +184,7 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY[] =
|
|||
"tls-ticket-key-memcached-max-retry";
|
||||
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] =
|
||||
"tls-ticket-key-memcached-max-fail";
|
||||
constexpr char SHRPX_OPT_REQUEST_PHASE_FILE[] = "request-phase-file";
|
||||
constexpr char SHRPX_OPT_RESPONSE_PHASE_FILE[] = "response-phase-file";
|
||||
constexpr char SHRPX_OPT_MRUBY_FILE[] = "mruby-file";
|
||||
constexpr char SHRPX_OPT_ACCEPT_PROXY_PROTOCOL[] = "accept-proxy-protocol";
|
||||
constexpr char SHRPX_OPT_FASTOPEN[] = "fastopen";
|
||||
|
||||
|
@ -325,8 +324,7 @@ struct Config {
|
|||
std::unique_ptr<char[]> user;
|
||||
std::unique_ptr<char[]> session_cache_memcached_host;
|
||||
std::unique_ptr<char[]> tls_ticket_key_memcached_host;
|
||||
std::unique_ptr<char[]> request_phase_file;
|
||||
std::unique_ptr<char[]> response_phase_file;
|
||||
std::unique_ptr<char[]> mruby_file;
|
||||
FILE *http2_upstream_dump_request_header;
|
||||
FILE *http2_upstream_dump_response_header;
|
||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||
|
|
|
@ -37,10 +37,8 @@ namespace shrpx {
|
|||
|
||||
namespace mruby {
|
||||
|
||||
MRubyContext::MRubyContext(mrb_state *mrb, RProc *on_request_proc,
|
||||
RProc *on_response_proc)
|
||||
: mrb_(mrb), on_request_proc_(on_request_proc),
|
||||
on_response_proc_(on_response_proc), running_(false) {}
|
||||
MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
|
||||
: mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
|
||||
|
||||
MRubyContext::~MRubyContext() {
|
||||
if (mrb_) {
|
||||
|
@ -48,22 +46,38 @@ MRubyContext::~MRubyContext() {
|
|||
}
|
||||
}
|
||||
|
||||
int MRubyContext::run_request_proc(Downstream *downstream, RProc *proc,
|
||||
int phase) {
|
||||
if (!proc || running_) {
|
||||
int MRubyContext::run_app(Downstream *downstream, int phase) {
|
||||
if (!mrb_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
|
||||
MRubyAssocData data{downstream, phase};
|
||||
|
||||
mrb_->ud = &data;
|
||||
|
||||
int rv = 0;
|
||||
auto ai = mrb_gc_arena_save(mrb_);
|
||||
auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); });
|
||||
|
||||
auto res = mrb_run(mrb_, proc, mrb_top_self(mrb_));
|
||||
const char *method;
|
||||
switch (phase) {
|
||||
case PHASE_REQUEST:
|
||||
if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) {
|
||||
return 0;
|
||||
}
|
||||
method = "on_req";
|
||||
break;
|
||||
case PHASE_RESPONSE:
|
||||
if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) {
|
||||
return 0;
|
||||
}
|
||||
method = "on_resp";
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
auto res = mrb_funcall(mrb_, app_, method, 1, env_);
|
||||
(void)res;
|
||||
|
||||
if (mrb_->exc) {
|
||||
|
@ -71,18 +85,16 @@ int MRubyContext::run_request_proc(Downstream *downstream, RProc *proc,
|
|||
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||
rv = -1;
|
||||
}
|
||||
auto error =
|
||||
mrb_str_ptr(mrb_funcall(mrb_, mrb_obj_value(mrb_->exc), "inspect", 0));
|
||||
|
||||
auto exc = mrb_obj_value(mrb_->exc);
|
||||
auto inspect = mrb_inspect(mrb_, exc);
|
||||
|
||||
LOG(ERROR) << "Exception caught while executing mruby code: "
|
||||
<< error->as.heap.ptr;
|
||||
mrb_->exc = 0;
|
||||
<< mrb_str_to_cstr(mrb_, inspect);
|
||||
}
|
||||
|
||||
mrb_->ud = nullptr;
|
||||
|
||||
mrb_gc_arena_restore(mrb_, ai);
|
||||
|
||||
if (data.request_headers_dirty) {
|
||||
downstream->index_request_headers();
|
||||
}
|
||||
|
@ -91,17 +103,15 @@ int MRubyContext::run_request_proc(Downstream *downstream, RProc *proc,
|
|||
downstream->index_response_headers();
|
||||
}
|
||||
|
||||
running_ = false;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int MRubyContext::run_on_request_proc(Downstream *downstream) {
|
||||
return run_request_proc(downstream, on_request_proc_, PHASE_REQUEST);
|
||||
return run_app(downstream, PHASE_REQUEST);
|
||||
}
|
||||
|
||||
int MRubyContext::run_on_response_proc(Downstream *downstream) {
|
||||
return run_request_proc(downstream, on_response_proc_, PHASE_RESPONSE);
|
||||
return run_app(downstream, PHASE_RESPONSE);
|
||||
}
|
||||
|
||||
void MRubyContext::delete_downstream(Downstream *downstream) {
|
||||
|
@ -111,6 +121,26 @@ void MRubyContext::delete_downstream(Downstream *downstream) {
|
|||
delete_downstream_from_module(mrb_, downstream);
|
||||
}
|
||||
|
||||
namespace {
|
||||
mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
|
||||
mrb->ud = nullptr;
|
||||
|
||||
auto res = mrb_run(mrb, proc, mrb_top_self(mrb));
|
||||
|
||||
if (mrb->exc) {
|
||||
auto exc = mrb_obj_value(mrb->exc);
|
||||
auto inspect = mrb_inspect(mrb, exc);
|
||||
|
||||
LOG(ERROR) << "Exception caught while executing mruby code: "
|
||||
<< mrb_str_to_cstr(mrb, inspect);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Based on
|
||||
// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
|
||||
// very hard to write these kind of code because mruby has almost no
|
||||
|
@ -155,12 +185,9 @@ RProc *compile(mrb_state *mrb, const char *filename) {
|
|||
return proc;
|
||||
}
|
||||
|
||||
std::unique_ptr<MRubyContext> create_mruby_context() {
|
||||
auto req_file = get_config()->request_phase_file.get();
|
||||
auto res_file = get_config()->response_phase_file.get();
|
||||
|
||||
if (!req_file && !res_file) {
|
||||
return make_unique<MRubyContext>(nullptr, nullptr, nullptr);
|
||||
std::unique_ptr<MRubyContext> create_mruby_context(const char *filename) {
|
||||
if (!filename) {
|
||||
return make_unique<MRubyContext>(nullptr, mrb_nil_value(), mrb_nil_value());
|
||||
}
|
||||
|
||||
auto mrb = mrb_open();
|
||||
|
@ -169,25 +196,34 @@ std::unique_ptr<MRubyContext> create_mruby_context() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
init_module(mrb);
|
||||
auto ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
auto req_proc = compile(mrb, req_file);
|
||||
auto req_proc = compile(mrb, filename);
|
||||
|
||||
if (req_file && !req_proc) {
|
||||
LOG(ERROR) << "Could not compile mruby code " << req_file;
|
||||
if (!req_proc) {
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
LOG(ERROR) << "Could not compile mruby code " << filename;
|
||||
mrb_close(mrb);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto res_proc = compile(mrb, res_file);
|
||||
auto env = init_module(mrb);
|
||||
|
||||
if (res_file && !res_proc) {
|
||||
LOG(ERROR) << "Could not compile mruby code " << res_file;
|
||||
auto app = instantiate_app(mrb, req_proc);
|
||||
if (mrb_nil_p(app)) {
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
LOG(ERROR) << "Could not instantiate mruby app from " << filename;
|
||||
mrb_close(mrb);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_unique<MRubyContext>(mrb, req_proc, res_proc);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
|
||||
// TODO These are not necessary, because we retain app and env?
|
||||
mrb_gc_protect(mrb, env);
|
||||
mrb_gc_protect(mrb, app);
|
||||
|
||||
return make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
|
||||
}
|
||||
|
||||
mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
|
||||
|
|
|
@ -40,21 +40,20 @@ namespace mruby {
|
|||
|
||||
class MRubyContext {
|
||||
public:
|
||||
MRubyContext(mrb_state *mrb, RProc *on_request_proc, RProc *on_response_proc);
|
||||
MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env);
|
||||
~MRubyContext();
|
||||
|
||||
int run_on_request_proc(Downstream *downstream);
|
||||
int run_on_response_proc(Downstream *downstream);
|
||||
|
||||
int run_request_proc(Downstream *downstream, RProc *proc, int phase);
|
||||
int run_app(Downstream *downstream, int phase);
|
||||
|
||||
void delete_downstream(Downstream *downstream);
|
||||
|
||||
private:
|
||||
mrb_state *mrb_;
|
||||
RProc *on_request_proc_;
|
||||
RProc *on_response_proc_;
|
||||
bool running_;
|
||||
mrb_value app_;
|
||||
mrb_value env_;
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -72,7 +71,7 @@ struct MRubyAssocData {
|
|||
|
||||
RProc *compile(mrb_state *mrb, const char *filename);
|
||||
|
||||
std::unique_ptr<MRubyContext> create_mruby_context();
|
||||
std::unique_ptr<MRubyContext> create_mruby_context(const char *filename);
|
||||
|
||||
// Return interned |ptr|.
|
||||
mrb_sym intern_ptr(mrb_state *mrb, void *ptr);
|
||||
|
|
|
@ -41,36 +41,21 @@ namespace shrpx {
|
|||
namespace mruby {
|
||||
|
||||
namespace {
|
||||
mrb_value run(mrb_state *mrb, mrb_value self) {
|
||||
mrb_value b;
|
||||
mrb_get_args(mrb, "&", &b);
|
||||
|
||||
if (mrb_nil_p(b)) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value create_env(mrb_state *mrb) {
|
||||
auto module = mrb_module_get(mrb, "Nghttpx");
|
||||
|
||||
auto env_sym = mrb_intern_lit(mrb, "env");
|
||||
auto env = mrb_obj_iv_get(mrb, reinterpret_cast<RObject *>(module), env_sym);
|
||||
|
||||
if (mrb_nil_p(env)) {
|
||||
auto env_class = mrb_class_get_under(mrb, module, "Env");
|
||||
auto request_class = mrb_class_get_under(mrb, module, "Request");
|
||||
auto response_class = mrb_class_get_under(mrb, module, "Response");
|
||||
|
||||
env = mrb_obj_new(mrb, env_class, 0, nullptr);
|
||||
auto env = mrb_obj_new(mrb, env_class, 0, nullptr);
|
||||
auto req = mrb_obj_new(mrb, request_class, 0, nullptr);
|
||||
auto resp = mrb_obj_new(mrb, response_class, 0, nullptr);
|
||||
|
||||
mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "req"), req);
|
||||
mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "resp"), resp);
|
||||
|
||||
mrb_obj_iv_set(mrb, reinterpret_cast<RObject *>(module), env_sym, env);
|
||||
}
|
||||
|
||||
std::array<mrb_value, 1> args{{env}};
|
||||
return mrb_yield_argv(mrb, b, args.size(), args.data());
|
||||
return env;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -85,11 +70,9 @@ void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream) {
|
|||
mrb_iv_remove(mrb, env, intern_ptr(mrb, downstream));
|
||||
}
|
||||
|
||||
void init_module(mrb_state *mrb) {
|
||||
mrb_value init_module(mrb_state *mrb) {
|
||||
auto module = mrb_define_module(mrb, "Nghttpx");
|
||||
|
||||
mrb_define_class_method(mrb, module, "run", run,
|
||||
MRB_ARGS_REQ(1) | MRB_ARGS_BLOCK());
|
||||
mrb_define_const(mrb, module, "REQUEST_PHASE",
|
||||
mrb_fixnum_value(PHASE_REQUEST));
|
||||
mrb_define_const(mrb, module, "RESPONSE_PHASE",
|
||||
|
@ -98,6 +81,8 @@ void init_module(mrb_state *mrb) {
|
|||
init_env_class(mrb, module);
|
||||
init_request_class(mrb, module);
|
||||
init_response_class(mrb, module);
|
||||
|
||||
return create_env(mrb);
|
||||
}
|
||||
|
||||
mrb_value create_headers_hash(mrb_state *mrb, const Headers &headers) {
|
||||
|
|
|
@ -39,7 +39,7 @@ class Downstream;
|
|||
|
||||
namespace mruby {
|
||||
|
||||
void init_module(mrb_state *mrb);
|
||||
mrb_value init_module(mrb_state *mrb);
|
||||
|
||||
void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream);
|
||||
|
||||
|
|
|
@ -270,7 +270,8 @@ MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
|
|||
|
||||
#ifdef HAVE_MRUBY
|
||||
int Worker::create_mruby_context() {
|
||||
mruby_ctx_ = mruby::create_mruby_context();
|
||||
auto mruby_file = get_config()->mruby_file.get();
|
||||
mruby_ctx_ = mruby::create_mruby_context(mruby_file);
|
||||
if (!mruby_ctx_) {
|
||||
return -1;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue