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.
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+
5
+ require 'lib/em-http'
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)
@@ -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
+