Configure > Using Mruby
mruby is a lightweight implementation of the Ruby programming language. With H2O, users can implement their own request handling logic using mruby, either to generate responses or to fix-up the request / response.
Rack-based Programming Interface
The interface between the mruby program and the H2O server is based on Rack interface specification. Below is a simple configuration that returns hello world.
paths:
"/":
mruby.handler: |
Proc.new do |env|
[200, {'content-type' => 'text/plain'}, ["Hello world\n"]]
end
It should be noted that as of H2O version 1.7.0, there are limitations when compared to ordinary web application server with support for Rack such as Unicorn:
- no libraries provided as part of Rack is available (only the interface is compatible)
In addition to the Rack interface specification, H2O recognizes status code 399
which can be used to delegate request to the next handler.
The feature can be used to implement access control and response header modifiers.
Access Control
By using the 399
status code, it is possible to implement access control using mruby.
The example below restricts access to requests from 192.168.
private address.
paths:
"/":
mruby.handler: |
lambda do |env|
if /\A192\.168\./.match(env["REMOTE_ADDR"])
return [399, {}, []]
end
[403, {'content-type' => 'text/plain'}, ["access forbidden\n"]]
end
Support for Basic Authentication is also provided by an mruby script.
Delegating the Request
When enabled using the reproxy
directive, it is possible to delegate the request from the mruby handler to any other handler.
paths:
"/":
mruby.handler: |
lambda do |env|
if /\/user\/([^\/]+)/.match(env["PATH_INFO"])
return [307, {"x-reproxy-url" => "/user.php?user=#{$1}"}, []]
end
return [399, {}, []]
end
Modifying the Response
When the mruby handler returns status code 399
, H2O delegates the request to the next handler while preserving the headers emitted by the handler.
The feature can be used to add extra headers to the response.
For example, the following example sets cache-control
header for requests against .css
and .js
files.
paths:
"/":
mruby.handler: |
Proc.new do |env|
headers = {}
if /\.(css|js)\z/.match(env["PATH_INFO"])
headers["cache-control"] = "max-age=86400"
end
[399, headers, []]
end
file.dir: /path/to/doc-root
Or in the example below, the handler triggers HTTP/2 server push with the use of Link: rel=preload
headers, and then requests a FastCGI application to process the request.
paths:
"/":
mruby.handler: |
Proc.new do |env|
push_paths = []
# push css and js when request is to dir root or HTML
if /(\/|\.html)\z/.match(env["PATH_INFO"])
push_paths << ["/css/style.css", "style"]
push_paths << ["/js/app.js", "script"]
end
[399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p[0]}>; rel=preload; as=#{p[1]}"}.join("\n")}, []]
end
fastcgi.connect: ...
Using the HTTP Client
Starting from version 1.7, a HTTP client API is provided. HTTP requests issued through the API will be handled asynchronously; the client does not block the event loop of the HTTP server.
paths:
"/":
mruby.handler: |
Proc.new do |env|
req = http_request("http://example.com")
status, headers, body = req.join
[status, headers, body]
end
http_request
is the method that issues a HTTP request.
The method takes two arguments.
First argument is the target URI.
Second argument is an optional hash; method
(defaults to GET
), header
, body
attributes are recognized.
The method returns a promise object.
When #join
method of the promise is invoked, a three-argument array containing the status code, response headers, and the body is returned.
The response body is also a promise.
Applications can choose from three ways when dealing with the body: a) call #each
method to receive the contents, b) call #join
to retrieve the body as a string, c) return the object as the response body of the mruby handler.
The header and the body object passed to http_request
should conform to the requirements laid out by the Rack specification for request header and request body.
The response header and the response body object returned by the #join
method of the promise returned by http_request
conforms to the requirements of the Rack specification.
Since the API provides an asynchronous HTTP client, it is possible to effectively issue multiple HTTP requests concurrently and merge them into a single response.
When HTTPS is used, servers are verified using the properties of proxy.ssl.cafile
and proxy.ssl.verify-peer
specified at the global level.
Timeouts defined for the proxy handler (proxy.timeout.*
) are applied to the requests that are issued by the http_request
method.
Logging Arbitrary Variable
In version 2.3, it is possible from mruby to set and log an arbitrary-named variable that is associated to a HTTP request.
A HTTP response header that starts with x-fallthru-set-
is handled specially by the H2O server. Instead of sending the header downstream, the server accepts the value as a request environment variable, taking the suffix of the header name as the name of the variable.
This example shows how to read request data, parse json and then log data from mruby.
paths:
"/":
mruby.handler: |
Proc.new do |env|
input = env["rack.input"] ? env["rack.input"].read : '{"default": "true"}'
parsed_json = JSON.parse(input)
parsed_json["time"] = Time.now.to_i
logdata = parsed_json.to_s
[204, {"x-fallthru-set-POSTDATA" => logdata}, []]
end
access-log:
path: /path/to/access-log.json
escape: json
format: '{"POST": %{POSTDATA}e}'