http_tools 0.4.4 → 0.4.5

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.
@@ -29,7 +29,7 @@ possible.
29
29
 
30
30
  parser = HTTPTools::Parser.new
31
31
  parser.on(:header) do
32
- puts parser.status_code + " " + parser.message
32
+ puts parser.status_code.to_s + " " + parser.message
33
33
  puts parser.header.inspect
34
34
  end
35
35
  parser.on(:finish) {print parser.body}
@@ -72,7 +72,7 @@ responses. It can be used as a mixin or class methods on HTTPTools::Builder.
72
72
 
73
73
  (The MIT License)
74
74
 
75
- Copyright (c) 2011 Matthew Sadler
75
+ Copyright (c) 2012, 2011 Matthew Sadler
76
76
 
77
77
  Permission is hereby granted, free of charge, to any person obtaining a copy
78
78
  of this software and associated documentation files (the "Software"), to deal
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  require_base = File.expand_path('../http_tools', __FILE__)
2
3
  require require_base + '/encoding'
3
4
  require require_base + '/parser'
@@ -58,6 +59,7 @@ module HTTPTools
58
59
  STATUS_DESCRIPTIONS = {
59
60
  100 => "Continue",
60
61
  101 => "Switching Protocols",
62
+ 102 => "Processing",
61
63
  200 => "OK",
62
64
  201 => "Created",
63
65
  202 => "Accepted",
@@ -66,6 +68,7 @@ module HTTPTools
66
68
  205 => "Reset Content",
67
69
  206 => "Partial Content",
68
70
  207 => "Multi-Status",
71
+ 208 => "Already Reported",
69
72
  226 => "IM Used",
70
73
  300 => "Multiple Choices",
71
74
  301 => "Moved Permanently",
@@ -73,7 +76,9 @@ module HTTPTools
73
76
  303 => "See Other",
74
77
  304 => "Not Modified",
75
78
  305 => "Use Proxy",
79
+ 306 => "Reserved",
76
80
  307 => "Temporary Redirect",
81
+ 308 => "Permanent Redirect",
77
82
  400 => "Bad Request",
78
83
  401 => "Unauthorized",
79
84
  402 => "Payment Required",
@@ -97,6 +102,9 @@ module HTTPTools
97
102
  423 => "Locked",
98
103
  424 => "Failed Dependency",
99
104
  426 => "Upgrade Required",
105
+ 428 => "Precondition Required",
106
+ 429 => "Too Many Requests",
107
+ 431 => "Request Header Fields Too Large",
100
108
  500 => "Internal Server Error",
101
109
  501 => "Not Implemented",
102
110
  502 => "Bad Gateway",
@@ -104,13 +112,16 @@ module HTTPTools
104
112
  504 => "Gateway Timeout",
105
113
  505 => "HTTP Version Not Supported",
106
114
  506 => "Variant Also Negotiates",
107
- 507 => "Insufficient Storage"}.freeze
115
+ 507 => "Insufficient Storage",
116
+ 508 => "Loop Detected",
117
+ 510 => "Not Extended",
118
+ 511 => "Network Authentication Required"}.freeze
108
119
  STATUS_DESCRIPTIONS.values.each {|val| val.freeze}
109
120
 
110
121
  # :stopdoc: hide from rdoc as it makes a mess
111
122
  STATUS_LINES = {}
112
123
  STATUS_CODES.each do |name, code|
113
- line = "#{code} #{STATUS_DESCRIPTIONS[code]}"
124
+ line = "#{code} #{STATUS_DESCRIPTIONS[code]}".freeze
114
125
  STATUS_LINES[name] = line
115
126
  STATUS_LINES[code] = line
116
127
  end
@@ -119,7 +130,10 @@ module HTTPTools
119
130
 
120
131
  METHODS = %W{GET POST HEAD PUT DELETE OPTIONS TRACE CONNECT}.freeze
121
132
 
122
- NO_BODY = {204 => true, 304 => true} # presence of key tested, not value
133
+ #--
134
+ # presence of key tested, not value
135
+ #++
136
+ NO_BODY = {204 => true, 205 => true, 304 => true}
123
137
  100.upto(199) {|status_code| NO_BODY[status_code] = true}
124
138
  NO_BODY.freeze
125
139
 
@@ -1,6 +1,7 @@
1
+ # encoding: ASCII-8BIT
1
2
  module HTTPTools
2
3
 
3
- # HTTPTools::Builder is a provides a simple interface to build HTTP requests &
4
+ # HTTPTools::Builder a provides a simple interface to build HTTP requests &
4
5
  # responses. It can be used as a mixin or class methods on HTTPTools::Builder.
5
6
  #
6
7
  module Builder
@@ -21,8 +22,8 @@ module HTTPTools
21
22
  # Builder.response(:ok, "Set-Cookie" => ["a=b", "c=d"])
22
23
  # Builder.response(:ok, "Set-Cookie" => "a=b\nc=d")
23
24
  #
24
- def response(status, headers={})
25
- "HTTP/1.1 #{STATUS_LINES[status]}\r\n#{format_headers(headers)}\r\n"
25
+ def response(code, headers={})
26
+ "HTTP/1.1 #{STATUS_LINES[code] || code}\r\n#{format_headers(headers)}\r\n"
26
27
  end
27
28
 
28
29
  # :call-seq: Builder.request(method, host, path="/", headers={}) -> string
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  require 'strscan'
2
3
 
3
4
  module HTTPTools
@@ -21,7 +22,7 @@ module HTTPTools
21
22
 
22
23
  # :call-seq: Encoding.url_encode(string) -> encoded_string
23
24
  #
24
- # URL encode a string, eg "le café" becomes "le+caf%c3%a9"
25
+ # URL encode a string, e.g. "le café" becomes "le+caf%c3%a9"
25
26
  #
26
27
  def url_encode(string)
27
28
  string.gsub(/[^a-z0-9._~-]+/i) do |match|
@@ -32,23 +33,22 @@ module HTTPTools
32
33
 
33
34
  # :call-seq: Encoding.url_decode(encoded_string) -> string
34
35
  #
35
- # URL decode a string, eg "le+caf%c3%a9" becomes "le café"
36
+ # URL decode a string, e.g. "le+caf%c3%a9" becomes "le café"
36
37
  #
37
38
  def url_decode(string)
38
39
  string.tr(PLUS, SPACE).gsub(/(%[0-9a-f]{2})+/i) do |match|
39
- r = [match.delete(PERCENT)].pack(HEX_BIG_ENDIAN_REPEATING)
40
- r.respond_to?(:force_encoding) ? r.force_encoding(string.encoding) : r
40
+ [match.delete(PERCENT)].pack(HEX_BIG_ENDIAN_REPEATING)
41
41
  end
42
42
  end
43
43
 
44
44
  # :call-seq: Encoding.www_form_encode(hash) -> string
45
45
  #
46
46
  # Takes a Hash and converts it to a String as if it was a HTML form being
47
- # submitted, eg
47
+ # submitted, e.g.
48
48
  # {"query" => "fish", "lang" => "en"} becomes "query=fish&lang=en"
49
49
  #
50
50
  # To get multiple key value pairs with the same key use an array as the
51
- # value, eg
51
+ # value, e.g.
52
52
  # {"lang" => ["en", "fr"]} become "lang=en&lang=fr"
53
53
  #
54
54
  def www_form_encode(hash)
@@ -65,10 +65,10 @@ module HTTPTools
65
65
  #
66
66
  # Takes a String resulting from a HTML form being submitted, and converts it
67
67
  # to a hash,
68
- # eg "lang=en&query=fish" becomes {"lang" => "en", "query" => "fish"}
68
+ # e.g. "lang=en&query=fish" becomes {"lang" => "en", "query" => "fish"}
69
69
  #
70
70
  # Multiple key value pairs with the same key will become a single key with
71
- # an array value, eg "lang=en&lang=fr" becomes {"lang" => ["en", "fr"]}
71
+ # an array value, e.g. "lang=en&lang=fr" becomes {"lang" => ["en", "fr"]}
72
72
  #
73
73
  def www_form_decode(string)
74
74
  out = {}
@@ -88,10 +88,10 @@ module HTTPTools
88
88
  # Encoding.transfer_encoding_chunked_encode(string) -> encoded_string
89
89
  #
90
90
  # Returns string as a 'chunked' transfer encoding encoded string, suitable
91
- # for a streaming response from a HTTP server, eg
92
- # "foo" becomes "3\r\nfoo\r\n"
91
+ # for a streaming response from a HTTP server, e.g.
92
+ # "foo" becomes "3\\r\\nfoo\\r\\n"
93
93
  #
94
- # chunked responses should be terminted with a empty chunk, eg "0\r\n",
94
+ # chunked responses should be terminted with a empty chunk, e.g. "0\\r\\n",
95
95
  # passing an empty string or nil will generate the empty chunk.
96
96
  #
97
97
  def transfer_encoding_chunked_encode(string)
@@ -107,14 +107,12 @@ module HTTPTools
107
107
  #
108
108
  # Decoding a complete chunked response will return an array containing
109
109
  # the decoded response and nil.
110
- # Example:
111
110
  # encoded_string = "3\r\nfoo\r\n3\r\nbar\r\n0\r\n"
112
111
  # Encoding.transfer_encoding_chunked_decode(encoded_string)
113
112
  # #=> ["foobar", nil]
114
113
  #
115
114
  # Decoding a partial response will return an array of the response decoded
116
115
  # so far, and the remainder of the encoded string.
117
- # Example
118
116
  # encoded_string = "3\r\nfoo\r\n3\r\nba"
119
117
  # Encoding.transfer_encoding_chunked_decode(encoded_string)
120
118
  # #=> ["foo", "3\r\nba"]
@@ -126,7 +124,7 @@ module HTTPTools
126
124
  # #=> ["foobar", ""]
127
125
  #
128
126
  # If nothing can be decoded the first element in the array will be nil and
129
- # the second the remainder
127
+ # the second the remainder.
130
128
  # encoded_string = "3\r\nfo"
131
129
  # Encoding.transfer_encoding_chunked_decode(encoded_string)
132
130
  # #=> [nil, "3\r\nfo"]
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  require 'strscan'
2
3
  require 'stringio'
3
4
 
@@ -6,14 +7,14 @@ module HTTPTools
6
7
  # HTTPTools::Parser is a pure Ruby HTTP request & response parser with an
7
8
  # evented API.
8
9
  #
9
- # The HTTP message can be fed in to the parser piece by piece as it comes over
10
- # the wire, and the parser will call its callbacks as it works it's way
10
+ # The HTTP message can be fed into the parser piece by piece as it comes over
11
+ # the wire, and the parser will call its callbacks as it works its way
11
12
  # through the message.
12
13
  #
13
14
  # Example:
14
15
  # parser = HTTPTools::Parser.new
15
16
  # parser.on(:header) do
16
- # puts parser.status_code + " " + parser.message
17
+ # puts parser.status_code.to_s + " " + parser.message
17
18
  # puts parser.header.inspect
18
19
  # end
19
20
  # parser.on(:finish) {print parser.body}
@@ -60,7 +61,7 @@ module HTTPTools
60
61
  HTTP_ = "HTTP_".freeze
61
62
  LOWERCASE = "a-z-".freeze
62
63
  UPPERCASE = "A-Z_".freeze
63
- NO_HTTP_ = {"CONTENT_LENGTH" => true, "CONTENT_TYPE" => true}
64
+ NO_HTTP_ = {"CONTENT_LENGTH" => true, "CONTENT_TYPE" => true}.freeze
64
65
  # :startdoc:
65
66
  EVENTS = %W{header stream trailer finish error}.map {|e| e.freeze}.freeze
66
67
 
@@ -108,7 +109,7 @@ module HTTPTools
108
109
  # :call-seq: parser.concat(data) -> parser
109
110
  # parser << data -> parser
110
111
  #
111
- # Feed data in to the parser and trigger callbacks.
112
+ # Feed data into the parser and trigger callbacks.
112
113
  #
113
114
  # Will raise HTTPTools::ParseError on error, unless a callback has been set
114
115
  # for the :error event, in which case the callback will recieve the error
@@ -127,10 +128,10 @@ module HTTPTools
127
128
  # before headers are complete.
128
129
  #
129
130
  # "SERVER_NAME" and "SERVER_PORT" are only supplied if they can be
130
- # determined from the request (eg, they are present in the "Host" header).
131
+ # determined from the request (e.g., they are present in the "Host" header).
131
132
  #
132
133
  # "rack.input" is only supplied if #env is called after parsing the request
133
- # has finsished, and no listener is set for the `stream` event
134
+ # has finsished, and no listener is set for the +stream+ event
134
135
  #
135
136
  # If not supplied, you must ensure "SERVER_NAME", "SERVER_PORT", and
136
137
  # "rack.input" are present to make the environment hash fully Rack compliant
@@ -236,7 +237,7 @@ module HTTPTools
236
237
  # :call-seq: parser.reset -> parser
237
238
  #
238
239
  # Reset the parser so it can be used to process a new request.
239
- # Callbacks/delegates will not be removed.
240
+ # Callbacks will not be removed.
240
241
  #
241
242
  def reset
242
243
  @state = :start
@@ -272,23 +273,22 @@ module HTTPTools
272
273
  # Adding a second callback for an event will overwite the existing callback.
273
274
  #
274
275
  # Events:
275
- # [header] Called when headers are complete
276
+ # [header] Called when headers are complete
276
277
  #
277
- # [stream] Supplied with one argument, the last chunk of body data fed
278
- # in to the parser as a String, e.g. "<h1>Hello". If no
279
- # listener is set for this event the body can be retrieved with
280
- # #body
278
+ # [stream] Supplied with one argument, the last chunk of body data fed in
279
+ # to the parser as a String, e.g. "<h1>Hello". If no listener is
280
+ # set for this event the body can be retrieved with #body
281
281
  #
282
- # [trailer] Called on the completion of the trailer, if present
282
+ # [trailer] Called on the completion of the trailer, if present
283
283
  #
284
- # [finish] Called on completion of the entire message. Any unconsumed
285
- # data (such as the start of the next message with keepalive)
286
- # can be retrieved with #rest
284
+ # [finish] Called on completion of the entire message. Any unconsumed data
285
+ # (such as the start of the next message with keepalive) can be
286
+ # retrieved with #rest
287
287
  #
288
- # [error] Supplied with one argument, an error encountered while
289
- # parsing as a HTTPTools::ParseError. If a listener isn't
290
- # registered for this event, an exception will be raised when
291
- # an error is encountered
288
+ # [error] Supplied with one argument, an error encountered while parsing
289
+ # as a HTTPTools::ParseError. If a listener isn't registered for
290
+ # this event, an exception will be raised when an error is
291
+ # encountered
292
292
  #
293
293
  def add_listener(event, proc=nil, &block)
294
294
  instance_variable_set(:"@#{event}_callback", proc || block)
@@ -1,12 +1,23 @@
1
1
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
2
  require base + '/http_tools'
3
3
  require 'rubygems'
4
- require 'ruby-prof'
4
+ # require 'ruby-prof'
5
5
 
6
6
  request = "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-gb) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en-gb\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n"
7
7
  parser = HTTPTools::Parser.new
8
8
 
9
- result = RubyProf.profile do
10
- parser << request
9
+ # result = RubyProf.profile do
10
+ # parser << request
11
+ # end
12
+ # RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
13
+
14
+
15
+ # pprof.rb --pdf /tmp/request_profile > /tmp/request_profile.pdf
16
+
17
+ require "perftools"
18
+ PerfTools::CpuProfiler.start("/tmp/request_profile") do
19
+ # 100_000.times do
20
+ parser << request
21
+ # parser.reset
22
+ # end
11
23
  end
12
- RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
3
  require base + '/http_tools'
3
4
  require 'test/unit'
@@ -1,9 +1,15 @@
1
+ # encoding: ASCII-8BIT
1
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
3
  require base + '/http_tools'
3
4
  require 'test/unit'
4
5
 
5
6
  class BuilderResponseTest < Test::Unit::TestCase
6
7
 
8
+ def ruby_one_nine_or_greater?
9
+ ruby_version = RUBY_VERSION.split(".").map {|d| d.to_i}
10
+ ruby_version[0] > 1 || (ruby_version[0] == 1 && ruby_version[1] >= 9)
11
+ end
12
+
7
13
  def test_status_ok
8
14
  result = HTTPTools::Builder.response(:ok)
9
15
 
@@ -22,11 +28,22 @@ class BuilderResponseTest < Test::Unit::TestCase
22
28
  assert_equal("HTTP/1.1 500 Internal Server Error\r\n\r\n", result)
23
29
  end
24
30
 
31
+ def test_status_unrecognised
32
+ result = HTTPTools::Builder.response(600)
33
+
34
+ assert_equal("HTTP/1.1 600\r\n\r\n", result)
35
+ end
36
+
25
37
  def test_headers
26
38
  result = HTTPTools::Builder.response(:ok, "Content-Type" => "text/html", "Content-Length" => 1024)
27
39
 
28
40
  expected = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 1024\r\n\r\n"
29
- assert_equal(expected, result)
41
+ if ruby_one_nine_or_greater?
42
+ assert_equal(expected, result)
43
+ else
44
+ other_possible_order = "HTTP/1.1 200 OK\r\nContent-Length: 1024\r\nContent-Type: text/html\r\n\r\n"
45
+ assert([expected, other_possible_order].include?(result))
46
+ end
30
47
  end
31
48
 
32
49
  def test_newline_separated_multi_value_headers
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
3
  require base + '/http_tools'
3
4
  require 'test/unit'
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # encoding: ASCII-8BIT
2
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
3
  require base + '/http_tools'
4
4
  require 'test/unit'
@@ -32,7 +32,12 @@ class URLEncodingTest < Test::Unit::TestCase
32
32
  def test_decode_latin_capital_letter_a_with_grave
33
33
  result = HTTPTools::Encoding.url_decode("%C3%80")
34
34
 
35
- assert_equal("À", result)
35
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "macruby"
36
+ # work around macruby not respecting the coding comment
37
+ assert_equal("À".force_encoding("ASCII-8BIT"), result)
38
+ else
39
+ assert_equal("À", result)
40
+ end
36
41
  end
37
42
 
38
43
  end
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
3
  require base + '/http_tools'
3
4
  require 'test/unit'
@@ -1,3 +1,4 @@
1
+ # encoding: ASCII-8BIT
1
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
3
  require base + '/http_tools'
3
4
  require 'test/unit'
@@ -172,6 +173,22 @@ GET /foo#ba
172
173
  MESSAGE
173
174
  end
174
175
 
176
+ def test_path_with_space
177
+ parser = HTTPTools::Parser.new
178
+
179
+ error = assert_raise(HTTPTools::ParseError) do
180
+ parser << "GET /foo bar HTTP/1.1\r\n"
181
+ end
182
+
183
+ return unless "".respond_to?(:lines)
184
+ assert_equal(<<-MESSAGE.chomp, error.message)
185
+ Invalid version specifier at line 1, char 10
186
+
187
+ GET /foo bar HTTP/1.1\\r\\n
188
+ ^
189
+ MESSAGE
190
+ end
191
+
175
192
  def test_with_header
176
193
  parser = HTTPTools::Parser.new
177
194
  method = nil
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # encoding: ASCII-8BIT
2
2
  base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
3
  require base + '/http_tools'
4
4
  require 'test/unit'
@@ -98,10 +98,14 @@ class ParserResponseTest < Test::Unit::TestCase
98
98
  code, message = parser.status_code, parser.message
99
99
  end
100
100
 
101
- parser << "HTTP/1.1 403 Accès interdit\r\n\r\n"
101
+ parser << "HTTP/1.1 403 Acc\xC3\xA8s interdit\r\n\r\n"
102
102
 
103
103
  assert_equal(403, code)
104
- assert_equal("Accès interdit", message)
104
+ expected_message = "Acc\xC3\xA8s interdit"
105
+ if expected_message.respond_to?(:force_encoding)
106
+ expected_message.force_encoding(message.encoding)
107
+ end
108
+ assert_equal(expected_message, message)
105
109
  assert(!parser.finished?, "parser should not be finished")
106
110
  end
107
111
 
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: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 4
10
- version: 0.4.4
9
+ - 5
10
+ version: 0.4.5
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-12-20 00:00:00 +00:00
18
+ date: 2013-02-05 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies: []
21
21