astro-em-http-request 0.1.3.20090419
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.
- data/.autotest +1 -0
- data/LICENSE +58 -0
- data/README.rdoc +52 -0
- data/Rakefile +80 -0
- data/ext/buffer/em_buffer.c +630 -0
- data/ext/buffer/extconf.rb +53 -0
- data/ext/http11_client/ext_help.h +14 -0
- data/ext/http11_client/extconf.rb +6 -0
- data/ext/http11_client/http11_client.c +302 -0
- data/ext/http11_client/http11_parser.c +403 -0
- data/ext/http11_client/http11_parser.h +48 -0
- data/ext/http11_client/http11_parser.rl +173 -0
- data/lib/em-http.rb +17 -0
- data/lib/em-http/client.rb +428 -0
- data/lib/em-http/decoders.rb +119 -0
- data/lib/em-http/multi.rb +51 -0
- data/lib/em-http/request.rb +77 -0
- data/test/helper.rb +5 -0
- data/test/stallion.rb +127 -0
- data/test/test_multi.rb +34 -0
- data/test/test_request.rb +264 -0
- metadata +83 -0
@@ -0,0 +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
|
@@ -0,0 +1,173 @@
|
|
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
|
+
#include "http11_parser.h"
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <assert.h>
|
9
|
+
#include <stdlib.h>
|
10
|
+
#include <ctype.h>
|
11
|
+
#include <string.h>
|
12
|
+
|
13
|
+
#define LEN(AT, FPC) (FPC - buffer - parser->AT)
|
14
|
+
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
|
15
|
+
#define PTR_TO(F) (buffer + parser->F)
|
16
|
+
#define L(M) fprintf(stderr, "" # M "\n");
|
17
|
+
|
18
|
+
|
19
|
+
/** machine **/
|
20
|
+
%%{
|
21
|
+
machine httpclient_parser;
|
22
|
+
|
23
|
+
action mark {MARK(mark, fpc); }
|
24
|
+
|
25
|
+
action start_field { MARK(field_start, fpc); }
|
26
|
+
|
27
|
+
action write_field {
|
28
|
+
parser->field_len = LEN(field_start, fpc);
|
29
|
+
}
|
30
|
+
|
31
|
+
action start_value { MARK(mark, fpc); }
|
32
|
+
|
33
|
+
action write_value {
|
34
|
+
parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
|
35
|
+
}
|
36
|
+
|
37
|
+
action reason_phrase {
|
38
|
+
parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
39
|
+
}
|
40
|
+
|
41
|
+
action status_code {
|
42
|
+
parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
43
|
+
}
|
44
|
+
|
45
|
+
action http_version {
|
46
|
+
parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
47
|
+
}
|
48
|
+
|
49
|
+
action chunk_size {
|
50
|
+
parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
51
|
+
}
|
52
|
+
|
53
|
+
action last_chunk {
|
54
|
+
parser->last_chunk(parser->data, NULL, 0);
|
55
|
+
}
|
56
|
+
|
57
|
+
action done {
|
58
|
+
parser->body_start = fpc - buffer + 1;
|
59
|
+
if(parser->header_done != NULL)
|
60
|
+
parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
|
61
|
+
fbreak;
|
62
|
+
}
|
63
|
+
|
64
|
+
# line endings
|
65
|
+
CRLF = "\r\n";
|
66
|
+
|
67
|
+
# character types
|
68
|
+
CTL = (cntrl | 127);
|
69
|
+
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
70
|
+
|
71
|
+
# elements
|
72
|
+
token = (ascii -- (CTL | tspecials));
|
73
|
+
|
74
|
+
Reason_Phrase = (any -- CRLF)* >mark %reason_phrase;
|
75
|
+
Status_Code = digit{3} >mark %status_code;
|
76
|
+
http_number = (digit+ "." digit+) ;
|
77
|
+
HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
|
78
|
+
Status_Line = HTTP_Version " " Status_Code " "? Reason_Phrase :> CRLF;
|
79
|
+
|
80
|
+
field_name = token+ >start_field %write_field;
|
81
|
+
field_value = any* >start_value %write_value;
|
82
|
+
message_header = field_name ":" " "* field_value :> CRLF;
|
83
|
+
|
84
|
+
Response = Status_Line (message_header)* (CRLF @done);
|
85
|
+
|
86
|
+
chunk_ext_val = token+;
|
87
|
+
chunk_ext_name = token+;
|
88
|
+
chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
|
89
|
+
last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
|
90
|
+
chunk_size = xdigit+;
|
91
|
+
chunk = chunk_size >mark %chunk_size chunk_extension space* :> (CRLF @done);
|
92
|
+
Chunked_Header = (chunk | last_chunk);
|
93
|
+
|
94
|
+
main := Response | Chunked_Header;
|
95
|
+
}%%
|
96
|
+
|
97
|
+
/** Data **/
|
98
|
+
%% write data;
|
99
|
+
|
100
|
+
int httpclient_parser_init(httpclient_parser *parser) {
|
101
|
+
int cs = 0;
|
102
|
+
%% write init;
|
103
|
+
parser->cs = cs;
|
104
|
+
parser->body_start = 0;
|
105
|
+
parser->content_len = 0;
|
106
|
+
parser->mark = 0;
|
107
|
+
parser->nread = 0;
|
108
|
+
parser->field_len = 0;
|
109
|
+
parser->field_start = 0;
|
110
|
+
|
111
|
+
return(1);
|
112
|
+
}
|
113
|
+
|
114
|
+
|
115
|
+
/** exec **/
|
116
|
+
size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
|
117
|
+
const char *p, *pe;
|
118
|
+
int cs = parser->cs;
|
119
|
+
|
120
|
+
assert(off <= len && "offset past end of buffer");
|
121
|
+
|
122
|
+
p = buffer+off;
|
123
|
+
pe = buffer+len;
|
124
|
+
|
125
|
+
assert(*pe == '\0' && "pointer does not end on NUL");
|
126
|
+
assert(pe - p == len - off && "pointers aren't same distance");
|
127
|
+
|
128
|
+
|
129
|
+
%% write exec;
|
130
|
+
|
131
|
+
parser->cs = cs;
|
132
|
+
parser->nread += p - (buffer + off);
|
133
|
+
|
134
|
+
assert(p <= pe && "buffer overflow after parsing execute");
|
135
|
+
assert(parser->nread <= len && "nread longer than length");
|
136
|
+
assert(parser->body_start <= len && "body starts after buffer end");
|
137
|
+
assert(parser->mark < len && "mark is after buffer end");
|
138
|
+
assert(parser->field_len <= len && "field has length longer than whole buffer");
|
139
|
+
assert(parser->field_start < len && "field starts after buffer end");
|
140
|
+
|
141
|
+
if(parser->body_start) {
|
142
|
+
/* final \r\n combo encountered so stop right here */
|
143
|
+
%%write eof;
|
144
|
+
parser->nread++;
|
145
|
+
}
|
146
|
+
|
147
|
+
return(parser->nread);
|
148
|
+
}
|
149
|
+
|
150
|
+
int httpclient_parser_finish(httpclient_parser *parser)
|
151
|
+
{
|
152
|
+
int cs = parser->cs;
|
153
|
+
|
154
|
+
%%write eof;
|
155
|
+
|
156
|
+
parser->cs = cs;
|
157
|
+
|
158
|
+
if (httpclient_parser_has_error(parser) ) {
|
159
|
+
return -1;
|
160
|
+
} else if (httpclient_parser_is_finished(parser) ) {
|
161
|
+
return 1;
|
162
|
+
} else {
|
163
|
+
return 0;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
int httpclient_parser_has_error(httpclient_parser *parser) {
|
168
|
+
return parser->cs == httpclient_parser_error;
|
169
|
+
}
|
170
|
+
|
171
|
+
int httpclient_parser_is_finished(httpclient_parser *parser) {
|
172
|
+
return parser->cs == httpclient_parser_first_final;
|
173
|
+
}
|
data/lib/em-http.rb
ADDED
@@ -0,0 +1,17 @@
|
|
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 'rubygems'
|
8
|
+
require 'eventmachine'
|
9
|
+
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + '/http11_client'
|
12
|
+
require File.dirname(__FILE__) + '/em_buffer'
|
13
|
+
|
14
|
+
require File.dirname(__FILE__) + '/em-http/client'
|
15
|
+
require File.dirname(__FILE__) + '/em-http/multi'
|
16
|
+
require File.dirname(__FILE__) + '/em-http/request'
|
17
|
+
require File.dirname(__FILE__) + '/em-http/decoders'
|
@@ -0,0 +1,428 @@
|
|
1
|
+
# -*- coding: undecided -*-
|
2
|
+
# #--
|
3
|
+
# Copyright (C)2008 Ilya Grigorik
|
4
|
+
#
|
5
|
+
# Includes portion originally Copyright (C)2007 Tony Arcieri
|
6
|
+
# Includes portion originally Copyright (C)2005 Zed Shaw
|
7
|
+
# You can redistribute this under the terms of the Ruby
|
8
|
+
# license See file LICENSE for details
|
9
|
+
# #--
|
10
|
+
|
11
|
+
module EventMachine
|
12
|
+
|
13
|
+
# A simple hash is returned for each request made by HttpClient with the
|
14
|
+
# headers that were given by the server for that request.
|
15
|
+
class HttpResponseHeader < Hash
|
16
|
+
# The reason returned in the http response ("OK","File not found",etc.)
|
17
|
+
attr_accessor :http_reason
|
18
|
+
|
19
|
+
# The HTTP version returned.
|
20
|
+
attr_accessor :http_version
|
21
|
+
|
22
|
+
# The status code (as a string!)
|
23
|
+
attr_accessor :http_status
|
24
|
+
|
25
|
+
# HTTP response status as an integer
|
26
|
+
def status
|
27
|
+
Integer(http_status) rescue nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Length of content as an integer, or nil if chunked/unspecified
|
31
|
+
def content_length
|
32
|
+
Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Is the transfer encoding chunked?
|
36
|
+
def chunked_encoding?
|
37
|
+
/chunked/i === self[HttpClient::TRANSFER_ENCODING]
|
38
|
+
end
|
39
|
+
|
40
|
+
def keep_alive?
|
41
|
+
/keep-alive/i === self[HttpClient::KEEP_ALIVE]
|
42
|
+
end
|
43
|
+
|
44
|
+
def compressed?
|
45
|
+
/gzip|compressed|deflate/i === self[HttpClient::CONTENT_ENCODING]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class HttpChunkHeader < Hash
|
50
|
+
# When parsing chunked encodings this is set
|
51
|
+
attr_accessor :http_chunk_size
|
52
|
+
|
53
|
+
# Size of the chunk as an integer
|
54
|
+
def chunk_size
|
55
|
+
return @chunk_size unless @chunk_size.nil?
|
56
|
+
@chunk_size = @http_chunk_size ? @http_chunk_size.to_i(base=16) : 0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Methods for building HTTP requests
|
61
|
+
module HttpEncoding
|
62
|
+
HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
|
63
|
+
FIELD_ENCODING = "%s: %s\r\n"
|
64
|
+
BASIC_AUTH_ENCODING = "%s: Basic %s\r\n"
|
65
|
+
|
66
|
+
# Escapes a URI.
|
67
|
+
def escape(s)
|
68
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
69
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
70
|
+
}.tr(' ', '+')
|
71
|
+
end
|
72
|
+
|
73
|
+
# Unescapes a URI escaped string.
|
74
|
+
def unescape(s)
|
75
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
76
|
+
[$1.delete('%')].pack('H*')
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Map all header keys to a downcased string version
|
81
|
+
def munge_header_keys(head)
|
82
|
+
head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
|
83
|
+
end
|
84
|
+
|
85
|
+
# HTTP is kind of retarded that you have to specify a Host header, but if
|
86
|
+
# you include port 80 then further redirects will tack on the :80 which is
|
87
|
+
# annoying.
|
88
|
+
def encode_host
|
89
|
+
@uri.host + (@uri.port.to_i != 80 ? ":#{@uri.port}" : "")
|
90
|
+
end
|
91
|
+
|
92
|
+
def encode_request(method, path, query)
|
93
|
+
HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query)]
|
94
|
+
end
|
95
|
+
|
96
|
+
def encode_query(path, query)
|
97
|
+
return path unless query
|
98
|
+
if query.kind_of? String
|
99
|
+
return "#{path}?#{query}"
|
100
|
+
else
|
101
|
+
return path + "?" + query.map { |k, v| encode_param(k, v) }.join('&')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# URL encodes query parameters:
|
106
|
+
# single k=v, or a URL encoded array, if v is an array of values
|
107
|
+
def encode_param(k, v)
|
108
|
+
if v.is_a?(Array)
|
109
|
+
v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
|
110
|
+
else
|
111
|
+
escape(k) + "=" + escape(v)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Encode a field in an HTTP header
|
116
|
+
def encode_field(k, v)
|
117
|
+
FIELD_ENCODING % [k, v]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Encode basic auth in an HTTP header
|
121
|
+
def encode_basic_auth(k,v)
|
122
|
+
BASIC_AUTH_ENCODING % [k, Base64.encode64(v.join(":")).chomp]
|
123
|
+
end
|
124
|
+
|
125
|
+
def encode_headers(head)
|
126
|
+
head.inject('') do |result, (key, value)|
|
127
|
+
# Munge keys from foo-bar-baz to Foo-Bar-Baz
|
128
|
+
key = key.split('-').map { |k| k.capitalize }.join('-')
|
129
|
+
unless key == "Authorization"
|
130
|
+
result << encode_field(key, value)
|
131
|
+
else
|
132
|
+
result << encode_basic_auth(key, value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def encode_cookies(cookies)
|
138
|
+
cookies.inject('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class HttpClient < Connection
|
143
|
+
include EventMachine::Deferrable
|
144
|
+
include HttpEncoding
|
145
|
+
|
146
|
+
TRANSFER_ENCODING="TRANSFER_ENCODING"
|
147
|
+
CONTENT_ENCODING="CONTENT_ENCODING"
|
148
|
+
CONTENT_LENGTH="CONTENT_LENGTH"
|
149
|
+
KEEP_ALIVE="CONNECTION"
|
150
|
+
SET_COOKIE="SET_COOKIE"
|
151
|
+
LOCATION="LOCATION"
|
152
|
+
HOST="HOST"
|
153
|
+
CRLF="\r\n"
|
154
|
+
|
155
|
+
attr_accessor :method, :options, :uri
|
156
|
+
attr_reader :response, :response_header, :errors
|
157
|
+
|
158
|
+
def post_init
|
159
|
+
@parser = HttpClientParser.new
|
160
|
+
@data = EventMachine::Buffer.new
|
161
|
+
@response_header = HttpResponseHeader.new
|
162
|
+
@chunk_header = HttpChunkHeader.new
|
163
|
+
|
164
|
+
@state = :response_header
|
165
|
+
@parser_nbytes = 0
|
166
|
+
@response = ''
|
167
|
+
@inflate = []
|
168
|
+
@errors = ''
|
169
|
+
@content_decoder = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# start HTTP request once we establish connection to host
|
173
|
+
def connection_completed
|
174
|
+
send_request_header
|
175
|
+
send_request_body
|
176
|
+
end
|
177
|
+
|
178
|
+
# request is done, invoke the callback
|
179
|
+
def on_request_complete
|
180
|
+
begin
|
181
|
+
@content_decoder.finalize! if @content_decoder
|
182
|
+
rescue HttpDecoders::DecoderError
|
183
|
+
on_error "Content-decoder error"
|
184
|
+
end
|
185
|
+
unbind
|
186
|
+
end
|
187
|
+
|
188
|
+
# request failed, invoke errback
|
189
|
+
def on_error(msg)
|
190
|
+
@errors = msg
|
191
|
+
unbind
|
192
|
+
end
|
193
|
+
|
194
|
+
def send_request_header
|
195
|
+
query = @options[:query]
|
196
|
+
head = @options[:head] ? munge_header_keys(@options[:head]) : {}
|
197
|
+
body = @options[:body]
|
198
|
+
|
199
|
+
# Set the Host header if it hasn't been specified already
|
200
|
+
head['host'] ||= encode_host
|
201
|
+
|
202
|
+
# Set the Content-Length if body is given
|
203
|
+
head['content-length'] = body.length if body
|
204
|
+
|
205
|
+
# Set the User-Agent if it hasn't been specified
|
206
|
+
head['user-agent'] ||= "EventMachine HttpClient"
|
207
|
+
|
208
|
+
# Set auto-inflate flags
|
209
|
+
if head['accept-encoding']
|
210
|
+
@inflate = head['accept-encoding'].split(',').map {|t| t.strip}
|
211
|
+
end
|
212
|
+
|
213
|
+
# Build the request
|
214
|
+
request_header = encode_request(@method, @uri.path, query)
|
215
|
+
request_header << encode_headers(head)
|
216
|
+
request_header << CRLF
|
217
|
+
|
218
|
+
send_data request_header
|
219
|
+
end
|
220
|
+
|
221
|
+
def send_request_body
|
222
|
+
send_data @options[:body] if @options[:body]
|
223
|
+
end
|
224
|
+
|
225
|
+
def receive_data(data)
|
226
|
+
@data << data
|
227
|
+
dispatch
|
228
|
+
end
|
229
|
+
|
230
|
+
# Called when part of the body has been read
|
231
|
+
def on_body_data(data)
|
232
|
+
if @content_decoder
|
233
|
+
begin
|
234
|
+
@content_decoder << data
|
235
|
+
rescue HttpDecoders::DecoderError
|
236
|
+
on_error "Content-decoder error"
|
237
|
+
end
|
238
|
+
else
|
239
|
+
on_decoded_body_data(data)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def on_decoded_body_data(data)
|
244
|
+
if (on_response = @options[:on_response])
|
245
|
+
on_response.call(data)
|
246
|
+
else
|
247
|
+
@response << data
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def unbind
|
252
|
+
(@state == :finished) ? succeed : fail
|
253
|
+
close_connection
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Response processing
|
258
|
+
#
|
259
|
+
|
260
|
+
def dispatch
|
261
|
+
while case @state
|
262
|
+
when :response_header
|
263
|
+
parse_response_header
|
264
|
+
when :chunk_header
|
265
|
+
parse_chunk_header
|
266
|
+
when :chunk_body
|
267
|
+
process_chunk_body
|
268
|
+
when :chunk_footer
|
269
|
+
process_chunk_footer
|
270
|
+
when :response_footer
|
271
|
+
process_response_footer
|
272
|
+
when :body
|
273
|
+
process_body
|
274
|
+
when :finished, :invalid
|
275
|
+
break
|
276
|
+
else raise RuntimeError, "invalid state: #{@state}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def parse_header(header)
|
282
|
+
return false if @data.empty?
|
283
|
+
|
284
|
+
begin
|
285
|
+
@parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
|
286
|
+
rescue EventMachine::HttpClientParserError
|
287
|
+
@state = :invalid
|
288
|
+
on_error "invalid HTTP format, parsing fails"
|
289
|
+
end
|
290
|
+
|
291
|
+
return false unless @parser.finished?
|
292
|
+
|
293
|
+
# Clear parsed data from the buffer
|
294
|
+
@data.read(@parser_nbytes)
|
295
|
+
@parser.reset
|
296
|
+
@parser_nbytes = 0
|
297
|
+
|
298
|
+
true
|
299
|
+
end
|
300
|
+
|
301
|
+
def parse_response_header
|
302
|
+
return false unless parse_header(@response_header)
|
303
|
+
|
304
|
+
unless @response_header.http_status and @response_header.http_reason
|
305
|
+
@state = :invalid
|
306
|
+
on_error "no HTTP response"
|
307
|
+
return false
|
308
|
+
end
|
309
|
+
|
310
|
+
if @response_header.chunked_encoding?
|
311
|
+
@state = :chunk_header
|
312
|
+
else
|
313
|
+
@state = :body
|
314
|
+
@bytes_remaining = @response_header.content_length
|
315
|
+
end
|
316
|
+
|
317
|
+
if @inflate.include?(response_header[CONTENT_ENCODING]) &&
|
318
|
+
decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
|
319
|
+
begin
|
320
|
+
@content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
|
321
|
+
rescue HttpDecoders::DecoderError
|
322
|
+
on_error "Content-decoder error"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
true
|
327
|
+
end
|
328
|
+
|
329
|
+
def parse_chunk_header
|
330
|
+
return false unless parse_header(@chunk_header)
|
331
|
+
|
332
|
+
@bytes_remaining = @chunk_header.chunk_size
|
333
|
+
@chunk_header = HttpChunkHeader.new
|
334
|
+
|
335
|
+
@state = @bytes_remaining > 0 ? :chunk_body : :response_footer
|
336
|
+
true
|
337
|
+
end
|
338
|
+
|
339
|
+
def process_chunk_body
|
340
|
+
if @data.size < @bytes_remaining
|
341
|
+
@bytes_remaining -= @data.size
|
342
|
+
on_body_data @data.read
|
343
|
+
return false
|
344
|
+
end
|
345
|
+
|
346
|
+
on_body_data @data.read(@bytes_remaining)
|
347
|
+
@bytes_remaining = 0
|
348
|
+
|
349
|
+
@state = :chunk_footer
|
350
|
+
true
|
351
|
+
end
|
352
|
+
|
353
|
+
def process_chunk_footer
|
354
|
+
return false if @data.size < 2
|
355
|
+
|
356
|
+
if @data.read(2) == CRLF
|
357
|
+
@state = :chunk_header
|
358
|
+
else
|
359
|
+
@state = :invalid
|
360
|
+
on_error "non-CRLF chunk footer"
|
361
|
+
end
|
362
|
+
|
363
|
+
true
|
364
|
+
end
|
365
|
+
|
366
|
+
def process_response_footer
|
367
|
+
return false if @data.size < 2
|
368
|
+
|
369
|
+
if @data.read(2) == CRLF
|
370
|
+
if @data.empty?
|
371
|
+
@state = :finished
|
372
|
+
on_request_complete
|
373
|
+
else
|
374
|
+
@state = :invalid
|
375
|
+
on_error "garbage at end of chunked response"
|
376
|
+
end
|
377
|
+
else
|
378
|
+
@state = :invalid
|
379
|
+
on_error "non-CRLF response footer"
|
380
|
+
end
|
381
|
+
|
382
|
+
false
|
383
|
+
end
|
384
|
+
|
385
|
+
def process_body
|
386
|
+
if @bytes_remaining.nil?
|
387
|
+
on_body_data @data.read
|
388
|
+
return false
|
389
|
+
end
|
390
|
+
|
391
|
+
if @bytes_remaining.zero?
|
392
|
+
@state = :finished
|
393
|
+
on_request_complete
|
394
|
+
return false
|
395
|
+
end
|
396
|
+
|
397
|
+
if @data.size < @bytes_remaining
|
398
|
+
@bytes_remaining -= @data.size
|
399
|
+
on_body_data @data.read
|
400
|
+
return false
|
401
|
+
end
|
402
|
+
|
403
|
+
on_body_data @data.read(@bytes_remaining)
|
404
|
+
@bytes_remaining = 0
|
405
|
+
|
406
|
+
# If Keep-Alive is enabled, the server may be pushing more data to us
|
407
|
+
# after the first request is complete. Hence, finish first request, and
|
408
|
+
# reset state.
|
409
|
+
if @response_header.keep_alive?
|
410
|
+
@data.clear # hard reset, TODO: add support for keep-alive connections!
|
411
|
+
@state = :finished
|
412
|
+
on_request_complete
|
413
|
+
|
414
|
+
else
|
415
|
+
if @data.empty?
|
416
|
+
@state = :finished
|
417
|
+
on_request_complete
|
418
|
+
else
|
419
|
+
@state = :invalid
|
420
|
+
on_error "garbage at end of body"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
false
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|