tmm1-em-http-request 0.1.0
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 +36 -0
- data/Rakefile +72 -0
- data/ext/buffer/em_buffer.c +625 -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/client.rb +404 -0
- data/lib/em-http/multi.rb +51 -0
- data/lib/em-http/request.rb +63 -0
- data/lib/em-http.rb +16 -0
- data/test/helper.rb +5 -0
- data/test/stallion.rb +123 -0
- data/test/test_multi.rb +34 -0
- data/test/test_request.rb +179 -0
- metadata +82 -0
@@ -0,0 +1,63 @@
|
|
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
|
+
attr_reader :response, :headers
|
28
|
+
|
29
|
+
def initialize(host, headers = {})
|
30
|
+
@headers = headers
|
31
|
+
@uri = URI::parse(host)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Send an HTTP request and consume the response. Supported options:
|
35
|
+
#
|
36
|
+
# head: {Key: Value}
|
37
|
+
# Specify an HTTP header, e.g. {'Connection': 'close'}
|
38
|
+
#
|
39
|
+
# query: {Key: Value}
|
40
|
+
# Specify query string parameters (auto-escaped)
|
41
|
+
#
|
42
|
+
# body: String
|
43
|
+
# Specify the request body (you must encode it for now)
|
44
|
+
#
|
45
|
+
|
46
|
+
def get options = {}; send_request(:get, options); end
|
47
|
+
def post options = {}; send_request(:post, options); end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def send_request(method, options)
|
52
|
+
raise ArgumentError, "invalid request path" unless /^\// === @uri.path
|
53
|
+
|
54
|
+
method = method.to_s.upcase
|
55
|
+
|
56
|
+
EventMachine.connect(@uri.host, @uri.port, EventMachine::HttpClient) { |c|
|
57
|
+
c.uri = @uri
|
58
|
+
c.method = method
|
59
|
+
c.options = options
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/em-http.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2008 Ilya Grigorik
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'zlib'
|
10
|
+
|
11
|
+
require 'http11_client'
|
12
|
+
require 'em_buffer'
|
13
|
+
|
14
|
+
require 'em-http/client'
|
15
|
+
require 'em-http/multi'
|
16
|
+
require 'em-http/request'
|
data/test/helper.rb
ADDED
data/test/stallion.rb
ADDED
@@ -0,0 +1,123 @@
|
|
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 == '/gzip'
|
87
|
+
io = StringIO.new
|
88
|
+
gzip = Zlib::GzipWriter.new(io)
|
89
|
+
gzip << "compressed"
|
90
|
+
gzip.close
|
91
|
+
|
92
|
+
stable.response.write io.string
|
93
|
+
stable.response["Content-Encoding"] = "gzip"
|
94
|
+
|
95
|
+
elsif stable.request.path_info == '/deflate'
|
96
|
+
stable.response.write Zlib::Deflate.deflate("compressed")
|
97
|
+
stable.response["Content-Encoding"] = "deflate"
|
98
|
+
|
99
|
+
elsif stable.request.env["HTTP_IF_NONE_MATCH"]
|
100
|
+
stable.response.status = 304
|
101
|
+
|
102
|
+
elsif stable.request.env["HTTP_AUTHORIZATION"]
|
103
|
+
auth = "Basic %s" % Base64.encode64(['user', 'pass'].join(':')).chomp
|
104
|
+
|
105
|
+
if auth == stable.request.env["HTTP_AUTHORIZATION"]
|
106
|
+
stable.response.status = 200
|
107
|
+
stable.response.write 'success'
|
108
|
+
else
|
109
|
+
stable.response.status = 401
|
110
|
+
end
|
111
|
+
|
112
|
+
elsif
|
113
|
+
stable.response.write 'Hello, World!'
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Thread.new do
|
120
|
+
Stallion.run :Host => '127.0.0.1', :Port => 8080
|
121
|
+
end
|
122
|
+
|
123
|
+
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,179 @@
|
|
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 invalid host" do
|
12
|
+
EventMachine.run {
|
13
|
+
http = EventMachine::HttpRequest.new('http://169.169.169.169/').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 missing path" do
|
23
|
+
EventMachine.run {
|
24
|
+
lambda {
|
25
|
+
EventMachine::HttpRequest.new('http://www.google.com').get
|
26
|
+
}.should raise_error(ArgumentError)
|
27
|
+
|
28
|
+
EventMachine.stop
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should perform successfull GET" do
|
33
|
+
EventMachine.run {
|
34
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
35
|
+
|
36
|
+
http.errback { failed }
|
37
|
+
http.callback {
|
38
|
+
http.response_header.status.should == 200
|
39
|
+
http.response.should match(/Hello/)
|
40
|
+
EventMachine.stop
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return 404 on invalid path" do
|
46
|
+
EventMachine.run {
|
47
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
48
|
+
|
49
|
+
http.errback { failed }
|
50
|
+
http.callback {
|
51
|
+
http.response_header.status.should == 404
|
52
|
+
EventMachine.stop
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should pass query parameters" do
|
58
|
+
EventMachine.run {
|
59
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => {:q => 'test'}
|
60
|
+
|
61
|
+
http.errback { failed }
|
62
|
+
http.callback {
|
63
|
+
http.response_header.status.should == 200
|
64
|
+
http.response.should match(/test/)
|
65
|
+
EventMachine.stop
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should encode an array of query parameters" do
|
71
|
+
EventMachine.run {
|
72
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get :query => {:hash => ['value1', 'value2']}
|
73
|
+
|
74
|
+
http.errback { failed }
|
75
|
+
http.callback {
|
76
|
+
http.response_header.status.should == 200
|
77
|
+
http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
|
78
|
+
EventMachine.stop
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should perform successfull POST" do
|
84
|
+
EventMachine.run {
|
85
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data"
|
86
|
+
|
87
|
+
http.errback { failed }
|
88
|
+
http.callback {
|
89
|
+
http.response_header.status.should == 200
|
90
|
+
http.response.should match(/test/)
|
91
|
+
EventMachine.stop
|
92
|
+
}
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should perform successfull GET with custom header" do
|
97
|
+
EventMachine.run {
|
98
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'if-none-match' => 'evar!'}
|
99
|
+
|
100
|
+
http.errback { failed }
|
101
|
+
http.callback {
|
102
|
+
http.response_header.status.should == 304
|
103
|
+
EventMachine.stop
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should perform a streaming GET" do
|
109
|
+
EventMachine.run {
|
110
|
+
|
111
|
+
# digg.com uses chunked encoding
|
112
|
+
http = EventMachine::HttpRequest.new('http://www.digg.com/').get
|
113
|
+
|
114
|
+
http.errback { failed }
|
115
|
+
http.callback {
|
116
|
+
http.response_header.status == 200
|
117
|
+
EventMachine.stop
|
118
|
+
}
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should perform basic auth" do
|
123
|
+
EventMachine.run {
|
124
|
+
|
125
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'authorization' => ['user', 'pass']}
|
126
|
+
|
127
|
+
http.errback { failed }
|
128
|
+
http.callback {
|
129
|
+
http.response_header.status == 200
|
130
|
+
EventMachine.stop
|
131
|
+
}
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should work with keep-alive servers" do
|
136
|
+
EventMachine.run {
|
137
|
+
|
138
|
+
http = EventMachine::HttpRequest.new('http://mexicodiario.com/touch.public.json.php').get
|
139
|
+
|
140
|
+
http.errback { failed }
|
141
|
+
http.callback {
|
142
|
+
http.response_header.status == 200
|
143
|
+
EventMachine.stop
|
144
|
+
}
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should detect deflate encoding" do
|
149
|
+
EventMachine.run {
|
150
|
+
|
151
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate"}
|
152
|
+
|
153
|
+
http.errback { failed }
|
154
|
+
http.callback {
|
155
|
+
http.response_header.status == 200
|
156
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
157
|
+
http.response.should == "compressed"
|
158
|
+
|
159
|
+
EventMachine.stop
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should detect gzip encoding" do
|
165
|
+
EventMachine.run {
|
166
|
+
|
167
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
|
168
|
+
|
169
|
+
http.errback { failed }
|
170
|
+
http.callback {
|
171
|
+
http.response_header.status == 200
|
172
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
173
|
+
http.response.should == "compressed"
|
174
|
+
|
175
|
+
EventMachine.stop
|
176
|
+
}
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tmm1-em-http-request
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilya Grigorik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-15 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.12.2
|
24
|
+
version:
|
25
|
+
description: EventMachine based HTTP Request interface
|
26
|
+
email: ilya@igvita.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions:
|
30
|
+
- ext/buffer/extconf.rb
|
31
|
+
- ext/http11_client/extconf.rb
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- .autotest
|
36
|
+
- LICENSE
|
37
|
+
- README
|
38
|
+
- Rakefile
|
39
|
+
- ext/buffer/em_buffer.c
|
40
|
+
- ext/buffer/extconf.rb
|
41
|
+
- ext/http11_client/ext_help.h
|
42
|
+
- ext/http11_client/extconf.rb
|
43
|
+
- ext/http11_client/http11_client.c
|
44
|
+
- ext/http11_client/http11_parser.c
|
45
|
+
- ext/http11_client/http11_parser.h
|
46
|
+
- ext/http11_client/http11_parser.rl
|
47
|
+
- lib/em-http.rb
|
48
|
+
- lib/em-http/client.rb
|
49
|
+
- lib/em-http/multi.rb
|
50
|
+
- lib/em-http/request.rb
|
51
|
+
- test/helper.rb
|
52
|
+
- test/stallion.rb
|
53
|
+
- test/test_multi.rb
|
54
|
+
- test/test_request.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/igrigorik/em-http-request
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.2.0
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: EventMachine based HTTP Request interface
|
81
|
+
test_files: []
|
82
|
+
|