Skip to content

Commit

Permalink
Merge pull request #67 from kentaro/rack-based-api
Browse files Browse the repository at this point in the history
Support Rack based API for mruby in httpd
  • Loading branch information
matsumotory committed Sep 15, 2015
2 parents 7ca07f4 + 01c29e9 commit 52301b4
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 0 deletions.
1 change: 1 addition & 0 deletions build_config.rb
Expand Up @@ -25,6 +25,7 @@

# mod_mruby extended class
conf.gem '../mrbgems/mod_mruby_mrblib'
conf.gem '../mrbgems/rack-based-api'

# use memcached
# conf.gem :github => 'matsumoto-r/mruby-memcached'
Expand Down
4 changes: 4 additions & 0 deletions mrbgems/rack-based-api/mrbgem.rake
@@ -0,0 +1,4 @@
MRuby::Gem::Specification.new('mruby-rack-based-api') do |spec|
spec.license = 'MIT'
spec.authors = 'MATSUMOTO Ryosuke'
end
66 changes: 66 additions & 0 deletions mrbgems/rack-based-api/mrblib/rack.rb
@@ -0,0 +1,66 @@
module Kernel
module Rack
Server = get_server_class

def self.build_env r, c
env = {}
env = {
"REQUEST_METHOD" => r.method,
"SCRIPT_NAME" => "",
"PATH_INFO" => r.uri,
"REQUEST_URI" => r.unparsed_uri,
"QUERY_STRING" => r.args,
"SERVER_NAME" => r.hostname,
"SERVER_ADDR" => c.local_ip,
"SERVER_PORT" => c.local_port.to_s,
"REMOTE_ADDR" => c.remote_ip,
"REMOTE_PORT" => c.remote_port.to_s,
"rack.url_scheme" => r.scheme,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.hijack?" => false,
"server.name" => server_name,
"server.version" => Server.server_version,
}

# add rquest headers into env
r.headers_in.all.keys.each do |k|
env["HTTP_#{k.upcase.gsub('-', '_')}"] = r.headers_in[k]
end

env
end

def self.build_response r, res
if res[1].kind_of?(Hash)
res[1].keys.each { |k| r.headers_out[k] = res[1][k] }
elsif res[1].kind_of?(Array)
res[1].each { |ary| r.headers_out[ary[0]] = ary[1] }
else
raise TypeError, "response headers arg type must be Array or Hash"
end
if res[2].kind_of?(Array)
res[2].each { |b| Server.rputs b.to_s }
else
raise TypeError, "response body arg type must be Array"
end

if res[0].to_i == 200
Server.return Server::OK
else
Server.return res[0].to_i
end
end
end

def run obj
Server = get_server_class
r = Server::Request.new
c = Server::Connection.new

env = Kernel::Rack.build_env r, c
res = obj.call env
Kernel::Rack.build_response r, res
end
end
Empty file.
10 changes: 10 additions & 0 deletions src/ap_mrb_request.c
Expand Up @@ -80,6 +80,14 @@ AP_MRB_GET_REQUEST_VALUE(user)
AP_MRB_GET_REQUEST_VALUE(ap_auth_type)
AP_MRB_GET_REQUEST_VALUE(unparsed_uri)
AP_MRB_GET_REQUEST_VALUE(uri)

static mrb_value ap_mrb_get_request_scheme(mrb_state *mrb, mrb_value str)
{
request_rec *r = ap_mrb_get_request();
char *val = ap_http_scheme(r);
return mrb_str_new(mrb, val, strlen(val));
}

AP_MRB_GET_REQUEST_VALUE(filename)
AP_MRB_GET_REQUEST_VALUE(canonical_filename)
AP_MRB_GET_REQUEST_VALUE(path_info)
Expand Down Expand Up @@ -777,6 +785,8 @@ void ap_mruby_request_init(mrb_state *mrb, struct RClass *class_core)
MRB_ARGS_ANY());
mrb_define_method(mrb, class_request, "uri", ap_mrb_get_request_uri,
MRB_ARGS_NONE());
mrb_define_method(mrb, class_request, "scheme", ap_mrb_get_request_scheme,
MRB_ARGS_NONE());
mrb_define_method(mrb, class_request, "filename=",
ap_mrb_set_request_filename, MRB_ARGS_ANY());
mrb_define_method(mrb, class_request, "filename", ap_mrb_get_request_filename,
Expand Down
5 changes: 5 additions & 0 deletions test/build_config.rb
Expand Up @@ -25,9 +25,14 @@
conf.gem :github => 'matsumoto-r/mruby-mod-mruby-ext'
conf.gem :github => 'matsumoto-r/mruby-simpletest'
conf.gem :github => 'mattn/mruby-http'
conf.gem :github => 'mattn/mruby-json'
conf.gem :github => 'iij/mruby-io'
conf.gem :github => 'iij/mruby-socket'
conf.gem :github => 'iij/mruby-pack'

conf.gem '../mrbgems/mod_mruby_mrblib'
conf.gem '../mrbgems/rack-based-api'

# include the default GEMs
conf.gembox 'default'

Expand Down
109 changes: 109 additions & 0 deletions test/conf/mod_mruby_test.conf
@@ -1,5 +1,10 @@
Loglevel debug
Listen 38080

<Location /server-version>
mrubyHandlerMiddleCode 'Apache.rputs Apache.server_version'
</Location>

# test for echo
<Location /echo-test>
mrubyHandlerMiddle __MOD_MRUBY_TEST_DIR__/htdocs/echo.rb
Expand Down Expand Up @@ -114,3 +119,107 @@ Listen 127.0.0.1:8081
mrubyHandlerMiddleCode 'Apache.log Apache::APLOG_DEBUG, "FJRRWZQJUQTKPAUP"'
</Location>

# test for rack-based api
# you need semicolon and backslash at the last of line if you want linefeed
<Location /rack_base>
mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[200, {"x-hoge" => "foo"}, ["rack body"]]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base1>
mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[ \
200, \
{"x-hoge" => "foo", "x-foo" => "hoge"}, \
["rack", " body"] \
]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base2>
mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[ \
200, \
[["x-hoge", "foo"], ["x-foo", "hoge"]], \
["rack", " body"] \
]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base3>
mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[404, [], []]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base_env>
mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[200, [], [JSON.generate(env)]]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base_2phase>
mrubyAccessCheckerMiddleCode ' \
class AccessCheck; \
def call(env); \
if env["HTTP_AUTH_TOKEN"] == "aaabbbccc"; \
[ \
Apache::DECLINED, \
{"x-client-ip" => env["REMOTE_ADDR"]}, \
[] \
]; \
else; \
[403, {}, []]; \
end; \
end; \
end; \
run AccessCheck.new '

mrubyHandlerMiddleCode ' \
class RackTest; \
def call(env); \
[200, [], ["OK"]]; \
end; \
end; \
run RackTest.new '
</Location>

<Location /rack_base_push>
mrubyHandlerMiddleCode ' \
p = Proc.new do |env|; \
push_paths = []; \
if env["PATH_INFO"] == "/rack_base_push/index.txt"; \
push_paths << "/index.js"; \
end; \
[ \
200, \
push_paths.empty? ? {} : { \
"link" => push_paths.map{|p| \
"<#{p}>; rel=preload"}.join() \
}, \
["push"] \
]; \
end; \
run p '
</Location>
75 changes: 75 additions & 0 deletions test/t/mod_mruby.rb
Expand Up @@ -16,6 +16,8 @@ def base

t = SimpleTest.new "mod_mruby test"

server_version = HttpRequest.new.get(base + '/server-version')["body"]

t.assert('mod_mruby', 'location /hello-inline') do
res = HttpRequest.new.get base + '/hello-inline'
t.assert_equal 'hello', res["body"]
Expand Down Expand Up @@ -128,4 +130,77 @@ def base
res = HttpRequest.new.get base + '/logger'
end

t.assert('mod_mruby', 'location /rack_base') do
res = HttpRequest.new.get base + '/rack_base'
t.assert_equal "rack body", res["body"]
t.assert_equal "foo", res["x-hoge"]
t.assert_equal 200, res.code
end

t.assert('mod_mruby', 'location /rack_base1') do
res = HttpRequest.new.get base + '/rack_base1'
t.assert_equal "rack body", res["body"]
t.assert_equal "foo", res["x-hoge"]
t.assert_equal "hoge", res["x-foo"]
t.assert_equal 200, res.code
end

t.assert('mod_mruby', 'location /rack_base2') do
res = HttpRequest.new.get base + '/rack_base2'
t.assert_equal "rack body", res["body"]
t.assert_equal "foo", res["x-hoge"]
t.assert_equal "hoge", res["x-foo"]
t.assert_equal 200, res.code
end

t.assert('mod_mruby', 'location /rack_base3') do
res = HttpRequest.new.get base + '/rack_base3'
t.assert_equal 404, res.code
end

t.assert('mod_mruby', 'location /rack_base_env') do
res = HttpRequest.new.get base + '/rack_base_env?a=1&b=1', nil, {"Host" => "apache.example.com:38080", "x-hoge" => "foo"}
body = JSON.parse res["body"]

t.assert_equal "GET", body["REQUEST_METHOD"]
t.assert_equal "", body["SCRIPT_NAME"]
t.assert_equal "/rack_base_env", body["PATH_INFO"]
t.assert_equal "/rack_base_env?a=1&b=1", body["REQUEST_URI"]
t.assert_equal "a=1&b=1", body["QUERY_STRING"]
t.assert_equal "apache.example.com", body["SERVER_NAME"]
t.assert_equal "127.0.0.1", body["SERVER_ADDR"]
t.assert_equal "38080", body["SERVER_PORT"]
t.assert_equal "127.0.0.1", body["REMOTE_ADDR"]
t.assert_equal "http", body["rack.url_scheme"]
t.assert_false body["rack.multithread"]
t.assert_true body["rack.multiprocess"]
t.assert_false body["rack.run_once"]
t.assert_false body["rack.hijack?"]
t.assert_equal "Apache", body["server.name"]
t.assert_equal server_version, body["server.version"]
t.assert_equal "*/*", body["HTTP_ACCEPT"]
t.assert_equal "close", body["HTTP_CONNECTION"]
t.assert_equal "apache.example.com:38080", body["HTTP_HOST"]
t.assert_equal "foo", body["HTTP_X_HOGE"]
t.assert_equal 200, res.code
end

t.assert('mod_mruby', 'location /rack_base_2phase') do
res = HttpRequest.new.get base + '/rack_base_2phase', nil, {"auth-token" => "aaabbbccc"}
t.assert_equal "OK", res["body"]
t.assert_equal "127.0.0.1", res["x-client-ip"]
t.assert_equal 200, res.code
end

t.assert('mod_mruby', 'location /rack_base_2phase') do
res = HttpRequest.new.get base + '/rack_base_2phase', nil, {"auth-token" => "cccbbbaaa"}
t.assert_equal 403, res.code
end

t.assert('mod_mruby', 'location /rack_base_push/index.txt') do
res = HttpRequest.new.get base + '/rack_base_push/index.txt'
t.assert_equal 200, res.code
t.assert_equal "</index.js>; rel=preload", res["link"]
end

t.report

0 comments on commit 52301b4

Please sign in to comment.