http_tools 0.2.0 → 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.
- 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
|