| 1 |
# HTTPAccess2 - HTTP accessing library. |
|---|
| 2 |
# Copyright (C) 2000-2005 NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>. |
|---|
| 3 |
|
|---|
| 4 |
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can |
|---|
| 5 |
# redistribute it and/or modify it under the same terms of Ruby's license; |
|---|
| 6 |
# either the dual license version in 2003, or any later version. |
|---|
| 7 |
|
|---|
| 8 |
# http-access2.rb is based on http-access.rb in http-access/0.0.4. Some part |
|---|
| 9 |
# of code in http-access.rb was recycled in http-access2.rb. Those part is |
|---|
| 10 |
# copyrighted by Maehashi-san. |
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
# Ruby standard library |
|---|
| 14 |
require 'timeout' |
|---|
| 15 |
require 'uri' |
|---|
| 16 |
require 'socket' |
|---|
| 17 |
require 'thread' |
|---|
| 18 |
|
|---|
| 19 |
# Extra library |
|---|
| 20 |
require 'http-access2/http' |
|---|
| 21 |
require 'http-access2/cookie' |
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
module HTTPAccess2 |
|---|
| 25 |
VERSION = '2.0.7' |
|---|
| 26 |
RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" |
|---|
| 27 |
s = %w$Id$ |
|---|
| 28 |
RCS_FILE, RCS_REVISION = s[1][/.*(?=,v$)/], s[2] |
|---|
| 29 |
|
|---|
| 30 |
SSLEnabled = begin |
|---|
| 31 |
require 'openssl' |
|---|
| 32 |
true |
|---|
| 33 |
rescue LoadError |
|---|
| 34 |
false |
|---|
| 35 |
end |
|---|
| 36 |
|
|---|
| 37 |
DEBUG_SSL = true |
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
module Util |
|---|
| 41 |
def urify(uri) |
|---|
| 42 |
if uri.is_a?(URI) |
|---|
| 43 |
uri |
|---|
| 44 |
else |
|---|
| 45 |
URI.parse(uri.to_s) |
|---|
| 46 |
end |
|---|
| 47 |
end |
|---|
| 48 |
end |
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
# DESCRIPTION |
|---|
| 52 |
# HTTPAccess2::Client -- Client to retrieve web resources via HTTP. |
|---|
| 53 |
# |
|---|
| 54 |
# How to create your client. |
|---|
| 55 |
# 1. Create simple client. |
|---|
| 56 |
# clnt = HTTPAccess2::Client.new |
|---|
| 57 |
# |
|---|
| 58 |
# 2. Accessing resources through HTTP proxy. |
|---|
| 59 |
# clnt = HTTPAccess2::Client.new("http://myproxy:8080") |
|---|
| 60 |
# |
|---|
| 61 |
# 3. Set User-Agent and From in HTTP request header.(nil means "No proxy") |
|---|
| 62 |
# clnt = HTTPAccess2::Client.new(nil, "MyAgent", "nahi@keynauts.com") |
|---|
| 63 |
# |
|---|
| 64 |
# How to retrieve web resources. |
|---|
| 65 |
# 1. Get content of specified URL. |
|---|
| 66 |
# puts clnt.get_content("http://www.ruby-lang.org/en/") |
|---|
| 67 |
# |
|---|
| 68 |
# 2. Do HEAD request. |
|---|
| 69 |
# res = clnt.head(uri) |
|---|
| 70 |
# |
|---|
| 71 |
# 3. Do GET request with query. |
|---|
| 72 |
# res = clnt.get(uri) |
|---|
| 73 |
# |
|---|
| 74 |
# 4. Do POST request. |
|---|
| 75 |
# res = clnt.post(uri) |
|---|
| 76 |
# res = clnt.get|post|head(uri, proxy) |
|---|
| 77 |
# |
|---|
| 78 |
class Client |
|---|
| 79 |
include Util |
|---|
| 80 |
|
|---|
| 81 |
attr_reader :agent_name |
|---|
| 82 |
attr_reader :from |
|---|
| 83 |
attr_reader :ssl_config |
|---|
| 84 |
attr_accessor :cookie_manager |
|---|
| 85 |
attr_reader :test_loopback_response |
|---|
| 86 |
|
|---|
| 87 |
class << self |
|---|
| 88 |
%w(get_content head get post put delete options trace).each do |name| |
|---|
| 89 |
eval <<-EOD |
|---|
| 90 |
def #{name}(*arg) |
|---|
| 91 |
new.#{name}(*arg) |
|---|
| 92 |
end |
|---|
| 93 |
EOD |
|---|
| 94 |
end |
|---|
| 95 |
end |
|---|
| 96 |
|
|---|
| 97 |
# SYNOPSIS |
|---|
| 98 |
# Client.new(proxy = nil, agent_name = nil, from = nil) |
|---|
| 99 |
# |
|---|
| 100 |
# ARGS |
|---|
| 101 |
# proxy A String of HTTP proxy URL. ex. "http://proxy:8080". |
|---|
| 102 |
# agent_name A String for "User-Agent" HTTP request header. |
|---|
| 103 |
# from A String for "From" HTTP request header. |
|---|
| 104 |
# |
|---|
| 105 |
# DESCRIPTION |
|---|
| 106 |
# Create an instance. |
|---|
| 107 |
# SSLConfig cannot be re-initialized. Create new client. |
|---|
| 108 |
# |
|---|
| 109 |
def initialize(proxy = nil, agent_name = nil, from = nil) |
|---|
| 110 |
@proxy = nil # assigned later. |
|---|
| 111 |
@no_proxy = nil |
|---|
| 112 |
@agent_name = agent_name |
|---|
| 113 |
@from = from |
|---|
| 114 |
@basic_auth = BasicAuth.new(self) |
|---|
| 115 |
@debug_dev = nil |
|---|
| 116 |
@ssl_config = SSLConfig.new(self) |
|---|
| 117 |
@redirect_uri_callback = method(:default_redirect_uri_callback) |
|---|
| 118 |
@test_loopback_response = [] |
|---|
| 119 |
@session_manager = SessionManager.new |
|---|
| 120 |
@session_manager.agent_name = @agent_name |
|---|
| 121 |
@session_manager.from = @from |
|---|
| 122 |
@session_manager.ssl_config = @ssl_config |
|---|
| 123 |
@cookie_manager = WebAgent::CookieManager.new |
|---|
| 124 |
self.proxy = proxy |
|---|
| 125 |
end |
|---|
| 126 |
|
|---|
| 127 |
def debug_dev |
|---|
| 128 |
@debug_dev |
|---|
| 129 |
end |
|---|
| 130 |
|
|---|
| 131 |
def debug_dev=(dev) |
|---|
| 132 |
@debug_dev = dev |
|---|
| 133 |
reset_all |
|---|
| 134 |
@session_manager.debug_dev = dev |
|---|
| 135 |
end |
|---|
| 136 |
|
|---|
| 137 |
def protocol_version |
|---|
| 138 |
@session_manager.protocol_version |
|---|
| 139 |
end |
|---|
| 140 |
|
|---|
| 141 |
def protocol_version=(protocol_version) |
|---|
| 142 |
reset_all |
|---|
| 143 |
@session_manager.protocol_version = protocol_version |
|---|
| 144 |
end |
|---|
| 145 |
|
|---|
| 146 |
def connect_timeout |
|---|
| 147 |
@session_manager.connect_timeout |
|---|
| 148 |
end |
|---|
| 149 |
|
|---|
| 150 |
def connect_timeout=(connect_timeout) |
|---|
| 151 |
reset_all |
|---|
| 152 |
@session_manager.connect_timeout = connect_timeout |
|---|
| 153 |
end |
|---|
| 154 |
|
|---|
| 155 |
def send_timeout |
|---|
| 156 |
@session_manager.send_timeout |
|---|
| 157 |
end |
|---|
| 158 |
|
|---|
| 159 |
def send_timeout=(send_timeout) |
|---|
| 160 |
reset_all |
|---|
| 161 |
@session_manager.send_timeout = send_timeout |
|---|
| 162 |
end |
|---|
| 163 |
|
|---|
| 164 |
def receive_timeout |
|---|
| 165 |
@session_manager.receive_timeout |
|---|
| 166 |
end |
|---|
| 167 |
|
|---|
| 168 |
def receive_timeout=(receive_timeout) |
|---|
| 169 |
reset_all |
|---|
| 170 |
@session_manager.receive_timeout = receive_timeout |
|---|
| 171 |
end |
|---|
| 172 |
|
|---|
| 173 |
def proxy |
|---|
| 174 |
@proxy |
|---|
| 175 |
end |
|---|
| 176 |
|
|---|
| 177 |
def proxy=(proxy) |
|---|
| 178 |
if proxy.nil? |
|---|
| 179 |
@proxy = nil |
|---|
| 180 |
else |
|---|
| 181 |
@proxy = urify(proxy) |
|---|
| 182 |
if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or |
|---|
| 183 |
@proxy.host == nil or @proxy.port == nil |
|---|
| 184 |
raise ArgumentError.new("unsupported proxy `#{proxy}'") |
|---|
| 185 |
end |
|---|
| 186 |
end |
|---|
| 187 |
reset_all |
|---|
| 188 |
@proxy |
|---|
| 189 |
end |
|---|
| 190 |
|
|---|
| 191 |
def no_proxy |
|---|
| 192 |
@no_proxy |
|---|
| 193 |
end |
|---|
| 194 |
|
|---|
| 195 |
def no_proxy=(no_proxy) |
|---|
| 196 |
@no_proxy = no_proxy |
|---|
| 197 |
reset_all |
|---|
| 198 |
end |
|---|
| 199 |
|
|---|
| 200 |
# if your ruby is older than 2005-09-06, do not set socket_sync = false to |
|---|
| 201 |
# avoid an SSL socket blocking bug in openssl/buffering.rb. |
|---|
| 202 |
def socket_sync=(socket_sync) |
|---|
| 203 |
@session_manager.socket_sync = socket_sync |
|---|
| 204 |
end |
|---|
| 205 |
|
|---|
| 206 |
def set_basic_auth(uri, user_id, passwd) |
|---|
| 207 |
uri = urify(uri) |
|---|
| 208 |
@basic_auth.set(uri, user_id, passwd) |
|---|
| 209 |
end |
|---|
| 210 |
|
|---|
| 211 |
def set_cookie_store(filename) |
|---|
| 212 |
if @cookie_manager.cookies_file |
|---|
| 213 |
raise RuntimeError.new("overriding cookie file location") |
|---|
| 214 |
end |
|---|
| 215 |
@cookie_manager.cookies_file = filename |
|---|
| 216 |
@cookie_manager.load_cookies if filename |
|---|
| 217 |
end |
|---|
| 218 |
|
|---|
| 219 |
def save_cookie_store |
|---|
| 220 |
@cookie_manager.save_cookies |
|---|
| 221 |
end |
|---|
| 222 |
|
|---|
| 223 |
def redirect_uri_callback=(redirect_uri_callback) |
|---|
| 224 |
@redirect_uri_callback = redirect_uri_callback |
|---|
| 225 |
end |
|---|
| 226 |
|
|---|
| 227 |
# SYNOPSIS |
|---|
| 228 |
# Client#get_content(uri, query = nil, extheader = {}, &block = nil) |
|---|
| 229 |
# |
|---|
| 230 |
# ARGS |
|---|
| 231 |
# uri an_URI or a_string of uri to connect. |
|---|
| 232 |
# query a_hash or an_array of query part. e.g. { "a" => "b" }. |
|---|
| 233 |
# Give an array to pass multiple value like |
|---|
| 234 |
# [["a" => "b"], ["a" => "c"]]. |
|---|
| 235 |
# extheader a_hash of extra headers like { "SOAPAction" => "urn:foo" }. |
|---|
| 236 |
# &block Give a block to get chunked message-body of response like |
|---|
| 237 |
# get_content(uri) { |chunked_body| ... } |
|---|
| 238 |
# Size of each chunk may not be the same. |
|---|
| 239 |
# |
|---|
| 240 |
# DESCRIPTION |
|---|
| 241 |
# Get a_sring of message-body of response. |
|---|
| 242 |
# |
|---|
| 243 |
def get_content(uri, query = nil, extheader = {}, &block) |
|---|
| 244 |
retry_connect(uri, query) { |uri, query| |
|---|
| 245 |
get(uri, query, extheader, &block) |
|---|
| 246 |
}.content |
|---|
| 247 |
end |
|---|
| 248 |
|
|---|
| 249 |
def post_content(uri, body = nil, extheader = {}, &block) |
|---|
| 250 |
retry_connect(uri, nil) { |uri, query| |
|---|
| 251 |
post(uri, body, extheader, &block) |
|---|
| 252 |
}.content |
|---|
| 253 |
end |
|---|
| 254 |
|
|---|
| 255 |
def strict_redirect_uri_callback(uri, res) |
|---|
| 256 |
newuri = URI.parse(res.header['location'][0]) |
|---|
| 257 |
puts "Redirect to: #{newuri}" if $DEBUG |
|---|
| 258 |
newuri |
|---|
| 259 |
end |
|---|
| 260 |
|
|---|
| 261 |
def default_redirect_uri_callback(uri, res) |
|---|
| 262 |
newuri = URI.parse(res.header['location'][0]) |
|---|
| 263 |
unless newuri.is_a?(URI::HTTP) |
|---|
| 264 |
newuri = uri + newuri |
|---|
| 265 |
STDERR.puts( |
|---|
| 266 |
"could be a relative URI in location header which is not recommended") |
|---|
| 267 |
STDERR.puts( |
|---|
| 268 |
"'The field value consists of a single absolute URI' in HTTP spec") |
|---|
| 269 |
end |
|---|
| 270 |
puts "Redirect to: #{newuri}" if $DEBUG |
|---|
| 271 |
newuri |
|---|
| 272 |
end |
|---|
| 273 |
|
|---|
| 274 |
def head(uri, query = nil, extheader = {}) |
|---|
| 275 |
request('HEAD', uri, query, nil, extheader) |
|---|
| 276 |
end |
|---|
| 277 |
|
|---|
| 278 |
def get(uri, query = nil, extheader = {}, &block) |
|---|
| 279 |
request('GET', uri, query, nil, extheader, &block) |
|---|
| 280 |
end |
|---|
| 281 |
|
|---|
| 282 |
def post(uri, body = nil, extheader = {}, &block) |
|---|
| 283 |
request('POST', uri, nil, body, extheader, &block) |
|---|
| 284 |
end |
|---|
| 285 |
|
|---|
| 286 |
def put(uri, body = nil, extheader = {}, &block) |
|---|
| 287 |
request('PUT', uri, nil, body, extheader, &block) |
|---|
| 288 |
end |
|---|
| 289 |
|
|---|
| 290 |
def delete(uri, extheader = {}, &block) |
|---|
| 291 |
request('DELETE', uri, nil, nil, extheader, &block) |
|---|
| 292 |
end |
|---|
| 293 |
|
|---|
| 294 |
def options(uri, extheader = {}, &block) |
|---|
| 295 |
request('OPTIONS', uri, nil, nil, extheader, &block) |
|---|
| 296 |
end |
|---|
| 297 |
|
|---|
| 298 |
def trace(uri, query = nil, body = nil, extheader = {}, &block) |
|---|
| 299 |
request('TRACE', uri, query, body, extheader, &block) |
|---|
| 300 |
end |
|---|
| 301 |
|
|---|
| 302 |
def request(method, uri, query = nil, body = nil, extheader = {}, &block) |
|---|
| 303 |
conn = Connection.new |
|---|
| 304 |
conn_request(conn, method, uri, query, body, extheader, &block) |
|---|
| 305 |
conn.pop |
|---|
| 306 |
end |
|---|
| 307 |
|
|---|
| 308 |
# Async interface. |
|---|
| 309 |
|
|---|
| 310 |
def head_async(uri, query = nil, extheader = {}) |
|---|
| 311 |
request_async('HEAD', uri, query, nil, extheader) |
|---|
| 312 |
end |
|---|
| 313 |
|
|---|
| 314 |
def get_async(uri, query = nil, extheader = {}) |
|---|
| 315 |
request_async('GET', uri, query, nil, extheader) |
|---|
| 316 |
end |
|---|
| 317 |
|
|---|
| 318 |
def post_async(uri, body = nil, extheader = {}) |
|---|
| 319 |
request_async('POST', uri, nil, body, extheader) |
|---|
| 320 |
end |
|---|
| 321 |
|
|---|
| 322 |
def put_async(uri, body = nil, extheader = {}) |
|---|
| 323 |
request_async('PUT', uri, nil, body, extheader) |
|---|
| 324 |
end |
|---|
| 325 |
|
|---|
| 326 |
def delete_async(uri, extheader = {}) |
|---|
| 327 |
request_async('DELETE', uri, nil, nil, extheader) |
|---|
| 328 |
end |
|---|
| 329 |
|
|---|
| 330 |
def options_async(uri, extheader = {}) |
|---|
| 331 |
request_async('OPTIONS', uri, nil, nil, extheader) |
|---|
| 332 |
end |
|---|
| 333 |
|
|---|
| 334 |
def trace_async(uri, query = nil, body = nil, extheader = {}) |
|---|
| 335 |
request_async('TRACE', uri, query, body, extheader) |
|---|
| 336 |
end |
|---|
| 337 |
|
|---|
| 338 |
def request_async(method, uri, query = nil, body = nil, extheader = {}) |
|---|
| 339 |
conn = Connection.new |
|---|
| 340 |
t = Thread.new(conn) { |tconn| |
|---|
| 341 |
conn_request(tconn, method, uri, query, body, extheader) |
|---|
| 342 |
} |
|---|
| 343 |
conn.async_thread = t |
|---|
| 344 |
conn |
|---|
| 345 |
end |
|---|
| 346 |
|
|---|
| 347 |
## |
|---|
| 348 |
# Multiple call interface. |
|---|
| 349 |
|
|---|
| 350 |
# ??? |
|---|
| 351 |
|
|---|
| 352 |
## |
|---|
| 353 |
# Management interface. |
|---|
| 354 |
|
|---|
| 355 |
def reset(uri) |
|---|
| 356 |
uri = urify(uri) |
|---|
| 357 |
@session_manager.reset(uri) |
|---|
| 358 |
end |
|---|
| 359 |
|
|---|
| 360 |
def reset_all |
|---|
| 361 |
@session_manager.reset_all |
|---|
| 362 |
end |
|---|
| 363 |
|
|---|
| 364 |
private |
|---|
| 365 |
|
|---|
| 366 |
def retry_connect(uri, query = nil) |
|---|
| 367 |
retry_number = 0 |
|---|
| 368 |
while retry_number < 10 |
|---|
| 369 |
res = yield(uri, query) |
|---|
| 370 |
if res.status == HTTP::Status::OK |
|---|
| 371 |
return res |
|---|
| 372 |
elsif HTTP::Status.redirect?(res.status) |
|---|
| 373 |
uri = @redirect_uri_callback.call(uri, res) |
|---|
| 374 |
query = nil |
|---|
| 375 |
retry_number += 1 |
|---|
| 376 |
else |
|---|
| 377 |
raise RuntimeError.new("Unexpected response: #{res.header.inspect}") |
|---|
| 378 |
end |
|---|
| 379 |
end |
|---|
| 380 |
raise RuntimeError.new("Retry count exceeded.") |
|---|
| 381 |
end |
|---|
| 382 |
|
|---|
| 383 |
def conn_request(conn, method, uri, query, body, extheader, &block) |
|---|
| 384 |
uri = urify(uri) |
|---|
| 385 |
proxy = no_proxy?(uri) ? nil : @proxy |
|---|
| 386 |
begin |
|---|
| 387 |
req = create_request(method, uri, query, body, extheader, !proxy.nil?) |
|---|
| 388 |
do_get_block(req, proxy, conn, &block) |
|---|
| 389 |
rescue Session::KeepAliveDisconnected |
|---|
| 390 |
req = create_request(method, uri, query, body, extheader, !proxy.nil?) |
|---|
| 391 |
do_get_block(req, proxy, conn, &block) |
|---|
| 392 |
end |
|---|
| 393 |
end |
|---|
| 394 |
|
|---|
| 395 |
def create_request(method, uri, query, body, extheader, proxy) |
|---|
| 396 |
if extheader.is_a?(Hash) |
|---|
| 397 |
extheader = extheader.to_a |
|---|
| 398 |
end |
|---|
| 399 |
cred = @basic_auth.get(uri) |
|---|
| 400 |
if cred |
|---|
| 401 |
extheader << ['Authorization', "Basic " << cred] |
|---|
| 402 |
end |
|---|
| 403 |
if cookies = @cookie_manager.find(uri) |
|---|
| 404 |
extheader << ['Cookie', cookies] |
|---|
| 405 |
end |
|---|
| 406 |
boundary = nil |
|---|
| 407 |
content_type = extheader.find { |key, value| |
|---|
| 408 |
key.downcase == 'content-type' |
|---|
| 409 |
} |
|---|
| 410 |
if content_type && content_type[1] =~ /boundary=(.+)\z/ |
|---|
| 411 |
boundary = $1 |
|---|
| 412 |
end |
|---|
| 413 |
req = HTTP::Message.new_request(method, uri, query, body, proxy, boundary) |
|---|
| 414 |
extheader.each do |key, value| |
|---|
| 415 |
req.header.set(key, value) |
|---|
| 416 |
end |
|---|
| 417 |
if content_type.nil? and !body.nil? |
|---|
| 418 |
req.header.set('content-type', 'application/x-www-form-urlencoded') |
|---|
| 419 |
end |
|---|
| 420 |
req |
|---|
| 421 |
end |
|---|
| 422 |
|
|---|
| 423 |
NO_PROXY_HOSTS = ['localhost'] |
|---|
| 424 |
|
|---|
| 425 |
def no_proxy?(uri) |
|---|
| 426 |
if !@proxy or NO_PROXY_HOSTS.include?(uri.host) |
|---|
| 427 |
return true |
|---|
| 428 |
end |
|---|
| 429 |
unless @no_proxy |
|---|
| 430 |
return false |
|---|
| 431 |
end |
|---|
| 432 |
@no_proxy.scan(/([^:,]+)(?::(\d+))?/) do |host, port| |
|---|
| 433 |
if /(\A|\.)#{Regexp.quote(host)}\z/i =~ uri.host && |
|---|
| 434 |
(!port || uri.port == port.to_i) |
|---|
| 435 |
return true |
|---|
| 436 |
end |
|---|
| 437 |
end |
|---|
| 438 |
false |
|---|
| 439 |
end |
|---|
| 440 |
|
|---|
| 441 |
# !! CAUTION !! |
|---|
| 442 |
# Method 'do_get*' runs under MT conditon. Be careful to change. |
|---|
| 443 |
def do_get_block(req, proxy, conn, &block) |
|---|
| 444 |
if str = @test_loopback_response.shift |
|---|
| 445 |
dump_dummy_request_response(req.body.dump, str) if @debug_dev |
|---|
| 446 |
conn.push(HTTP::Message.new_response(str)) |
|---|
| 447 |
return |
|---|
| 448 |
end |
|---|
| 449 |
content = '' |
|---|
| 450 |
res = HTTP::Message.new_response(content) |
|---|
| 451 |
@debug_dev << "= Request\n\n" if @debug_dev |
|---|
| 452 |
sess = @session_manager.query(req, proxy) |
|---|
| 453 |
@debug_dev << "\n\n= Response\n\n" if @debug_dev |
|---|
| 454 |
do_get_header(req, res, sess) |
|---|
| 455 |
conn.push(res) |
|---|
| 456 |
sess.get_data() do |str| |
|---|
| 457 |
block.call(str) if block |
|---|
| 458 |
content << str |
|---|
| 459 |
end |
|---|
| 460 |
@session_manager.keep(sess) unless sess.closed? |
|---|
| 461 |
end |
|---|
| 462 |
|
|---|
| 463 |
def do_get_stream(req, proxy, conn) |
|---|
| 464 |
if str = @test_loopback_response.shift |
|---|
| 465 |
dump_dummy_request_response(req.body.dump, str) if @debug_dev |
|---|
| 466 |
conn.push(HTTP::Message.new_response(str)) |
|---|
| 467 |
return |
|---|
| 468 |
end |
|---|
| 469 |
piper, pipew = IO.pipe |
|---|
| 470 |
res = HTTP::Message.new_response(piper) |
|---|
| 471 |
@debug_dev << "= Request\n\n" if @debug_dev |
|---|
| 472 |
sess = @session_manager.query(req, proxy) |
|---|
| 473 |
@debug_dev << "\n\n= Response\n\n" if @debug_dev |
|---|
| 474 |
do_get_header(req, res, sess) |
|---|
| 475 |
conn.push(res) |
|---|
| 476 |
sess.get_data() do |str| |
|---|
| 477 |
pipew.syswrite(str) |
|---|
| 478 |
end |
|---|
| 479 |
pipew.close |
|---|
| 480 |
@session_manager.keep(sess) unless sess.closed? |
|---|
| 481 |
end |
|---|
| 482 |
|
|---|
| 483 |
def do_get_header(req, res, sess) |
|---|
| 484 |
res.version, res.status, res.reason = sess.get_status |
|---|
| 485 |
sess.get_header().each do |line| |
|---|
| 486 |
unless /^([^:]+)\s*:\s*(.*)$/ =~ line |
|---|
| 487 |
raise RuntimeError.new("Unparsable header: '#{line}'.") if $DEBUG |
|---|
| 488 |
end |
|---|
| 489 |
res.header.set($1, $2) |
|---|
| 490 |
end |
|---|
| 491 |
if res.header['set-cookie'] |
|---|
| 492 |
res.header['set-cookie'].each do |cookie| |
|---|
| 493 |
@cookie_manager.parse(cookie, req.header.request_uri) |
|---|
| 494 |
end |
|---|
| 495 |
end |
|---|
| 496 |
end |
|---|
| 497 |
|
|---|
| 498 |
def dump_dummy_request_response(req, res) |
|---|
| 499 |
@debug_dev << "= Dummy Request\n\n" |
|---|
| 500 |
@debug_dev << req |
|---|
| 501 |
@debug_dev << "\n\n= Dummy Response\n\n" |
|---|
| 502 |
@debug_dev << res |
|---|
| 503 |
end |
|---|
| 504 |
end |
|---|
| 505 |
|
|---|
| 506 |
|
|---|
| 507 |
# HTTPAccess2::SSLConfig -- SSL configuration of a client. |
|---|
| 508 |
# |
|---|
| 509 |
class SSLConfig # :nodoc: |
|---|
| 510 |
attr_reader :client_cert |
|---|
| 511 |
attr_reader :client_key |
|---|
| 512 |
attr_reader :client_ca |
|---|
| 513 |
|
|---|
| 514 |
attr_reader :verify_mode |
|---|
| 515 |
attr_reader :verify_depth |
|---|
| 516 |
attr_reader :verify_callback |
|---|
| 517 |
|
|---|
| 518 |
attr_reader :timeout |
|---|
| 519 |
attr_reader :options |
|---|
| 520 |
attr_reader :ciphers |
|---|
| 521 |
|
|---|
| 522 |
attr_reader :cert_store # don't use if you don't know what it is. |
|---|
| 523 |
|
|---|
| 524 |
def initialize(client) |
|---|
| 525 |
return unless SSLEnabled |
|---|
| 526 |
@client = client |
|---|
| 527 |
@cert_store = OpenSSL::X509::Store.new |
|---|
| 528 |
@client_cert = @client_key = @client_ca = nil |
|---|
| 529 |
@verify_mode = OpenSSL::SSL::VERIFY_PEER | |
|---|
| 530 |
OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT |
|---|
| 531 |
@verify_depth = nil |
|---|
| 532 |
@verify_callback = nil |
|---|
| 533 |
@dest = nil |
|---|
| 534 |
@timeout = nil |
|---|
| 535 |
@options = defined?(OpenSSL::SSL::OP_ALL) ? |
|---|
| 536 |
OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2 : nil |
|---|
| 537 |
@ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" |
|---|
| 538 |
end |
|---|
| 539 |
|
|---|
| 540 |
def set_client_cert_file(cert_file, key_file) |
|---|
| 541 |
@client_cert = OpenSSL::X509::Certificate.new(File.open(cert_file).read) |
|---|
| 542 |
@client_key = OpenSSL::PKey::RSA.new(File.open(key_file).read) |
|---|
| 543 |
change_notify |
|---|
| 544 |
end |
|---|
| 545 |
|
|---|
| 546 |
def set_trust_ca(trust_ca_file_or_hashed_dir) |
|---|
| 547 |
if FileTest.directory?(trust_ca_file_or_hashed_dir) |
|---|
| 548 |
@cert_store.add_path(trust_ca_file_or_hashed_dir) |
|---|
| 549 |
else |
|---|
| 550 |
@cert_store.add_file(trust_ca_file_or_hashed_dir) |
|---|
| 551 |
end |
|---|
| 552 |
change_notify |
|---|
| 553 |
end |
|---|
| 554 |
|
|---|
| 555 |
def set_crl(crl_file) |
|---|
| 556 |
crl = OpenSSL::X509::CRL.new(File.open(crl_file).read) |
|---|
| 557 |
@cert_store.add_crl(crl) |
|---|
| 558 |
@cert_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL |
|---|
| 559 |
change_notify |
|---|
| 560 |
end |
|---|
| 561 |
|
|---|
| 562 |
def client_cert=(client_cert) |
|---|
| 563 |
@client_cert = client_cert |
|---|
| 564 |
change_notify |
|---|
| 565 |
end |
|---|
| 566 |
|
|---|
| 567 |
def client_key=(client_key) |
|---|
| 568 |
@client_key = client_key |
|---|
| 569 |
change_notify |
|---|
| 570 |
end |
|---|
| 571 |
|
|---|
| 572 |
def client_ca=(client_ca) |
|---|
| 573 |
@client_ca = client_ca |
|---|
| 574 |
change_notify |
|---|
| 575 |
end |
|---|
| 576 |
|
|---|
| 577 |
def verify_mode=(verify_mode) |
|---|
| 578 |
@verify_mode = verify_mode |
|---|
| 579 |
change_notify |
|---|
| 580 |
end |
|---|
| 581 |
|
|---|
| 582 |
def verify_depth=(verify_depth) |
|---|
| 583 |
@verify_depth = verify_depth |
|---|
| 584 |
change_notify |
|---|
| 585 |
end |
|---|
| 586 |
|
|---|
| 587 |
def verify_callback=(verify_callback) |
|---|
| 588 |
@verify_callback = verify_callback |
|---|
| 589 |
change_notify |
|---|
| 590 |
end |
|---|
| 591 |
|
|---|
| 592 |
def timeout=(timeout) |
|---|
| 593 |
@timeout = timeout |
|---|
| 594 |
change_notify |
|---|
| 595 |
end |
|---|
| 596 |
|
|---|
| 597 |
def options=(options) |
|---|
| 598 |
@options = options |
|---|
| 599 |
change_notify |
|---|
| 600 |
end |
|---|
| 601 |
|
|---|
| 602 |
def ciphers=(ciphers) |
|---|
| 603 |
@ciphers = ciphers |
|---|
| 604 |
change_notify |
|---|
| 605 |
end |
|---|
| 606 |
|
|---|
| 607 |
# don't use if you don't know what it is. |
|---|
| 608 |
def cert_store=(cert_store) |
|---|
| 609 |
@cert_store = cert_store |
|---|
| 610 |
change_notify |
|---|
| 611 |
end |
|---|
| 612 |
|
|---|
| 613 |
# interfaces for SSLSocketWrap. |
|---|
| 614 |
|
|---|
| 615 |
def set_context(ctx) |
|---|
| 616 |
# Verification: Use Store#verify_callback instead of SSLContext#verify*? |
|---|
| 617 |
ctx.cert_store = @cert_store |
|---|
| 618 |
ctx.verify_mode = @verify_mode |
|---|
| 619 |
ctx.verify_depth = @verify_depth if @verify_depth |
|---|
| 620 |
ctx.verify_callback = @verify_callback || method(:default_verify_callback) |
|---|
| 621 |
# SSL config |
|---|
| 622 |
ctx.cert = @client_cert |
|---|
| 623 |
ctx.key = @client_key |
|---|
| 624 |
ctx.client_ca = @client_ca |
|---|
| 625 |
ctx.timeout = @timeout |
|---|
| 626 |
ctx.options = @options |
|---|
| 627 |
ctx.ciphers = @ciphers |
|---|
| 628 |
end |
|---|
| 629 |
|
|---|
| 630 |
# this definition must match with the one in ext/openssl/lib/openssl/ssl.rb |
|---|
| 631 |
def post_connection_check(peer_cert, hostname) |
|---|
| 632 |
check_common_name = true |
|---|
| 633 |
cert = peer_cert |
|---|
| 634 |
cert.extensions.each{|ext| |
|---|
| 635 |
next if ext.oid != "subjectAltName" |
|---|
| 636 |
ext.value.split(/,\s+/).each{|general_name| |
|---|
| 637 |
if /\ADNS:(.*)/ =~ general_name |
|---|
| 638 |
check_common_name = false |
|---|
| 639 |
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+") |
|---|
| 640 |
return true if /\A#{reg}\z/i =~ hostname |
|---|
| 641 |
elsif /\AIP Address:(.*)/ =~ general_name |
|---|
| 642 |
check_common_name = false |
|---|
| 643 |
return true if $1 == hostname |
|---|
| 644 |
end |
|---|
| 645 |
} |
|---|
| 646 |
} |
|---|
| 647 |
if check_common_name |
|---|
| 648 |
cert.subject.to_a.each{|oid, value| |
|---|
| 649 |
if oid == "CN" && value.casecmp(hostname) == 0 |
|---|
| 650 |
return true |
|---|
| 651 |
end |
|---|
| 652 |
} |
|---|
| 653 |
end |
|---|
| 654 |
raise OpenSSL::SSL::SSLError, "hostname not match" |
|---|
| 655 |
end |
|---|
| 656 |
|
|---|
| 657 |
# Default callback for verification: only dumps error. |
|---|
| 658 |
def default_verify_callback(is_ok, ctx) |
|---|
| 659 |
if $DEBUG |
|---|
| 660 |
puts "#{ is_ok ? 'ok' : 'ng' }: #{ctx.current_cert.subject}" |
|---|
| 661 |
end |
|---|
| 662 |
if !is_ok |
|---|
| 663 |
depth = ctx.error_depth |
|---|
| 664 |
code = ctx.error |
|---|
| 665 |
msg = ctx.error_string |
|---|
| 666 |
STDERR.puts "at depth #{depth} - #{code}: #{msg}" |
|---|
| 667 |
end |
|---|
| 668 |
is_ok |
|---|
| 669 |
end |
|---|
| 670 |
|
|---|
| 671 |
# Sample callback method: CAUTION: does not check CRL/ARL. |
|---|
| 672 |
def sample_verify_callback(is_ok, ctx) |
|---|
| 673 |
unless is_ok |
|---|
| 674 |
depth = ctx.error_depth |
|---|
| 675 |
code = ctx.error |
|---|
| 676 |
msg = ctx.error_string |
|---|
| 677 |
STDERR.puts "at depth #{depth} - #{code}: #{msg}" if $DEBUG |
|---|
| 678 |
return false |
|---|
| 679 |
end |
|---|
| 680 |
|
|---|
| 681 |
cert = ctx.current_cert |
|---|
| 682 |
self_signed = false |
|---|
| 683 |
ca = false |
|---|
| 684 |
pathlen = nil |
|---|
| 685 |
server_auth = true |
|---|
| 686 |
self_signed = (cert.subject.cmp(cert.issuer) == 0) |
|---|
| 687 |
|
|---|
| 688 |
# Check extensions whatever its criticality is. (sample) |
|---|
| 689 |
cert.extensions.each do |ex| |
|---|
| 690 |
case ex.oid |
|---|
| 691 |
when 'basicConstraints' |
|---|
| 692 |
/CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value |
|---|
| 693 |
ca = ($1 == 'TRUE') |
|---|
| 694 |
pathlen = $2.to_i |
|---|
| 695 |
when 'keyUsage' |
|---|
| 696 |
usage = ex.value.split(/\s*,\s*/) |
|---|
| 697 |
ca = usage.include?('Certificate Sign') |
|---|
| 698 |
server_auth = usage.include?('Key Encipherment') |
|---|
| 699 |
when 'extendedKeyUsage' |
|---|
| 700 |
usage = ex.value.split(/\s*,\s*/) |
|---|
| 701 |
server_auth = usage.include?('Netscape Server Gated Crypto') |
|---|
| 702 |
when 'nsCertType' |
|---|
| 703 |
usage = ex.value.split(/\s*,\s*/) |
|---|
| 704 |
ca = usage.include?('SSL CA') |
|---|
| 705 |
server_auth = usage.include?('SSL Server') |
|---|
| 706 |
end |
|---|
| 707 |
end |
|---|
| 708 |
|
|---|
| 709 |
if self_signed |
|---|
| 710 |
STDERR.puts 'self signing CA' if $DEBUG |
|---|
| 711 |
return true |
|---|
| 712 |
elsif ca |
|---|
| 713 |
STDERR.puts 'middle level CA' if $DEBUG |
|---|
| 714 |
return true |
|---|
| 715 |
elsif server_auth |
|---|
| 716 |
STDERR.puts 'for server authentication' if $DEBUG |
|---|
| 717 |
return true |
|---|
| 718 |
end |
|---|
| 719 |
|
|---|
| 720 |
return false |
|---|
| 721 |
end |
|---|
| 722 |
|
|---|
| 723 |
private |
|---|
| 724 |
|
|---|
| 725 |
def change_notify |
|---|
| 726 |
@client.reset_all |
|---|
| 727 |
end |
|---|
| 728 |
end |
|---|
| 729 |
|
|---|
| 730 |
|
|---|
| 731 |
# HTTPAccess2::BasicAuth -- BasicAuth repository. |
|---|
| 732 |
# |
|---|
| 733 |
class BasicAuth # :nodoc: |
|---|
| 734 |
def initialize(client) |
|---|
| 735 |
@client = client |
|---|
| 736 |
@auth = {} |
|---|
| 737 |
end |
|---|
| 738 |
|
|---|
| 739 |
def set(uri, user_id, passwd) |
|---|
| 740 |
uri = uri.clone |
|---|
| 741 |
uri.path = uri.path.sub(/\/[^\/]*$/, '/') |
|---|
| 742 |
@auth[uri] = ["#{user_id}:#{passwd}"].pack('m').strip |
|---|
| 743 |
@client.reset_all |
|---|
| 744 |
end |
|---|
| 745 |
|
|---|
| 746 |
def get(uri) |
|---|
| 747 |
@auth.each do |realm_uri, cred| |
|---|
| 748 |
if ((realm_uri.host == uri.host) and |
|---|
| 749 |
(realm_uri.scheme == uri.scheme) and |
|---|
| 750 |
(realm_uri.port == uri.port) and |
|---|
| 751 |
uri.path.upcase.index(realm_uri.path.upcase) == 0) |
|---|
| 752 |
return cred |
|---|
| 753 |
end |
|---|
| 754 |
end |
|---|
| 755 |
nil |
|---|
| 756 |
end |
|---|
| 757 |
end |
|---|
| 758 |
|
|---|
| 759 |
|
|---|
| 760 |
# HTTPAccess2::Site -- manage a site(host and port) |
|---|
| 761 |
# |
|---|
| 762 |
class Site # :nodoc: |
|---|
| 763 |
attr_accessor :scheme |
|---|
| 764 |
attr_accessor :host |
|---|
| 765 |
attr_reader :port |
|---|
| 766 |
|
|---|
| 767 |
def initialize(uri = nil) |
|---|
| 768 |
if uri |
|---|
| 769 |
@scheme = uri.scheme |
|---|
| 770 |
@host = uri.host |
|---|
| 771 |
@port = uri.port.to_i |
|---|
| 772 |
else |
|---|
| 773 |
@scheme = 'tcp' |
|---|
| 774 |
@host = '0.0.0.0' |
|---|
| 775 |
@port = 0 |
|---|
| 776 |
end |
|---|
| 777 |
end |
|---|
| 778 |
|
|---|
| 779 |
def addr |
|---|
| 780 |
"#{@scheme}://#{@host}:#{@port.to_s}" |
|---|
| 781 |
end |
|---|
| 782 |
|
|---|
| 783 |
def port=(port) |
|---|
| 784 |
@port = port.to_i |
|---|
| 785 |
end |
|---|
| 786 |
|
|---|
| 787 |
def ==(rhs) |
|---|
| 788 |
if rhs.is_a?(Site) |
|---|
| 789 |
((@scheme == rhs.scheme) and (@host == rhs.host) and (@port == rhs.port)) |
|---|
| 790 |
else |
|---|
| 791 |
false |
|---|
| 792 |
end |
|---|
| 793 |
end |
|---|
| 794 |
|
|---|
| 795 |
def to_s |
|---|
| 796 |
addr |
|---|
| 797 |
end |
|---|
| 798 |
|
|---|
| 799 |
def inspect |
|---|
| 800 |
sprintf("#<%s:0x%x %s>", self.class.name, __id__, addr) |
|---|
| 801 |
end |
|---|
| 802 |
end |
|---|
| 803 |
|
|---|
| 804 |
|
|---|
| 805 |
# HTTPAccess2::Connection -- magage a connection(one request and response to it). |
|---|
| 806 |
# |
|---|
| 807 |
class Connection # :nodoc: |
|---|
| 808 |
attr_accessor :async_thread |
|---|
| 809 |
|
|---|
| 810 |
def initialize(header_queue = [], body_queue = []) |
|---|
| 811 |
@headers = header_queue |
|---|
| 812 |
@body = body_queue |
|---|
| 813 |
@async_thread = nil |
|---|
| 814 |
@queue = Queue.new |
|---|
| 815 |
end |
|---|
| 816 |
|
|---|
| 817 |
def finished? |
|---|
| 818 |
if !@async_thread |
|---|
| 819 |
# Not in async mode. |
|---|
| 820 |
true |
|---|
| 821 |
elsif @async_thread.alive? |
|---|
| 822 |
# Working... |
|---|
| 823 |
false |
|---|
| 824 |
else |
|---|
| 825 |
# Async thread have been finished. |
|---|
| 826 |
@async_thread.join |
|---|
| 827 |
true |
|---|
| 828 |
end |
|---|
| 829 |
end |
|---|
| 830 |
|
|---|
| 831 |
def pop |
|---|
| 832 |
@queue.pop |
|---|
| 833 |
end |
|---|
| 834 |
|
|---|
| 835 |
def push(result) |
|---|
| 836 |
@queue.push(result) |
|---|
| 837 |
end |
|---|
| 838 |
|
|---|
| 839 |
def join |
|---|
| 840 |
unless @async_thread |
|---|
| 841 |
false |
|---|
| 842 |
else |
|---|
| 843 |
@async_thread.join |
|---|
| 844 |
end |
|---|
| 845 |
end |
|---|
| 846 |
end |
|---|
| 847 |
|
|---|
| 848 |
|
|---|
| 849 |
# HTTPAccess2::SessionManager -- manage several sessions. |
|---|
| 850 |
# |
|---|
| 851 |
class SessionManager # :nodoc: |
|---|
| 852 |
attr_accessor :agent_name # Name of this client. |
|---|
| 853 |
attr_accessor :from # Owner of this client. |
|---|
| 854 |
|
|---|
| 855 |
attr_accessor :protocol_version # Requested protocol version |
|---|
| 856 |
attr_accessor :chunk_size # Chunk size for chunked request |
|---|
| 857 |
attr_accessor :debug_dev # Device for dumping log for debugging |
|---|
| 858 |
attr_accessor :socket_sync # Boolean value for Socket#sync |
|---|
| 859 |
|
|---|
| 860 |
# These parameters are not used now... |
|---|
| 861 |
attr_accessor :connect_timeout |
|---|
| 862 |
attr_accessor :connect_retry # Maximum retry count. 0 for infinite. |
|---|
| 863 |
attr_accessor :send_timeout |
|---|
| 864 |
attr_accessor :receive_timeout |
|---|
| 865 |
attr_accessor :read_block_size |
|---|
| 866 |
|
|---|
| 867 |
attr_accessor :ssl_config |
|---|
| 868 |
|
|---|
| 869 |
def initialize |
|---|
| 870 |
@proxy = nil |
|---|
| 871 |
|
|---|
| 872 |
@agent_name = nil |
|---|
| 873 |
@from = nil |
|---|
| 874 |
|
|---|
| 875 |
@protocol_version = nil |
|---|
| 876 |
@debug_dev = nil |
|---|
| 877 |
@socket_sync = true |
|---|
| 878 |
@chunk_size = 4096 |
|---|
| 879 |
|
|---|
| 880 |
@connect_timeout = 60 |
|---|
| 881 |
@connect_retry = 1 |
|---|
| 882 |
@send_timeout = 120 |
|---|
| 883 |
@receive_timeout = 60 # For each read_block_size bytes |
|---|
| 884 |
@read_block_size = 8192 |
|---|
| 885 |
|
|---|
| 886 |
@ssl_config = nil |
|---|
| 887 |
|
|---|
| 888 |
@sess_pool = [] |
|---|
| 889 |
@sess_pool_mutex = Mutex.new |
|---|
| 890 |
end |
|---|
| 891 |
|
|---|
| 892 |
def proxy=(proxy) |
|---|
| 893 |
if proxy.nil? |
|---|
| 894 |
@proxy = nil |
|---|
| 895 |
else |
|---|
| 896 |
@proxy = Site.new(proxy) |
|---|
| 897 |
end |
|---|
| 898 |
end |
|---|
| 899 |
|
|---|
| 900 |
def query(req, proxy) |
|---|
| 901 |
req.body.chunk_size = @chunk_size |
|---|
| 902 |
dest_site = Site.new(req.header.request_uri) |
|---|
| 903 |
proxy_site = if proxy |
|---|
| 904 |
Site.new(proxy) |
|---|
| 905 |
else |
|---|
| 906 |
@proxy |
|---|
| 907 |
end |
|---|
| 908 |
sess = open(dest_site, proxy_site) |
|---|
| 909 |
begin |
|---|
| 910 |
sess.query(req) |
|---|
| 911 |
rescue |
|---|
| 912 |
sess.close |
|---|
| 913 |
raise |
|---|
| 914 |
end |
|---|
| 915 |
sess |
|---|
| 916 |
end |
|---|
| 917 |
|
|---|
| 918 |
def reset(uri) |
|---|
| 919 |
site = Site.new(uri) |
|---|
| 920 |
close(site) |
|---|
| 921 |
end |
|---|
| 922 |
|
|---|
| 923 |
def reset_all |
|---|
| 924 |
close_all |
|---|
| 925 |
end |
|---|
| 926 |
|
|---|
| 927 |
def keep(sess) |
|---|
| 928 |
add_cached_session(sess) |
|---|
| 929 |
end |
|---|
| 930 |
|
|---|
| 931 |
private |
|---|
| 932 |
|
|---|
| 933 |
def open(dest, proxy = nil) |
|---|
| 934 |
sess = nil |
|---|
| 935 |
if cached = get_cached_session(dest) |
|---|
| 936 |
sess = cached |
|---|
| 937 |
else |
|---|
| 938 |
sess = Session.new(dest, @agent_name, @from) |
|---|
| 939 |
sess.proxy = proxy |
|---|
| 940 |
sess.socket_sync = @socket_sync |
|---|
| 941 |
sess.requested_version = @protocol_version if @protocol_version |
|---|
| 942 |
sess.connect_timeout = @connect_timeout |
|---|
| 943 |
sess.connect_retry = @connect_retry |
|---|
| 944 |
sess.send_timeout = @send_timeout |
|---|
| 945 |
sess.receive_timeout = @receive_timeout |
|---|
| 946 |
sess.read_block_size = @read_block_size |
|---|
| 947 |
sess.ssl_config = @ssl_config |
|---|
| 948 |
sess.debug_dev = @debug_dev |
|---|
| 949 |
end |
|---|
| 950 |
sess |
|---|
| 951 |
end |
|---|
| 952 |
|
|---|
| 953 |
def close_all |
|---|
| 954 |
each_sess do |sess| |
|---|
| 955 |
sess.close |
|---|
| 956 |
end |
|---|
| 957 |
@sess_pool.clear |
|---|
| 958 |
end |
|---|
| 959 |
|
|---|
| 960 |
def close(dest) |
|---|
| 961 |
if cached = get_cached_session(dest) |
|---|
| 962 |
cached.close |
|---|
| 963 |
true |
|---|
| 964 |
else |
|---|
| 965 |
false |
|---|
| 966 |
end |
|---|
| 967 |
end |
|---|
| 968 |
|
|---|
| 969 |
def get_cached_session(dest) |
|---|
| 970 |
cached = nil |
|---|
| 971 |
@sess_pool_mutex.synchronize do |
|---|
| 972 |
new_pool = [] |
|---|
| 973 |
@sess_pool.each do |s| |
|---|
| 974 |
if s.dest == dest |
|---|
| 975 |
cached = s |
|---|
| 976 |
else |
|---|
| 977 |
new_pool << s |
|---|
| 978 |
end |
|---|
| 979 |
end |
|---|
| 980 |
@sess_pool = new_pool |
|---|
| 981 |
end |
|---|
| 982 |
cached |
|---|
| 983 |
end |
|---|
| 984 |
|
|---|
| 985 |
def add_cached_session(sess) |
|---|
| 986 |
@sess_pool_mutex.synchronize do |
|---|
| 987 |
@sess_pool << sess |
|---|
| 988 |
end |
|---|
| 989 |
end |
|---|
| 990 |
|
|---|
| 991 |
def each_sess |
|---|
| 992 |
@sess_pool_mutex.synchronize do |
|---|
| 993 |
@sess_pool.each do |sess| |
|---|
| 994 |
yield(sess) |
|---|
| 995 |
end |
|---|
| 996 |
end |
|---|
| 997 |
end |
|---|
| 998 |
end |
|---|
| 999 |
|
|---|
| 1000 |
|
|---|
| 1001 |
# HTTPAccess2::SSLSocketWrap |
|---|
| 1002 |
# |
|---|
| 1003 |
class SSLSocketWrap |
|---|
| 1004 |
def initialize(socket, context, debug_dev = nil) |
|---|
| 1005 |
unless SSLEnabled |
|---|
| 1006 |
raise RuntimeError.new( |
|---|
| 1007 |
"Ruby/OpenSSL module is required for https access.") |
|---|
| 1008 |
end |
|---|
| 1009 |
@context = context |
|---|
| 1010 |
@socket = socket |
|---|
| 1011 |
@ssl_socket = create_ssl_socket(@socket) |
|---|
| 1012 |
@debug_dev = debug_dev |
|---|
| 1013 |
end |
|---|
| 1014 |
|
|---|
| 1015 |
def ssl_connect |
|---|
| 1016 |
@ssl_socket.connect |
|---|
| 1017 |
end |
|---|
| 1018 |
|
|---|
| 1019 |
def post_connection_check(host) |
|---|
| 1020 |
verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE |
|---|
| 1021 |
if verify_mode == OpenSSL::SSL::VERIFY_NONE |
|---|
| 1022 |
return |
|---|
| 1023 |
elsif @ssl_socket.peer_cert.nil? and |
|---|
| 1024 |
check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT) |
|---|
| 1025 |
raise OpenSSL::SSL::SSLError, "no peer cert" |
|---|
| 1026 |
end |
|---|
| 1027 |
hostname = host.host |
|---|
| 1028 |
if @ssl_socket.respond_to?(:post_connection_check) |
|---|
| 1029 |
@ssl_socket.post_connection_check(hostname) |
|---|
| 1030 |
end |
|---|
| 1031 |
@context.post_connection_check(@ssl_socket.peer_cert, hostname) |
|---|
| 1032 |
end |
|---|
| 1033 |
|
|---|
| 1034 |
def peer_cert |
|---|
| 1035 |
@ssl_socket.peer_cert |
|---|
| 1036 |
end |
|---|
| 1037 |
|
|---|
| 1038 |
def addr |
|---|
| 1039 |
@socket.addr |
|---|
| 1040 |
end |
|---|
| 1041 |
|
|---|
| 1042 |
def close |
|---|
| 1043 |
@ssl_socket.close |
|---|
| 1044 |
@socket.close |
|---|
| 1045 |
end |
|---|
| 1046 |
|
|---|
| 1047 |
def closed? |
|---|
| 1048 |
@socket.closed? |
|---|
| 1049 |
end |
|---|
| 1050 |
|
|---|
| 1051 |
def eof? |
|---|
| 1052 |
@ssl_socket.eof? |
|---|
| 1053 |
end |
|---|
| 1054 |
|
|---|
| 1055 |
def gets(*args) |
|---|
| 1056 |
str = @ssl_socket.gets(*args) |
|---|
| 1057 |
@debug_dev << str if @debug_dev |
|---|
| 1058 |
str |
|---|
| 1059 |
end |
|---|
| 1060 |
|
|---|
| 1061 |
def read(*args) |
|---|
| 1062 |
str = @ssl_socket.read(*args) |
|---|
| 1063 |
@debug_dev << str if @debug_dev |
|---|
| 1064 |
str |
|---|
| 1065 |
end |
|---|
| 1066 |
|
|---|
| 1067 |
def <<(str) |
|---|
| 1068 |
rv = @ssl_socket.write(str) |
|---|
| 1069 |
@debug_dev << str if @debug_dev |
|---|
| 1070 |
rv |
|---|
| 1071 |
end |
|---|
| 1072 |
|
|---|
| 1073 |
def flush |
|---|
| 1074 |
@ssl_socket.flush |
|---|
| 1075 |
end |
|---|
| 1076 |
|
|---|
| 1077 |
def sync |
|---|
| 1078 |
@ssl_socket.sync |
|---|
| 1079 |
end |
|---|
| 1080 |
|
|---|
| 1081 |
def sync=(sync) |
|---|
| 1082 |
@ssl_socket.sync = sync |
|---|
| 1083 |
end |
|---|
| 1084 |
|
|---|
| 1085 |
private |
|---|
| 1086 |
|
|---|
| 1087 |
def check_mask(value, mask) |
|---|
| 1088 |
value & mask == mask |
|---|
| 1089 |
end |
|---|
| 1090 |
|
|---|
| 1091 |
def create_ssl_socket(socket) |
|---|
| 1092 |
ssl_socket = nil |
|---|
| 1093 |
if OpenSSL::SSL.const_defined?("SSLContext") |
|---|
| 1094 |
ctx = OpenSSL::SSL::SSLContext.new |
|---|
| 1095 |
@context.set_context(ctx) |
|---|
| 1096 |
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx) |
|---|
| 1097 |
else |
|---|
| 1098 |
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket) |
|---|
| 1099 |
@context.set_context(ssl_socket) |
|---|
| 1100 |
end |
|---|
| 1101 |
ssl_socket |
|---|
| 1102 |
end |
|---|
| 1103 |
end |
|---|
| 1104 |
|
|---|
| 1105 |
|
|---|
| 1106 |
# HTTPAccess2::DebugSocket -- debugging support |
|---|
| 1107 |
# |
|---|
| 1108 |
class DebugSocket < TCPSocket |
|---|
| 1109 |
attr_accessor :debug_dev # Device for logging. |
|---|
| 1110 |
|
|---|
| 1111 |
class << self |
|---|
| 1112 |
def create_socket(host, port, debug_dev) |
|---|
| 1113 |
debug_dev << "! CONNECT TO #{host}:#{port}\n" |
|---|
| 1114 |
socket = new(host, port) |
|---|
| 1115 |
socket.debug_dev = debug_dev |
|---|
| 1116 |
socket.log_connect |
|---|
| 1117 |
socket |
|---|
| 1118 |
end |
|---|
| 1119 |
|
|---|
| 1120 |
private :new |
|---|
| 1121 |
end |
|---|
| 1122 |
|
|---|
| 1123 |
def initialize(*args) |
|---|
| 1124 |
super |
|---|
| 1125 |
@debug_dev = nil |
|---|
| 1126 |
end |
|---|
| 1127 |
|
|---|
| 1128 |
def log_connect |
|---|
| 1129 |
@debug_dev << '! CONNECTION ESTABLISHED' << "\n" |
|---|
| 1130 |
end |
|---|
| 1131 |
|
|---|
| 1132 |
def close |
|---|
| 1133 |
super |
|---|
| 1134 |
@debug_dev << '! CONNECTION CLOSED' << "\n" |
|---|
| 1135 |
end |
|---|
| 1136 |
|
|---|
| 1137 |
def gets(*args) |
|---|
| 1138 |
str = super |
|---|
| 1139 |
@debug_dev << str if str |
|---|
| 1140 |
str |
|---|
| 1141 |
end |
|---|
| 1142 |
|
|---|
| 1143 |
def read(*args) |
|---|
| 1144 |
str = super |
|---|
| 1145 |
@debug_dev << str if str |
|---|
| 1146 |
str |
|---|
| 1147 |
end |
|---|
| 1148 |
|
|---|
| 1149 |
def <<(str) |
|---|
| 1150 |
super |
|---|
| 1151 |
@debug_dev << str |
|---|
| 1152 |
end |
|---|
| 1153 |
end |
|---|
| 1154 |
|
|---|
| 1155 |
|
|---|
| 1156 |
# HTTPAccess2::Session -- manage http session with one site. |
|---|
| 1157 |
# One or more TCP sessions with the site may be created. |
|---|
| 1158 |
# Only 1 TCP session is live at the same time. |
|---|
| 1159 |
# |
|---|
| 1160 |
class Session # :nodoc: |
|---|
| 1161 |
|
|---|
| 1162 |
class Error < StandardError # :nodoc: |
|---|
| 1163 |
end |
|---|
| 1164 |
|
|---|
| 1165 |
class InvalidState < Error # :nodoc: |
|---|
| 1166 |
end |
|---|
| 1167 |
|
|---|
| 1168 |
class BadResponse < Error # :nodoc: |
|---|
| 1169 |
end |
|---|
| 1170 |
|
|---|
| 1171 |
class KeepAliveDisconnected < Error # :nodoc: |
|---|
| 1172 |
end |
|---|
| 1173 |
|
|---|
| 1174 |
attr_reader :dest # Destination site |
|---|
| 1175 |
attr_reader :src # Source site |
|---|
| 1176 |
attr_accessor :proxy # Proxy site |
|---|
| 1177 |
attr_accessor :socket_sync # Boolean value for Socket#sync |
|---|
| 1178 |
|
|---|
| 1179 |
attr_accessor :requested_version # Requested protocol version |
|---|
| 1180 |
|
|---|
| 1181 |
attr_accessor :debug_dev # Device for dumping log for debugging |
|---|
| 1182 |
|
|---|
| 1183 |
# These session parameters are not used now... |
|---|
| 1184 |
attr_accessor :connect_timeout |
|---|
| 1185 |
attr_accessor :connect_retry |
|---|
| 1186 |
attr_accessor :send_timeout |
|---|
| 1187 |
attr_accessor :receive_timeout |
|---|
| 1188 |
attr_accessor :read_block_size |
|---|
| 1189 |
|
|---|
| 1190 |
attr_accessor :ssl_config |
|---|
| 1191 |
|
|---|
| 1192 |
def initialize(dest, user_agent, from) |
|---|
| 1193 |
@dest = dest |
|---|
| 1194 |
@src = Site.new |
|---|
| 1195 |
@proxy = nil |
|---|
| 1196 |
@socket_sync = true |
|---|
| 1197 |
@requested_version = nil |
|---|
| 1198 |
|
|---|
| 1199 |
@debug_dev = nil |
|---|
| 1200 |
|
|---|
| 1201 |
@connect_timeout = nil |
|---|
| 1202 |
@connect_retry = 1 |
|---|
| 1203 |
@send_timeout = nil |
|---|
| 1204 |
@receive_timeout = nil |
|---|
| 1205 |
@read_block_size = nil |
|---|
| 1206 |
|
|---|
| 1207 |
@ssl_config = nil |
|---|
| 1208 |
|
|---|
| 1209 |
@user_agent = user_agent |
|---|
| 1210 |
@from = from |
|---|
| 1211 |
@state = :INIT |
|---|
| 1212 |
|
|---|
| 1213 |
@requests = [] |
|---|
| 1214 |
|
|---|
| 1215 |
@status = nil |
|---|
| 1216 |
@reason = nil |
|---|
| 1217 |
@headers = [] |
|---|
| 1218 |
|
|---|
| 1219 |
@socket = nil |
|---|
| 1220 |
end |
|---|
| 1221 |
|
|---|
| 1222 |
# Send a request to the server |
|---|
| 1223 |
def query(req) |
|---|
| 1224 |
connect() if @state == :INIT |
|---|
| 1225 |
begin |
|---|
| 1226 |
timeout(@send_timeout) do |
|---|
| 1227 |
set_header(req) |
|---|
| 1228 |
req.dump(@socket) |
|---|
| 1229 |
# flush the IO stream as IO::sync mode is false |
|---|
| 1230 |
@socket.flush unless @socket_sync |
|---|
| 1231 |
end |
|---|
| 1232 |
rescue Errno::ECONNABORTED, Errno::ECONNRESET |
|---|
| 1233 |
close |
|---|
| 1234 |
raise KeepAliveDisconnected.new |
|---|
| 1235 |
rescue |
|---|
| 1236 |
if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError) |
|---|
| 1237 |
raise KeepAliveDisconnected.new |
|---|
| 1238 |
elsif $!.is_a?(TimeoutError) |
|---|
| 1239 |
close |
|---|
| 1240 |
raise |
|---|
| 1241 |
else |
|---|
| 1242 |
raise |
|---|
| 1243 |
end |
|---|
| 1244 |
end |
|---|
| 1245 |
|
|---|
| 1246 |
@state = :META if @state == :WAIT |
|---|
| 1247 |
@next_connection = nil |
|---|
| 1248 |
@requests.push(req) |
|---|
| 1249 |
end |
|---|
| 1250 |
|
|---|
| 1251 |
def close |
|---|
| 1252 |
if !@socket.nil? and !@socket.closed? |
|---|
| 1253 |
@socket.flush |
|---|
| 1254 |
@socket.close |
|---|
| 1255 |
end |
|---|
| 1256 |
@state = :INIT |
|---|
| 1257 |
end |
|---|
| 1258 |
|
|---|
| 1259 |
def closed? |
|---|
| 1260 |
@state == :INIT |
|---|
| 1261 |
end |
|---|
| 1262 |
|
|---|
| 1263 |
def get_status |
|---|
| 1264 |
version = status = reason = nil |
|---|
| 1265 |
begin |
|---|
| 1266 |
if @state != :META |
|---|
| 1267 |
raise RuntimeError.new("get_status must be called at the beginning of a session.") |
|---|
| 1268 |
end |
|---|
| 1269 |
version, status, reason = read_header() |
|---|
| 1270 |
rescue |
|---|
| 1271 |
close |
|---|
| 1272 |
raise |
|---|
| 1273 |
end |
|---|
| 1274 |
return version, status, reason |
|---|
| 1275 |
end |
|---|
| 1276 |
|
|---|
| 1277 |
def get_header(&block) |
|---|
| 1278 |
begin |
|---|
| 1279 |
read_header() if @state == :META |
|---|
| 1280 |
rescue |
|---|
| 1281 |
close |
|---|
| 1282 |
raise |
|---|
| 1283 |
end |
|---|
| 1284 |
if block |
|---|
| 1285 |
@headers.each do |line| |
|---|
| 1286 |
block.call(line) |
|---|
| 1287 |
end |
|---|
| 1288 |
else |
|---|
| 1289 |
@headers |
|---|
| 1290 |
end |
|---|
| 1291 |
end |
|---|
| 1292 |
|
|---|
| 1293 |
def eof? |
|---|
| 1294 |
if !@content_length.nil? |
|---|
| 1295 |
@content_length == 0 |
|---|
| 1296 |
elsif @readbuf.length > 0 |
|---|
| 1297 |
false |
|---|
| 1298 |
else |
|---|
| 1299 |
@socket.closed? or @socket.eof? |
|---|
| 1300 |
end |
|---|
| 1301 |
end |
|---|
| 1302 |
|
|---|
| 1303 |
def get_data(&block) |
|---|
| 1304 |
begin |
|---|
| 1305 |
read_header() if @state == :META |
|---|
| 1306 |
return nil if @state != :DATA |
|---|
| 1307 |
unless @state == :DATA |
|---|
| 1308 |
raise InvalidState.new('state != DATA') |
|---|
| 1309 |
end |
|---|
| 1310 |
data = nil |
|---|
| 1311 |
if block |
|---|
| 1312 |
until eof? |
|---|
| 1313 |
begin |
|---|
| 1314 |
timeout(@receive_timeout) do |
|---|
| 1315 |
data = read_body() |
|---|
| 1316 |
end |
|---|
| 1317 |
rescue TimeoutError |
|---|
| 1318 |
raise |
|---|
| 1319 |
end |
|---|
| 1320 |
block.call(data) if data |
|---|
| 1321 |
end |
|---|
| 1322 |
data = nil # Calling with block returns nil. |
|---|
| 1323 |
else |
|---|
| 1324 |
begin |
|---|
| 1325 |
timeout(@receive_timeout) do |
|---|
| 1326 |
data = read_body() |
|---|
| 1327 |
end |
|---|
| 1328 |
rescue TimeoutError |
|---|
| 1329 |
raise |
|---|
| 1330 |
end |
|---|
| 1331 |
end |
|---|
| 1332 |
rescue |
|---|
| 1333 |
close |
|---|
| 1334 |
raise |
|---|
| 1335 |
end |
|---|
| 1336 |
if eof? |
|---|
| 1337 |
if @next_connection |
|---|
| 1338 |
@state = :WAIT |
|---|
| 1339 |
else |
|---|
| 1340 |
close |
|---|
| 1341 |
end |
|---|
| 1342 |
end |
|---|
| 1343 |
data |
|---|
| 1344 |
end |
|---|
| 1345 |
|
|---|
| 1346 |
private |
|---|
| 1347 |
|
|---|
| 1348 |
LibNames = "(#{RCS_FILE}/#{RCS_REVISION}, #{RUBY_VERSION_STRING})" |
|---|
| 1349 |
|
|---|
| 1350 |
def set_header(req) |
|---|
| 1351 |
req.version = @requested_version if @requested_version |
|---|
| 1352 |
if @user_agent |
|---|
| 1353 |
req.header.set('User-Agent', "#{@user_agent} #{LibNames}") |
|---|
| 1354 |
end |
|---|
| 1355 |
if @from |
|---|
| 1356 |
req.header.set('From', @from) |
|---|
| 1357 |
end |
|---|
| 1358 |
req.header.set('Date', HTTP.http_date(Time.now)) |
|---|
| 1359 |
end |
|---|
| 1360 |
|
|---|
| 1361 |
# Connect to the server |
|---|
| 1362 |
def connect |
|---|
| 1363 |
site = @proxy || @dest |
|---|
| 1364 |
begin |
|---|
| 1365 |
retry_number = 0 |
|---|
| 1366 |
timeout(@connect_timeout) do |
|---|
| 1367 |
@socket = create_socket(site) |
|---|
| 1368 |
begin |
|---|
| 1369 |
@src.host = @socket.addr[3] |
|---|
| 1370 |
@src.port = @socket.addr[1] |
|---|
| 1371 |
rescue SocketError |
|---|
| 1372 |
# to avoid IPSocket#addr problem on Mac OS X 10.3 + ruby-1.8.1. |
|---|
| 1373 |
# cf. [ruby-talk:84909], [ruby-talk:95827] |
|---|
| 1374 |
end |
|---|
| 1375 |
if @dest.scheme == 'https' |
|---|
| 1376 |
@socket = create_ssl_socket(@socket) |
|---|
| 1377 |
connect_ssl_proxy(@socket) if @proxy |
|---|
| 1378 |
@socket.ssl_connect |
|---|
| 1379 |
@socket.post_connection_check(@dest) |
|---|
| 1380 |
end |
|---|
| 1381 |
# Use Ruby internal buffering instead of passing data immediatly |
|---|
| 1382 |
# to the underlying layer |
|---|
| 1383 |
# => we need to to call explicitely flush on the socket |
|---|
| 1384 |
@socket.sync = @socket_sync |
|---|
| 1385 |
end |
|---|
| 1386 |
rescue TimeoutError |
|---|
| 1387 |
if @connect_retry == 0 |
|---|
| 1388 |
retry |
|---|
| 1389 |
else |
|---|
| 1390 |
retry_number += 1 |
|---|
| 1391 |
retry if retry_number < @connect_retry |
|---|
| 1392 |
end |
|---|
| 1393 |
close |
|---|
| 1394 |
raise |
|---|
| 1395 |
end |
|---|
| 1396 |
|
|---|
| 1397 |
@state = :WAIT |
|---|
| 1398 |
@readbuf = '' |
|---|
| 1399 |
end |
|---|
| 1400 |
|
|---|
| 1401 |
def create_socket(site) |
|---|
| 1402 |
begin |
|---|
| 1403 |
if @debug_dev |
|---|
| 1404 |
DebugSocket.create_socket(site.host, site.port, @debug_dev) |
|---|
| 1405 |
else |
|---|
| 1406 |
TCPSocket.new(site.host, site.port) |
|---|
| 1407 |
end |
|---|
| 1408 |
rescue SystemCallError => e |
|---|
| 1409 |
e.message << " (#{site.host}, ##{site.port})" |
|---|
| 1410 |
raise |
|---|
| 1411 |
end |
|---|
| 1412 |
end |
|---|
| 1413 |
|
|---|
| 1414 |
# wrap socket with OpenSSL. |
|---|
| 1415 |
def create_ssl_socket(raw_socket) |
|---|
| 1416 |
SSLSocketWrap.new(raw_socket, @ssl_config, (DEBUG_SSL ? @debug_dev : nil)) |
|---|
| 1417 |
end |
|---|
| 1418 |
|
|---|
| 1419 |
def connect_ssl_proxy(socket) |
|---|
| 1420 |
socket << sprintf("CONNECT %s:%s HTTP/1.1\r\n\r\n", @dest.host, @dest.port) |
|---|
| 1421 |
parse_header(socket) |
|---|
| 1422 |
unless @status == 200 |
|---|
| 1423 |
raise BadResponse.new( |
|---|
| 1424 |
"connect to ssl proxy failed with status #{@status} #{@reason}") |
|---|
| 1425 |
end |
|---|
| 1426 |
end |
|---|
| 1427 |
|
|---|
| 1428 |
# Read status block. |
|---|
| 1429 |
def read_header |
|---|
| 1430 |
if @state == :DATA |
|---|
| 1431 |
get_data {} |
|---|
| 1432 |
check_state() |
|---|
| 1433 |
end |
|---|
| 1434 |
unless @state == :META |
|---|
| 1435 |
raise InvalidState, 'state != :META' |
|---|
| 1436 |
end |
|---|
| 1437 |
parse_header(@socket) |
|---|
| 1438 |
@content_length = nil |
|---|
| 1439 |
@chunked = false |
|---|
| 1440 |
@headers.each do |line| |
|---|
| 1441 |
case line |
|---|
| 1442 |
when /^Content-Length:\s+(\d+)/i |
|---|
| 1443 |
@content_length = $1.to_i |
|---|
| 1444 |
when /^Transfer-Encoding:\s+chunked/i |
|---|
| 1445 |
@chunked = true |
|---|
| 1446 |
@content_length = true # how? |
|---|
| 1447 |
@chunk_length = 0 |
|---|
| 1448 |
when /^Connection:\s+([\-\w]+)/i, /^Proxy-Connection:\s+([\-\w]+)/i |
|---|
| 1449 |
case $1 |
|---|
| 1450 |
when /^Keep-Alive$/i |
|---|
| 1451 |
@next_connection = true |
|---|
| 1452 |
when /^close$/i |
|---|
| 1453 |
@next_connection = false |
|---|
| 1454 |
end |
|---|
| 1455 |
else |
|---|
| 1456 |
# Nothing to parse. |
|---|
| 1457 |
end |
|---|
| 1458 |
end |
|---|
| 1459 |
|
|---|
| 1460 |
# Head of the request has been parsed. |
|---|
| 1461 |
@state = :DATA |
|---|
| 1462 |
req = @requests.shift |
|---|
| 1463 |
|
|---|
| 1464 |
if req.header.request_method == 'HEAD' |
|---|
| 1465 |
@content_length = 0 |
|---|
| 1466 |
if @next_connection |
|---|
| 1467 |
@state = :WAIT |
|---|
| 1468 |
else |
|---|
| 1469 |
close |
|---|
| 1470 |
end |
|---|
| 1471 |
end |
|---|
| 1472 |
@next_connection = false unless @content_length |
|---|
| 1473 |
return [@version, @status, @reason] |
|---|
| 1474 |
end |
|---|
| 1475 |
|
|---|
| 1476 |
StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d+)(?:\s+([^\r\n]+))?\r?\n\z) |
|---|
| 1477 |
def parse_header(socket) |
|---|
| 1478 |
begin |
|---|
| 1479 |
timeout(@receive_timeout) do |
|---|
| 1480 |
begin |
|---|
| 1481 |
initial_line = socket.gets("\n") |
|---|
| 1482 |
if initial_line.nil? |
|---|
| 1483 |
raise KeepAliveDisconnected.new |
|---|
| 1484 |
end |
|---|
| 1485 |
if StatusParseRegexp =~ initial_line |
|---|
| 1486 |
@version, @status, @reason = $1, $2.to_i, $3 |
|---|
| 1487 |
@next_connection = HTTP.keep_alive_enabled?(@version) |
|---|
| 1488 |
else |
|---|
| 1489 |
@version = '0.9' |
|---|
| 1490 |
@status = nil |
|---|
| 1491 |
@reason = nil |
|---|
| 1492 |
@next_connection = false |
|---|
| 1493 |
@readbuf = initial_line |
|---|
| 1494 |
break |
|---|
| 1495 |
end |
|---|
| 1496 |
@headers = [] |
|---|
| 1497 |
while true |
|---|
| 1498 |
line = socket.gets("\n") |
|---|
| 1499 |
unless line |
|---|
| 1500 |
raise BadResponse.new('Unexpected EOF.') |
|---|
| 1501 |
end |
|---|
| 1502 |
line.sub!(/\r?\n\z/, '') |
|---|
| 1503 |
break if line.empty? |
|---|
| 1504 |
if line.sub!(/^\t/, '') |
|---|
| 1505 |
@headers[-1] << line |
|---|
| 1506 |
else |
|---|
| 1507 |
@headers.push(line) |
|---|
| 1508 |
end |
|---|
| 1509 |
end |
|---|
| 1510 |
end while (@version == '1.1' && @status == 100) |
|---|
| 1511 |
end |
|---|
| 1512 |
rescue TimeoutError |
|---|
| 1513 |
raise |
|---|
| 1514 |
end |
|---|
| 1515 |
end |
|---|
| 1516 |
|
|---|
| 1517 |
def read_body |
|---|
| 1518 |
if @chunked |
|---|
| 1519 |
return read_body_chunked() |
|---|
| 1520 |
elsif @content_length == 0 |
|---|
| 1521 |
return nil |
|---|
| 1522 |
elsif @content_length |
|---|
| 1523 |
return read_body_length() |
|---|
| 1524 |
else |
|---|
| 1525 |
if @readbuf.length > 0 |
|---|
| 1526 |
data = @readbuf |
|---|
| 1527 |
@readbuf = '' |
|---|
| 1528 |
return data |
|---|
| 1529 |
else |
|---|
| 1530 |
data = @socket.read(@read_block_size) |
|---|
| 1531 |
data = nil if data.empty? # Absorbing interface mismatch. |
|---|
| 1532 |
return data |
|---|
| 1533 |
end |
|---|
| 1534 |
end |
|---|
| 1535 |
end |
|---|
| 1536 |
|
|---|
| 1537 |
def read_body_length |
|---|
| 1538 |
maxbytes = @read_block_size |
|---|
| 1539 |
if @readbuf.length > 0 |
|---|
| 1540 |
data = @readbuf[0, @content_length] |
|---|
| 1541 |
@readbuf[0, @content_length] = '' |
|---|
| 1542 |
@content_length -= data.length |
|---|
| 1543 |
return data |
|---|
| 1544 |
end |
|---|
| 1545 |
maxbytes = @content_length if maxbytes > @content_length |
|---|
| 1546 |
data = @socket.read(maxbytes) |
|---|
| 1547 |
if data |
|---|
| 1548 |
@content_length -= data.length |
|---|
| 1549 |
else |
|---|
| 1550 |
@content_length = 0 |
|---|
| 1551 |
end |
|---|
| 1552 |
return data |
|---|
| 1553 |
end |
|---|
| 1554 |
|
|---|
| 1555 |
RS = "\r\n" |
|---|
| 1556 |
ChunkDelimiter = "0#{RS}" |
|---|
| 1557 |
ChunkTrailer = "0#{RS}#{RS}" |
|---|
| 1558 |
def read_body_chunked |
|---|
| 1559 |
if @chunk_length == 0 |
|---|
| 1560 |
until (i = @readbuf.index(RS)) |
|---|
| 1561 |
@readbuf << @socket.gets(RS) |
|---|
| 1562 |
end |
|---|
| 1563 |
i += 2 |
|---|
| 1564 |
if @readbuf[0, i] == ChunkDelimiter |
|---|
| 1565 |
@content_length = 0 |
|---|
| 1566 |
unless @readbuf[0, 5] == ChunkTrailer |
|---|
| 1567 |
@readbuf << @socket.gets(RS) |
|---|
| 1568 |
end |
|---|
| 1569 |
@readbuf[0, 5] = '' |
|---|
| 1570 |
return nil |
|---|
| 1571 |
end |
|---|
| 1572 |
@chunk_length = @readbuf[0, i].hex |
|---|
| 1573 |
@readbuf[0, i] = '' |
|---|
| 1574 |
end |
|---|
| 1575 |
while @readbuf.length < @chunk_length + 2 |
|---|
| 1576 |
@readbuf << @socket.read(@chunk_length + 2 - @readbuf.length) |
|---|
| 1577 |
end |
|---|
| 1578 |
data = @readbuf[0, @chunk_length] |
|---|
| 1579 |
@readbuf[0, @chunk_length + 2] = '' |
|---|
| 1580 |
@chunk_length = 0 |
|---|
| 1581 |
return data |
|---|
| 1582 |
end |
|---|
| 1583 |
|
|---|
| 1584 |
def check_state |
|---|
| 1585 |
if @state == :DATA |
|---|
| 1586 |
if eof? |
|---|
| 1587 |
if @next_connection |
|---|
| 1588 |
if @requests.empty? |
|---|
| 1589 |
@state = :WAIT |
|---|
| 1590 |
else |
|---|
| 1591 |
@state = :META |
|---|
| 1592 |
end |
|---|
| 1593 |
end |
|---|
| 1594 |
end |
|---|
| 1595 |
end |
|---|
| 1596 |
end |
|---|
| 1597 |
end |
|---|
| 1598 |
|
|---|
| 1599 |
|
|---|
| 1600 |
end |
|---|
| 1601 |
|
|---|
| 1602 |
|
|---|
| 1603 |
HTTPClient = HTTPAccess2::Client |
|---|