astro-em-http-request 0.1.3.20090419
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +1 -0
- data/LICENSE +58 -0
- data/README.rdoc +52 -0
- data/Rakefile +80 -0
- data/ext/buffer/em_buffer.c +630 -0
- data/ext/buffer/extconf.rb +53 -0
- data/ext/http11_client/ext_help.h +14 -0
- data/ext/http11_client/extconf.rb +6 -0
- data/ext/http11_client/http11_client.c +302 -0
- data/ext/http11_client/http11_parser.c +403 -0
- data/ext/http11_client/http11_parser.h +48 -0
- data/ext/http11_client/http11_parser.rl +173 -0
- data/lib/em-http.rb +17 -0
- data/lib/em-http/client.rb +428 -0
- data/lib/em-http/decoders.rb +119 -0
- data/lib/em-http/multi.rb +51 -0
- data/lib/em-http/request.rb +77 -0
- data/test/helper.rb +5 -0
- data/test/stallion.rb +127 -0
- data/test/test_multi.rb +34 -0
- data/test/test_request.rb +264 -0
- metadata +83 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Provides a unified callback interface to decompression libraries.
|
5
|
+
module EventMachine::HttpDecoders
|
6
|
+
|
7
|
+
class DecoderError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def accepted_encodings
|
12
|
+
DECODERS.inject([]) { |r,d| r + d.encoding_names }
|
13
|
+
end
|
14
|
+
|
15
|
+
def decoder_for_encoding(encoding)
|
16
|
+
DECODERS.each { |d|
|
17
|
+
return d if d.encoding_names.include? encoding
|
18
|
+
}
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Base
|
24
|
+
def self.encoding_names
|
25
|
+
name = to_s.split('::').last.downcase
|
26
|
+
[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# chunk_callback:: [Block] To handle a decompressed chunk
|
31
|
+
def initialize(&chunk_callback)
|
32
|
+
@chunk_callback = chunk_callback
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(compressed)
|
36
|
+
return unless compressed && compressed.size > 0
|
37
|
+
|
38
|
+
decompressed = decompress(compressed)
|
39
|
+
receive_decompressed decompressed
|
40
|
+
end
|
41
|
+
|
42
|
+
def finalize!
|
43
|
+
decompressed = finalize
|
44
|
+
receive_decompressed decompressed
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def receive_decompressed(decompressed)
|
50
|
+
if decompressed && decompressed.size > 0
|
51
|
+
@chunk_callback.call(decompressed)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
##
|
58
|
+
# Must return a part of decompressed
|
59
|
+
def decompress(compressed)
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# May return last part
|
65
|
+
def finalize
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Deflate < Base
|
71
|
+
def decompress(compressed)
|
72
|
+
begin
|
73
|
+
@zstream ||= Zlib::Inflate.new(nil)
|
74
|
+
@zstream.inflate(compressed)
|
75
|
+
rescue Zlib::Error
|
76
|
+
raise DecoderError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def finalize
|
81
|
+
return nil unless @zstream
|
82
|
+
|
83
|
+
begin
|
84
|
+
r = @zstream.inflate(nil)
|
85
|
+
@zstream.close
|
86
|
+
r
|
87
|
+
rescue Zlib::Error
|
88
|
+
raise DecoderError
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Oneshot decompressor, due to lack of a streaming Gzip reader
|
95
|
+
# implementation. We may steal code from Zliby to improve this.
|
96
|
+
#
|
97
|
+
# For now, do not put `gzip' or `compressed' in your accept-encoding
|
98
|
+
# header if you expect much data through the :on_response interface.
|
99
|
+
class GZip < Base
|
100
|
+
def self.encoding_names
|
101
|
+
%w(gzip compressed)
|
102
|
+
end
|
103
|
+
|
104
|
+
def decompress(compressed)
|
105
|
+
@buf ||= ''
|
106
|
+
@buf += compressed
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def finalize
|
111
|
+
Zlib::GzipReader.new(StringIO.new(@buf)).read
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
DECODERS = [Deflate, GZip]
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
4
|
+
# which allows you to open multiple parallel connections and return only when all
|
5
|
+
# of them finish. (i.e. ideal for parallelizing workloads)
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# EventMachine.run {
|
10
|
+
#
|
11
|
+
# multi = EventMachine::MultiRequest.new
|
12
|
+
#
|
13
|
+
# # add multiple requests to the multi-handler
|
14
|
+
# multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
15
|
+
# multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
16
|
+
#
|
17
|
+
# multi.callback {
|
18
|
+
# p multi.responses[:succeeded]
|
19
|
+
# p multi.responses[:failed]
|
20
|
+
#
|
21
|
+
# EventMachine.stop
|
22
|
+
# }
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
|
26
|
+
class MultiRequest
|
27
|
+
include EventMachine::Deferrable
|
28
|
+
|
29
|
+
attr_reader :requests, :responses
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@requests = []
|
33
|
+
@responses = {:succeeded => [], :failed => []}
|
34
|
+
end
|
35
|
+
|
36
|
+
def add(conn)
|
37
|
+
conn.callback { @responses[:succeeded].push(conn); check_progress }
|
38
|
+
conn.errback { @responses[:failed].push(conn); check_progress }
|
39
|
+
|
40
|
+
@requests.push(conn)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
# invoke callback if all requests have completed
|
46
|
+
def check_progress
|
47
|
+
succeed if (@responses[:succeeded].size + @responses[:failed].size) == @requests.size
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
|
6
|
+
# EventMachine based HTTP request class with support for streaming consumption
|
7
|
+
# of the response. Response is parsed with a Ragel-generated whitelist parser
|
8
|
+
# which supports chunked HTTP encoding.
|
9
|
+
#
|
10
|
+
# == Example
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# EventMachine.run {
|
14
|
+
# http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}
|
15
|
+
#
|
16
|
+
# http.callback {
|
17
|
+
# p http.response_header.status
|
18
|
+
# p http.response_header
|
19
|
+
# p http.response
|
20
|
+
#
|
21
|
+
# EventMachine.stop
|
22
|
+
# }
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
|
26
|
+
class HttpRequest
|
27
|
+
|
28
|
+
def initialize(host, headers = {})
|
29
|
+
@headers = headers
|
30
|
+
@uri = URI::parse(host) unless host.kind_of? URI
|
31
|
+
end
|
32
|
+
|
33
|
+
# Send an HTTP request and consume the response. Supported options:
|
34
|
+
#
|
35
|
+
# head: {Key: Value}
|
36
|
+
# Specify an HTTP header, e.g. {'Connection': 'close'}
|
37
|
+
#
|
38
|
+
# query: {Key: Value}
|
39
|
+
# Specify query string parameters (auto-escaped)
|
40
|
+
#
|
41
|
+
# body: String
|
42
|
+
# Specify the request body (you must encode it for now)
|
43
|
+
#
|
44
|
+
# on_response: Proc
|
45
|
+
# Called for each response body chunk (you may assume HTTP 200
|
46
|
+
# OK then)
|
47
|
+
#
|
48
|
+
# host: String
|
49
|
+
# Manually specify TCP connect host address, independent of
|
50
|
+
# Host: header
|
51
|
+
|
52
|
+
def get options = {}; send_request(:get, options); end
|
53
|
+
def post options = {}; send_request(:post, options); end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def send_request(method, options)
|
58
|
+
raise ArgumentError, "invalid request path" unless /^\// === @uri.path
|
59
|
+
|
60
|
+
method = method.to_s.upcase
|
61
|
+
begin
|
62
|
+
host = options[:host] || @uri.host
|
63
|
+
EventMachine.connect(host, @uri.port, EventMachine::HttpClient) { |c|
|
64
|
+
c.uri = @uri
|
65
|
+
c.method = method
|
66
|
+
c.options = options
|
67
|
+
c.comm_inactivity_timeout = options[:timeout] || 5
|
68
|
+
}
|
69
|
+
rescue RuntimeError => e
|
70
|
+
raise e unless e.message == "no connection"
|
71
|
+
conn = EventMachine::HttpClient.new("")
|
72
|
+
conn.on_error("no connection")
|
73
|
+
conn
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/test/helper.rb
ADDED
data/test/stallion.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# #--
|
2
|
+
# Includes portion originally Copyright (C)2008 Michael Fellinger
|
3
|
+
# license See file LICENSE for details
|
4
|
+
# #--
|
5
|
+
|
6
|
+
require 'rack'
|
7
|
+
|
8
|
+
module Stallion
|
9
|
+
class Mount
|
10
|
+
def initialize(name, *methods, &block)
|
11
|
+
@name, @methods, @block = name, methods, block
|
12
|
+
end
|
13
|
+
|
14
|
+
def ride
|
15
|
+
@block.call
|
16
|
+
end
|
17
|
+
|
18
|
+
def match?(request)
|
19
|
+
method = request['REQUEST_METHOD']
|
20
|
+
right_method = @methods.empty? or @methods.include?(method)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Stable
|
25
|
+
attr_reader :request, :response
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@boxes = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def in(path, *methods, &block)
|
32
|
+
mount = Mount.new(path, *methods, &block)
|
33
|
+
@boxes[[path, methods]] = mount
|
34
|
+
mount
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(request, response)
|
38
|
+
@request, @response = request, response
|
39
|
+
@boxes.each do |(path, methods), mount|
|
40
|
+
if mount.match?(request)
|
41
|
+
mount.ride
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
STABLES = {}
|
48
|
+
|
49
|
+
def self.saddle(name = nil)
|
50
|
+
STABLES[name] = stable = Stable.new
|
51
|
+
yield stable
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.run(options = {})
|
55
|
+
options = {:Host => "127.0.0.1", :Port => 8080}.merge(options)
|
56
|
+
Rack::Handler::Mongrel.run(Rack::Lint.new(self), options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.call(env)
|
60
|
+
request = Rack::Request.new(env)
|
61
|
+
response = Rack::Response.new
|
62
|
+
|
63
|
+
STABLES.each do |name, stable|
|
64
|
+
stable.call(request, response)
|
65
|
+
end
|
66
|
+
|
67
|
+
response.finish
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Stallion.saddle :spec do |stable|
|
72
|
+
stable.in '/' do
|
73
|
+
|
74
|
+
if stable.request.path_info == '/fail'
|
75
|
+
stable.response.status = 404
|
76
|
+
|
77
|
+
elsif stable.request.query_string == 'q=test'
|
78
|
+
stable.response.write 'test'
|
79
|
+
|
80
|
+
elsif stable.request.path_info == '/echo_query'
|
81
|
+
stable.response.write stable.request.query_string
|
82
|
+
|
83
|
+
elsif stable.request.post?
|
84
|
+
stable.response.write 'test'
|
85
|
+
|
86
|
+
elsif stable.request.path_info == '/timeout'
|
87
|
+
sleep(10)
|
88
|
+
stable.response.write 'timeout'
|
89
|
+
|
90
|
+
elsif stable.request.path_info == '/gzip'
|
91
|
+
io = StringIO.new
|
92
|
+
gzip = Zlib::GzipWriter.new(io)
|
93
|
+
gzip << "compressed"
|
94
|
+
gzip.close
|
95
|
+
|
96
|
+
stable.response.write io.string
|
97
|
+
stable.response["Content-Encoding"] = "gzip"
|
98
|
+
|
99
|
+
elsif stable.request.path_info == '/deflate'
|
100
|
+
stable.response.write Zlib::Deflate.deflate("compressed")
|
101
|
+
stable.response["Content-Encoding"] = "deflate"
|
102
|
+
|
103
|
+
elsif stable.request.env["HTTP_IF_NONE_MATCH"]
|
104
|
+
stable.response.status = 304
|
105
|
+
|
106
|
+
elsif stable.request.env["HTTP_AUTHORIZATION"]
|
107
|
+
auth = "Basic %s" % Base64.encode64(['user', 'pass'].join(':')).chomp
|
108
|
+
|
109
|
+
if auth == stable.request.env["HTTP_AUTHORIZATION"]
|
110
|
+
stable.response.status = 200
|
111
|
+
stable.response.write 'success'
|
112
|
+
else
|
113
|
+
stable.response.status = 401
|
114
|
+
end
|
115
|
+
|
116
|
+
elsif
|
117
|
+
stable.response.write 'Hello, World!'
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Thread.new do
|
124
|
+
Stallion.run :Host => '127.0.0.1', :Port => 8080
|
125
|
+
end
|
126
|
+
|
127
|
+
sleep(2)
|
data/test/test_multi.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
require 'test/stallion'
|
3
|
+
|
4
|
+
describe EventMachine::MultiRequest do
|
5
|
+
|
6
|
+
def failed
|
7
|
+
EventMachine.stop
|
8
|
+
fail
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should submit multiple requests in parallel and return once all of them are complete" do
|
12
|
+
EventMachine.run {
|
13
|
+
|
14
|
+
# create an instance of multi-request handler, and the requests themselves
|
15
|
+
multi = EventMachine::MultiRequest.new
|
16
|
+
|
17
|
+
# add multiple requests to the multi-handler
|
18
|
+
multi.add(EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'}))
|
19
|
+
multi.add(EventMachine::HttpRequest.new('http://169.169.169.169/').get)
|
20
|
+
|
21
|
+
multi.callback {
|
22
|
+
# verify successfull request
|
23
|
+
multi.responses[:succeeded].size.should == 1
|
24
|
+
multi.responses[:succeeded].first.response.should match(/test/)
|
25
|
+
|
26
|
+
# verify invalid requests
|
27
|
+
multi.responses[:failed].size.should == 1
|
28
|
+
multi.responses[:failed].first.response_header.status.should == 0
|
29
|
+
|
30
|
+
EventMachine.stop
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
require 'test/stallion'
|
3
|
+
|
4
|
+
describe EventMachine::HttpRequest do
|
5
|
+
|
6
|
+
def failed
|
7
|
+
EventMachine.stop
|
8
|
+
fail
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should fail GET on DNS timeout" do
|
12
|
+
EventMachine.run {
|
13
|
+
http = EventMachine::HttpRequest.new('http://127.1.1.1/').get
|
14
|
+
http.callback { failed }
|
15
|
+
http.errback {
|
16
|
+
http.response_header.status.should == 0
|
17
|
+
EventMachine.stop
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should fail GET on invalid host" do
|
23
|
+
EventMachine.run {
|
24
|
+
http = EventMachine::HttpRequest.new('http://google1.com/').get
|
25
|
+
http.callback { failed }
|
26
|
+
http.errback {
|
27
|
+
http.response_header.status.should == 0
|
28
|
+
http.errors.should match(/no connection/)
|
29
|
+
EventMachine.stop
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should fail GET on missing path" do
|
35
|
+
EventMachine.run {
|
36
|
+
lambda {
|
37
|
+
EventMachine::HttpRequest.new('http://www.google.com').get
|
38
|
+
}.should raise_error(ArgumentError)
|
39
|
+
|
40
|
+
EventMachine.stop
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should perform successfull GET" do
|
45
|
+
EventMachine.run {
|
46
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
47
|
+
|
48
|
+
http.errback { failed }
|
49
|
+
http.callback {
|
50
|
+
http.response_header.status.should == 200
|
51
|
+
http.response.should match(/Hello/)
|
52
|
+
EventMachine.stop
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return 404 on invalid path" do
|
58
|
+
EventMachine.run {
|
59
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
60
|
+
|
61
|
+
http.errback { failed }
|
62
|
+
http.callback {
|
63
|
+
http.response_header.status.should == 404
|
64
|
+
EventMachine.stop
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should build query parameters from Hash" do
|
70
|
+
EventMachine.run {
|
71
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => {:q => 'test'}
|
72
|
+
|
73
|
+
http.errback { failed }
|
74
|
+
http.callback {
|
75
|
+
http.response_header.status.should == 200
|
76
|
+
http.response.should match(/test/)
|
77
|
+
EventMachine.stop
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should pass query parameters string" do
|
83
|
+
EventMachine.run {
|
84
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => "q=test"
|
85
|
+
|
86
|
+
http.errback { failed }
|
87
|
+
http.callback {
|
88
|
+
http.response_header.status.should == 200
|
89
|
+
http.response.should match(/test/)
|
90
|
+
EventMachine.stop
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should encode an array of query parameters" do
|
96
|
+
EventMachine.run {
|
97
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get :query => {:hash => ['value1', 'value2']}
|
98
|
+
|
99
|
+
http.errback { failed }
|
100
|
+
http.callback {
|
101
|
+
http.response_header.status.should == 200
|
102
|
+
http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
|
103
|
+
EventMachine.stop
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should perform successfull POST" do
|
109
|
+
EventMachine.run {
|
110
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data"
|
111
|
+
|
112
|
+
http.errback { failed }
|
113
|
+
http.callback {
|
114
|
+
http.response_header.status.should == 200
|
115
|
+
http.response.should match(/test/)
|
116
|
+
EventMachine.stop
|
117
|
+
}
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should perform successfull GET with custom header" do
|
122
|
+
EventMachine.run {
|
123
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'if-none-match' => 'evar!'}
|
124
|
+
|
125
|
+
http.errback { failed }
|
126
|
+
http.callback {
|
127
|
+
http.response_header.status.should == 304
|
128
|
+
EventMachine.stop
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should perform a streaming GET" do
|
134
|
+
EventMachine.run {
|
135
|
+
|
136
|
+
# digg.com uses chunked encoding
|
137
|
+
http = EventMachine::HttpRequest.new('http://digg.com/').get
|
138
|
+
|
139
|
+
http.errback { failed }
|
140
|
+
http.callback {
|
141
|
+
http.response_header.status.should == 200
|
142
|
+
EventMachine.stop
|
143
|
+
}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should perform basic auth" do
|
148
|
+
EventMachine.run {
|
149
|
+
|
150
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'authorization' => ['user', 'pass']}
|
151
|
+
|
152
|
+
http.errback { failed }
|
153
|
+
http.callback {
|
154
|
+
http.response_header.status.should == 200
|
155
|
+
EventMachine.stop
|
156
|
+
}
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should work with keep-alive servers" do
|
161
|
+
EventMachine.run {
|
162
|
+
|
163
|
+
http = EventMachine::HttpRequest.new('http://mexicodiario.com/touch.public.json.php').get
|
164
|
+
|
165
|
+
http.errback { failed }
|
166
|
+
http.callback {
|
167
|
+
http.response_header.status.should == 200
|
168
|
+
EventMachine.stop
|
169
|
+
}
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should detect deflate encoding" do
|
174
|
+
EventMachine.run {
|
175
|
+
|
176
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate"}
|
177
|
+
|
178
|
+
http.errback { failed }
|
179
|
+
http.callback {
|
180
|
+
http.response_header.status.should == 200
|
181
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
182
|
+
http.response.should == "compressed"
|
183
|
+
|
184
|
+
EventMachine.stop
|
185
|
+
}
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should detect gzip encoding" do
|
190
|
+
EventMachine.run {
|
191
|
+
|
192
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
|
193
|
+
|
194
|
+
http.errback { failed }
|
195
|
+
http.callback {
|
196
|
+
http.response_header.status.should == 200
|
197
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
198
|
+
http.response.should == "compressed"
|
199
|
+
|
200
|
+
EventMachine.stop
|
201
|
+
}
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should timeout after 10 seconds" do
|
206
|
+
EventMachine.run {
|
207
|
+
t = Time.now.to_i
|
208
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 2
|
209
|
+
|
210
|
+
http.errback {
|
211
|
+
(Time.now.to_i - t).should == 2
|
212
|
+
EventMachine.stop
|
213
|
+
}
|
214
|
+
http.callback { failed }
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should optionally pass the response body progressively" do
|
219
|
+
EventMachine.run {
|
220
|
+
body = ''
|
221
|
+
on_body = lambda { |chunk| body += chunk }
|
222
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :on_response => on_body
|
223
|
+
|
224
|
+
http.errback { failed }
|
225
|
+
http.callback {
|
226
|
+
http.response_header.status.should == 200
|
227
|
+
http.response.should == ''
|
228
|
+
body.should match(/Hello/)
|
229
|
+
EventMachine.stop
|
230
|
+
}
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should optionally pass the deflate-encoded response body progressively" do
|
235
|
+
EventMachine.run {
|
236
|
+
body = ''
|
237
|
+
on_body = lambda { |chunk| body += chunk }
|
238
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"},
|
239
|
+
:on_response => on_body
|
240
|
+
|
241
|
+
http.errback { failed }
|
242
|
+
http.callback {
|
243
|
+
http.response_header.status.should == 200
|
244
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
245
|
+
http.response.should == ''
|
246
|
+
body.should == "compressed"
|
247
|
+
EventMachine.stop
|
248
|
+
}
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should respect manually-passed host address" do
|
253
|
+
EventMachine.run {
|
254
|
+
http = EventMachine::HttpRequest.new('http://127.1.1.1:8080/').get :host => '127.0.0.1'
|
255
|
+
|
256
|
+
http.errback { failed }
|
257
|
+
http.callback {
|
258
|
+
http.response_header.status.should == 200
|
259
|
+
http.response.should match(/Hello/)
|
260
|
+
EventMachine.stop
|
261
|
+
}
|
262
|
+
}
|
263
|
+
end
|
264
|
+
end
|