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.
- data/README.rdoc +2 -2
- data/lib/http_tools.rb +17 -3
- data/lib/http_tools/builder.rb +4 -3
- data/lib/http_tools/encoding.rb +12 -14
- data/lib/http_tools/parser.rb +21 -21
- data/profile/parser/request_profile.rb +15 -4
- data/test/builder/request_test.rb +1 -0
- data/test/builder/response_test.rb +18 -1
- data/test/encoding/transfer_encoding_chunked_test.rb +1 -0
- data/test/encoding/url_encoding_test.rb +7 -2
- data/test/encoding/www_form_test.rb +1 -0
- data/test/parser/request_test.rb +17 -0
- data/test/parser/response_test.rb +7 -3
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -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
|
data/lib/http_tools.rb
CHANGED
@@ -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"
|
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
|
-
|
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
|
|
data/lib/http_tools/builder.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
1
2
|
module HTTPTools
|
2
3
|
|
3
|
-
# HTTPTools::Builder
|
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(
|
25
|
-
"HTTP/1.1 #{STATUS_LINES[
|
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
|
data/lib/http_tools/encoding.rb
CHANGED
@@ -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,
|
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,
|
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
|
-
|
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,
|
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,
|
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
|
-
#
|
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,
|
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,
|
92
|
-
# "foo" becomes "3
|
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,
|
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"]
|
data/lib/http_tools/parser.rb
CHANGED
@@ -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
|
10
|
-
# the wire, and the parser will call its callbacks as it works
|
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
|
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 (
|
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
|
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
|
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]
|
276
|
+
# [header] Called when headers are complete
|
276
277
|
#
|
277
|
-
# [stream]
|
278
|
-
#
|
279
|
-
#
|
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]
|
282
|
+
# [trailer] Called on the completion of the trailer, if present
|
283
283
|
#
|
284
|
-
# [finish]
|
285
|
-
#
|
286
|
-
#
|
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]
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
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
|
-
|
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,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
|
-
|
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,4 +1,4 @@
|
|
1
|
-
# encoding:
|
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
|
-
|
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
|
data/test/parser/request_test.rb
CHANGED
@@ -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:
|
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
|
101
|
+
parser << "HTTP/1.1 403 Acc\xC3\xA8s interdit\r\n\r\n"
|
102
102
|
|
103
103
|
assert_equal(403, code)
|
104
|
-
|
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:
|
4
|
+
hash: 5
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 4
|
9
|
-
-
|
10
|
-
version: 0.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:
|
18
|
+
date: 2013-02-05 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|