em-http-request 0.2.15 → 0.3.0

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.

@@ -1,48 +1,48 @@
1
- /**
2
- * Copyright (c) 2005 Zed A. Shaw
3
- * You can redistribute it and/or modify it under the same terms as Ruby.
4
- */
5
-
6
- #ifndef http11_parser_h
7
- #define http11_parser_h
8
-
9
- #include <sys/types.h>
10
-
11
- #if defined(_WIN32)
12
- #include <stddef.h>
13
- #endif
14
-
15
- typedef void (*element_cb)(void *data, const char *at, size_t length);
16
- typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
-
18
- typedef struct httpclient_parser {
19
- int cs;
20
- size_t body_start;
21
- int content_len;
22
- size_t nread;
23
- size_t mark;
24
- size_t field_start;
25
- size_t field_len;
26
-
27
- void *data;
28
-
29
- field_cb http_field;
30
- element_cb reason_phrase;
31
- element_cb status_code;
32
- element_cb chunk_size;
33
- element_cb http_version;
34
- element_cb header_done;
35
- element_cb last_chunk;
36
-
37
-
38
- } httpclient_parser;
39
-
40
- int httpclient_parser_init(httpclient_parser *parser);
41
- int httpclient_parser_finish(httpclient_parser *parser);
42
- size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
- int httpclient_parser_has_error(httpclient_parser *parser);
44
- int httpclient_parser_is_finished(httpclient_parser *parser);
45
-
46
- #define httpclient_parser_nread(parser) (parser)->nread
47
-
48
- #endif
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ */
5
+
6
+ #ifndef http11_parser_h
7
+ #define http11_parser_h
8
+
9
+ #include <sys/types.h>
10
+
11
+ #if defined(_WIN32)
12
+ #include <stddef.h>
13
+ #endif
14
+
15
+ typedef void (*element_cb)(void *data, const char *at, size_t length);
16
+ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
+
18
+ typedef struct httpclient_parser {
19
+ int cs;
20
+ size_t body_start;
21
+ int content_len;
22
+ size_t nread;
23
+ size_t mark;
24
+ size_t field_start;
25
+ size_t field_len;
26
+
27
+ void *data;
28
+
29
+ field_cb http_field;
30
+ element_cb reason_phrase;
31
+ element_cb status_code;
32
+ element_cb chunk_size;
33
+ element_cb http_version;
34
+ element_cb header_done;
35
+ element_cb last_chunk;
36
+
37
+
38
+ } httpclient_parser;
39
+
40
+ int httpclient_parser_init(httpclient_parser *parser);
41
+ int httpclient_parser_finish(httpclient_parser *parser);
42
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
+ int httpclient_parser_has_error(httpclient_parser *parser);
44
+ int httpclient_parser_is_finished(httpclient_parser *parser);
45
+
46
+ #define httpclient_parser_nread(parser) (parser)->nread
47
+
48
+ #endif
@@ -1,20 +1,19 @@
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
1
  require 'eventmachine'
8
- require 'socket'
2
+ require 'escape_utils'
3
+ require 'addressable/uri'
9
4
 
10
- require File.dirname(__FILE__) + '/http11_client'
11
- require File.dirname(__FILE__) + '/em_buffer'
5
+ require 'base64'
6
+ require 'socket'
12
7
 
13
- require File.dirname(__FILE__) + '/em-http/core_ext/bytesize'
8
+ require 'http11_client'
9
+ require 'em_buffer'
14
10
 
15
- require File.dirname(__FILE__) + '/em-http/client'
16
- require File.dirname(__FILE__) + '/em-http/multi'
17
- require File.dirname(__FILE__) + '/em-http/request'
18
- require File.dirname(__FILE__) + '/em-http/decoders'
19
- require File.dirname(__FILE__) + '/em-http/http_options'
20
- require File.dirname(__FILE__) + '/em-http/mock'
11
+ require 'em-http/core_ext/bytesize'
12
+ require 'em-http/http_header'
13
+ require 'em-http/http_encoding'
14
+ require 'em-http/http_options'
15
+ require 'em-http/client'
16
+ require 'em-http/multi'
17
+ require 'em-http/request'
18
+ require 'em-http/decoders'
19
+ require 'em-http/mock'
@@ -9,223 +9,14 @@
9
9
 
10
10
  module EventMachine
11
11
 
12
- # A simple hash is returned for each request made by HttpClient with the
13
- # headers that were given by the server for that request.
14
- class HttpResponseHeader < Hash
15
- # The reason returned in the http response ("OK","File not found",etc.)
16
- attr_accessor :http_reason
17
-
18
- # The HTTP version returned.
19
- attr_accessor :http_version
20
-
21
- # The status code (as a string!)
22
- attr_accessor :http_status
23
-
24
- # E-Tag
25
- def etag
26
- self[HttpClient::ETAG]
27
- end
28
-
29
- def last_modified
30
- self[HttpClient::LAST_MODIFIED]
31
- end
32
-
33
- # HTTP response status as an integer
34
- def status
35
- Integer(http_status) rescue 0
36
- end
37
-
38
- # Length of content as an integer, or nil if chunked/unspecified
39
- def content_length
40
- @content_length ||= ((s = self[HttpClient::CONTENT_LENGTH]) &&
41
- (s =~ /^(\d+)$/)) ? $1.to_i : nil
42
- end
43
-
44
- # Cookie header from the server
45
- def cookie
46
- self[HttpClient::SET_COOKIE]
47
- end
48
-
49
- # Is the transfer encoding chunked?
50
- def chunked_encoding?
51
- /chunked/i === self[HttpClient::TRANSFER_ENCODING]
52
- end
53
-
54
- def keep_alive?
55
- /keep-alive/i === self[HttpClient::KEEP_ALIVE]
56
- end
57
-
58
- def compressed?
59
- /gzip|compressed|deflate/i === self[HttpClient::CONTENT_ENCODING]
60
- end
61
-
62
- def location
63
- self[HttpClient::LOCATION]
64
- end
65
- end
66
-
67
- class HttpChunkHeader < Hash
68
- # When parsing chunked encodings this is set
69
- attr_accessor :http_chunk_size
70
-
71
- def initialize
72
- super
73
- @http_chunk_size = '0'
74
- end
75
-
76
- # Size of the chunk as an integer
77
- def chunk_size
78
- @http_chunk_size.to_i(base=16)
79
- end
80
- end
81
-
82
- # Methods for building HTTP requests
83
- module HttpEncoding
84
- HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
85
- FIELD_ENCODING = "%s: %s\r\n"
86
-
87
- # Escapes a URI.
88
- def escape(s)
89
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
90
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
91
- }
92
- end
93
-
94
- # Unescapes a URI escaped string.
95
- def unescape(s)
96
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
97
- [$1.delete('%')].pack('H*')
98
- }
99
- end
100
-
101
- if ''.respond_to?(:bytesize)
102
- def bytesize(string)
103
- string.bytesize
104
- end
105
- else
106
- def bytesize(string)
107
- string.size
108
- end
109
- end
110
-
111
- # Map all header keys to a downcased string version
112
- def munge_header_keys(head)
113
- head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
114
- end
115
-
116
- # HTTP is kind of retarded that you have to specify a Host header, but if
117
- # you include port 80 then further redirects will tack on the :80 which is
118
- # annoying.
119
- def encode_host
120
- if @uri.port == 80 || @uri.port == 443
121
- return @uri.host
122
- else
123
- @uri.host + ":#{@uri.port}"
124
- end
125
- end
126
-
127
- def encode_request(method, uri, query, proxy)
128
- query = encode_query(uri, query)
129
-
130
- # Non CONNECT proxies require that you provide the full request
131
- # uri in request header, as opposed to a relative path.
132
- query = uri.join(query) if proxy && proxy[:type] != :socks && !proxy[:use_connect]
133
-
134
- HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
135
- end
136
-
137
- def encode_query(uri, query)
138
- encoded_query = if query.kind_of?(Hash)
139
- query.map { |k, v| encode_param(k, v) }.join('&')
140
- else
141
- query.to_s
142
- end
143
-
144
- if !uri.query.to_s.empty?
145
- encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
146
- end
147
- encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
148
- end
149
-
150
- # URL encodes query parameters:
151
- # single k=v, or a URL encoded array, if v is an array of values
152
- def encode_param(k, v)
153
- if v.is_a?(Array)
154
- v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
155
- else
156
- escape(k) + "=" + escape(v)
157
- end
158
- end
159
-
160
- def form_encode_body(obj)
161
- pairs = []
162
- recursive = Proc.new do |h, prefix|
163
- h.each do |k,v|
164
- key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]"
165
-
166
- if v.is_a? Array
167
- nh = Hash.new
168
- v.size.times { |t| nh[t] = v[t] }
169
- recursive.call(nh, key)
170
-
171
- elsif v.is_a? Hash
172
- recursive.call(v, key)
173
- else
174
- pairs << "#{key}=#{escape(v)}"
175
- end
176
- end
177
- end
178
-
179
- recursive.call(obj, '')
180
- return pairs.join('&')
181
- end
182
-
183
- # Encode a field in an HTTP header
184
- def encode_field(k, v)
185
- FIELD_ENCODING % [k, v]
186
- end
187
-
188
- # Encode basic auth in an HTTP header
189
- # In: Array ([user, pass]) - for basic auth
190
- # String - custom auth string (OAuth, etc)
191
- def encode_auth(k,v)
192
- if v.is_a? Array
193
- FIELD_ENCODING % [k, ["Basic", Base64.encode64(v.join(":")).chomp].join(" ")]
194
- else
195
- encode_field(k,v)
196
- end
197
- end
198
-
199
- def encode_headers(head)
200
- head.inject('') do |result, (key, value)|
201
- # Munge keys from foo-bar-baz to Foo-Bar-Baz
202
- key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
203
- result << case key
204
- when 'Authorization', 'Proxy-authorization'
205
- encode_auth(key, value)
206
- else
207
- encode_field(key, value)
208
- end
209
- end
210
- end
211
-
212
- def encode_cookie(cookie)
213
- if cookie.is_a? Hash
214
- cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" }
215
- else
216
- cookie
217
- end
218
- end
219
- end
220
-
221
12
  class HttpClient < Connection
222
13
  include EventMachine::Deferrable
223
- include HttpEncoding
14
+ include EventMachine::HttpEncoding
224
15
 
225
16
  TRANSFER_ENCODING="TRANSFER_ENCODING"
226
17
  CONTENT_ENCODING="CONTENT_ENCODING"
227
18
  CONTENT_LENGTH="CONTENT_LENGTH"
228
- CONTENT_TYPE="CONTENT_TYPE".freeze
19
+ CONTENT_TYPE="CONTENT_TYPE"
229
20
  LAST_MODIFIED="LAST_MODIFIED"
230
21
  KEEP_ALIVE="CONNECTION"
231
22
  SET_COOKIE="SET_COOKIE"
@@ -236,7 +27,7 @@ module EventMachine
236
27
  CRLF="\r\n"
237
28
 
238
29
  attr_accessor :method, :options, :uri
239
- attr_reader :response, :response_header, :error, :redirects, :last_effective_url
30
+ attr_reader :response, :response_header, :error, :redirects, :last_effective_url, :content_charset
240
31
 
241
32
  def post_init
242
33
  @parser = HttpClientParser.new
@@ -425,7 +216,12 @@ module EventMachine
425
216
 
426
217
  # Set content-type header if missing and body is a Ruby hash
427
218
  if not head['content-type'] and options[:body].is_a? Hash
428
- head['content-type'] = "application/x-www-form-urlencoded"
219
+ head['content-type'] = 'application/x-www-form-urlencoded'
220
+ end
221
+
222
+ # Set connection close unless keepalive
223
+ unless options[:keepalive]
224
+ head['connection'] = 'close'
429
225
  end
430
226
  end
431
227
 
@@ -474,7 +270,7 @@ module EventMachine
474
270
  end
475
271
 
476
272
  def on_decoded_body_data(data)
477
- data.force_encoding @content_charset if @content_charset
273
+ data.force_encoding @content_charset if @content_charset
478
274
  if @stream
479
275
  @stream.call(data)
480
276
  else
@@ -652,7 +448,7 @@ module EventMachine
652
448
  end
653
449
 
654
450
  if ''.respond_to?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(response_header[CONTENT_TYPE])
655
- @content_charset = Encoding.find $1
451
+ @content_charset = Encoding.find($1.gsub(/^\"|\"$/, '')) rescue Encoding.default_external
656
452
  end
657
453
 
658
454
  true
@@ -852,13 +648,10 @@ module EventMachine
852
648
  on_request_complete
853
649
 
854
650
  else
855
- if @data.empty?
856
- @state = :finished
857
- on_request_complete
858
- else
859
- @state = :invalid
860
- on_error "garbage at end of body"
861
- end
651
+
652
+ @data.clear
653
+ @state = :finished
654
+ on_request_complete
862
655
  end
863
656
 
864
657
  false
@@ -0,0 +1,135 @@
1
+ module EventMachine
2
+ module HttpEncoding
3
+ HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
4
+ FIELD_ENCODING = "%s: %s\r\n"
5
+
6
+ # Escapes a URI.
7
+ def escape(s)
8
+ EscapeUtils.escape_url(s.to_s)
9
+ end
10
+
11
+ # Unescapes a URI escaped string.
12
+ def unescape(s)
13
+ EscapeUtils.unescape_url(s.to_s)
14
+ end
15
+
16
+ if ''.respond_to?(:bytesize)
17
+ def bytesize(string)
18
+ string.bytesize
19
+ end
20
+ else
21
+ def bytesize(string)
22
+ string.size
23
+ end
24
+ end
25
+
26
+ # Map all header keys to a downcased string version
27
+ def munge_header_keys(head)
28
+ head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
29
+ end
30
+
31
+ # HTTP is kind of retarded that you have to specify a Host header, but if
32
+ # you include port 80 then further redirects will tack on the :80 which is
33
+ # annoying.
34
+ def encode_host
35
+ if @uri.port == 80 || @uri.port == 443
36
+ return @uri.host
37
+ else
38
+ @uri.host + ":#{@uri.port}"
39
+ end
40
+ end
41
+
42
+ def encode_request(method, uri, query, proxy)
43
+ query = encode_query(uri, query)
44
+
45
+ # Non CONNECT proxies require that you provide the full request
46
+ # uri in request header, as opposed to a relative path.
47
+ query = uri.join(query) if proxy && proxy[:type] != :socks && !proxy[:use_connect]
48
+
49
+ HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
50
+ end
51
+
52
+ def encode_query(uri, query)
53
+ encoded_query = if query.kind_of?(Hash)
54
+ query.map { |k, v| encode_param(k, v) }.join('&')
55
+ else
56
+ query.to_s
57
+ end
58
+
59
+ if !uri.query.to_s.empty?
60
+ encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
61
+ end
62
+ encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
63
+ end
64
+
65
+ # URL encodes query parameters:
66
+ # single k=v, or a URL encoded array, if v is an array of values
67
+ def encode_param(k, v)
68
+ if v.is_a?(Array)
69
+ v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
70
+ else
71
+ escape(k) + "=" + escape(v)
72
+ end
73
+ end
74
+
75
+ def form_encode_body(obj)
76
+ pairs = []
77
+ recursive = Proc.new do |h, prefix|
78
+ h.each do |k,v|
79
+ key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]"
80
+
81
+ if v.is_a? Array
82
+ nh = Hash.new
83
+ v.size.times { |t| nh[t] = v[t] }
84
+ recursive.call(nh, key)
85
+
86
+ elsif v.is_a? Hash
87
+ recursive.call(v, key)
88
+ else
89
+ pairs << "#{key}=#{escape(v)}"
90
+ end
91
+ end
92
+ end
93
+
94
+ recursive.call(obj, '')
95
+ return pairs.join('&')
96
+ end
97
+
98
+ # Encode a field in an HTTP header
99
+ def encode_field(k, v)
100
+ FIELD_ENCODING % [k, v]
101
+ end
102
+
103
+ # Encode basic auth in an HTTP header
104
+ # In: Array ([user, pass]) - for basic auth
105
+ # String - custom auth string (OAuth, etc)
106
+ def encode_auth(k,v)
107
+ if v.is_a? Array
108
+ FIELD_ENCODING % [k, ["Basic", Base64.encode64(v.join(":")).chomp].join(" ")]
109
+ else
110
+ encode_field(k,v)
111
+ end
112
+ end
113
+
114
+ def encode_headers(head)
115
+ head.inject('') do |result, (key, value)|
116
+ # Munge keys from foo-bar-baz to Foo-Bar-Baz
117
+ key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
118
+ result << case key
119
+ when 'Authorization', 'Proxy-Authorization'
120
+ encode_auth(key, value)
121
+ else
122
+ encode_field(key, value)
123
+ end
124
+ end
125
+ end
126
+
127
+ def encode_cookie(cookie)
128
+ if cookie.is_a? Hash
129
+ cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" }
130
+ else
131
+ cookie
132
+ end
133
+ end
134
+ end
135
+ end