sham_rack使ってみた

# [sham_rack](https://github.com/mdub/sham_rack)とは

```README.md
ShamRack plumbs HTTP requests into Rack.
(ShamRackはHTTPリクエストをRackの中へ送り込みます。)
```

HTTPリクエストをRackに放り込んで、登録した処理を行う。
Rackミドルウェアのように見える。

# 何ができるか

HTTPリクエストをリクエストホストに大して投げる前にフックして、あたかも処理されたようにレスポンスを返すことができる。
つまり、StubやMockのようなことができる。

# 注意点

2015/01/16追記。

あるホストをsham_rackに追加すると、そのホストに対する全てリクエストがsham_rackに食われてしまう(ように見える)。

sham_rackではNet::HTTPにShamRack::NetHTTPをextendしているため、
Net:HTTPを使ったライブラリのリクエストはsham_rackに流れてしまう。
これを回避するためには、TCPSocketやExconの様なsocketを使った通信を行う他ない。
(勉強中なので他のやり方があったら是非教えてください)

# 使い方

ShamRack.atでリクエストホストを第一引数に、期待する処理をブロックで渡す。
ブロックはRackミドルウェアのcallメソッド内の処理を書くのとほぼ同じ。
ブロック内ではリクエストの情報をRackのEnvとして受け取れる。
ブロックで渡した処理はアプリとしてShamRackに登録される。

## HTTPリクエストのStub

例えば、http://www.google.co.jp/ にGETしたら"Hello, world!"が返ってくるようにStubする。

```get_http_google_top.rb
require 'sham_rack'

ShamRack.at("www.google.co.jp") do |env|
  if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
    ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
  end
end

response = Net::HTTP.new("www.google.co.jp", 80).get("/")
response.code  #=> "200"
response.body  #=> "Hello, world!"
```

## HTTPSリクエストのStub

HTTPSの場合は、ShamRack.atの第二引数に443番ポートを指定する。

```get_https_google_top.rb
require 'sham_rack'

ShamRack.at("www.google.co.jp", 443) do |env|
  if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
    ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
  end
end

https = Net::HTTP.new("www.google.co.jp", 443)
https.use_ssl = true
response = https.get("/")
response.code  #=> "200"
response.body  #=> "Hello, world!"
```

## 複数のリクエスト先のStub

複数書くだけ。

```get_google_top.rb
ShamRack.at("www.google.co.jp") do |env|
  if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
    ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
  end
end

ShamRack.at("www.google.co.jp", 443) do |env|
  if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
    ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
  end
end
```

## 他のStubの書き方

2015/01/16追記。

sham_rackのREADME.mdにも書いてあるが、以下のようにstubすると、同一ホストで登録していないパスへのアクセスが404になる。
これはdefault_responseとしてsham_rackに定義されている。

```sham_rack_stub.rb
@stub_app = ShamRack.at("www.google.co.jp", 443).stub
@stub_app.register_resource("/", "Hello, world!", "text/plain")

https = Net::HTTP.new("www.google.co.jp", 443)
https.use_ssl = true
response = https.get("/")
response.code  #=> "200"
response.body  #=> "Hello, world!"

response = https.get("/")
response.code  #=> "404"
response.body  #=> "Not found"
```

## Stubを止める場合

アプリをアンマウントする。登録したアプリ全てがアンマウントされる。

```get_http_google_top_and_stub_top.rb

ShamRack.at("www.google.co.jp") do |env|
  if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
    ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
  end
end

puts "----- google (stub) -----"
uri = URI.parse("http://www.google.co.jp/")
res = http(uri).get(uri.path)
p res.code  #=> "200"
p res.body  #=> "Hello, world!"

ShamRack.unmount_all

puts "----- google -----"
uri = URI.parse("http://www.google.co.jp/")
res = http(uri).get(uri.path)
p res.code  #=> "200"
p res.body  #=> "<!doctype html><html itemscope=\"\" itemtype=..."
```

## ShamRackにRackミドルウェアも追加する場合

2015/01/16追記。

```use_middleware.rb
class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
      @app.call(env)
    else
      [404, { "Content-type" => "text/plain" }, ["Not Found"]]
    end
  end
end

rackapp = Proc.new do
  ["200 OK", { "Content-type" => "text/plain" }, ["Hello, world!"]]
end
app = MyMiddleware.new(rackapp)


ShamRack.at("www.google.co.jp") do |env|
  app.call(env)
end
```

README.mdに書いてある通り、以下のような書き方もできる。

```rb
ShamRack.at("www.google.co.jp").rackup do |env|
  use MyMiddleware
  run rackapp
end
```

# 感想

* Rack層でstubができるのですごく便利！
* テストやMock開発に役立ちそう