em-http-request 0.2.12 → 0.2.13
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/Changelog.md +5 -0
- data/README.md +12 -2
- data/VERSION +1 -1
- data/em-http-request.gemspec +4 -2
- data/examples/socks5.rb +26 -0
- data/lib/em-http.rb +20 -19
- data/lib/em-http/client.rb +167 -10
- data/lib/em-http/core_ext/bytesize.rb +5 -5
- data/lib/em-http/core_ext/hash.rb +53 -53
- data/spec/hash_spec.rb +24 -24
- data/spec/request_spec.rb +25 -0
- data/spec/spec.opts +6 -6
- data/spec/stallion.rb +5 -1
- metadata +5 -3
data/Changelog.md
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Asynchronous HTTP client for Ruby, based on EventMachine runtime.
|
|
9
9
|
- Basic-Auth & OAuth support
|
10
10
|
- Custom timeout support
|
11
11
|
- Stream response processing
|
12
|
-
- Proxy support (with SSL Tunneling)
|
12
|
+
- Proxy support (with SSL Tunneling): CONNECT, direct & SOCKS5
|
13
13
|
- Auto-follow 3xx redirects with custom max depth
|
14
14
|
- Bi-directional communication with web-socket services
|
15
15
|
- [Native mocking support](http://wiki.github.com/igrigorik/em-http-request/mocking-httprequest) and through [Webmock](http://github.com/bblimke/webmock)
|
@@ -114,7 +114,6 @@ Allows you to efficiently stream a (large) file from disk via EventMachine's Fil
|
|
114
114
|
|
115
115
|
Proxy example
|
116
116
|
-------------
|
117
|
-
|
118
117
|
Full transparent proxy support with support for SSL tunneling.
|
119
118
|
|
120
119
|
EventMachine.run {
|
@@ -124,6 +123,17 @@ Full transparent proxy support with support for SSL tunneling.
|
|
124
123
|
:authorization => ['username', 'password'] # authorization is optional
|
125
124
|
}
|
126
125
|
|
126
|
+
SOCKS5 Proxy example
|
127
|
+
-------------
|
128
|
+
Tunnel your requests via connect via SOCKS5 proxies (ssh -D port somehost).
|
129
|
+
|
130
|
+
EventMachine.run {
|
131
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
|
132
|
+
:host => 'www.myproxy.com',
|
133
|
+
:port => 8080,
|
134
|
+
:type => :socks
|
135
|
+
}
|
136
|
+
|
127
137
|
Auto-follow 3xx redirects
|
128
138
|
-------------------------
|
129
139
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.13
|
data/em-http-request.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{em-http-request}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.13"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ilya Grigorik"]
|
12
|
-
s.date = %q{2010-09-
|
12
|
+
s.date = %q{2010-09-25}
|
13
13
|
s.description = %q{EventMachine based, async HTTP Request interface}
|
14
14
|
s.email = %q{ilya@igvita.com}
|
15
15
|
s.extensions = ["ext/buffer/extconf.rb", "ext/http11_client/extconf.rb"]
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
"examples/fetch.rb",
|
29
29
|
"examples/fibered-http.rb",
|
30
30
|
"examples/oauth-tweet.rb",
|
31
|
+
"examples/socks5.rb",
|
31
32
|
"examples/websocket-handler.rb",
|
32
33
|
"examples/websocket-server.rb",
|
33
34
|
"ext/buffer/em_buffer.c",
|
@@ -76,6 +77,7 @@ Gem::Specification.new do |s|
|
|
76
77
|
"examples/fetch.rb",
|
77
78
|
"examples/fibered-http.rb",
|
78
79
|
"examples/oauth-tweet.rb",
|
80
|
+
"examples/socks5.rb",
|
79
81
|
"examples/websocket-handler.rb",
|
80
82
|
"examples/websocket-server.rb"
|
81
83
|
]
|
data/examples/socks5.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require '../lib/em-http'
|
4
|
+
|
5
|
+
EM.run do
|
6
|
+
# Establish a SOCKS5 tunnel via SSH
|
7
|
+
# ssh -D 8000 some_remote_machine
|
8
|
+
|
9
|
+
# http = EM::HttpRequest.new('http://whatismyip.org/').get({
|
10
|
+
http = EM::HttpRequest.new('http://igvita.com/').get({
|
11
|
+
:proxy => {:host => '127.0.0.1', :port => 8000, :type => :socks},
|
12
|
+
:redirects => 2
|
13
|
+
})
|
14
|
+
|
15
|
+
http.callback {
|
16
|
+
puts "#{http.response_header.status} - #{http.response.length} bytes\n"
|
17
|
+
puts http.response
|
18
|
+
EM.stop
|
19
|
+
}
|
20
|
+
|
21
|
+
http.errback {
|
22
|
+
puts "Error: " + http.error
|
23
|
+
puts http.inspect
|
24
|
+
EM.stop
|
25
|
+
}
|
26
|
+
end
|
data/lib/em-http.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
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 'eventmachine'
|
8
|
-
|
9
|
-
|
10
|
-
require File.dirname(__FILE__) + '/
|
11
|
-
|
12
|
-
|
13
|
-
require File.dirname(__FILE__) + '/em-http/core_ext/
|
14
|
-
|
15
|
-
|
16
|
-
require File.dirname(__FILE__) + '/em-http/
|
17
|
-
require File.dirname(__FILE__) + '/em-http/
|
18
|
-
require File.dirname(__FILE__) + '/em-http/
|
19
|
-
require File.dirname(__FILE__) + '/em-http/
|
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 'eventmachine'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/http11_client'
|
11
|
+
require File.dirname(__FILE__) + '/em_buffer'
|
12
|
+
|
13
|
+
require File.dirname(__FILE__) + '/em-http/core_ext/hash'
|
14
|
+
require File.dirname(__FILE__) + '/em-http/core_ext/bytesize'
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/em-http/client'
|
17
|
+
require File.dirname(__FILE__) + '/em-http/multi'
|
18
|
+
require File.dirname(__FILE__) + '/em-http/request'
|
19
|
+
require File.dirname(__FILE__) + '/em-http/decoders'
|
20
|
+
require File.dirname(__FILE__) + '/em-http/http_options'
|
20
21
|
require File.dirname(__FILE__) + '/em-http/mock'
|
data/lib/em-http/client.rb
CHANGED
@@ -125,7 +125,7 @@ module EventMachine
|
|
125
125
|
|
126
126
|
# Non CONNECT proxies require that you provide the full request
|
127
127
|
# uri in request header, as opposed to a relative path.
|
128
|
-
query = uri.join(query) if proxy
|
128
|
+
query = uri.join(query) if proxy && proxy[:type] != :socks && !proxy[:use_connect]
|
129
129
|
|
130
130
|
HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
|
131
131
|
end
|
@@ -224,19 +224,28 @@ module EventMachine
|
|
224
224
|
@stream = nil
|
225
225
|
@disconnect = nil
|
226
226
|
@state = :response_header
|
227
|
+
@socks_state = nil
|
227
228
|
end
|
228
229
|
|
229
230
|
# start HTTP request once we establish connection to host
|
230
231
|
def connection_completed
|
232
|
+
# if a socks proxy is specified, then a connection request
|
233
|
+
# has to be made to the socks server and we need to wait
|
234
|
+
# for a response code
|
235
|
+
if socks_proxy? and @state == :response_header
|
236
|
+
@state = :connect_socks_proxy
|
237
|
+
send_socks_handshake
|
238
|
+
|
231
239
|
# if we need to negotiate the proxy connection first, then
|
232
240
|
# issue a CONNECT query and wait for 200 response
|
233
|
-
|
234
|
-
@state = :
|
241
|
+
elsif connect_proxy? and @state == :response_header
|
242
|
+
@state = :connect_http_proxy
|
235
243
|
send_request_header
|
236
244
|
|
237
245
|
# if connecting via proxy, then state will be :proxy_connected,
|
238
246
|
# indicating successful tunnel. from here, initiate normal http
|
239
247
|
# exchange
|
248
|
+
|
240
249
|
else
|
241
250
|
@state = :response_header
|
242
251
|
ssl = @options[:tls] || @options[:ssl] || {}
|
@@ -306,9 +315,44 @@ module EventMachine
|
|
306
315
|
end
|
307
316
|
end
|
308
317
|
|
318
|
+
# determines if there is enough data in the buffer
|
319
|
+
def has_bytes?(num)
|
320
|
+
@data.size >= num
|
321
|
+
end
|
322
|
+
|
309
323
|
def websocket?; @uri.scheme == 'ws'; end
|
310
324
|
def proxy?; !@options[:proxy].nil?; end
|
311
|
-
|
325
|
+
|
326
|
+
# determines if a proxy should be used that uses
|
327
|
+
# http-headers as proxy-mechanism
|
328
|
+
#
|
329
|
+
# this is the default proxy type if none is specified
|
330
|
+
def http_proxy?; proxy? && [nil, :http].include?(@options[:proxy][:type]); end
|
331
|
+
|
332
|
+
# determines if a http-proxy should be used with
|
333
|
+
# the CONNECT verb
|
334
|
+
def connect_proxy?; http_proxy? && (@options[:proxy][:use_connect] == true); end
|
335
|
+
|
336
|
+
# determines if a SOCKS5 proxy should be used
|
337
|
+
def socks_proxy?; proxy? && (@options[:proxy][:type] == :socks); end
|
338
|
+
|
339
|
+
def socks_methods
|
340
|
+
methods = []
|
341
|
+
methods << 2 if !options[:proxy][:authorization].nil? # 2 => Username/Password Authentication
|
342
|
+
methods << 0 # 0 => No Authentication Required
|
343
|
+
|
344
|
+
methods
|
345
|
+
end
|
346
|
+
|
347
|
+
def send_socks_handshake
|
348
|
+
# Method Negotiation as described on
|
349
|
+
# http://www.faqs.org/rfcs/rfc1928.html Section 3
|
350
|
+
|
351
|
+
@socks_state = :method_negotiation
|
352
|
+
|
353
|
+
methods = socks_methods
|
354
|
+
send_data [5, methods.size].pack('CC') + methods.pack('C*')
|
355
|
+
end
|
312
356
|
|
313
357
|
def send_request_header
|
314
358
|
query = @options[:query]
|
@@ -319,7 +363,7 @@ module EventMachine
|
|
319
363
|
|
320
364
|
request_header = nil
|
321
365
|
|
322
|
-
if
|
366
|
+
if http_proxy?
|
323
367
|
# initialize headers for the http proxy
|
324
368
|
head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
|
325
369
|
head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
|
@@ -327,7 +371,7 @@ module EventMachine
|
|
327
371
|
# if we need to negotiate the tunnel connection first, then
|
328
372
|
# issue a CONNECT query to the proxy first. This is an optional
|
329
373
|
# flag, by default we will provide full URIs to the proxy
|
330
|
-
if @state == :
|
374
|
+
if @state == :connect_http_proxy
|
331
375
|
request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
|
332
376
|
end
|
333
377
|
end
|
@@ -443,7 +487,9 @@ module EventMachine
|
|
443
487
|
|
444
488
|
def dispatch
|
445
489
|
while case @state
|
446
|
-
when :
|
490
|
+
when :connect_socks_proxy
|
491
|
+
parse_socks_response
|
492
|
+
when :connect_http_proxy
|
447
493
|
parse_response_header
|
448
494
|
when :response_header
|
449
495
|
parse_response_header
|
@@ -499,7 +545,7 @@ module EventMachine
|
|
499
545
|
return false
|
500
546
|
end
|
501
547
|
|
502
|
-
if @state == :
|
548
|
+
if @state == :connect_http_proxy
|
503
549
|
# when a successfull tunnel is established, the proxy responds with a
|
504
550
|
# 200 response code. from here, the tunnel is transparent.
|
505
551
|
if @response_header.http_status.to_i == 200
|
@@ -535,10 +581,13 @@ module EventMachine
|
|
535
581
|
end
|
536
582
|
end
|
537
583
|
|
538
|
-
#
|
584
|
+
# Fire callbacks immediately after recieving header requests
|
585
|
+
# if the request method is HEAD. In case of a redirect, terminate
|
586
|
+
# current connection and reinitialize the process.
|
539
587
|
if @method == "HEAD"
|
540
588
|
@state = :finished
|
541
|
-
|
589
|
+
close_connection
|
590
|
+
return false
|
542
591
|
end
|
543
592
|
|
544
593
|
if websocket?
|
@@ -570,6 +619,114 @@ module EventMachine
|
|
570
619
|
true
|
571
620
|
end
|
572
621
|
|
622
|
+
def send_socks_connect_request
|
623
|
+
# TO-DO: Implement address types for IPv6 and Domain
|
624
|
+
begin
|
625
|
+
ip_address = Socket.gethostbyname(@uri.host).last
|
626
|
+
send_data [5, 1, 0, 1, ip_address, @uri.port].flatten.pack('CCCCA4n')
|
627
|
+
|
628
|
+
rescue
|
629
|
+
@state = :invalid
|
630
|
+
on_error "could not resolve host", true
|
631
|
+
return false
|
632
|
+
end
|
633
|
+
|
634
|
+
true
|
635
|
+
end
|
636
|
+
|
637
|
+
# parses socks 5 server responses as specified
|
638
|
+
# on http://www.faqs.org/rfcs/rfc1928.html
|
639
|
+
def parse_socks_response
|
640
|
+
if @socks_state == :method_negotiation
|
641
|
+
return false unless has_bytes? 2
|
642
|
+
|
643
|
+
_, method = @data.read(2).unpack('CC')
|
644
|
+
|
645
|
+
if socks_methods.include?(method)
|
646
|
+
if method == 0
|
647
|
+
@socks_state = :connecting
|
648
|
+
|
649
|
+
return send_socks_connect_request
|
650
|
+
|
651
|
+
elsif method == 2
|
652
|
+
@socks_state = :authenticating
|
653
|
+
|
654
|
+
credentials = @options[:proxy][:authorization]
|
655
|
+
if credentials.size < 2
|
656
|
+
@state = :invalid
|
657
|
+
on_error "username and password are not supplied"
|
658
|
+
return false
|
659
|
+
end
|
660
|
+
|
661
|
+
username, password = credentials
|
662
|
+
|
663
|
+
send_data [5, username.length, username, password.length, password].pack('CCA*CA*')
|
664
|
+
end
|
665
|
+
|
666
|
+
else
|
667
|
+
@state = :invalid
|
668
|
+
on_error "proxy did not accept method"
|
669
|
+
return false
|
670
|
+
end
|
671
|
+
|
672
|
+
elsif @socks_state == :authenticating
|
673
|
+
return false unless has_bytes? 2
|
674
|
+
|
675
|
+
_, status_code = @data.read(2).unpack('CC')
|
676
|
+
|
677
|
+
if status_code == 0
|
678
|
+
# success
|
679
|
+
@socks_state = :connecting
|
680
|
+
|
681
|
+
return send_socks_connect_request
|
682
|
+
|
683
|
+
else
|
684
|
+
# error
|
685
|
+
@state = :invalid
|
686
|
+
on_error "access denied by proxy"
|
687
|
+
return false
|
688
|
+
end
|
689
|
+
|
690
|
+
elsif @socks_state == :connecting
|
691
|
+
return false unless has_bytes? 10
|
692
|
+
|
693
|
+
_, response_code, _, address_type, _, _ = @data.read(10).unpack('CCCCNn')
|
694
|
+
|
695
|
+
if response_code == 0
|
696
|
+
# success
|
697
|
+
@socks_state = :connected
|
698
|
+
@state = :proxy_connected
|
699
|
+
|
700
|
+
@response_header = HttpResponseHeader.new
|
701
|
+
|
702
|
+
# connection_completed will invoke actions to
|
703
|
+
# start sending all http data transparently
|
704
|
+
# over the socks connection
|
705
|
+
connection_completed
|
706
|
+
|
707
|
+
else
|
708
|
+
# error
|
709
|
+
@state = :invalid
|
710
|
+
|
711
|
+
error_messages = {
|
712
|
+
1 => "general socks server failure",
|
713
|
+
2 => "connection not allowed by ruleset",
|
714
|
+
3 => "network unreachable",
|
715
|
+
4 => "host unreachable",
|
716
|
+
5 => "connection refused",
|
717
|
+
6 => "TTL expired",
|
718
|
+
7 => "command not supported",
|
719
|
+
8 => "address type not supported"
|
720
|
+
}
|
721
|
+
error_message = error_messages[response_code] || "unknown error (code: #{response_code})"
|
722
|
+
on_error "socks5 connect error: #{error_message}"
|
723
|
+
return false
|
724
|
+
end
|
725
|
+
end
|
726
|
+
|
727
|
+
true
|
728
|
+
end
|
729
|
+
|
573
730
|
def parse_chunk_header
|
574
731
|
return false unless parse_header(@chunk_header)
|
575
732
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
# bytesize was introduced in 1.8.7+
|
2
|
-
if RUBY_VERSION <= "1.8.6"
|
3
|
-
class String
|
4
|
-
def bytesize; self.size; end
|
5
|
-
end
|
1
|
+
# bytesize was introduced in 1.8.7+
|
2
|
+
if RUBY_VERSION <= "1.8.6"
|
3
|
+
class String
|
4
|
+
def bytesize; self.size; end
|
5
|
+
end
|
6
6
|
end
|
@@ -1,53 +1,53 @@
|
|
1
|
-
class Hash
|
2
|
-
# Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
|
3
|
-
# Convert this hash to a query string:
|
4
|
-
#
|
5
|
-
# { :name => "Bob",
|
6
|
-
# :address => {
|
7
|
-
# :street => '111 Ruby Ave.',
|
8
|
-
# :city => 'Ruby Central',
|
9
|
-
# :phones => ['111-111-1111', '222-222-2222']
|
10
|
-
# }
|
11
|
-
# }.to_params
|
12
|
-
# #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
|
13
|
-
#
|
14
|
-
def to_params
|
15
|
-
params = ''
|
16
|
-
stack = []
|
17
|
-
|
18
|
-
each do |k, v|
|
19
|
-
if v.is_a?(Hash)
|
20
|
-
stack << [k,v]
|
21
|
-
elsif v.is_a?(Array)
|
22
|
-
stack << [k,Hash.from_array(v)]
|
23
|
-
else
|
24
|
-
params << "#{k}=#{v}&"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
stack.each do |parent, hash|
|
29
|
-
hash.each do |k, v|
|
30
|
-
if v.is_a?(Hash)
|
31
|
-
stack << ["#{parent}[#{k}]", v]
|
32
|
-
else
|
33
|
-
params << "#{parent}[#{k}]=#{v}&"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
params.chop! # trailing &
|
39
|
-
params
|
40
|
-
end
|
41
|
-
|
42
|
-
##
|
43
|
-
# Builds a hash from an array with keys as array indices.
|
44
|
-
def self.from_array(array = [])
|
45
|
-
h = Hash.new
|
46
|
-
array.size.times do |t|
|
47
|
-
h[t] = array[t]
|
48
|
-
end
|
49
|
-
h
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
1
|
+
class Hash
|
2
|
+
# Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
|
3
|
+
# Convert this hash to a query string:
|
4
|
+
#
|
5
|
+
# { :name => "Bob",
|
6
|
+
# :address => {
|
7
|
+
# :street => '111 Ruby Ave.',
|
8
|
+
# :city => 'Ruby Central',
|
9
|
+
# :phones => ['111-111-1111', '222-222-2222']
|
10
|
+
# }
|
11
|
+
# }.to_params
|
12
|
+
# #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
|
13
|
+
#
|
14
|
+
def to_params
|
15
|
+
params = ''
|
16
|
+
stack = []
|
17
|
+
|
18
|
+
each do |k, v|
|
19
|
+
if v.is_a?(Hash)
|
20
|
+
stack << [k,v]
|
21
|
+
elsif v.is_a?(Array)
|
22
|
+
stack << [k,Hash.from_array(v)]
|
23
|
+
else
|
24
|
+
params << "#{k}=#{v}&"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
stack.each do |parent, hash|
|
29
|
+
hash.each do |k, v|
|
30
|
+
if v.is_a?(Hash)
|
31
|
+
stack << ["#{parent}[#{k}]", v]
|
32
|
+
else
|
33
|
+
params << "#{parent}[#{k}]=#{v}&"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
params.chop! # trailing &
|
39
|
+
params
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Builds a hash from an array with keys as array indices.
|
44
|
+
def self.from_array(array = [])
|
45
|
+
h = Hash.new
|
46
|
+
array.size.times do |t|
|
47
|
+
h[t] = array[t]
|
48
|
+
end
|
49
|
+
h
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
data/spec/hash_spec.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
require 'spec/helper'
|
2
|
-
|
3
|
-
describe Hash do
|
4
|
-
|
5
|
-
describe ".to_params" do
|
6
|
-
it "should transform a basic hash into HTTP POST Params" do
|
7
|
-
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
|
8
|
-
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
|
9
|
-
end
|
10
|
-
|
11
|
-
it "should transform a more complex hash into HTTP POST Params" do
|
12
|
-
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
|
13
|
-
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
|
14
|
-
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
|
15
|
-
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should transform a very complex hash into HTTP POST Params" do
|
19
|
-
params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
|
20
|
-
params.should include "a=a"
|
21
|
-
params.should include "b[0][d]=d"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
1
|
+
require 'spec/helper'
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
|
5
|
+
describe ".to_params" do
|
6
|
+
it "should transform a basic hash into HTTP POST Params" do
|
7
|
+
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
|
8
|
+
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should transform a more complex hash into HTTP POST Params" do
|
12
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
|
13
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
|
14
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
|
15
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should transform a very complex hash into HTTP POST Params" do
|
19
|
+
params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
|
20
|
+
params.should include "a=a"
|
21
|
+
params.should include "b[0][d]=d"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/request_spec.rb
CHANGED
@@ -99,6 +99,31 @@ describe EventMachine::HttpRequest do
|
|
99
99
|
}
|
100
100
|
}
|
101
101
|
end
|
102
|
+
|
103
|
+
it "should follow redirects on HEAD method" do
|
104
|
+
EventMachine.run {
|
105
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/head').head :redirects => 1
|
106
|
+
http.errback { failed }
|
107
|
+
http.callback {
|
108
|
+
http.response_header.status.should == 200
|
109
|
+
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
|
110
|
+
EM.stop
|
111
|
+
}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should follow redirects on HEAD method (external)" do
|
116
|
+
|
117
|
+
EventMachine.run {
|
118
|
+
http = EventMachine::HttpRequest.new('http://www.google.com/').head :redirects => 1
|
119
|
+
http.errback { failed }
|
120
|
+
http.callback {
|
121
|
+
http.response_header.status.should == 200
|
122
|
+
EM.stop
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
102
127
|
end
|
103
128
|
|
104
129
|
it "should perform successfull GET with a URI passed as argument" do
|
data/spec/spec.opts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
--colour
|
2
|
-
--format
|
3
|
-
specdoc
|
4
|
-
--loadby
|
5
|
-
mtime
|
6
|
-
--reverse
|
1
|
+
--colour
|
2
|
+
--format
|
3
|
+
specdoc
|
4
|
+
--loadby
|
5
|
+
mtime
|
6
|
+
--reverse
|
7
7
|
--backtrace
|
data/spec/stallion.rb
CHANGED
@@ -85,7 +85,7 @@ Stallion.saddle :spec do |stable|
|
|
85
85
|
elsif stable.request.path_info == '/echo_content_length'
|
86
86
|
stable.response.write stable.request.content_length
|
87
87
|
|
88
|
-
elsif stable.request.head?
|
88
|
+
elsif stable.request.head? && stable.request.path_info == '/'
|
89
89
|
stable.response.status = 200
|
90
90
|
|
91
91
|
elsif stable.request.delete?
|
@@ -121,6 +121,10 @@ Stallion.saddle :spec do |stable|
|
|
121
121
|
stable.response.status = 301
|
122
122
|
stable.response["Location"] = "http://127.0.0.1:8080"
|
123
123
|
|
124
|
+
elsif stable.request.path_info == '/redirect/head'
|
125
|
+
stable.response.status = 301
|
126
|
+
stable.response["Location"] = "/"
|
127
|
+
|
124
128
|
elsif stable.request.path_info == '/redirect/nohost'
|
125
129
|
stable.response.status = 301
|
126
130
|
stable.response["Location"] = "http:/"
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 13
|
9
|
+
version: 0.2.13
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ilya Grigorik
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-25 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -68,6 +68,7 @@ files:
|
|
68
68
|
- examples/fetch.rb
|
69
69
|
- examples/fibered-http.rb
|
70
70
|
- examples/oauth-tweet.rb
|
71
|
+
- examples/socks5.rb
|
71
72
|
- examples/websocket-handler.rb
|
72
73
|
- examples/websocket-server.rb
|
73
74
|
- ext/buffer/em_buffer.c
|
@@ -142,5 +143,6 @@ test_files:
|
|
142
143
|
- examples/fetch.rb
|
143
144
|
- examples/fibered-http.rb
|
144
145
|
- examples/oauth-tweet.rb
|
146
|
+
- examples/socks5.rb
|
145
147
|
- examples/websocket-handler.rb
|
146
148
|
- examples/websocket-server.rb
|