http_tools 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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