From f1eb7638d15b82e879b9d0dd203074e64c3c189f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 6 Oct 2015 00:10:42 +0900 Subject: [PATCH] nghttpx: Change mruby script handling This commit changes nghttpx's mruby script handling. Previously we have 2 options to specify the mruby script file to be run on request and on response. Now they are merged into 1 option, namely --mruby-file. It now must return object. On request, the object's on_req(env) method is invoked with env object. Similarly, on response, the object's on_resp(env) method is invoked. The specification of Env object has not changed. --- gennghttpxfun.py | 3 +- integration-tests/nghttpx_http1_test.go | 28 +++---- integration-tests/nghttpx_http2_test.go | 28 +++---- integration-tests/nghttpx_spdy_test.go | 28 +++---- integration-tests/req-return.rb | 12 +++ integration-tests/req-set-header.rb | 8 +- integration-tests/resp-return.rb | 12 +++ integration-tests/resp-set-header.rb | 8 +- integration-tests/return.rb | 8 -- src/shrpx.cc | 11 +-- src/shrpx_config.cc | 26 ++---- src/shrpx_config.h | 6 +- src/shrpx_mruby.cc | 104 ++++++++++++++++-------- src/shrpx_mruby.h | 11 ++- src/shrpx_mruby_module.cc | 41 +++------- src/shrpx_mruby_module.h | 2 +- src/shrpx_worker.cc | 3 +- 17 files changed, 181 insertions(+), 158 deletions(-) create mode 100644 integration-tests/req-return.rb create mode 100644 integration-tests/resp-return.rb delete mode 100644 integration-tests/return.rb diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 90a4ffbb..06e859b6 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -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", diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go index 03524bb6..e10f0d2d 100644 --- a/integration-tests/nghttpx_http1_test.go +++ b/integration-tests/nghttpx_http1_test.go @@ -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) } } diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index 52eebf91..6d027659 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -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) } } diff --git a/integration-tests/nghttpx_spdy_test.go b/integration-tests/nghttpx_spdy_test.go index e0447fad..5f92887f 100644 --- a/integration-tests/nghttpx_spdy_test.go +++ b/integration-tests/nghttpx_spdy_test.go @@ -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) } } diff --git a/integration-tests/req-return.rb b/integration-tests/req-return.rb new file mode 100644 index 00000000..51d315ef --- /dev/null +++ b/integration-tests/req-return.rb @@ -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 diff --git a/integration-tests/req-set-header.rb b/integration-tests/req-set-header.rb index 27e9dc34..986f128a 100644 --- a/integration-tests/req-set-header.rb +++ b/integration-tests/req-set-header.rb @@ -1,3 +1,7 @@ -Nghttpx.run do |env| - env.req.set_header "User-Agent", "mruby" +class App + def on_req(env) + env.req.set_header "User-Agent", "mruby" + end end + +App.new diff --git a/integration-tests/resp-return.rb b/integration-tests/resp-return.rb new file mode 100644 index 00000000..fbbd775c --- /dev/null +++ b/integration-tests/resp-return.rb @@ -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 diff --git a/integration-tests/resp-set-header.rb b/integration-tests/resp-set-header.rb index b947ce3b..228837a0 100644 --- a/integration-tests/resp-set-header.rb +++ b/integration-tests/resp-set-header.rb @@ -1,3 +1,7 @@ -Nghttpx.run do |env| - env.resp.set_header "Alpha", "bravo" +class App + def on_resp(env) + env.resp.set_header "Alpha", "bravo" + end end + +App.new diff --git a/integration-tests/return.rb b/integration-tests/return.rb deleted file mode 100644 index 907d837f..00000000 --- a/integration-tests/return.rb +++ /dev/null @@ -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 diff --git a/src/shrpx.cc b/src/shrpx.cc index 5935142e..1010a0f8 100644 --- a/src/shrpx.cc +++ b/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 diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index d1d7bd52..eacc48b9 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -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."; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 8290b269..98fce3a9 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -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 user; std::unique_ptr session_cache_memcached_host; std::unique_ptr tls_ticket_key_memcached_host; - std::unique_ptr request_phase_file; - std::unique_ptr response_phase_file; + std::unique_ptr mruby_file; FILE *http2_upstream_dump_request_header; FILE *http2_upstream_dump_response_header; nghttp2_session_callbacks *http2_upstream_callbacks; diff --git a/src/shrpx_mruby.cc b/src/shrpx_mruby.cc index 974f0004..7c6f5d40 100644 --- a/src/shrpx_mruby.cc +++ b/src/shrpx_mruby.cc @@ -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 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(nullptr, nullptr, nullptr); +std::unique_ptr create_mruby_context(const char *filename) { + if (!filename) { + return make_unique(nullptr, mrb_nil_value(), mrb_nil_value()); } auto mrb = mrb_open(); @@ -169,25 +196,34 @@ std::unique_ptr 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(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(mrb, std::move(app), std::move(env)); } mrb_sym intern_ptr(mrb_state *mrb, void *ptr) { diff --git a/src/shrpx_mruby.h b/src/shrpx_mruby.h index a75e4975..95f0430c 100644 --- a/src/shrpx_mruby.h +++ b/src/shrpx_mruby.h @@ -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 create_mruby_context(); +std::unique_ptr create_mruby_context(const char *filename); // Return interned |ptr|. mrb_sym intern_ptr(mrb_state *mrb, void *ptr); diff --git a/src/shrpx_mruby_module.cc b/src/shrpx_mruby_module.cc index e5e397ee..e2f17fb1 100644 --- a/src/shrpx_mruby_module.cc +++ b/src/shrpx_mruby_module.cc @@ -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(module), env_sym); + 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"); - 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"); + 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); - 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_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(module), env_sym, env); - } - - std::array 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) { diff --git a/src/shrpx_mruby_module.h b/src/shrpx_mruby_module.h index 8d5274e2..3e417f88 100644 --- a/src/shrpx_mruby_module.h +++ b/src/shrpx_mruby_module.h @@ -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); diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index c116cec0..665a3d45 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -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; }