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.
@@ -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.method
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}
@@ -21,7 +21,8 @@ Benchmark.bm(41) do |x|
21
21
 
22
22
  x.report("HTTPTools::Parser (reset, with callbacks)") do
23
23
  parser = HTTPTools::Parser.new
24
- parser.on(:headers) {}
24
+ parser.on(:header) {}
25
+ parser.on(:finish) {}
25
26
  10_000.times do
26
27
  parser << request
27
28
  parser.reset
@@ -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 = {"SERVER_NAME" => host, "SERVER_PORT" => port,
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 |remainder|
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
- if keep_alive
61
- parser.reset
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
@@ -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 = Hash.new {|hash, key| hash[key] = false}
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 = Hash.new {|hash, key| hash[key] = false}
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'
@@ -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 an empty
125
- # string and the second the remainder
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
- # => ["", "3\r\nfo"]
129
+ # => [nil, "3\r\nfo"]
129
130
  #
130
131
  # Example use:
131
132
  # include Encoding
@@ -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.method
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 do |event|
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
- REQUEST_URI = "REQUEST_URI".freeze
48
- FRAGMENT = "FRAGMENT".freeze
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
- :fragment, :version, :status_code, :message, :header, :trailer
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
- # The following are not supplied, and must be added to make the environment
112
- # hash fully Rack compliant: SERVER_NAME, SERVER_PORT, rack.input
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.merge(
117
- REQUEST_METHOD => @request_method,
118
- REQUEST_URI => @request_uri)
119
- if @path_info
120
- env[PATH_INFO] = @path_info
121
- env[QUERY_STRING] = @query_string
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[FRAGMENT] = @fragment if @fragment
124
- @header.each {|k, val| env[HTTP_ + k.tr(LOWERCASE, UPPERCASE)] = val}
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) {|arg1 [, arg2]| block} -> parser
210
+ # :call-seq: parser.add_listener(event) {|arg| block} -> parser
205
211
  # parser.add_listener(event, proc) -> parser
206
- # parser.on(event) {|arg1 [, arg2]| block} -> parser
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] Supplied with one argument, any data left in the parser's
223
- # buffer after the end of the HTTP message (likely nil, but
224
- # possibly the start of the next message)
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.skip(/HTTP\//i)
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;\/?:@&=+$,%_.!~*')(#-]*(?=( |\r\n))/i)
262
+ @request_uri = @buffer.scan(/[a-z0-9;\/?:@&=+$,%_.!~*')(-]*(?=( |\r\n))/i)
257
263
  if @request_uri
258
- @fragment = @request_uri.slice!(/#[a-z0-9;\/?:@&=+$,%_.!~*')(-]+\Z/i)
259
- @fragment.slice!(0) if @fragment
260
- if @request_uri =~ /^\//i
261
- @path_info = @request_uri.dup
262
- @query_string = @path_info.slice!(/\?[a-z0-9;\/?:@&=+$,%_.!~*')(-]*/i)
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.chop!
279
+ @version.strip!
280
+ @version.upcase!
295
281
  key_or_newline
296
- elsif @buffer.eos? || @buffer.check(/\d+(\.(\d+\r?)?)?\Z/i)
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? || @buffer.check(/\d+(\.(\d+)?)?\Z/i)
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]*\r?\n/i)
359
+ value = @buffer.scan(/[^\x00\n\x7f]*\n/i)
369
360
  if value
370
361
  value.chop!
371
- if ARRAY_VALUE_HEADERS[@last_key]
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]+\r?\Z/i)
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 @force_no_body || NO_BODY[@status_code]
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]+\r?\n/i)
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]+\r?\Z/i)
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
- remainder = @buffer.respond_to?(:rest) ? @buffer.rest : @buffer
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
 
@@ -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#qux HTTP/1.1\r\n\r\n"
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
- result = nil
85
+ uri, path, query = nil
86
86
 
87
87
  parser.add_listener(:header) do
88
- result = parser.request_uri
89
- end
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
- assert_nil(result)
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
- parser.add_listener(:header) do
134
- path = parser.path_info
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
- parser.add_listener(:header) do
150
- uri = parser.request_uri
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.x", version)
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(nil, env["SERVER_NAME"])
476
- assert_equal(nil, env["SERVER_PORT"])
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(nil, env["SERVER_NAME"])
509
- assert_equal(nil, env["SERVER_PORT"])
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.x", version)
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) {|r| remainder = r}
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) {|r| remainder = r}
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: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.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-04-10 00:00:00 +01:00
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