em-http-request 0.2.15 → 0.3.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.

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