astro-em-http-request 0.2.3 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +42 -4
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/examples/oauth-tweet.rb +50 -0
- data/ext/http11_client/http11_client.c +27 -1
- data/lib/em-http.rb +3 -5
- data/lib/em-http/client.rb +165 -53
- data/lib/em-http/mock.rb +74 -0
- data/lib/em-http/request.rb +45 -35
- data/spec/fixtures/google.ca +21 -0
- data/spec/hash_spec.rb +24 -0
- data/spec/helper.rb +7 -0
- data/spec/mock_spec.rb +72 -0
- data/{test/test_multi.rb → spec/multi_spec.rb} +3 -3
- data/{test/test_request.rb → spec/request_spec.rb} +192 -30
- data/{test → spec}/stallion.rb +75 -8
- data/{test → spec}/stub_server.rb +0 -0
- metadata +21 -14
- data/test/helper.rb +0 -5
- data/test/test_hash.rb +0 -19
data/README.rdoc
CHANGED
@@ -3,8 +3,10 @@
|
|
3
3
|
EventMachine based HTTP Request interface. Supports streaming response processing, uses Ragel HTTP parser.
|
4
4
|
- Simple interface for single & parallel requests via deferred callbacks
|
5
5
|
- Automatic gzip & deflate decoding
|
6
|
-
- Basic-Auth support
|
7
|
-
- Custom timeouts
|
6
|
+
- Basic-Auth & OAuth support
|
7
|
+
- Custom timeouts
|
8
|
+
- Proxy support (with SSL Tunneling)
|
9
|
+
- Bi-directional communication with web-socket services
|
8
10
|
|
9
11
|
Screencast / Demo of using EM-HTTP-Request:
|
10
12
|
- http://everburning.com/news/eventmachine-screencast-em-http-request/
|
@@ -35,6 +37,7 @@ Screencast / Demo of using EM-HTTP-Request:
|
|
35
37
|
}
|
36
38
|
|
37
39
|
== Multi request example
|
40
|
+
Fire and wait for multiple requess to complete via the MultiRequest interface.
|
38
41
|
|
39
42
|
EventMachine.run {
|
40
43
|
multi = EventMachine::MultiRequest.new
|
@@ -52,6 +55,7 @@ Screencast / Demo of using EM-HTTP-Request:
|
|
52
55
|
}
|
53
56
|
|
54
57
|
== Basic-Auth example
|
58
|
+
Full basic author support. For OAuth, check examples/oauth-tweet.rb file.
|
55
59
|
|
56
60
|
EventMachine.run {
|
57
61
|
http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => ['user', 'pass']}
|
@@ -59,13 +63,12 @@ Screencast / Demo of using EM-HTTP-Request:
|
|
59
63
|
http.errback { failed }
|
60
64
|
http.callback {
|
61
65
|
p http.response_header
|
62
|
-
|
63
66
|
EventMachine.stop
|
64
67
|
}
|
65
68
|
}
|
66
69
|
|
67
|
-
== POST example
|
68
70
|
|
71
|
+
== POST example
|
69
72
|
EventMachine.run {
|
70
73
|
http1 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => {"key1" => 1, "key2" => [2,3]}
|
71
74
|
http2 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => "some data"
|
@@ -74,9 +77,44 @@ Screencast / Demo of using EM-HTTP-Request:
|
|
74
77
|
}
|
75
78
|
|
76
79
|
== Streaming body processing
|
80
|
+
Allows you to consume an HTTP stream of content in real-time. Each time a new piece of conent is pushed
|
81
|
+
to the client, it is passed to the stream callback for you to operate on.
|
82
|
+
|
77
83
|
EventMachine.run {
|
78
84
|
http = EventMachine::HttpRequest.new('http://www.website.com/').get
|
79
85
|
http.stream { |chunk| print chunk }
|
80
86
|
|
81
87
|
# ...
|
82
88
|
}
|
89
|
+
|
90
|
+
== Proxy example
|
91
|
+
Full transparent proxy support with support for SSL tunneling.
|
92
|
+
|
93
|
+
EventMachine.run {
|
94
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
|
95
|
+
:host => 'www.myproxy.com',
|
96
|
+
:port => 8080,
|
97
|
+
:authorization => ['username', 'password'] # authorization is optional
|
98
|
+
}
|
99
|
+
|
100
|
+
== WebSocket example
|
101
|
+
Bi-directional communication with WebSockets: simply pass in a ws:// resource and the client will
|
102
|
+
negotiate the connection upgrade for you. On successfull handshake the callback is invoked, and
|
103
|
+
any incoming messages will be passed to the stream callback. The client can also send data to the
|
104
|
+
server at will by calling the "send" method!
|
105
|
+
- http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/
|
106
|
+
|
107
|
+
EventMachine.run {
|
108
|
+
http = EventMachine::HttpRequest.new("ws://yourservice.com/websocket").get :timeout => 0
|
109
|
+
|
110
|
+
http.errback { puts "oops" }
|
111
|
+
http.callback {
|
112
|
+
puts "WebSocket connected!"
|
113
|
+
http.send("Hello client")
|
114
|
+
}
|
115
|
+
|
116
|
+
http.stream { |msg|
|
117
|
+
puts "Recieved: #{msg}"
|
118
|
+
http.send "Pong: #{msg}"
|
119
|
+
}
|
120
|
+
}
|
data/Rakefile
CHANGED
@@ -35,7 +35,7 @@ task :ragel do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
task :spec do
|
38
|
-
|
38
|
+
sh 'spec spec/*_spec.rb'
|
39
39
|
end
|
40
40
|
|
41
41
|
def make(makedir)
|
@@ -91,11 +91,12 @@ begin
|
|
91
91
|
gemspec.description = gemspec.summary
|
92
92
|
gemspec.email = "ilya@igvita.com"
|
93
93
|
gemspec.homepage = "http://github.com/igrigorik/em-http-request"
|
94
|
-
gemspec.authors = ["Ilya Grigorik", "Stephan Maka"]
|
94
|
+
gemspec.authors = ["Ilya Grigorik", "Stephan Maka", "Julien Genestoux"]
|
95
95
|
gemspec.extensions = ["ext/buffer/extconf.rb" , "ext/http11_client/extconf.rb"]
|
96
96
|
gemspec.add_dependency('eventmachine', '>= 0.12.2')
|
97
97
|
gemspec.add_dependency('addressable', '>= 2.0.0')
|
98
98
|
gemspec.rubyforge_project = "em-http-request"
|
99
|
+
gemspec.files = FileList[`git ls-files`.split]
|
99
100
|
end
|
100
101
|
|
101
102
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.6
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Courtesy of Darcy Laycock:
|
2
|
+
# http://gist.github.com/265261
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
require 'em-http'
|
8
|
+
require 'oauth'
|
9
|
+
|
10
|
+
# At a minimum, require 'oauth/request_proxy/em_http_request'
|
11
|
+
# for this example, we'll use Net::HTTP like support.
|
12
|
+
require 'oauth/client/em_http'
|
13
|
+
|
14
|
+
# You need two things: an oauth consumer and an access token.
|
15
|
+
# You need to generate an access token, I suggest looking elsewhere how to do that or wait for a full tutorial.
|
16
|
+
# For a consumer key / consumer secret, signup for an app at:
|
17
|
+
# http://twitter.com/apps/new
|
18
|
+
|
19
|
+
# Edit in your details.
|
20
|
+
CONSUMER_KEY = ""
|
21
|
+
CONSUMER_SECRET = ""
|
22
|
+
ACCESS_TOKEN = ""
|
23
|
+
ACCESS_TOKEN_SECRET = ""
|
24
|
+
|
25
|
+
def twitter_oauth_consumer
|
26
|
+
@twitter_oauth_consumer ||= OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => "http://twitter.com")
|
27
|
+
end
|
28
|
+
|
29
|
+
def twitter_oauth_access_token
|
30
|
+
@twitter_oauth_access_token ||= OAuth::AccessToken.new(twitter_oauth_consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
|
31
|
+
end
|
32
|
+
|
33
|
+
EM.run do
|
34
|
+
|
35
|
+
request = EventMachine::HttpRequest.new('http://twitter.com/statuses/update.json')
|
36
|
+
http = request.post(:body => {'status' => 'Hello Twitter from em-http-request with OAuth'}, :head => {"Content-Type" => "application/x-www-form-urlencoded"}) do |client|
|
37
|
+
twitter_oauth_consumer.sign!(client, twitter_oauth_access_token)
|
38
|
+
end
|
39
|
+
|
40
|
+
http.callback do
|
41
|
+
puts "Response: #{http.response} (Code: #{http.response_header.status})"
|
42
|
+
EM.stop_event_loop
|
43
|
+
end
|
44
|
+
|
45
|
+
http.errback do
|
46
|
+
puts "Failed to post"
|
47
|
+
EM.stop_event_loop
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -21,12 +21,27 @@ static VALUE eHttpClientParserError;
|
|
21
21
|
#define id_chunk_size rb_intern("@http_chunk_size")
|
22
22
|
#define id_last_chunk rb_intern("@last_chunk")
|
23
23
|
|
24
|
+
#ifndef RHASH_TBL
|
25
|
+
/* rb_hash_lookup() is only in Ruby 1.8.7 */
|
26
|
+
static VALUE rb_hash_lookup(VALUE hash, VALUE key)
|
27
|
+
{
|
28
|
+
VALUE val;
|
29
|
+
|
30
|
+
if (!st_lookup(RHASH(hash)->tbl, key, &val)) {
|
31
|
+
return Qnil; /* without Hash#default */
|
32
|
+
}
|
33
|
+
|
34
|
+
return val;
|
35
|
+
}
|
36
|
+
#endif
|
37
|
+
|
24
38
|
void client_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
|
25
39
|
{
|
26
40
|
char *ch, *end;
|
27
41
|
VALUE req = (VALUE)data;
|
28
42
|
VALUE v = Qnil;
|
29
43
|
VALUE f = Qnil;
|
44
|
+
VALUE el = Qnil;
|
30
45
|
|
31
46
|
v = rb_str_new(value, vlen);
|
32
47
|
f = rb_str_new(field, flen);
|
@@ -41,7 +56,18 @@ void client_http_field(void *data, const char *field, size_t flen, const char *v
|
|
41
56
|
}
|
42
57
|
}
|
43
58
|
|
44
|
-
|
59
|
+
el = rb_hash_lookup(req, f);
|
60
|
+
switch(TYPE(el)) {
|
61
|
+
case T_ARRAY:
|
62
|
+
rb_ary_push(el, v);
|
63
|
+
break;
|
64
|
+
case T_STRING:
|
65
|
+
rb_hash_aset(req, f, rb_ary_new3(2, el, v));
|
66
|
+
break;
|
67
|
+
default:
|
68
|
+
rb_hash_aset(req, f, v);
|
69
|
+
break;
|
70
|
+
}
|
45
71
|
}
|
46
72
|
|
47
73
|
void client_reason_phrase(void *data, const char *at, size_t length)
|
data/lib/em-http.rb
CHANGED
@@ -3,10 +3,8 @@
|
|
3
3
|
# You can redistribute this under the terms of the Ruby license
|
4
4
|
# See file LICENSE for details
|
5
5
|
#++
|
6
|
-
|
7
|
-
require '
|
8
|
-
require 'eventmachine'
|
9
|
-
|
6
|
+
|
7
|
+
require 'eventmachine'
|
10
8
|
|
11
9
|
require File.dirname(__FILE__) + '/http11_client'
|
12
10
|
require File.dirname(__FILE__) + '/em_buffer'
|
@@ -15,4 +13,4 @@ require File.dirname(__FILE__) + '/em-http/core_ext/hash'
|
|
15
13
|
require File.dirname(__FILE__) + '/em-http/client'
|
16
14
|
require File.dirname(__FILE__) + '/em-http/multi'
|
17
15
|
require File.dirname(__FILE__) + '/em-http/request'
|
18
|
-
require File.dirname(__FILE__) + '/em-http/decoders'
|
16
|
+
require File.dirname(__FILE__) + '/em-http/decoders'
|
data/lib/em-http/client.rb
CHANGED
@@ -39,7 +39,7 @@ module EventMachine
|
|
39
39
|
# Length of content as an integer, or nil if chunked/unspecified
|
40
40
|
def content_length
|
41
41
|
@content_length ||= ((s = self[HttpClient::CONTENT_LENGTH]) &&
|
42
|
-
|
42
|
+
(s =~ /^(\d+)$/)) ? $1.to_i : nil
|
43
43
|
end
|
44
44
|
|
45
45
|
# Cookie header from the server
|
@@ -80,7 +80,6 @@ module EventMachine
|
|
80
80
|
module HttpEncoding
|
81
81
|
HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
|
82
82
|
FIELD_ENCODING = "%s: %s\r\n"
|
83
|
-
BASIC_AUTH_ENCODING = "%s: Basic %s\r\n"
|
84
83
|
|
85
84
|
# Escapes a URI.
|
86
85
|
def escape(s)
|
@@ -108,18 +107,18 @@ module EventMachine
|
|
108
107
|
@uri.host + (@uri.port != 80 ? ":#{@uri.port}" : "")
|
109
108
|
end
|
110
109
|
|
111
|
-
def encode_request(method, path, query)
|
112
|
-
HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query)]
|
110
|
+
def encode_request(method, path, query, uri_query)
|
111
|
+
HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query, uri_query)]
|
113
112
|
end
|
114
113
|
|
115
|
-
def encode_query(path, query)
|
114
|
+
def encode_query(path, query, uri_query)
|
116
115
|
encoded_query = if query.kind_of?(Hash)
|
117
116
|
query.map { |k, v| encode_param(k, v) }.join('&')
|
118
117
|
else
|
119
118
|
query.to_s
|
120
119
|
end
|
121
|
-
if
|
122
|
-
encoded_query = [encoded_query,
|
120
|
+
if !uri_query.to_s.empty?
|
121
|
+
encoded_query = [encoded_query, uri_query].reject {|part| part.empty?}.join("&")
|
123
122
|
end
|
124
123
|
return path if encoded_query.to_s.empty?
|
125
124
|
"#{path}?#{encoded_query}"
|
@@ -141,18 +140,25 @@ module EventMachine
|
|
141
140
|
end
|
142
141
|
|
143
142
|
# Encode basic auth in an HTTP header
|
144
|
-
|
145
|
-
|
143
|
+
# In: Array ([user, pass]) - for basic auth
|
144
|
+
# String - custom auth string (OAuth, etc)
|
145
|
+
def encode_auth(k,v)
|
146
|
+
if v.is_a? Array
|
147
|
+
FIELD_ENCODING % [k, ["Basic", Base64.encode64(v.join(":")).chomp].join(" ")]
|
148
|
+
else
|
149
|
+
encode_field(k,v)
|
150
|
+
end
|
146
151
|
end
|
147
152
|
|
148
153
|
def encode_headers(head)
|
149
154
|
head.inject('') do |result, (key, value)|
|
150
155
|
# Munge keys from foo-bar-baz to Foo-Bar-Baz
|
151
|
-
key = key.split('-').map { |k| k.capitalize }.join('-')
|
152
|
-
|
153
|
-
|
156
|
+
key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
|
157
|
+
result << case key
|
158
|
+
when 'Authorization', 'Proxy-authorization'
|
159
|
+
encode_auth(key, value)
|
154
160
|
else
|
155
|
-
|
161
|
+
encode_field(key, value)
|
156
162
|
end
|
157
163
|
end
|
158
164
|
end
|
@@ -185,31 +191,43 @@ module EventMachine
|
|
185
191
|
def post_init
|
186
192
|
@parser = HttpClientParser.new
|
187
193
|
@data = EventMachine::Buffer.new
|
188
|
-
@response_header = HttpResponseHeader.new
|
189
194
|
@chunk_header = HttpChunkHeader.new
|
190
|
-
|
191
|
-
@state = :response_header
|
195
|
+
@response_header = HttpResponseHeader.new
|
192
196
|
@parser_nbytes = 0
|
193
197
|
@response = ''
|
194
198
|
@errors = ''
|
195
199
|
@content_decoder = nil
|
196
200
|
@stream = nil
|
201
|
+
@state = :response_header
|
202
|
+
@bytes_received = 0
|
203
|
+
@options = {}
|
197
204
|
end
|
198
205
|
|
199
206
|
# start HTTP request once we establish connection to host
|
200
|
-
def connection_completed
|
201
|
-
if
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
207
|
+
def connection_completed
|
208
|
+
# if connecting to proxy, then first negotiate the connection
|
209
|
+
# to intermediate server and wait for 200 response
|
210
|
+
if @options[:proxy] and @state == :response_header
|
211
|
+
@state = :response_proxy
|
212
|
+
send_request_header
|
213
|
+
|
214
|
+
# if connecting via proxy, then state will be :proxy_connected,
|
215
|
+
# indicating successful tunnel. from here, initiate normal http
|
216
|
+
# exchange
|
217
|
+
else
|
218
|
+
if @options[:max_connection_duration]
|
219
|
+
EM.add_timer(@options[:max_connection_duration]) {
|
220
|
+
@aborted = true
|
221
|
+
on_error("Max Connection Duration Exceeded (#{@options[:max_connection_duration]}s.)")
|
222
|
+
}
|
223
|
+
end
|
224
|
+
@state = :response_header
|
225
|
+
|
226
|
+
ssl = @options[:tls] || @options[:ssl] || {}
|
227
|
+
start_tls(ssl) if @uri.scheme == "https" #or @uri.port == 443 # A user might not want https even on port 443.
|
228
|
+
send_request_header
|
229
|
+
send_request_body
|
230
|
+
end
|
213
231
|
end
|
214
232
|
|
215
233
|
# request is done, invoke the callback
|
@@ -236,43 +254,87 @@ module EventMachine
|
|
236
254
|
@stream = blk
|
237
255
|
end
|
238
256
|
|
257
|
+
# raw data push from the client (WebSocket) should
|
258
|
+
# only be invoked after handshake, otherwise it will
|
259
|
+
# inject data into the header exchange
|
260
|
+
#
|
261
|
+
# frames need to start with 0x00-0x7f byte and end with
|
262
|
+
# an 0xFF byte. Per spec, we can also set the first
|
263
|
+
# byte to a value betweent 0x80 and 0xFF, followed by
|
264
|
+
# a leading length indicator
|
265
|
+
def send(data)
|
266
|
+
if @state == :websocket
|
267
|
+
send_data("\x00#{data}\xff")
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
239
271
|
def normalize_body
|
240
|
-
|
241
|
-
@options[:body].
|
242
|
-
|
243
|
-
|
272
|
+
@normalized_body ||= begin
|
273
|
+
if @options[:body].is_a? Hash
|
274
|
+
@options[:body].to_params
|
275
|
+
else
|
276
|
+
@options[:body]
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def normalize_uri
|
282
|
+
@normalized_uri ||= begin
|
283
|
+
uri = @uri.dup
|
284
|
+
encoded_query = encode_query(@uri.path, @options[:query], @uri.query)
|
285
|
+
path, query = encoded_query.split("?", 2)
|
286
|
+
uri.query = query unless encoded_query.empty?
|
287
|
+
uri.path = path
|
288
|
+
uri
|
244
289
|
end
|
245
290
|
end
|
246
291
|
|
292
|
+
def websocket?; @uri.scheme == 'ws'; end
|
293
|
+
|
247
294
|
def send_request_header
|
248
295
|
query = @options[:query]
|
249
296
|
head = @options[:head] ? munge_header_keys(@options[:head]) : {}
|
250
297
|
body = normalize_body
|
298
|
+
request_header = nil
|
299
|
+
|
300
|
+
if @state == :response_proxy
|
301
|
+
proxy = @options[:proxy]
|
302
|
+
|
303
|
+
# initialize headers to establish the HTTP tunnel
|
304
|
+
head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
|
305
|
+
head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
|
306
|
+
request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
|
307
|
+
|
308
|
+
elsif websocket?
|
309
|
+
head['upgrade'] = 'WebSocket'
|
310
|
+
head['connection'] = 'Upgrade'
|
311
|
+
head['origin'] = @options[:origin] || @uri.host
|
312
|
+
|
313
|
+
else
|
314
|
+
# Set the Content-Length if body is given
|
315
|
+
head['content-length'] = body.length if body
|
316
|
+
|
317
|
+
# Set the cookie header if provided
|
318
|
+
if cookie = head.delete('cookie')
|
319
|
+
head['cookie'] = encode_cookie(cookie)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Set content-type header if missing and body is a Ruby hash
|
323
|
+
if not head['content-type'] and options[:body].is_a? Hash
|
324
|
+
head['content-type'] = "application/x-www-form-urlencoded"
|
325
|
+
end
|
326
|
+
end
|
251
327
|
|
252
328
|
# Set the Host header if it hasn't been specified already
|
253
329
|
head['host'] ||= encode_host
|
254
330
|
|
255
|
-
# Set the Content-Length if body is given
|
256
|
-
head['content-length'] = body.length if body
|
257
|
-
|
258
331
|
# Set the User-Agent if it hasn't been specified
|
259
332
|
head['user-agent'] ||= "EventMachine HttpClient"
|
260
333
|
|
261
|
-
#
|
262
|
-
|
263
|
-
head['cookie'] = encode_cookie(cookie)
|
264
|
-
end
|
265
|
-
|
266
|
-
# Set content-type header if missing and body is a Ruby hash
|
267
|
-
if not head['content-type'] and options[:body].is_a? Hash
|
268
|
-
head['content-type'] = "application/x-www-form-urlencoded"
|
269
|
-
end
|
270
|
-
|
271
|
-
# Build the request
|
272
|
-
request_header = encode_request(@method, @uri.path, query)
|
334
|
+
# Build the request headers
|
335
|
+
request_header ||= encode_request(@method, @uri.path, query, @uri.query)
|
273
336
|
request_header << encode_headers(head)
|
274
337
|
request_header << CRLF
|
275
|
-
|
276
338
|
send_data request_header
|
277
339
|
end
|
278
340
|
|
@@ -333,6 +395,8 @@ module EventMachine
|
|
333
395
|
|
334
396
|
def dispatch
|
335
397
|
while case @state
|
398
|
+
when :response_proxy
|
399
|
+
parse_response_proxy
|
336
400
|
when :response_header
|
337
401
|
parse_response_header
|
338
402
|
when :chunk_header
|
@@ -345,6 +409,8 @@ module EventMachine
|
|
345
409
|
process_response_footer
|
346
410
|
when :body
|
347
411
|
process_body
|
412
|
+
when :websocket
|
413
|
+
process_websocket
|
348
414
|
when :finished, :invalid
|
349
415
|
break
|
350
416
|
else raise RuntimeError, "invalid state: #{@state}"
|
@@ -370,6 +436,28 @@ module EventMachine
|
|
370
436
|
|
371
437
|
true
|
372
438
|
end
|
439
|
+
|
440
|
+
# TODO: refactor with parse_response_header
|
441
|
+
def parse_response_proxy
|
442
|
+
return false unless parse_header(@response_header)
|
443
|
+
|
444
|
+
unless @response_header.http_status and @response_header.http_reason
|
445
|
+
@state = :invalid
|
446
|
+
on_error "no HTTP response"
|
447
|
+
return false
|
448
|
+
end
|
449
|
+
|
450
|
+
# when a successfull tunnel is established, the proxy responds with a
|
451
|
+
# 200 response code. from here, the tunnel is transparent.
|
452
|
+
if @response_header.http_status.to_i == 200
|
453
|
+
@response_header = HttpResponseHeader.new
|
454
|
+
connection_completed
|
455
|
+
else
|
456
|
+
@state = :invalid
|
457
|
+
on_error "proxy not accessible"
|
458
|
+
return false
|
459
|
+
end
|
460
|
+
end
|
373
461
|
|
374
462
|
def parse_response_header
|
375
463
|
return false unless parse_header(@response_header)
|
@@ -394,17 +482,25 @@ module EventMachine
|
|
394
482
|
end
|
395
483
|
end
|
396
484
|
|
397
|
-
# shortcircuit on HEAD requests
|
485
|
+
# shortcircuit on HEAD requests
|
398
486
|
if @method == "HEAD"
|
399
487
|
@state = :finished
|
400
488
|
on_request_complete
|
401
489
|
end
|
402
490
|
|
403
|
-
if
|
491
|
+
if websocket?
|
492
|
+
if @response_header.status == 101
|
493
|
+
@state = :websocket
|
494
|
+
succeed
|
495
|
+
else
|
496
|
+
fail "websocket handshake failed"
|
497
|
+
end
|
498
|
+
|
499
|
+
elsif @response_header.chunked_encoding?
|
404
500
|
@state = :chunk_header
|
405
501
|
elsif @response_header.content_length
|
406
502
|
@state = :body
|
407
|
-
@bytes_remaining = @response_header.content_length
|
503
|
+
@bytes_remaining = @response_header.content_length
|
408
504
|
else
|
409
505
|
@state = :body
|
410
506
|
@bytes_remaining = nil
|
@@ -519,7 +615,23 @@ module EventMachine
|
|
519
615
|
false
|
520
616
|
end
|
521
617
|
|
522
|
-
|
618
|
+
def process_websocket
|
619
|
+
return false if @data.empty?
|
620
|
+
|
621
|
+
# slice the message out of the buffer and pass in
|
622
|
+
# for processing, and buffer data otherwise
|
623
|
+
buffer = @data.read
|
624
|
+
while msg = buffer.slice!(/\000([^\377]*)\377/)
|
625
|
+
msg.gsub!(/^\x00|\xff$/, '')
|
626
|
+
@stream.call(msg)
|
627
|
+
end
|
628
|
+
|
629
|
+
# store remainder if message boundary has not yet
|
630
|
+
# been recieved
|
631
|
+
@data << buffer if not buffer.empty?
|
632
|
+
|
633
|
+
false
|
634
|
+
end
|
523
635
|
end
|
524
636
|
|
525
637
|
end
|