http_tools 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/bench/parser/request_bench.rb +2 -1
- data/example/http_server.rb +8 -10
- data/example/simple_http_client.rb +67 -0
- data/lib/http_tools.rb +3 -7
- data/lib/http_tools/encoding.rb +4 -3
- data/lib/http_tools/parser.rb +61 -71
- data/test/parser/request_test.rb +80 -45
- data/test/parser/response_test.rb +28 -5
- metadata +5 -4
data/README.rdoc
CHANGED
@@ -22,7 +22,7 @@ possible.
|
|
22
22
|
|
23
23
|
parser = HTTPTools::Parser.new
|
24
24
|
parser.on(:header) do |header|
|
25
|
-
puts parser.status_code + " " + parser.
|
25
|
+
puts parser.status_code + " " + parser.request_method
|
26
26
|
puts parser.header.inspect
|
27
27
|
end
|
28
28
|
parser.on(:stream) {|chunk| print chunk}
|
data/example/http_server.rb
CHANGED
@@ -6,7 +6,6 @@ require 'http_tools'
|
|
6
6
|
module HTTP
|
7
7
|
class Server
|
8
8
|
RACK_INPUT = "rack.input".freeze
|
9
|
-
NO_BODY = {"GET" => true, "HEAD" => true}
|
10
9
|
CONNECTION = "Connection".freeze
|
11
10
|
KEEP_ALIVE = "Keep-Alive".freeze
|
12
11
|
CLOSE = "close".freeze
|
@@ -16,8 +15,7 @@ module HTTP
|
|
16
15
|
host = options[:host] || options[:Host] || "0.0.0.0"
|
17
16
|
port = (options[:port] || options[:Port] || 9292).to_s
|
18
17
|
@app = app
|
19
|
-
@instance_env = {"
|
20
|
-
"rack.multithread" => true}
|
18
|
+
@instance_env = {"rack.multithread" => true}
|
21
19
|
@server = TCPServer.new(host, port)
|
22
20
|
@server.listen(1024)
|
23
21
|
end
|
@@ -44,12 +42,11 @@ module HTTP
|
|
44
42
|
env, input = nil
|
45
43
|
|
46
44
|
parser.on(:header) do
|
47
|
-
parser.force_no_body = NO_BODY[parser.request_method]
|
48
45
|
input = StringIO.new
|
49
46
|
env = parser.env.merge!(RACK_INPUT => input).merge!(@instance_env)
|
50
47
|
end
|
51
48
|
parser.on(:stream) {|chunk| input << chunk}
|
52
|
-
parser.on(:finish) do
|
49
|
+
parser.on(:finish) do
|
53
50
|
input.rewind
|
54
51
|
status, header, body = @app.call(env)
|
55
52
|
keep_alive = keep_alive?(parser.version, parser.header[CONNECTION])
|
@@ -57,11 +54,8 @@ module HTTP
|
|
57
54
|
socket << HTTPTools::Builder.response(status, header)
|
58
55
|
body.each {|chunk| socket << chunk}
|
59
56
|
body.close if body.respond_to?(:close)
|
60
|
-
|
61
|
-
|
62
|
-
parser << remainder.lstrip if remainder
|
63
|
-
throw :reset
|
64
|
-
end
|
57
|
+
remainder = parser.rest.lstrip
|
58
|
+
parser.reset << remainder and throw :reset if keep_alive
|
65
59
|
end
|
66
60
|
|
67
61
|
begin
|
@@ -80,3 +74,7 @@ module HTTP
|
|
80
74
|
|
81
75
|
end
|
82
76
|
end
|
77
|
+
|
78
|
+
HTTP::Server.run(proc do |env|
|
79
|
+
[200, {"Content-Length" => "5"}, "Hello"]
|
80
|
+
end)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'http_tools'
|
4
|
+
|
5
|
+
# Usage:
|
6
|
+
# uri = URI.parse("http://example.com/")
|
7
|
+
# client = HTTP::Client.new(uri.host, uri.port)
|
8
|
+
# response = client.get(uri.path)
|
9
|
+
#
|
10
|
+
# puts "#{response.status} #{response.message}"
|
11
|
+
# puts response.headers.inspect
|
12
|
+
# puts response.body
|
13
|
+
#
|
14
|
+
module HTTP
|
15
|
+
class Client
|
16
|
+
Response = Struct.new(:status, :message, :headers, :body)
|
17
|
+
|
18
|
+
def initialize(host, port=80)
|
19
|
+
@host, @port = host, port
|
20
|
+
end
|
21
|
+
|
22
|
+
def head(path, headers={})
|
23
|
+
request(:head, path, nil, headers, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(path, headers={})
|
27
|
+
request(:get, path, nil, headers)
|
28
|
+
end
|
29
|
+
|
30
|
+
def post(path, body=nil, headers={})
|
31
|
+
request(:post, path, body, headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(path, body=nil, headers={})
|
35
|
+
request(:put, path, body, headers)
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(path, headers={})
|
39
|
+
request(:delete, path, nil, headers)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def request(method, path, body=nil, headers={}, response_has_body=true)
|
44
|
+
parser = HTTPTools::Parser.new
|
45
|
+
parser.force_no_body = !response_has_body
|
46
|
+
response = nil
|
47
|
+
|
48
|
+
parser.on(:header) do
|
49
|
+
code, message, head = parser.status_code, parser.message, parser.header
|
50
|
+
response = Response.new(code, message, head, "")
|
51
|
+
end
|
52
|
+
parser.on(:stream) {|chunk| response.body << chunk}
|
53
|
+
|
54
|
+
socket = TCPSocket.new(@host, @port)
|
55
|
+
socket << HTTPTools::Builder.request(method, @host, path, headers)
|
56
|
+
socket << body if body
|
57
|
+
begin
|
58
|
+
parser << socket.sysread(1024 * 16)
|
59
|
+
rescue EOFError
|
60
|
+
break parser.finish
|
61
|
+
end until parser.finished?
|
62
|
+
socket.close
|
63
|
+
|
64
|
+
response
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/http_tools.rb
CHANGED
@@ -92,15 +92,11 @@ module HTTPTools
|
|
92
92
|
|
93
93
|
METHODS = %W{GET POST HEAD PUT DELETE OPTIONS TRACE CONNECT}.freeze
|
94
94
|
|
95
|
-
NO_BODY =
|
96
|
-
NO_BODY.merge!(204 => true, 304 => true, nil => false)
|
95
|
+
NO_BODY = {204 => true, 304 => true} # presence of key tested, not value
|
97
96
|
100.upto(199) {|status_code| NO_BODY[status_code] = true}
|
97
|
+
NO_BODY.freeze
|
98
98
|
|
99
|
-
ARRAY_VALUE_HEADERS =
|
100
|
-
ARRAY_VALUE_HEADERS.merge!("Set-Cookie" => true)
|
101
|
-
|
102
|
-
CRLF = "\r\n".freeze
|
103
|
-
SPACE = " ".freeze
|
99
|
+
ARRAY_VALUE_HEADERS = {"Set-Cookie" => true} # presence of key tested, not val
|
104
100
|
|
105
101
|
require_base = File.dirname(__FILE__) + '/http_tools/'
|
106
102
|
autoload :Encoding, require_base + 'encoding'
|
data/lib/http_tools/encoding.rb
CHANGED
@@ -11,6 +11,7 @@ module HTTPTools
|
|
11
11
|
HEX_BIG_ENDIAN_REPEATING = "H*".freeze
|
12
12
|
PERCENT = "%".freeze
|
13
13
|
PLUS = "+".freeze
|
14
|
+
SPACE = " ".freeze
|
14
15
|
AMPERSAND = "&".freeze
|
15
16
|
EQUALS = "=".freeze
|
16
17
|
CHUNK_FORMAT = "%x\r\n%s\r\n".freeze
|
@@ -121,11 +122,11 @@ module HTTPTools
|
|
121
122
|
# Encoding.transfer_encoding_chunked_decode(encoded_string)\
|
122
123
|
# => ["foobar", ""]
|
123
124
|
#
|
124
|
-
# If nothing can be decoded the first element in the array will be
|
125
|
-
#
|
125
|
+
# If nothing can be decoded the first element in the array will be nil and
|
126
|
+
# the second the remainder
|
126
127
|
# encoded_string = "3\r\nfo"
|
127
128
|
# Encoding.transfer_encoding_chunked_decode(encoded_string)\
|
128
|
-
# => [
|
129
|
+
# => [nil, "3\r\nfo"]
|
129
130
|
#
|
130
131
|
# Example use:
|
131
132
|
# include Encoding
|
data/lib/http_tools/parser.rb
CHANGED
@@ -12,7 +12,7 @@ module HTTPTools
|
|
12
12
|
# Example:
|
13
13
|
# parser = HTTPTools::Parser.new
|
14
14
|
# parser.on(:header) do |header|
|
15
|
-
# puts parser.status_code + " " + parser.
|
15
|
+
# puts parser.status_code + " " + parser.request_method
|
16
16
|
# puts parser.header.inspect
|
17
17
|
# end
|
18
18
|
# parser.on(:stream) {|chunk| print chunk}
|
@@ -37,20 +37,17 @@ module HTTPTools
|
|
37
37
|
CONNECTION = "Connection".freeze
|
38
38
|
CLOSE = "close".freeze
|
39
39
|
CHUNKED = "chunked".freeze
|
40
|
-
EVENTS = %W{header stream trailer finish error}.map
|
41
|
-
event.freeze
|
42
|
-
end.freeze
|
40
|
+
EVENTS = %W{header stream trailer finish error}.map {|e| e.freeze}.freeze
|
43
41
|
|
44
42
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
45
43
|
PATH_INFO = "PATH_INFO".freeze
|
46
44
|
QUERY_STRING = "QUERY_STRING".freeze
|
47
|
-
|
48
|
-
|
45
|
+
SERVER_NAME = "SERVER_NAME".freeze
|
46
|
+
SERVER_PORT = "SERVER_PORT".freeze
|
47
|
+
HTTP_HOST = "HTTP_HOST".freeze
|
49
48
|
|
50
49
|
PROTOTYPE_ENV = {
|
51
50
|
"SCRIPT_NAME" => "".freeze,
|
52
|
-
PATH_INFO => "/".freeze,
|
53
|
-
QUERY_STRING => "".freeze,
|
54
51
|
"rack.version" => [1, 1].freeze,
|
55
52
|
"rack.url_scheme" => "http".freeze,
|
56
53
|
"rack.errors" => STDERR,
|
@@ -61,10 +58,11 @@ module HTTPTools
|
|
61
58
|
HTTP_ = "HTTP_".freeze
|
62
59
|
LOWERCASE = "a-z-".freeze
|
63
60
|
UPPERCASE = "A-Z_".freeze
|
61
|
+
NO_HTTP_ = {"CONTENT_LENGTH" => true, "CONTENT_TYPE" => true}
|
64
62
|
|
65
63
|
attr_reader :state # :nodoc:
|
66
64
|
attr_reader :request_method, :path_info, :query_string, :request_uri,
|
67
|
-
:
|
65
|
+
:version, :status_code, :message, :header, :trailer
|
68
66
|
|
69
67
|
# Force parser to expect and parse a trailer when Trailer header missing.
|
70
68
|
attr_accessor :force_trailer
|
@@ -82,7 +80,6 @@ module HTTPTools
|
|
82
80
|
def initialize
|
83
81
|
@state = :start
|
84
82
|
@buffer = StringScanner.new("")
|
85
|
-
@buffer_backup_reference = @buffer
|
86
83
|
@header = {}
|
87
84
|
@trailer = {}
|
88
85
|
end
|
@@ -108,20 +105,23 @@ module HTTPTools
|
|
108
105
|
# Returns a Rack compatible environment hash. Will return nil if called
|
109
106
|
# before headers are complete.
|
110
107
|
#
|
111
|
-
#
|
112
|
-
# hash fully Rack compliant
|
108
|
+
# "rack.input" is not supplied and must be added to make the environment
|
109
|
+
# hash fully Rack compliant.
|
113
110
|
#
|
114
111
|
def env
|
115
112
|
return unless @header_complete
|
116
|
-
env = PROTOTYPE_ENV.
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
env = PROTOTYPE_ENV.dup
|
114
|
+
env[REQUEST_METHOD] = @request_method
|
115
|
+
env[PATH_INFO] = @path_info
|
116
|
+
env[QUERY_STRING] = @query_string
|
117
|
+
@header.each do |key, value|
|
118
|
+
upper_key = key.tr(LOWERCASE, UPPERCASE)
|
119
|
+
upper_key = HTTP_ + upper_key unless NO_HTTP_.key?(upper_key)
|
120
|
+
env[upper_key] = value
|
122
121
|
end
|
123
|
-
env[
|
124
|
-
|
122
|
+
host, port = env[HTTP_HOST].split(COLON)
|
123
|
+
env[SERVER_NAME] = host
|
124
|
+
env[SERVER_PORT] = port || "80"
|
125
125
|
@trailer.each {|k, val| env[HTTP_ + k.tr(LOWERCASE, UPPERCASE)] = val}
|
126
126
|
env
|
127
127
|
end
|
@@ -177,6 +177,14 @@ module HTTPTools
|
|
177
177
|
@state == :end_of_message
|
178
178
|
end
|
179
179
|
|
180
|
+
# :call-seq: parser.rest -> string
|
181
|
+
#
|
182
|
+
# Returns unconsumed data in the parser's buffer.
|
183
|
+
#
|
184
|
+
def rest
|
185
|
+
@buffer.rest
|
186
|
+
end
|
187
|
+
|
180
188
|
# :call-seq: parser.reset -> parser
|
181
189
|
#
|
182
190
|
# Reset the parser so it can be used to process a new request.
|
@@ -184,14 +192,12 @@ module HTTPTools
|
|
184
192
|
#
|
185
193
|
def reset
|
186
194
|
@state = :start
|
187
|
-
@buffer = @buffer_backup_reference
|
188
195
|
@buffer.string.replace("")
|
189
196
|
@buffer.reset
|
190
197
|
@request_method = nil
|
191
198
|
@path_info = nil
|
192
199
|
@query_string = nil
|
193
200
|
@request_uri = nil
|
194
|
-
@fragment = nil
|
195
201
|
@version = nil
|
196
202
|
@status_code = nil
|
197
203
|
@header = {}
|
@@ -201,9 +207,9 @@ module HTTPTools
|
|
201
207
|
self
|
202
208
|
end
|
203
209
|
|
204
|
-
# :call-seq: parser.add_listener(event) {|
|
210
|
+
# :call-seq: parser.add_listener(event) {|arg| block} -> parser
|
205
211
|
# parser.add_listener(event, proc) -> parser
|
206
|
-
# parser.on(event) {|
|
212
|
+
# parser.on(event) {|arg| block} -> parser
|
207
213
|
# parser.on(event, proc) -> parser
|
208
214
|
#
|
209
215
|
# Available events are :header, :stream, :trailer, :finish, and :error.
|
@@ -219,9 +225,9 @@ module HTTPTools
|
|
219
225
|
#
|
220
226
|
# [trailer] Called on the completion of the trailer, if present
|
221
227
|
#
|
222
|
-
# [finish]
|
223
|
-
#
|
224
|
-
#
|
228
|
+
# [finish] Called on completion of the entire message. Any unconsumed
|
229
|
+
# data (such as the start of the next message with keepalive)
|
230
|
+
# can be retrieved with #rest
|
225
231
|
#
|
226
232
|
# [error] Supplied with one argument, an error encountered while
|
227
233
|
# parsing as a HTTPTools::ParseError. If a listener isn't
|
@@ -241,7 +247,7 @@ module HTTPTools
|
|
241
247
|
@request_method.chop!
|
242
248
|
@request_method.upcase!
|
243
249
|
uri
|
244
|
-
elsif @buffer.
|
250
|
+
elsif @buffer.check(/HTTP\//i)
|
245
251
|
response_http_version
|
246
252
|
elsif @buffer.check(/[a-z]*\Z/i)
|
247
253
|
:start
|
@@ -253,16 +259,13 @@ module HTTPTools
|
|
253
259
|
end
|
254
260
|
|
255
261
|
def uri
|
256
|
-
@request_uri= @buffer.scan(/[a-z0-9;\/?:@&=+$,%_.!~*')(
|
262
|
+
@request_uri = @buffer.scan(/[a-z0-9;\/?:@&=+$,%_.!~*')(-]*(?=( |\r\n))/i)
|
257
263
|
if @request_uri
|
258
|
-
@
|
259
|
-
@
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
@query_string ? @query_string.slice!(0) : @query_string = ""
|
264
|
-
end
|
265
|
-
space_before_http
|
264
|
+
@path_info = @request_uri.dup
|
265
|
+
@path_info.slice!(/^([a-z0-9+.-]*:\/\/)?[^\/]+/i)
|
266
|
+
@query_string = @path_info.slice!(/\?[a-z0-9;\/?:@&=+$,%_.!~*')(-]*/i)
|
267
|
+
@query_string ? @query_string.slice!(0) : @query_string = ""
|
268
|
+
request_http_version
|
266
269
|
elsif @buffer.check(/[a-z0-9;\/?:@&=+$,%_.!~*')(#-]+\Z/i)
|
267
270
|
:uri
|
268
271
|
else
|
@@ -270,30 +273,16 @@ module HTTPTools
|
|
270
273
|
end
|
271
274
|
end
|
272
275
|
|
273
|
-
def space_before_http
|
274
|
-
if @buffer.skip(/ /i)
|
275
|
-
http
|
276
|
-
elsif @buffer.skip(/\r\n/i)
|
277
|
-
key_or_newline
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def http
|
282
|
-
if @buffer.skip(/HTTP\//i)
|
283
|
-
request_http_version
|
284
|
-
elsif @buffer.eos? || @buffer.check(/H(T(T(P\/?)?)?)?\Z/i)
|
285
|
-
:http
|
286
|
-
else
|
287
|
-
raise ParseError.new("Protocol not recognised")
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
276
|
def request_http_version
|
292
|
-
@version = @buffer.scan(/[0-9]+\.[0-9x]+\r\n/i)
|
277
|
+
@version = @buffer.scan(/ HTTP\/[0-9]+\.[0-9x]+\r\n/i)
|
293
278
|
if @version
|
294
|
-
@version.
|
279
|
+
@version.strip!
|
280
|
+
@version.upcase!
|
295
281
|
key_or_newline
|
296
|
-
elsif @buffer.
|
282
|
+
elsif @buffer.skip(/\r\n/i)
|
283
|
+
key_or_newline
|
284
|
+
elsif @buffer.eos? ||
|
285
|
+
@buffer.check(/ (H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?)?\Z/i)
|
297
286
|
:request_http_version
|
298
287
|
else
|
299
288
|
raise ParseError.new("Invalid version specifier")
|
@@ -301,11 +290,13 @@ module HTTPTools
|
|
301
290
|
end
|
302
291
|
|
303
292
|
def response_http_version
|
304
|
-
@version = @buffer.scan(/[0-9]+\.[0-9x]+ /i)
|
293
|
+
@version = @buffer.scan(/HTTP\/[0-9]+\.[0-9x]+ /i)
|
305
294
|
if @version
|
306
|
-
version.chop!
|
295
|
+
@version.chop!
|
296
|
+
@version.upcase!
|
307
297
|
status
|
308
|
-
elsif @buffer.eos? ||
|
298
|
+
elsif @buffer.eos? ||
|
299
|
+
@buffer.check(/H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?\Z/i)
|
309
300
|
:response_http_version
|
310
301
|
else
|
311
302
|
raise ParseError.new("Invalid version specifier")
|
@@ -365,16 +356,16 @@ module HTTPTools
|
|
365
356
|
end
|
366
357
|
|
367
358
|
def value
|
368
|
-
value = @buffer.scan(/[^\x00\n\x7f]*\
|
359
|
+
value = @buffer.scan(/[^\x00\n\x7f]*\n/i)
|
369
360
|
if value
|
370
361
|
value.chop!
|
371
|
-
if ARRAY_VALUE_HEADERS
|
362
|
+
if ARRAY_VALUE_HEADERS.key?(@last_key)
|
372
363
|
@header.fetch(@last_key) {@header[@last_key] = []}.push(value)
|
373
364
|
else
|
374
365
|
@header[@last_key] = value
|
375
366
|
end
|
376
367
|
key_or_newline
|
377
|
-
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\
|
368
|
+
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\Z/i)
|
378
369
|
:value
|
379
370
|
else
|
380
371
|
raise ParseError.new("Illegal character in field body")
|
@@ -382,7 +373,9 @@ module HTTPTools
|
|
382
373
|
end
|
383
374
|
|
384
375
|
def body
|
385
|
-
if @
|
376
|
+
if @request_method &&
|
377
|
+
!(@header.key?(CONTENT_LENGTH) || @header.key?(TRANSFER_ENCODING)) ||
|
378
|
+
NO_BODY.key?(@status_code) || @force_no_body
|
386
379
|
end_of_message
|
387
380
|
else
|
388
381
|
length = @header[CONTENT_LENGTH]
|
@@ -458,12 +451,12 @@ module HTTPTools
|
|
458
451
|
end
|
459
452
|
|
460
453
|
def trailer_value
|
461
|
-
value = @buffer.scan(/[^\000\n\177]+\
|
454
|
+
value = @buffer.scan(/[^\000\n\177]+\n/i)
|
462
455
|
if value
|
463
456
|
value.chop!
|
464
457
|
@trailer[@last_key] = value
|
465
458
|
trailer_key_or_newline
|
466
|
-
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\
|
459
|
+
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\Z/i)
|
467
460
|
:trailer_value
|
468
461
|
else
|
469
462
|
raise ParseError.new("Illegal character in field body")
|
@@ -472,10 +465,7 @@ module HTTPTools
|
|
472
465
|
|
473
466
|
def end_of_message
|
474
467
|
raise EndOfMessageError.new("Message ended") if @state == :end_of_message
|
475
|
-
|
476
|
-
if @finish_callback
|
477
|
-
@finish_callback.call((remainder if remainder.length > 0))
|
478
|
-
end
|
468
|
+
@finish_callback.call if @finish_callback
|
479
469
|
:end_of_message
|
480
470
|
end
|
481
471
|
|
data/test/parser/request_test.rb
CHANGED
@@ -66,7 +66,7 @@ class RequestTest < Test::Unit::TestCase
|
|
66
66
|
path, query = parser.path_info, parser.query_string
|
67
67
|
end
|
68
68
|
|
69
|
-
parser << "GET /foo%20bar/baz.html?key=value
|
69
|
+
parser << "GET /foo%20bar/baz.html?key=value HTTP/1.1\r\n\r\n"
|
70
70
|
|
71
71
|
assert_equal("/foo%20bar/baz.html", path)
|
72
72
|
assert_equal("key=value", query)
|
@@ -82,28 +82,19 @@ class RequestTest < Test::Unit::TestCase
|
|
82
82
|
|
83
83
|
def test_uri
|
84
84
|
parser = HTTPTools::Parser.new
|
85
|
-
|
85
|
+
uri, path, query = nil
|
86
86
|
|
87
87
|
parser.add_listener(:header) do
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
parser << "GET http://example.com/foo HTTP/1.1\r\n\r\n"
|
92
|
-
|
93
|
-
assert_equal("http://example.com/foo", result)
|
94
|
-
end
|
95
|
-
|
96
|
-
def test_path_callback_not_called_with_uri
|
97
|
-
parser = HTTPTools::Parser.new
|
98
|
-
result = nil
|
99
|
-
|
100
|
-
parser.add_listener(:header) do
|
101
|
-
result = parser.path_info
|
88
|
+
uri = parser.request_uri
|
89
|
+
path = parser.path_info
|
90
|
+
query = parser.query_string
|
102
91
|
end
|
103
92
|
|
104
|
-
parser << "GET http://example.com/foo HTTP/1.1\r\n\r\n"
|
93
|
+
parser << "GET http://example.com/foo?bar=baz HTTP/1.1\r\n\r\n"
|
105
94
|
|
106
|
-
|
95
|
+
assert_equal("http://example.com/foo?bar=baz", uri)
|
96
|
+
assert_equal("/foo", path)
|
97
|
+
assert_equal("bar=baz", query)
|
107
98
|
end
|
108
99
|
|
109
100
|
def test_uri_callback_called_with_path
|
@@ -127,34 +118,18 @@ class RequestTest < Test::Unit::TestCase
|
|
127
118
|
|
128
119
|
def test_fragment_with_path
|
129
120
|
parser = HTTPTools::Parser.new
|
130
|
-
path = nil
|
131
|
-
fragment = nil
|
132
121
|
|
133
|
-
|
134
|
-
|
135
|
-
fragment = parser.fragment
|
122
|
+
assert_raise(HTTPTools::ParseError) do
|
123
|
+
parser << "GET /foo#bar HTTP/1.1\r\n\r\n"
|
136
124
|
end
|
137
|
-
|
138
|
-
parser << "GET /foo#bar HTTP/1.1\r\n\r\n"
|
139
|
-
|
140
|
-
assert_equal("/foo", path)
|
141
|
-
assert_equal("bar", fragment)
|
142
125
|
end
|
143
126
|
|
144
127
|
def test_fragment_with_uri
|
145
128
|
parser = HTTPTools::Parser.new
|
146
|
-
uri = nil
|
147
|
-
fragment = nil
|
148
129
|
|
149
|
-
|
150
|
-
|
151
|
-
fragment = parser.fragment
|
130
|
+
assert_raise(HTTPTools::ParseError) do
|
131
|
+
parser << "GET http://example.com/foo#bar HTTP/1.1\r\n\r\n"
|
152
132
|
end
|
153
|
-
|
154
|
-
parser << "GET http://example.com/foo#bar HTTP/1.1\r\n\r\n"
|
155
|
-
|
156
|
-
assert_equal("http://example.com/foo", uri)
|
157
|
-
assert_equal("bar", fragment)
|
158
133
|
end
|
159
134
|
|
160
135
|
def test_with_header
|
@@ -322,7 +297,7 @@ class RequestTest < Test::Unit::TestCase
|
|
322
297
|
|
323
298
|
parser << "GET / HTTP/1.1\r\n\r\n"
|
324
299
|
|
325
|
-
assert_equal("1.1", version)
|
300
|
+
assert_equal("HTTP/1.1", version)
|
326
301
|
end
|
327
302
|
|
328
303
|
def test_protocol_without_version
|
@@ -339,7 +314,32 @@ class RequestTest < Test::Unit::TestCase
|
|
339
314
|
|
340
315
|
parser << "GET / HTTP/1.x\r\n\r\n"
|
341
316
|
|
342
|
-
assert_equal("1.
|
317
|
+
assert_equal("HTTP/1.X", version)
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_finish_without_body_trigger
|
321
|
+
parser = HTTPTools::Parser.new
|
322
|
+
|
323
|
+
parser << "GET / HTTP/1.1\r\n\r\n"
|
324
|
+
|
325
|
+
assert(parser.finished?, "parser should be finished")
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_finish_with_content_length_body_trigger
|
329
|
+
parser = HTTPTools::Parser.new
|
330
|
+
|
331
|
+
parser << "GET / HTTP/1.1\r\n"
|
332
|
+
parser << "Content-Length: 5\r\n\r\n"
|
333
|
+
|
334
|
+
assert(!parser.finished?, "parser should not be finished")
|
335
|
+
end
|
336
|
+
|
337
|
+
def test_finish_with_transfer_encoding_body_trigger
|
338
|
+
parser = HTTPTools::Parser.new
|
339
|
+
parser << "GET / HTTP/1.1\r\n"
|
340
|
+
parser << "Transfer-Encoding: chunked\r\n\r\n"
|
341
|
+
|
342
|
+
assert(!parser.finished?, "parser should not be finished")
|
343
343
|
end
|
344
344
|
|
345
345
|
def test_reset
|
@@ -416,7 +416,7 @@ class RequestTest < Test::Unit::TestCase
|
|
416
416
|
|
417
417
|
parser << "GET / http/1.1\r\n\r\n"
|
418
418
|
|
419
|
-
assert_equal("1.1", version)
|
419
|
+
assert_equal("HTTP/1.1", version)
|
420
420
|
end
|
421
421
|
|
422
422
|
def test_invalid_version
|
@@ -472,8 +472,8 @@ class RequestTest < Test::Unit::TestCase
|
|
472
472
|
assert_equal("", env["SCRIPT_NAME"])
|
473
473
|
assert_equal("/test", env["PATH_INFO"])
|
474
474
|
assert_equal("q=foo", env["QUERY_STRING"])
|
475
|
-
assert_equal(
|
476
|
-
assert_equal(
|
475
|
+
assert_equal("www.example.com", env["SERVER_NAME"])
|
476
|
+
assert_equal("80", env["SERVER_PORT"])
|
477
477
|
assert_equal("www.example.com", env["HTTP_HOST"])
|
478
478
|
assert_equal("text/html", env["HTTP_ACCEPT"])
|
479
479
|
|
@@ -505,8 +505,8 @@ class RequestTest < Test::Unit::TestCase
|
|
505
505
|
assert_equal("", env["SCRIPT_NAME"])
|
506
506
|
assert_equal("/submit", env["PATH_INFO"])
|
507
507
|
assert_equal("", env["QUERY_STRING"])
|
508
|
-
assert_equal(
|
509
|
-
assert_equal(
|
508
|
+
assert_equal("www.example.com", env["SERVER_NAME"])
|
509
|
+
assert_equal("80", env["SERVER_PORT"])
|
510
510
|
assert_equal("www.example.com", env["HTTP_HOST"])
|
511
511
|
assert_equal("chunked", env["HTTP_TRANSFER_ENCODING"])
|
512
512
|
assert_equal("X-Checksum", env["HTTP_TRAILER"])
|
@@ -521,4 +521,39 @@ class RequestTest < Test::Unit::TestCase
|
|
521
521
|
assert_equal(false, env["rack.run_once"])
|
522
522
|
end
|
523
523
|
|
524
|
+
def test_env_server_port
|
525
|
+
parser = HTTPTools::Parser.new
|
526
|
+
env = nil
|
527
|
+
parser.on(:header) {env = parser.env}
|
528
|
+
|
529
|
+
parser << "GET / HTTP/1.1\r\n"
|
530
|
+
parser << "Host: localhost:9292\r\n"
|
531
|
+
parser << "\r\n"
|
532
|
+
|
533
|
+
assert_equal("localhost", env["SERVER_NAME"])
|
534
|
+
assert_equal("9292", env["SERVER_PORT"])
|
535
|
+
assert_equal("localhost:9292", env["HTTP_HOST"])
|
536
|
+
end
|
537
|
+
|
538
|
+
def test_env_post
|
539
|
+
parser = HTTPTools::Parser.new
|
540
|
+
env = nil
|
541
|
+
parser.on(:header) {env = parser.env}
|
542
|
+
|
543
|
+
parser << "POST / HTTP/1.1\r\n"
|
544
|
+
parser << "Host: www.example.com\r\n"
|
545
|
+
parser << "Content-Length: 7\r\n"
|
546
|
+
parser << "Content-Type: application/x-www-form-urlencoded\r\n"
|
547
|
+
parser << "\r\n"
|
548
|
+
parser << "foo=bar"
|
549
|
+
|
550
|
+
assert_equal("POST", env["REQUEST_METHOD"])
|
551
|
+
assert(!env.key?("HTTP_CONTENT_LENGTH"), "env must not contain HTTP_CONTENT_LENGTH")
|
552
|
+
assert(!env.key?("HTTP_CONTENT_TYPE"), "env must not contain HTTP_CONTENT_TYPE")
|
553
|
+
assert_equal("7", env["CONTENT_LENGTH"])
|
554
|
+
assert_equal("application/x-www-form-urlencoded", env["CONTENT_TYPE"])
|
555
|
+
|
556
|
+
assert_equal(nil, env["rack.input"])
|
557
|
+
end
|
558
|
+
|
524
559
|
end
|
@@ -12,7 +12,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
12
12
|
|
13
13
|
parser << "HTTP/1.1 200 OK\r\n\r\n"
|
14
14
|
|
15
|
-
assert_equal("1.1", version)
|
15
|
+
assert_equal("HTTP/1.1", version)
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_one_dot_x_version
|
@@ -23,7 +23,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
23
23
|
|
24
24
|
parser << "HTTP/1.x 200 OK\r\n\r\n"
|
25
25
|
|
26
|
-
assert_equal("1.
|
26
|
+
assert_equal("HTTP/1.X", version)
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_ok
|
@@ -83,7 +83,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
83
83
|
|
84
84
|
parser << "HTTP/1.0 200 (OK)\r\n\r\n"
|
85
85
|
|
86
|
-
assert_equal("1.0", version)
|
86
|
+
assert_equal("HTTP/1.0", version)
|
87
87
|
assert_equal(200, code)
|
88
88
|
assert_equal("(OK)", message)
|
89
89
|
assert(!parser.finished?, "parser should not be finished")
|
@@ -715,7 +715,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
715
715
|
headers = parser.header
|
716
716
|
end
|
717
717
|
parser.add_listener(:stream) {|chunk| body << chunk}
|
718
|
-
parser.add_listener(:finish) {
|
718
|
+
parser.add_listener(:finish) {remainder = parser.rest}
|
719
719
|
|
720
720
|
parser << "HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"
|
721
721
|
parser << "<h1>Hello world</h1>HTTP/1.1 404 Not Found\r\n"
|
@@ -738,7 +738,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
738
738
|
headers = parser.header
|
739
739
|
end
|
740
740
|
parser.add_listener(:stream) {|chunk| body << chunk}
|
741
|
-
parser.add_listener(:finish) {
|
741
|
+
parser.add_listener(:finish) {remainder = parser.rest}
|
742
742
|
|
743
743
|
parser << "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
744
744
|
parser << "14\r\n<h1>Hello world</h1>\r\n0\r\nHTTP/1.1 404 Not Found\r\n"
|
@@ -750,6 +750,29 @@ class ResponseTest < Test::Unit::TestCase
|
|
750
750
|
assert(parser.finished?, "parser should be finished")
|
751
751
|
end
|
752
752
|
|
753
|
+
def test_finshed_without_rest
|
754
|
+
parser = HTTPTools::Parser.new
|
755
|
+
code, message, remainder = nil
|
756
|
+
body = ""
|
757
|
+
|
758
|
+
parser.add_listener(:header) do
|
759
|
+
code = parser.status_code
|
760
|
+
message = parser.message
|
761
|
+
headers = parser.header
|
762
|
+
end
|
763
|
+
parser.add_listener(:stream) {|chunk| body << chunk}
|
764
|
+
parser.add_listener(:finish) {remainder = parser.rest}
|
765
|
+
|
766
|
+
parser << "HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"
|
767
|
+
parser << "<h1>Hello world</h1>"
|
768
|
+
|
769
|
+
assert_equal(200, code)
|
770
|
+
assert_equal("OK", message)
|
771
|
+
assert_equal("<h1>Hello world</h1>", body)
|
772
|
+
assert_equal("", remainder)
|
773
|
+
assert(parser.finished?, "parser should be finished")
|
774
|
+
end
|
775
|
+
|
753
776
|
def test_trailer
|
754
777
|
parser = HTTPTools::Parser.new
|
755
778
|
trailer = nil
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matthew Sadler
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-05-10 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- profile/parser/response_profile.rb
|
50
50
|
- example/http_client.rb
|
51
51
|
- example/http_server.rb
|
52
|
+
- example/simple_http_client.rb
|
52
53
|
- README.rdoc
|
53
54
|
has_rdoc: true
|
54
55
|
homepage: http://github.com/matsadler/http_tools
|