Merge branch 'nghttpx-mruby'

This commit is contained in:
Tatsuhiro Tsujikawa 2015-10-08 23:32:42 +09:00
commit 8e6b92bdd7
18 changed files with 209 additions and 182 deletions

View File

@ -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,28 +376,35 @@ Modify requet path:
.. code-block:: ruby
Nghttpx.run do |env|
env.req.path = "/apps#{env.req.path}"
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|
allowed_clients = ["127.0.0.1", "::1"]
class App
def on_req(env)
allowed_clients = ["127.0.0.1", "::1"]
if env.req.path.start_with?("/log/") &&
!allowed_clients.include?(env.remote_addr) then
env.resp.status = 404
env.resp.return "permission denied"
if env.req.path.start_with?("/log/") &&
!allowed_clients.include?(env.remote_addr) then
env.resp.status = 404
env.resp.return "permission denied"
end
end
end
App.new
SEE ALSO
--------

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|
env.req.set_header "User-Agent", "mruby"
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|
env.resp.set_header "Alpha", "bravo"
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);
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<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;
}