http_parser 0.1.0 → 0.1.3
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/VERSION +1 -1
- data/lib/http/native_parser.rb +34 -7
- data/spec/http_parser_spec.rb +68 -3
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/http/native_parser.rb
CHANGED
@@ -63,16 +63,16 @@ module Http
|
|
63
63
|
# Regex used to match a header line. Lines suspected of
|
64
64
|
# being headers are also checked against the HeaderContinueMatch
|
65
65
|
# to deal with multiline headers
|
66
|
-
HeaderLineMatch = %r{^([a-zA-Z-]+):[ \t]*([[:print:]]
|
67
|
-
HeaderContinueMatch = %r{^[ \t]+([[:print:]]
|
66
|
+
HeaderLineMatch = %r{^([a-zA-Z-]+):[ \t]*([[:print:]]+?)\r?\n}
|
67
|
+
HeaderContinueMatch = %r{^[ \t]+([[:print:]]+?)\r?\n}
|
68
68
|
EmptyLineMatch = %r{^\r?\n}
|
69
69
|
|
70
70
|
# Regex used to match a size specification for a chunked segment
|
71
|
-
ChunkSizeLineMatch = %r{^[0-
|
71
|
+
ChunkSizeLineMatch = %r{^[0-9a-fA-F]+\r?\n}
|
72
72
|
|
73
73
|
# Used as a fallback in error detection for a malformed request line or header.
|
74
74
|
AnyLineMatch = %r{^.+?\r?\n}
|
75
|
-
|
75
|
+
|
76
76
|
def initialize(options = DefaultOptions)
|
77
77
|
@method = nil
|
78
78
|
@path = nil
|
@@ -163,7 +163,7 @@ module Http
|
|
163
163
|
if (can_have_body?)
|
164
164
|
if (@body_length >= @options[:min_tempfile_size])
|
165
165
|
@body = @options[:tempfile_class].new("http_parser")
|
166
|
-
@body.
|
166
|
+
File.unlink(@body.to_path) rescue nil # ruby 1.9.1 does Tempfile.unlink wrong, so we do it ourselves.
|
167
167
|
else
|
168
168
|
@body = StringIO.new
|
169
169
|
end
|
@@ -213,7 +213,7 @@ module Http
|
|
213
213
|
if (@body.length >= @options[:min_tempfile_size] && @body.kind_of?(StringIO))
|
214
214
|
@body_str = @body.string
|
215
215
|
@body = @options[:tempfile_class].new("http_parser")
|
216
|
-
@body.
|
216
|
+
File.unlink(@body.to_path) rescue nil # ruby 1.9.1 does Tempfile.unlink wrong, so we do it ourselves.
|
217
217
|
@body << @body_str
|
218
218
|
end
|
219
219
|
else
|
@@ -227,7 +227,7 @@ module Http
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
elsif (scanner.scan(ChunkSizeLineMatch))
|
230
|
-
@chunk_remain = scanner[0].to_i
|
230
|
+
@chunk_remain = scanner[0].to_i(16)
|
231
231
|
if (@chunk_remain < 1)
|
232
232
|
@state = :body_chunked_tail
|
233
233
|
end
|
@@ -286,6 +286,33 @@ module Http
|
|
286
286
|
end
|
287
287
|
private :normalize_header
|
288
288
|
|
289
|
+
# Given a basic rack environment, will properly fill it in
|
290
|
+
# with the information gleaned from the parsed request. Note that
|
291
|
+
# this only fills the subset that can be determined by the parser
|
292
|
+
# library. Namely, the only rack. variable set is rack.input. You should also
|
293
|
+
# have defaults in place for SERVER_NAME and SERVER_PORT, as they
|
294
|
+
# are required.
|
295
|
+
def fill_rack_env(env = {})
|
296
|
+
env["rack.input"] = @body || StringIO.new
|
297
|
+
env["REQUEST_METHOD"] = @method
|
298
|
+
env["SCRIPT_NAME"] = ""
|
299
|
+
env["REQUEST_URI"] = @path
|
300
|
+
env["PATH_INFO"], query = @path.split("?", 2)
|
301
|
+
env["QUERY_STRING"] = query || ""
|
302
|
+
if (@headers["HOST"] && !env["SERVER_NAME"])
|
303
|
+
env["SERVER_NAME"], port = @headers["HOST"].split(":", 2)
|
304
|
+
env["SERVER_PORT"] = port if port
|
305
|
+
end
|
306
|
+
@headers.each do |key, val|
|
307
|
+
if (key == 'CONTENT_LENGTH' || key == 'CONTENT_TYPE')
|
308
|
+
env[key] = val
|
309
|
+
else
|
310
|
+
env["HTTP_#{key}"] = val
|
311
|
+
end
|
312
|
+
end
|
313
|
+
return env
|
314
|
+
end
|
315
|
+
|
289
316
|
# Returns true if the request is completely done.
|
290
317
|
def done?
|
291
318
|
@state == :done
|
data/spec/http_parser_spec.rb
CHANGED
@@ -63,6 +63,71 @@ test_parsers.each do |parser|
|
|
63
63
|
p.body.read.should == "stuff"
|
64
64
|
end
|
65
65
|
|
66
|
+
describe "fill_rack_env" do
|
67
|
+
it "should fill in a simple request correctly" do
|
68
|
+
p = parser.new
|
69
|
+
p.parse("GET /blah HTTP/1.1\r\nHost: blorp\r\n\r\n")
|
70
|
+
p.done?.should be_true
|
71
|
+
env = p.fill_rack_env
|
72
|
+
env["rack.input"].should be_kind_of(StringIO)
|
73
|
+
env["REQUEST_METHOD"].should == "GET"
|
74
|
+
env["SCRIPT_NAME"].should == ""
|
75
|
+
env["REQUEST_URI"].should == "/blah"
|
76
|
+
env["PATH_INFO"].should == "/blah"
|
77
|
+
env["QUERY_STRING"].should == ""
|
78
|
+
env["SERVER_NAME"].should == "blorp"
|
79
|
+
env["SERVER_PORT"].should be_nil
|
80
|
+
env["HTTP_HOST"].should == "blorp"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should give Content-Type and Content-Length as CONTENT_* rather than HTTP_CONTENT_*" do
|
84
|
+
p = parser.new
|
85
|
+
p.parse("POST /blah HTTP/1.1\r\nContent-Type: text/text\r\nContent-Length: 4\r\n\r\ntest")
|
86
|
+
p.done?.should be_true
|
87
|
+
env = p.fill_rack_env
|
88
|
+
env["CONTENT_LENGTH"].should == "4"
|
89
|
+
env["CONTENT_TYPE"].should == "text/text"
|
90
|
+
env["HTTP_CONTENT_LENGTH"].should be_nil
|
91
|
+
env["HTTP_CONTENT_TYPE"].should be_nil
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should split the query string from the request uri" do
|
95
|
+
p = parser.new
|
96
|
+
p.parse("GET /blah?blorp HTTP/1.1\r\nHost: blorp\r\n\r\n")
|
97
|
+
p.done?.should be_true
|
98
|
+
env = p.fill_rack_env
|
99
|
+
env["REQUEST_URI"].should == "/blah?blorp"
|
100
|
+
env["PATH_INFO"].should == "/blah"
|
101
|
+
env["QUERY_STRING"].should == "blorp"
|
102
|
+
end
|
103
|
+
it "should split the query string from the path only once" do
|
104
|
+
p = parser.new
|
105
|
+
p.parse("GET /blah?blorp?bloop HTTP/1.1\r\nHost:blorp\r\n\r\n")
|
106
|
+
p.done?.should be_true
|
107
|
+
env = p.fill_rack_env
|
108
|
+
env["REQUEST_URI"].should == "/blah?blorp?bloop"
|
109
|
+
env["PATH_INFO"].should == "/blah"
|
110
|
+
env["QUERY_STRING"].should == "blorp?bloop"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should split the host from the port when doing SERVER_NAME/SERVER_PORT" do
|
114
|
+
p = parser.new
|
115
|
+
p.parse("GET /blah HTTP/1.1\r\nHost: blorp.com:1234\r\n\r\n")
|
116
|
+
p.done?.should be_true
|
117
|
+
env = p.fill_rack_env
|
118
|
+
env["SERVER_NAME"].should == "blorp.com"
|
119
|
+
env["SERVER_PORT"].should == "1234"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should not fill in SERVER_NAME and SERVER_PORT if SERVER_NAME is already set" do
|
123
|
+
p = parser.new
|
124
|
+
p.parse("GET /blah HTTP/1.1\r\nHost: blah.com:324\r\n\r\n")
|
125
|
+
p.done?.should be_true
|
126
|
+
env = p.fill_rack_env({"SERVER_NAME"=>"woop.com"})
|
127
|
+
env["SERVER_NAME"].should == "woop.com"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
66
131
|
it "should be able to parse two simple requests from the same string" do
|
67
132
|
req = <<REQ
|
68
133
|
GET /first HTTP/1.1\r
|
@@ -170,7 +235,7 @@ POST / HTTP/1.1\r
|
|
170
235
|
Host: blah.com\r
|
171
236
|
Transfer-Encoding: chunked\r
|
172
237
|
\r
|
173
|
-
|
238
|
+
A\r
|
174
239
|
stuffstuff\r
|
175
240
|
0\r
|
176
241
|
\r
|
@@ -214,7 +279,7 @@ Transfer-Encoding: chunked\r
|
|
214
279
|
\r
|
215
280
|
REQ
|
216
281
|
1.upto(200) do
|
217
|
-
p.parse("
|
282
|
+
p.parse("A\r\n")
|
218
283
|
p.parse("x"*10 + "\r\n")
|
219
284
|
end
|
220
285
|
p.parse("0\r\n\r\n")
|
@@ -405,7 +470,7 @@ POST / HTTP/1.1\r
|
|
405
470
|
Content-Length: 5
|
406
471
|
Transfer-Encoding: chunked
|
407
472
|
|
408
|
-
|
473
|
+
A
|
409
474
|
stuffstuff
|
410
475
|
0
|
411
476
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Graham Batty
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-03-
|
17
|
+
date: 2010-03-17 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|