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