net/http で Keep-Alive なやりとり

Ruby でよく使うライブラリ net/http なんですが,コネクション張り続けて通信するにはどうしたらいいんだろう.という話.

いろいろ弄った結果

ポイントは二つくらい


  1. 念のため Net::HTTP:Get のインスタンスに以下のようなヘッダエンティティtをくっつける.

    Net::HTTP::Get#['Connection'] = 'Keep-Alive'
    


  2. Net::HTTP.start や,Net::HTTP#start を使ってコネクションを張る

  3. 2回目以降の要求をする時において,前回の要求から時間が空いていると,サーバがコネクションを断ち切ってしまうので,コネクションの張り直し手続きが必要になる.
これらを踏まえて,3回ほど d.hatena.ne.jp に要求を送るスクリプトを書く.
また3回目の要求の際は,意図的に間隔を空けて,サーバからコネクションを断ち切られてしまった場合を再現している.

#!/usr/bin/ruby -Ku

require 'net/http'
require 'uri'

host = 'd.hatena.ne.jp'
port = 80
path = '/'
sleep_time = 7

# 要求・応答ヘッダ出力
def print_key_and_val(hash)
  puts "-- #{hash.class} --"
  hash.each do|k,v| puts "#{k} => \t#{v}" end
  puts ''
end

http = Net::HTTP.new(host,port)
req = Net::HTTP::Get.new(path)

# ポイント1
req['Connection'] = 'Keep-Alive'

# コネクションを張る(ポイント2
http.start

# 1回目の要求
puts '## First'
print_key_and_val req
print_key_and_val http.request(req)

# 2回目の要求
puts '## Second'
print_key_and_val req
print_key_and_val http.request(req)

# 間隔を空ける
puts "\n## sleep #{sleep_time}s\n\n"
sleep sleep_time # この間に,d.hatena サーバからコネクションを切られてしまう

# 3回目の要求(ポイント3
#  sleep 中に,サーバからコネクションを切られているので,うまくいかない.
#  EOFError が発生してしまう.この例外を捕捉し,コネクションを張りなおす
#  事で対処をした.
puts '## Third'
retry_flag = true # 張りなおしてもダメだった時を判別するためのフラグ
begin
  print_key_and_val req
  print_key_and_val http.request(req)
rescue EOFError => e
  puts '********************'
  puts '** Catch EOFError **'
  puts '********************'
  if retry_flag
    http.finish # 一度こちらからも切って,
    http.start  # コネクション張り張り直し
    retry_flag = false
    retry
  else
    raise EOFError, e.message
  end
end

# コネクションを切る
http.finish

実行結果

$ ruby http_timeout.rb
## First
-- Net::HTTP::Get --
connection =>   Keep-Alive
accept =>       */*

-- Net::HTTPOK --
vary =>         Accept-Encoding
connection =>   Keep-Alive
content-type =>         text/html; charset=euc-jp
date =>         Fri, 21 Mar 2008 09:06:09 GMT
server =>       Apache
keep-alive =>   timeout=7, max=5
transfer-encoding =>    chunked

## Second
-- Net::HTTP::Get --
connection =>   Keep-Alive
accept =>       */*
host =>         d.hatena.ne.jp

-- Net::HTTPOK --
vary =>         Accept-Encoding
connection =>   Keep-Alive
content-type =>         text/html; charset=euc-jp
date =>         Fri, 21 Mar 2008 09:06:09 GMT
server =>       Apache
keep-alive =>   timeout=7, max=4
transfer-encoding =>    chunked


## sleep 7s

## Third
-- Net::HTTP::Get --
connection =>   Keep-Alive
accept =>       */*
host =>         d.hatena.ne.jp

********************
** Catch EOFError **
********************
-- Net::HTTP::Get --
connection =>   Keep-Alive
accept =>       */*
host =>         d.hatena.ne.jp

-- Net::HTTPOK --
vary =>         Accept-Encoding
connection =>   Keep-Alive
content-type =>         text/html; charset=euc-jp
date =>         Fri, 21 Mar 2008 09:06:17 GMT
server =>       Apache
keep-alive =>   timeout=7, max=5
transfer-encoding =>    chunked
$

その他

実際,書く時は,イチイチ begin ... rescue ... end で EOFError を補足するよりも,def で EOFError 捕捉機能つきの要求メソッドを用意するか,下記のようにしてしまった方が,賢く見えるかも.

class Net::HTTP
  alias_method :old_request, :request
  def request(req, body = nil, &block)

    retry_flag = true

    begin
      old_request(req, body, &block)
    rescue EOFError => e
      if retry_flag
        retry_flag = false
        finish
        start
        retry
      else
        raise EOFError, e.message
      end
    end

  end
end

こうすれば,再接続を意識せずにコードが書ける.本当は
レスポンスヘッダ Keep-Alive の max値も考慮に入れなければならないんでしょうね...