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.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-10-06 00:10:42 +09:00
parent c71c357fa6
commit f1eb7638d1
17 changed files with 181 additions and 158 deletions

View File

@ -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",

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,7 @@
Nghttpx.run do |env|
class App
def on_resp(env)
env.resp.set_header "Alpha", "bravo"
end
end
App.new

View File

@ -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

View File

@ -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

View File

@ -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.";

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}