このサイトのHTTPS化にあたって nginx で書いていた rewrite のルールを h2o の mruby で処理するように変える必要がありました。
しかしベタで書くのも面倒なので、そこそこ機械的な置換ですむような書きかたができるようなライブラリを書いてしのぎました。
mruby.handler
paths:
"/":
proxy.reverse.url: http://localhost:5001
proxy.preserve-host: ON
mruby.handler: |
require "/srv/www/rewrite_rules.rb"
lambda do |env|
RewriteRules.rewrite(env) do
rewrite '/favicon.ico', '/images/favicon.ico', :break
rewrite '/apple-touch-icon.png', '/images/apple-touch-icon.png', :break
rewrite %r{^/2005/colors-canvas\.xhtml$}, '/2005/colors-canvas.html', :permanent
rewrite %r{^/2005/colors-canvas$}, '/2005/colors-canvas.html', :permanent
rewrite %r{^/logs/latest$}, '/', :permanent
rewrite %r{^/logs/latest.rdf$}, '/feed', :permanent
rewrite %r{^/logs/latest.atom$}, '/feed', :permanent
rewrite %r{^/latest\.rdf$}, '/feed', :permanent
rewrite %r{^/blog/index\.(rdf|atom)$}, '/feed', :permanent
rewrite %r{^/logs(/.+?)(\.(rdf|atom))$}, '/feed', :permanent
rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
rewrite %r{^/blog(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
rewrite %r{^/photo$}, '/photo/', :permanent
rewrite %r{^/(\d\d\d\d/\d\d(/\d\d)?)$}, '/\1/', :permanent
rewrite %r{^/\d\d\d\d/$}, '/', :redirect
rewrite %r{^/view-img(/.+?)$}, '\1', :permanent
end
end
h2o の path はディレクトリの指定しかできないみたいなので (自動的に末尾スラッシュがついたりする)、rewrite で同時にパス決め打ちのディスパッチも行っています。
rewrite_rules.rb
class RewriteRules
class RewriteRulesException < Exception
attr_reader :response
def initialize(response)
@response = response
end
end
attr_reader :env
attr_reader :path
def self.rewrite(env, &block)
self.new(env).run(&block)
end
def initialize(env, &block)
@env = env
@path_orig = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
@path = @path_orig.dup
end
def run(&block)
begin
instance_eval(&block)
if @path != @path_orig
return [ 307, { 'x-reproxy-url' => path }, [ ] ]
else
return [ 399, {}, [ ] ]
end
rescue RewriteRulesException => e
return e.response
end
end
def rewrite(regexp, replace, flag=:continue)
if @path.gsub!(regexp, replace)
case flag
when :redirect
raise RewriteRulesException.new([ 302, { 'Location' => @path }, [ ] ])
when :permanent
raise RewriteRulesException.new([ 301, { 'Location' => @path }, [ ] ])
when :break
raise RewriteRulesException.new([ 307, { 'x-reproxy-url' => @path }, [ ] ])
when :continue
# nothing
else
raise "unsupported flag: #{flag}"
end
end
end
end
require 'rspec'
describe RewriteRules do
it "should treat :permanent as 302 redirect" do
env = {
'PATH_INFO' => '/logs/latest'
}
expect(RewriteRules.rewrite(env) {
rewrite %r{^/logs/latest$}, '/', :permanent
rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
rewrite %r{^/foobar/}, '/bazbaz/'
}).to eq( [301, {"Location"=>"/"}, []])
end
it "should treat :redirect as 301 redirect" do
env = {
'PATH_INFO' => '/logs/piyo.html'
}
expect(RewriteRules.rewrite(env) {
rewrite %r{^/logs/latest$}, '/', :permanent
rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :redirect
rewrite %r{^/foobar/}, '/bazbaz/'
}).to eq([302, {"Location"=>"/piyo"}, []])
end
it "should treat as internal proxy by default" do
env = {
'PATH_INFO' => '/foobar/baz'
}
expect(RewriteRules.rewrite(env) {
rewrite %r{^/logs/latest$}, '/', :permanent
rewrite %r{^/logs(/.+?)(\.(x?html|xml|txt))?$}, '\1', :permanent
rewrite %r{^/foobar/}, '/bazbaz/'
}).to eq([307, {"x-reproxy-url"=>"/bazbaz/baz"}, []])
end
it "can write logic in dsl" do
env = {
'PATH_INFO' => '/foobar/baz',
'XXX' => true,
}
expect(RewriteRules.rewrite(env) {
if env['XXX']
rewrite %r{^/foobar/}, '/bazbaz/'
end
}).to eq([307, {"x-reproxy-url"=>"/bazbaz/baz"}, []])
env = {
'PATH_INFO' => '/foobar/baz',
'XXX' => false
}
expect(RewriteRules.rewrite(env) {
if env['XXX']
rewrite %r{^/foobar/}, '/bazbaz/'
end
}).to eq([399, {}, []])
end
end
ところで
rewrite ルールが ruby で書けるということは、すなわち自由にテスト可能であることを意味します。Apache の RewriteRule や nginx の rewrite をテストするのはかなり面倒なので、かなり強力で嬉しい感じがします。