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.
@@ -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