ably-em-http-request 1.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.github/workflows/ci.yml +22 -0
- data/.gitignore +9 -0
- data/.rspec +0 -0
- data/Changelog.md +78 -0
- data/Gemfile +14 -0
- data/LICENSE +21 -0
- data/README.md +66 -0
- data/Rakefile +10 -0
- data/ably-em-http-request.gemspec +33 -0
- data/benchmarks/clients.rb +170 -0
- data/benchmarks/em-excon.rb +87 -0
- data/benchmarks/em-profile.gif +0 -0
- data/benchmarks/em-profile.txt +65 -0
- data/benchmarks/server.rb +48 -0
- data/examples/.gitignore +1 -0
- data/examples/digest_auth/client.rb +25 -0
- data/examples/digest_auth/server.rb +28 -0
- data/examples/fetch.rb +30 -0
- data/examples/fibered-http.rb +51 -0
- data/examples/multi.rb +25 -0
- data/examples/oauth-tweet.rb +35 -0
- data/examples/socks5.rb +23 -0
- data/lib/em/io_streamer.rb +51 -0
- data/lib/em-http/client.rb +343 -0
- data/lib/em-http/core_ext/bytesize.rb +6 -0
- data/lib/em-http/decoders.rb +252 -0
- data/lib/em-http/http_client_options.rb +51 -0
- data/lib/em-http/http_connection.rb +408 -0
- data/lib/em-http/http_connection_options.rb +72 -0
- data/lib/em-http/http_encoding.rb +151 -0
- data/lib/em-http/http_header.rb +85 -0
- data/lib/em-http/http_status_codes.rb +59 -0
- data/lib/em-http/middleware/digest_auth.rb +114 -0
- data/lib/em-http/middleware/json_response.rb +17 -0
- data/lib/em-http/middleware/oauth.rb +42 -0
- data/lib/em-http/middleware/oauth2.rb +30 -0
- data/lib/em-http/multi.rb +59 -0
- data/lib/em-http/request.rb +25 -0
- data/lib/em-http/version.rb +7 -0
- data/lib/em-http-request.rb +1 -0
- data/lib/em-http.rb +20 -0
- data/spec/client_fiber_spec.rb +23 -0
- data/spec/client_spec.rb +1000 -0
- data/spec/digest_auth_spec.rb +48 -0
- data/spec/dns_spec.rb +41 -0
- data/spec/encoding_spec.rb +49 -0
- data/spec/external_spec.rb +146 -0
- data/spec/fixtures/google.ca +16 -0
- data/spec/fixtures/gzip-sample.gz +0 -0
- data/spec/gzip_spec.rb +91 -0
- data/spec/helper.rb +27 -0
- data/spec/http_proxy_spec.rb +268 -0
- data/spec/middleware/oauth2_spec.rb +15 -0
- data/spec/middleware_spec.rb +143 -0
- data/spec/multi_spec.rb +104 -0
- data/spec/pipelining_spec.rb +62 -0
- data/spec/redirect_spec.rb +430 -0
- data/spec/socksify_proxy_spec.rb +56 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/ssl_spec.rb +67 -0
- data/spec/stallion.rb +334 -0
- data/spec/stub_server.rb +45 -0
- metadata +269 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module AblyHttpRequest
|
2
|
+
class HttpConnectionOptions
|
3
|
+
attr_reader :host, :port, :tls, :proxy, :bind, :bind_port
|
4
|
+
attr_reader :connect_timeout, :inactivity_timeout
|
5
|
+
attr_writer :https
|
6
|
+
|
7
|
+
def initialize(uri, options)
|
8
|
+
@connect_timeout = options[:connect_timeout] || 5 # default connection setup timeout
|
9
|
+
@inactivity_timeout = options[:inactivity_timeout] ||= 10 # default connection inactivity (post-setup) timeout
|
10
|
+
|
11
|
+
@tls = options[:tls] || options[:ssl] || {}
|
12
|
+
|
13
|
+
if bind = options[:bind]
|
14
|
+
@bind = bind[:host] || '0.0.0.0'
|
15
|
+
|
16
|
+
# Eventmachine will open a UNIX socket if bind :port
|
17
|
+
# is explicitly set to nil
|
18
|
+
@bind_port = bind[:port]
|
19
|
+
end
|
20
|
+
|
21
|
+
uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
|
22
|
+
@https = uri.scheme == "https"
|
23
|
+
uri.port ||= (@https ? 443 : 80)
|
24
|
+
@tls[:sni_hostname] = uri.hostname
|
25
|
+
|
26
|
+
@proxy = options[:proxy] || proxy_from_env
|
27
|
+
|
28
|
+
if proxy
|
29
|
+
@host = proxy[:host]
|
30
|
+
@port = proxy[:port]
|
31
|
+
else
|
32
|
+
@host = uri.hostname
|
33
|
+
@port = uri.port
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def http_proxy?
|
38
|
+
@proxy && (@proxy[:type] == :http || @proxy[:type].nil?) && !@https
|
39
|
+
end
|
40
|
+
|
41
|
+
def connect_proxy?
|
42
|
+
@proxy && (@proxy[:type] == :http || @proxy[:type].nil?) && @https
|
43
|
+
end
|
44
|
+
|
45
|
+
def socks_proxy?
|
46
|
+
@proxy && (@proxy[:type] == :socks5)
|
47
|
+
end
|
48
|
+
|
49
|
+
def proxy_from_env
|
50
|
+
# TODO: Add support for $http_no_proxy or $no_proxy ?
|
51
|
+
proxy_str = if @https
|
52
|
+
ENV['HTTPS_PROXY'] || ENV['https_proxy']
|
53
|
+
else
|
54
|
+
ENV['HTTP_PROXY'] || ENV['http_proxy']
|
55
|
+
|
56
|
+
# Fall-back to $ALL_PROXY if none of the above env-vars have values
|
57
|
+
end || ENV['ALL_PROXY']
|
58
|
+
|
59
|
+
# Addressable::URI::parse will return `nil` if given `nil` and an empty URL for an empty string
|
60
|
+
# so, let's short-circuit that:
|
61
|
+
return if !proxy_str || proxy_str.empty?
|
62
|
+
|
63
|
+
proxy_env_uri = Addressable::URI::parse(proxy_str)
|
64
|
+
{ :host => proxy_env_uri.host, :port => proxy_env_uri.port, :type => :http }
|
65
|
+
|
66
|
+
rescue Addressable::URI::InvalidURIError
|
67
|
+
# An invalid env-var shouldn't crash the config step, IMHO.
|
68
|
+
# We should somehow log / warn about this, perhaps...
|
69
|
+
return
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
module HttpEncoding
|
4
|
+
HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
|
5
|
+
FIELD_ENCODING = "%s: %s\r\n"
|
6
|
+
|
7
|
+
def escape(s)
|
8
|
+
if defined?(EscapeUtils)
|
9
|
+
EscapeUtils.escape_url(s.to_s)
|
10
|
+
else
|
11
|
+
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) {
|
12
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def unescape(s)
|
18
|
+
if defined?(EscapeUtils)
|
19
|
+
EscapeUtils.unescape_url(s.to_s)
|
20
|
+
else
|
21
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {
|
22
|
+
[$1.delete('%')].pack('H*')
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if ''.respond_to?(:bytesize)
|
28
|
+
def bytesize(string)
|
29
|
+
string.bytesize
|
30
|
+
end
|
31
|
+
else
|
32
|
+
def bytesize(string)
|
33
|
+
string.size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Map all header keys to a downcased string version
|
38
|
+
def munge_header_keys(head)
|
39
|
+
head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
|
40
|
+
end
|
41
|
+
|
42
|
+
def encode_host
|
43
|
+
if @req.uri.port.nil? || @req.uri.port == 80 || @req.uri.port == 443
|
44
|
+
return @req.uri.host
|
45
|
+
else
|
46
|
+
@req.uri.host + ":#{@req.uri.port}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def encode_request(method, uri, query, connopts)
|
51
|
+
query = encode_query(uri, query)
|
52
|
+
|
53
|
+
# Non CONNECT proxies require that you provide the full request
|
54
|
+
# uri in request header, as opposed to a relative path.
|
55
|
+
# Don't modify the header with CONNECT proxies. It's unneeded and will
|
56
|
+
# cause 400 Bad Request errors with many standard setups.
|
57
|
+
if connopts.proxy && !connopts.connect_proxy?
|
58
|
+
query = uri.join(query)
|
59
|
+
# Drop the userinfo, it's been converted to a header and won't be
|
60
|
+
# accepted by the proxy
|
61
|
+
query.userinfo = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
|
65
|
+
end
|
66
|
+
|
67
|
+
def encode_query(uri, query)
|
68
|
+
encoded_query = if query.kind_of?(Hash)
|
69
|
+
query.map { |k, v| encode_param(k, v) }.join('&')
|
70
|
+
else
|
71
|
+
query.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
if uri && !uri.query.to_s.empty?
|
75
|
+
encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
|
76
|
+
end
|
77
|
+
encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# URL encodes query parameters:
|
81
|
+
# single k=v, or a URL encoded array, if v is an array of values
|
82
|
+
def encode_param(k, v)
|
83
|
+
if v.is_a?(Array)
|
84
|
+
v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
|
85
|
+
else
|
86
|
+
escape(k) + "=" + escape(v)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def form_encode_body(obj)
|
91
|
+
pairs = []
|
92
|
+
recursive = Proc.new do |h, prefix|
|
93
|
+
h.each do |k,v|
|
94
|
+
key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]"
|
95
|
+
|
96
|
+
if v.is_a? Array
|
97
|
+
nh = Hash.new
|
98
|
+
v.size.times { |t| nh[t] = v[t] }
|
99
|
+
recursive.call(nh, key)
|
100
|
+
|
101
|
+
elsif v.is_a? Hash
|
102
|
+
recursive.call(v, key)
|
103
|
+
else
|
104
|
+
pairs << "#{key}=#{escape(v)}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
recursive.call(obj, '')
|
110
|
+
return pairs.join('&')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Encode a field in an HTTP header
|
114
|
+
def encode_field(k, v)
|
115
|
+
FIELD_ENCODING % [k, v]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Encode basic auth in an HTTP header
|
119
|
+
# In: Array ([user, pass]) - for basic auth
|
120
|
+
# String - custom auth string (OAuth, etc)
|
121
|
+
def encode_auth(k,v)
|
122
|
+
if v.is_a? Array
|
123
|
+
FIELD_ENCODING % [k, ["Basic", Base64.strict_encode64(v.join(":")).split.join].join(" ")]
|
124
|
+
else
|
125
|
+
encode_field(k,v)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def encode_headers(head)
|
130
|
+
head.inject('') do |result, (key, value)|
|
131
|
+
# Munge keys from foo-bar-baz to Foo-Bar-Baz
|
132
|
+
key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
|
133
|
+
result << case key
|
134
|
+
when 'Authorization', 'Proxy-Authorization'
|
135
|
+
encode_auth(key, value)
|
136
|
+
else
|
137
|
+
encode_field(key, value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def encode_cookie(cookie)
|
143
|
+
if cookie.is_a? Hash
|
144
|
+
cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" }
|
145
|
+
else
|
146
|
+
cookie
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
# A simple hash is returned for each request made by HttpClient with the
|
4
|
+
# headers that were given by the server for that request.
|
5
|
+
class HttpResponseHeader < Hash
|
6
|
+
# The reason returned in the http response (string - e.g. "OK")
|
7
|
+
attr_accessor :http_reason
|
8
|
+
|
9
|
+
# The HTTP version returned (string - e.g. "1.1")
|
10
|
+
attr_accessor :http_version
|
11
|
+
|
12
|
+
# The status code (integer - e.g. 200)
|
13
|
+
attr_accessor :http_status
|
14
|
+
|
15
|
+
# Raw headers
|
16
|
+
attr_accessor :raw
|
17
|
+
|
18
|
+
# E-Tag
|
19
|
+
def etag
|
20
|
+
self[HttpClient::ETAG]
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_modified
|
24
|
+
self[HttpClient::LAST_MODIFIED]
|
25
|
+
end
|
26
|
+
|
27
|
+
# HTTP response status
|
28
|
+
def status
|
29
|
+
Integer(http_status) rescue 0
|
30
|
+
end
|
31
|
+
|
32
|
+
# Length of content as an integer, or nil if chunked/unspecified
|
33
|
+
def content_length
|
34
|
+
@content_length ||= ((s = self[HttpClient::CONTENT_LENGTH]) &&
|
35
|
+
(s =~ /^(\d+)$/)) ? $1.to_i : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# Cookie header from the server
|
39
|
+
def cookie
|
40
|
+
self[HttpClient::SET_COOKIE]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Is the transfer encoding chunked?
|
44
|
+
def chunked_encoding?
|
45
|
+
/chunked/i === self[HttpClient::TRANSFER_ENCODING]
|
46
|
+
end
|
47
|
+
|
48
|
+
def keepalive?
|
49
|
+
/keep-alive/i === self[HttpClient::KEEP_ALIVE]
|
50
|
+
end
|
51
|
+
|
52
|
+
def compressed?
|
53
|
+
/gzip|compressed|deflate/i === self[HttpClient::CONTENT_ENCODING]
|
54
|
+
end
|
55
|
+
|
56
|
+
def location
|
57
|
+
self[HttpClient::LOCATION]
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
super(key) || super(key.upcase.gsub('-','_'))
|
62
|
+
end
|
63
|
+
|
64
|
+
def informational?
|
65
|
+
100 <= status && 200 > status
|
66
|
+
end
|
67
|
+
|
68
|
+
def successful?
|
69
|
+
200 <= status && 300 > status
|
70
|
+
end
|
71
|
+
|
72
|
+
def redirection?
|
73
|
+
300 <= status && 400 > status
|
74
|
+
end
|
75
|
+
|
76
|
+
def client_error?
|
77
|
+
400 <= status && 500 > status
|
78
|
+
end
|
79
|
+
|
80
|
+
def server_error?
|
81
|
+
500 <= status && 600 > status
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
module HttpStatus
|
4
|
+
CODE = {
|
5
|
+
100 => 'Continue',
|
6
|
+
101 => 'Switching Protocols',
|
7
|
+
102 => 'Processing',
|
8
|
+
200 => 'OK',
|
9
|
+
201 => 'Created',
|
10
|
+
202 => 'Accepted',
|
11
|
+
203 => 'Non-Authoritative Information',
|
12
|
+
204 => 'No Content',
|
13
|
+
205 => 'Reset Content',
|
14
|
+
206 => 'Partial Content',
|
15
|
+
207 => 'Multi-Status',
|
16
|
+
226 => 'IM Used',
|
17
|
+
300 => 'Multiple Choices',
|
18
|
+
301 => 'Moved Permanently',
|
19
|
+
302 => 'Found',
|
20
|
+
303 => 'See Other',
|
21
|
+
304 => 'Not Modified',
|
22
|
+
305 => 'Use Proxy',
|
23
|
+
306 => 'Reserved',
|
24
|
+
307 => 'Temporary Redirect',
|
25
|
+
400 => 'Bad Request',
|
26
|
+
401 => 'Unauthorized',
|
27
|
+
402 => 'Payment Required',
|
28
|
+
403 => 'Forbidden',
|
29
|
+
404 => 'Not Found',
|
30
|
+
405 => 'Method Not Allowed',
|
31
|
+
406 => 'Not Acceptable',
|
32
|
+
407 => 'Proxy Authentication Required',
|
33
|
+
408 => 'Request Timeout',
|
34
|
+
409 => 'Conflict',
|
35
|
+
410 => 'Gone',
|
36
|
+
411 => 'Length Required',
|
37
|
+
412 => 'Precondition Failed',
|
38
|
+
413 => 'Request Entity Too Large',
|
39
|
+
414 => 'Request-URI Too Long',
|
40
|
+
415 => 'Unsupported Media Type',
|
41
|
+
416 => 'Requested Range Not Satisfiable',
|
42
|
+
417 => 'Expectation Failed',
|
43
|
+
422 => 'Unprocessable Entity',
|
44
|
+
423 => 'Locked',
|
45
|
+
424 => 'Failed Dependency',
|
46
|
+
426 => 'Upgrade Required',
|
47
|
+
500 => 'Internal Server Error',
|
48
|
+
501 => 'Not Implemented',
|
49
|
+
502 => 'Bad Gateway',
|
50
|
+
503 => 'Service Unavailable',
|
51
|
+
504 => 'Gateway Timeout',
|
52
|
+
505 => 'HTTP Version Not Supported',
|
53
|
+
506 => 'Variant Also Negotiates',
|
54
|
+
507 => 'Insufficient Storage',
|
55
|
+
510 => 'Not Extended'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
module Middleware
|
4
|
+
require 'digest'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
class DigestAuth
|
8
|
+
include EventMachine::AblyHttpRequest::HttpEncoding
|
9
|
+
|
10
|
+
attr_accessor :auth_digest, :is_digest_auth
|
11
|
+
|
12
|
+
def initialize(www_authenticate, opts = {})
|
13
|
+
@nonce_count = -1
|
14
|
+
@opts = opts
|
15
|
+
@digest_params = {
|
16
|
+
algorithm: 'MD5' # MD5 is the default hashing algorithm
|
17
|
+
}
|
18
|
+
if (@is_digest_auth = www_authenticate =~ /^Digest/)
|
19
|
+
get_params(www_authenticate)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def request(client, head, body)
|
24
|
+
# Allow HTTP basic auth fallback
|
25
|
+
if @is_digest_auth
|
26
|
+
head['Authorization'] = build_auth_digest(client.req.method, client.req.uri.path, @opts.merge(@digest_params))
|
27
|
+
else
|
28
|
+
head['Authorization'] = [@opts[:username], @opts[:password]]
|
29
|
+
end
|
30
|
+
[head, body]
|
31
|
+
end
|
32
|
+
|
33
|
+
def response(resp)
|
34
|
+
# If the server responds with the Authentication-Info header, set the nonce to the new value
|
35
|
+
if @is_digest_auth && (authentication_info = resp.response_header['Authentication-Info'])
|
36
|
+
authentication_info =~ /nextnonce="?(.*?)"?(,|\z)/
|
37
|
+
@digest_params[:nonce] = $1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_auth_digest(method, uri, params = nil)
|
42
|
+
params = @opts.merge(@digest_params) if !params
|
43
|
+
nonce_count = next_nonce
|
44
|
+
|
45
|
+
user = unescape params[:username]
|
46
|
+
password = unescape params[:password]
|
47
|
+
|
48
|
+
splitted_algorithm = params[:algorithm].split('-')
|
49
|
+
sess = "-sess" if splitted_algorithm[1]
|
50
|
+
raw_algorithm = splitted_algorithm[0]
|
51
|
+
if %w(MD5 SHA1 SHA2 SHA256 SHA384 SHA512 RMD160).include? raw_algorithm
|
52
|
+
algorithm = eval("Digest::#{raw_algorithm}")
|
53
|
+
else
|
54
|
+
raise "Unknown algorithm: #{raw_algorithm}"
|
55
|
+
end
|
56
|
+
qop = params[:qop]
|
57
|
+
cnonce = make_cnonce if qop or sess
|
58
|
+
a1 = if sess
|
59
|
+
[
|
60
|
+
algorithm.hexdigest("#{params[:username]}:#{params[:realm]}:#{params[:password]}"),
|
61
|
+
params[:nonce],
|
62
|
+
cnonce,
|
63
|
+
].join ':'
|
64
|
+
else
|
65
|
+
"#{params[:username]}:#{params[:realm]}:#{params[:password]}"
|
66
|
+
end
|
67
|
+
ha1 = algorithm.hexdigest a1
|
68
|
+
ha2 = algorithm.hexdigest "#{method}:#{uri}"
|
69
|
+
|
70
|
+
request_digest = [ha1, params[:nonce]]
|
71
|
+
request_digest.push(('%08x' % @nonce_count), cnonce, qop) if qop
|
72
|
+
request_digest << ha2
|
73
|
+
request_digest = request_digest.join ':'
|
74
|
+
header = [
|
75
|
+
"Digest username=\"#{params[:username]}\"",
|
76
|
+
"realm=\"#{params[:realm]}\"",
|
77
|
+
"algorithm=#{raw_algorithm}#{sess}",
|
78
|
+
"uri=\"#{uri}\"",
|
79
|
+
"nonce=\"#{params[:nonce]}\"",
|
80
|
+
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
|
81
|
+
]
|
82
|
+
if params[:qop]
|
83
|
+
header << "qop=#{qop}"
|
84
|
+
header << "nc=#{'%08x' % @nonce_count}"
|
85
|
+
header << "cnonce=\"#{cnonce}\""
|
86
|
+
end
|
87
|
+
header << "opaque=\"#{params[:opaque]}\"" if params.key? :opaque
|
88
|
+
header.join(', ')
|
89
|
+
end
|
90
|
+
|
91
|
+
# Process the WWW_AUTHENTICATE header to get the authentication parameters
|
92
|
+
def get_params(www_authenticate)
|
93
|
+
www_authenticate.scan(/(\w+)="?(.*?)"?(,|\z)/).each do |match|
|
94
|
+
@digest_params[match[0].to_sym] = match[1]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Generate a client nonce
|
99
|
+
def make_cnonce
|
100
|
+
Digest::MD5.hexdigest [
|
101
|
+
Time.now.to_i,
|
102
|
+
$$,
|
103
|
+
SecureRandom.random_number(2**32),
|
104
|
+
].join ':'
|
105
|
+
end
|
106
|
+
|
107
|
+
# Keep track of the nounce count
|
108
|
+
def next_nonce
|
109
|
+
@nonce_count += 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module AblyHttpRequest
|
5
|
+
module Middleware
|
6
|
+
class JSONResponse
|
7
|
+
def response(resp)
|
8
|
+
begin
|
9
|
+
body = MultiJson.load(resp.response)
|
10
|
+
resp.response = body
|
11
|
+
rescue => e
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'simple_oauth'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module AblyHttpRequest
|
5
|
+
module Middleware
|
6
|
+
|
7
|
+
class OAuth
|
8
|
+
include HttpEncoding
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@opts = opts.dup
|
12
|
+
# Allow both `oauth` gem and `simple_oauth` gem opts formats
|
13
|
+
@opts[:token] ||= @opts.delete(:access_token)
|
14
|
+
@opts[:token_secret] ||= @opts.delete(:access_token_secret)
|
15
|
+
end
|
16
|
+
|
17
|
+
def request(client, head, body)
|
18
|
+
request = client.req
|
19
|
+
uri = request.uri.join(encode_query(request.uri, request.query))
|
20
|
+
params = {}
|
21
|
+
|
22
|
+
# from https://github.com/oauth/oauth-ruby/blob/master/lib/oauth/request_proxy/em_http_request.rb
|
23
|
+
if ["POST", "PUT"].include?(request.method)
|
24
|
+
head["content-type"] ||= "application/x-www-form-urlencoded" if body.is_a? Hash
|
25
|
+
form_encoded = head["content-type"].to_s.downcase.start_with?("application/x-www-form-urlencoded")
|
26
|
+
|
27
|
+
if form_encoded
|
28
|
+
CGI.parse(client.normalize_body(body)).each do |k,v|
|
29
|
+
# Since `CGI.parse` always returns values as an array
|
30
|
+
params[k] = v.size == 1 ? v.first : v
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
head["Authorization"] = SimpleOAuth::Header.new(request.method, uri, params, @opts)
|
36
|
+
|
37
|
+
[head,body]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
module Middleware
|
4
|
+
class OAuth2
|
5
|
+
include EM::AblyHttpRequest::HttpEncoding
|
6
|
+
attr_accessor :access_token
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
self.access_token = opts[:access_token] or raise "No :access_token provided"
|
10
|
+
end
|
11
|
+
|
12
|
+
def request(client, head, body)
|
13
|
+
uri = client.req.uri.dup
|
14
|
+
update_uri! uri
|
15
|
+
client.req.set_uri uri
|
16
|
+
|
17
|
+
[head, body]
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_uri!(uri)
|
21
|
+
if uri.query.nil?
|
22
|
+
uri.query = encode_param(:access_token, access_token)
|
23
|
+
else
|
24
|
+
uri.query += "&#{encode_param(:access_token, access_token)}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
|
4
|
+
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
5
|
+
# which allows you to open multiple parallel connections and return only when all
|
6
|
+
# of them finish. (i.e. ideal for parallelizing workloads)
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# EventMachine.run {
|
11
|
+
#
|
12
|
+
# multi = EventMachine::AblyHttpRequest::MultiRequest.new
|
13
|
+
#
|
14
|
+
# # add multiple requests to the multi-handler
|
15
|
+
# multi.add(:a, EventMachine::AblyHttpRequest::HttpRequest.new('http://www.google.com/').get)
|
16
|
+
# multi.add(:b, EventMachine::AblyHttpRequest::HttpRequest.new('http://www.yahoo.com/').get)
|
17
|
+
#
|
18
|
+
# multi.callback {
|
19
|
+
# p multi.responses[:callback]
|
20
|
+
# p multi.responses[:errback]
|
21
|
+
#
|
22
|
+
# EventMachine.stop
|
23
|
+
# }
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
|
27
|
+
class MultiRequest
|
28
|
+
include EventMachine::Deferrable
|
29
|
+
|
30
|
+
attr_reader :requests, :responses
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@requests = {}
|
34
|
+
@responses = {:callback => {}, :errback => {}}
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(name, conn)
|
38
|
+
raise 'Duplicate Multi key' if @requests.key? name
|
39
|
+
|
40
|
+
@requests[name] = conn
|
41
|
+
|
42
|
+
conn.callback { @responses[:callback][name] = conn; check_progress }
|
43
|
+
conn.errback { @responses[:errback][name] = conn; check_progress }
|
44
|
+
end
|
45
|
+
|
46
|
+
def finished?
|
47
|
+
(@responses[:callback].size + @responses[:errback].size) == @requests.size
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
# invoke callback if all requests have completed
|
53
|
+
def check_progress
|
54
|
+
succeed(self) if finished?
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module AblyHttpRequest
|
3
|
+
class HttpRequest
|
4
|
+
@middleware = []
|
5
|
+
|
6
|
+
def self.new(uri, options={})
|
7
|
+
uri = uri.clone
|
8
|
+
connopt = ::AblyHttpRequest::HttpConnectionOptions.new(uri, options)
|
9
|
+
|
10
|
+
c = HttpConnection.new
|
11
|
+
c.connopts = connopt
|
12
|
+
c.uri = uri
|
13
|
+
c
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.use(klass, *args, &block)
|
17
|
+
@middleware << klass.new(*args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.middleware
|
21
|
+
@middleware
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'em-http'
|
data/lib/em-http.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em-socksify'
|
3
|
+
require 'addressable/uri'
|
4
|
+
require 'http/parser'
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
require 'socket'
|
8
|
+
require 'openssl'
|
9
|
+
|
10
|
+
require 'em-http/core_ext/bytesize'
|
11
|
+
require 'em-http/http_connection'
|
12
|
+
require 'em-http/http_header'
|
13
|
+
require 'em-http/http_encoding'
|
14
|
+
require 'em-http/http_status_codes'
|
15
|
+
require 'em-http/http_client_options'
|
16
|
+
require 'em-http/http_connection_options'
|
17
|
+
require 'em-http/client'
|
18
|
+
require 'em-http/multi'
|
19
|
+
require 'em-http/request'
|
20
|
+
require 'em-http/decoders'
|