h1p 0.1 → 0.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +6 -15
- data/README.md +220 -3
- data/benchmarks/bm_http1_parser.rb +23 -10
- data/examples/callable.rb +10 -0
- data/examples/http_server.rb +28 -30
- data/ext/h1p/h1p.c +11 -1
- data/h1p.gemspec +2 -3
- data/lib/h1p.rb +1 -1
- data/lib/h1p/version.rb +1 -1
- data/test/helper.rb +0 -1
- data/test/test_h1p.rb +10 -10
- metadata +8 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58f7bb5105298c97edf87c91b8038ccc50ac6732417b981331a39cf56711da9b
|
4
|
+
data.tar.gz: d5745229262d866ed3564eede8b52706b14cb791652886be9f0a3ec2bc44eaf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d64c4623aa5c1cffd019222f6b75e3ec07da72997a5118490e07437ee0b0449371da96d89f4755182703fc71bc0974f415e6c9eecffa1f173c3bcb59f894d376
|
7
|
+
data.tar.gz: 9df79da840b37ecacb1df7818a661a33560970b5c07e4b82250e29466b868c6087848b527a8123a1373ead55ec1a37a4626e1a702c04037a4515e66ac6220476
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,33 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
h1p (0.
|
4
|
+
h1p (0.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
|
10
|
-
|
11
|
-
minitest (5.11.3)
|
12
|
-
minitest-reporters (1.4.3)
|
13
|
-
ansi
|
14
|
-
builder
|
15
|
-
minitest (>= 5.0)
|
16
|
-
ruby-progressbar
|
17
|
-
rake (12.3.3)
|
9
|
+
minitest (5.14.4)
|
10
|
+
rake (13.0.6)
|
18
11
|
rake-compiler (1.1.1)
|
19
12
|
rake
|
20
|
-
ruby-progressbar (1.11.0)
|
21
13
|
|
22
14
|
PLATFORMS
|
23
15
|
ruby
|
24
16
|
|
25
17
|
DEPENDENCIES
|
26
18
|
h1p!
|
27
|
-
minitest (~> 5.
|
28
|
-
|
29
|
-
rake (~> 12.3.3)
|
19
|
+
minitest (~> 5.14.4)
|
20
|
+
rake (~> 13.0.6)
|
30
21
|
rake-compiler (= 1.1.1)
|
31
22
|
|
32
23
|
BUNDLED WITH
|
33
|
-
2.
|
24
|
+
2.2.26
|
data/README.md
CHANGED
@@ -1,5 +1,222 @@
|
|
1
|
-
#
|
1
|
+
# H1P - a blocking HTTP/1 parser for Ruby
|
2
2
|
|
3
|
-
h1p
|
3
|
+
[](http://rubygems.org/gems/h1p)
|
4
|
+
[](https://github.com/digital-fabric/h1p/actions?query=workflow%3ATests)
|
5
|
+
[](https://github.com/digital-fabric/h1p/blob/master/LICENSE)
|
4
6
|
|
5
|
-
HTTP
|
7
|
+
H1P is a blocking/synchronous HTTP/1 parser for Ruby with a simple and intuitive
|
8
|
+
API. Its design lends itself to writing HTTP servers in a sequential style. As
|
9
|
+
such, it might prove useful in conjunction with the new fiber scheduler
|
10
|
+
introduced in Ruby 3.0, but is also useful with a normal thread-based server
|
11
|
+
(see
|
12
|
+
[example](https://github.com/digital-fabric/h1p/blob/main/examples/http_server.rb).)
|
13
|
+
The H1P was originally written as part of
|
14
|
+
[Tipi](https://github.com/digital-fabric/tipi), a web server running on top of
|
15
|
+
[Polyphony](https://github.com/digital-fabric/polyphony).
|
16
|
+
|
17
|
+
> H1P is still a very young project and as such should be used with caution. It
|
18
|
+
> has not undergone any significant conformance or security testing, and its API
|
19
|
+
> is not yet stable.
|
20
|
+
|
21
|
+
## Features
|
22
|
+
|
23
|
+
- Simple, blocking/synchronous API
|
24
|
+
- Zero dependencies
|
25
|
+
- Transport-agnostic
|
26
|
+
- Support for chunked encoding
|
27
|
+
- Support for both `LF` and `CRLF` line breaks
|
28
|
+
- Track total incoming traffic
|
29
|
+
|
30
|
+
## Installing
|
31
|
+
|
32
|
+
If you're using bundler just add it to your `Gemfile`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
source 'https://rubygems.org'
|
36
|
+
|
37
|
+
gem 'h1p'
|
38
|
+
```
|
39
|
+
|
40
|
+
You can then run `bundle install` to install it. Otherwise, just run `gem install h1p`.
|
41
|
+
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
Start by creating an instance of H1P::Parser, passing a connection instance:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'h1p'
|
48
|
+
|
49
|
+
parser = H1P::Parser.new(conn)
|
50
|
+
```
|
51
|
+
|
52
|
+
To read the next request from the connection, call `#parse_headers`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
loop do
|
56
|
+
headers = parser.parse_headers
|
57
|
+
break unless headers
|
58
|
+
|
59
|
+
handle_request(headers)
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
The `#parse_headers` method returns a single hash containing the different HTTP
|
64
|
+
headers. In case the client has closed the connection, `#parse_headers` will
|
65
|
+
return `nil` (see the guard clause above).
|
66
|
+
|
67
|
+
In addition to the header keys and values, the resulting hash also contains the
|
68
|
+
following "pseudo-headers":
|
69
|
+
|
70
|
+
- `:method`: the HTTP method (in upper case)
|
71
|
+
- `:path`: the request target
|
72
|
+
- `:protocol`: the protocol used (either `'http/1.0'` or `'http/1.1'`)
|
73
|
+
- `:rx`: the total bytes read by the parser
|
74
|
+
|
75
|
+
The header keys are always lower-cased. Consider the following HTTP request:
|
76
|
+
|
77
|
+
```
|
78
|
+
GET /foo HTTP/1.1
|
79
|
+
Host: example.com
|
80
|
+
User-Agent: curl/7.74.0
|
81
|
+
Accept: */*
|
82
|
+
|
83
|
+
```
|
84
|
+
|
85
|
+
The request will be parsed into the following Ruby hash:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
{
|
89
|
+
":method" => "get",
|
90
|
+
":path" => "/foo",
|
91
|
+
":protocol" => "http/1.1",
|
92
|
+
"host" => "example.com",
|
93
|
+
"user-agent" => "curl/7.74.0",
|
94
|
+
"accept" => "*/*",
|
95
|
+
":rx" => 78
|
96
|
+
}
|
97
|
+
```
|
98
|
+
|
99
|
+
Multiple headers with the same key will be coalesced into a single key-value
|
100
|
+
where the value is an array containing the corresponding values. For example,
|
101
|
+
multiple `Cookie` headers will appear in the hash as a single `"cookie"` entry,
|
102
|
+
e.g. `{ "cookie" => ['a=1', 'b=2'] }`
|
103
|
+
|
104
|
+
### Handling of invalid requests
|
105
|
+
|
106
|
+
When an invalid request is encountered, the parser will raise a `H1P::Error`
|
107
|
+
exception. An incoming request may be considered invalid if an invalid character
|
108
|
+
has been encountered at any point in parsing the request, or if any of the
|
109
|
+
tokens have an invalid length. You can consult the limits used by the parser
|
110
|
+
[here](https://github.com/digital-fabric/h1p/blob/main/ext/h1p/limits.rb).
|
111
|
+
|
112
|
+
### Reading the request body
|
113
|
+
|
114
|
+
To read the request body use `#read_body`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# read entire body
|
118
|
+
body = parser.read_body
|
119
|
+
```
|
120
|
+
|
121
|
+
The H1P parser knows how to read both request bodies with a specified
|
122
|
+
`Content-Length` and request bodies in chunked encoding. The method call will
|
123
|
+
return when the entire body has been read. If the body is incomplete or has
|
124
|
+
invalid formatting, the parser will raise a `H1P::Error` exception.
|
125
|
+
|
126
|
+
You can also read a single chunk of the body by calling `#read_body_chunk`:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
# read a body chunk
|
130
|
+
chunk = parser.read_body_chunk(false)
|
131
|
+
|
132
|
+
# read chunk only from buffer:
|
133
|
+
chunk = parser.read_body_chunk(true)
|
134
|
+
```
|
135
|
+
|
136
|
+
If no more chunks are availble, `#read_body_chunk` will return nil. To test
|
137
|
+
whether the request is complete, you can call `#complete?`:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
headers = parser.parse_headers
|
141
|
+
unless parser.complete?
|
142
|
+
body = parser.read_body
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
The `#read_body` and `#read_body_chunk` methods will return `nil` if no body is
|
147
|
+
expected (based on the received headers).
|
148
|
+
|
149
|
+
## Parsing from arbitrary transports
|
150
|
+
|
151
|
+
The H1P parser was built to read from any arbitrary transport or source, as long
|
152
|
+
as they conform to one of two alternative interfaces:
|
153
|
+
|
154
|
+
- An object implementing a `__parser_read_method__` method, which returns any of
|
155
|
+
the following values:
|
156
|
+
|
157
|
+
- `:stock_readpartial` - to be used for instances of `IO`, `Socket`,
|
158
|
+
`TCPSocket`, `SSLSocket` etc.
|
159
|
+
- `:backend_read` - for use in Polyphony-based servers.
|
160
|
+
- `:backend_recv` - for use in Polyphony-based servers.
|
161
|
+
- `:readpartial` - for use in Polyphony-based servers.
|
162
|
+
|
163
|
+
- An object implementing a `call` method, such as a `Proc` or any other. The
|
164
|
+
call is given a single argument signifying the maximum number of bytes to
|
165
|
+
read, and is expected to return either a string with the read data, or `nil`
|
166
|
+
if no more data is available. The callable can be passed as an argument or as
|
167
|
+
a block. Here's an example for parsing from a callable:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
data = ['GET ', '/foo', " HTTP/1.1\r\n", "\r\n"]
|
171
|
+
data = ['GET ', '/foo', " HTTP/1.1\r\n", "\r\n"]
|
172
|
+
parser = H1P::Parser.new { data.shift }
|
173
|
+
parser.parse_headers
|
174
|
+
#=> {":method"=>"get", ":path"=>"/foo", ":protocol"=>"http/1.1", ":rx"=>21}
|
175
|
+
```
|
176
|
+
|
177
|
+
## Design
|
178
|
+
|
179
|
+
The H1P parser design is based on the following principles:
|
180
|
+
|
181
|
+
- Implement a blocking API for use with a sequential programming style.
|
182
|
+
- Minimize copying of data between buffers.
|
183
|
+
- Parse each piece of data only once.
|
184
|
+
- Minimize object and buffer allocations.
|
185
|
+
- Minimize the API surface area.
|
186
|
+
|
187
|
+
One of the unique aspects of H1P is that instead of the server needing to feed
|
188
|
+
data to the parser, the parser itself reads data from its source whenever it
|
189
|
+
needs more of it. If no data is yet available, the parser blocks until more data
|
190
|
+
is received.
|
191
|
+
|
192
|
+
The different parts of the request are parsed one byte at a time, and once each
|
193
|
+
token is considered complete, it is copied from the buffer into a new string, to
|
194
|
+
be stored in the headers hash.
|
195
|
+
|
196
|
+
## Performance
|
197
|
+
|
198
|
+
The included benchmark (against
|
199
|
+
[http_parser.rb](https://github.com/tmm1/http_parser.rb), based on the *old*
|
200
|
+
[node.js HTTP parser](https://github.com/nodejs/http-parser)) shows the H1P
|
201
|
+
parser to be about 10-20% slower than http_parser.rb.
|
202
|
+
|
203
|
+
However, in a fiber-based environment such as
|
204
|
+
[Polyphony](https://github.com/digital-fabric/polyphony), H1P is slightly
|
205
|
+
faster, as the overhead of dealing with pipelined requests (which will cause
|
206
|
+
`http_parser.rb` to emit callbacks multiple times) significantly affects its
|
207
|
+
performance.
|
208
|
+
|
209
|
+
## Roadmap
|
210
|
+
|
211
|
+
Here are some of the features and enhancements planned for H1P:
|
212
|
+
|
213
|
+
- Add conformance and security tests
|
214
|
+
- Add ability to parse HTTP/1 responses (for implementing HTTP clients)
|
215
|
+
- Add ability to splice the request body into an arbitrary fd
|
216
|
+
(Polyphony-specific)
|
217
|
+
- Improve performance
|
218
|
+
|
219
|
+
## Contributing
|
220
|
+
|
221
|
+
Issues and pull requests will be gladly accepted. If you have found this gem
|
222
|
+
useful, please let me know.
|
@@ -1,8 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\nUser-Agent: foobar\r\n\r\n"
|
4
4
|
|
5
|
-
HTTP_REQUEST =
|
5
|
+
# HTTP_REQUEST =
|
6
|
+
# "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n" +
|
7
|
+
# "Host: www.kittyhell.com\r\n" +
|
8
|
+
# "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n" +
|
9
|
+
# "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
10
|
+
# "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n" +
|
11
|
+
# "Accept-Encoding: gzip,deflate\r\n" +
|
12
|
+
# "Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n" +
|
13
|
+
# "Keep-Alive: 115\r\n" +
|
14
|
+
# "Connection: keep-alive\r\n" +
|
15
|
+
# "Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n\r\n"
|
6
16
|
|
7
17
|
def measure_time_and_allocs
|
8
18
|
4.times { GC.start }
|
@@ -31,10 +41,12 @@ def benchmark_other_http1_parser(iterations)
|
|
31
41
|
parser = Http::Parser.new
|
32
42
|
done = false
|
33
43
|
headers = nil
|
44
|
+
rx = 0
|
34
45
|
parser.on_headers_complete = proc do |h|
|
35
46
|
headers = h
|
36
47
|
headers[':method'] = parser.http_method
|
37
48
|
headers[':path'] = parser.request_url
|
49
|
+
headers[':rx'] = rx
|
38
50
|
end
|
39
51
|
parser.on_message_complete = proc { done = true }
|
40
52
|
|
@@ -42,8 +54,10 @@ def benchmark_other_http1_parser(iterations)
|
|
42
54
|
iterations.times do
|
43
55
|
o << HTTP_REQUEST
|
44
56
|
done = false
|
57
|
+
rx = 0
|
45
58
|
while !done
|
46
59
|
msg = i.readpartial(4096)
|
60
|
+
rx += msg.bytesize
|
47
61
|
parser << msg
|
48
62
|
end
|
49
63
|
end
|
@@ -52,11 +66,10 @@ def benchmark_other_http1_parser(iterations)
|
|
52
66
|
end
|
53
67
|
|
54
68
|
def benchmark_tipi_http1_parser(iterations)
|
55
|
-
STDOUT << "
|
56
|
-
require_relative '../lib/
|
69
|
+
STDOUT << "H1P parser: "
|
70
|
+
require_relative '../lib/h1p'
|
57
71
|
i, o = IO.pipe
|
58
|
-
|
59
|
-
parser = Tipi::HTTP1Parser.new(reader)
|
72
|
+
parser = H1P::Parser.new(i)
|
60
73
|
|
61
74
|
elapsed, allocated = measure_time_and_allocs do
|
62
75
|
iterations.times do
|
@@ -78,8 +91,8 @@ def fork_benchmark(method, iterations)
|
|
78
91
|
Process.wait(pid)
|
79
92
|
end
|
80
93
|
|
81
|
-
x =
|
82
|
-
|
83
|
-
|
94
|
+
x = 100000
|
95
|
+
fork_benchmark(:benchmark_other_http1_parser, x)
|
96
|
+
fork_benchmark(:benchmark_tipi_http1_parser, x)
|
84
97
|
|
85
|
-
benchmark_tipi_http1_parser(x)
|
98
|
+
# benchmark_tipi_http1_parser(x)
|
data/examples/http_server.rb
CHANGED
@@ -1,41 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
|
-
require '
|
5
|
-
|
6
|
-
opts = {
|
7
|
-
reuse_addr: true,
|
8
|
-
dont_linger: true
|
9
|
-
}
|
4
|
+
require 'h1p'
|
10
5
|
|
11
6
|
puts "pid: #{Process.pid}"
|
12
|
-
puts 'Listening on port
|
7
|
+
puts 'Listening on port 1234...'
|
13
8
|
|
14
|
-
|
15
|
-
# Thread.current.backend.idle_gc_period = 60
|
9
|
+
trap('SIGINT') { exit! }
|
16
10
|
|
17
|
-
|
11
|
+
def handle_client(conn)
|
12
|
+
Thread.new do
|
13
|
+
parser = H1P::Parser.new(conn)
|
14
|
+
loop do
|
15
|
+
headers = parser.parse_headers
|
16
|
+
break unless headers
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
req_body = parser.read_body
|
19
|
+
|
20
|
+
p headers: headers
|
21
|
+
p body: req_body
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
req.send_chunk("bar\n")
|
31
|
-
req.finish
|
32
|
-
elsif req.path == '/upload'
|
33
|
-
body = req.read
|
34
|
-
req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
|
35
|
-
else
|
36
|
-
req.respond("Hello world!\n")
|
23
|
+
resp = 'Hello, world!'
|
24
|
+
conn << "HTTP/1.1 200 OK\r\nContent-Length: #{resp.bytesize}\r\n\r\n#{resp}"
|
25
|
+
rescue H1P::Error => e
|
26
|
+
puts "Invalid request: #{e.message}"
|
27
|
+
ensure
|
28
|
+
conn.close
|
29
|
+
break
|
37
30
|
end
|
38
|
-
# p req.transfer_counts
|
39
31
|
end
|
40
|
-
|
41
|
-
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'socket'
|
35
|
+
server = TCPServer.new('0.0.0.0', 1234)
|
36
|
+
loop do
|
37
|
+
conn = server.accept
|
38
|
+
handle_client(conn)
|
39
|
+
end
|
data/ext/h1p/h1p.c
CHANGED
@@ -24,6 +24,7 @@ ID ID_parser_read_method;
|
|
24
24
|
ID ID_read;
|
25
25
|
ID ID_readpartial;
|
26
26
|
ID ID_to_i;
|
27
|
+
ID ID_upcase;
|
27
28
|
|
28
29
|
static VALUE mPolyphony = Qnil;
|
29
30
|
static VALUE cError;
|
@@ -145,6 +146,7 @@ VALUE Parser_initialize(VALUE self, VALUE io) {
|
|
145
146
|
////////////////////////////////////////////////////////////////////////////////
|
146
147
|
|
147
148
|
#define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
|
149
|
+
#define str_upcase(str) (rb_funcall((str), ID_upcase, 0))
|
148
150
|
|
149
151
|
#define FILL_BUFFER_OR_GOTO_EOF(state) { if (!fill_buffer(state)) goto eof; }
|
150
152
|
|
@@ -155,6 +157,7 @@ VALUE Parser_initialize(VALUE self, VALUE io) {
|
|
155
157
|
#define BUFFER_PTR(state, pos) ((state)->ptr + pos)
|
156
158
|
#define BUFFER_STR(state, pos, len) (rb_obj_freeze(rb_utf8_str_new((state)->ptr + pos, len)))
|
157
159
|
#define BUFFER_STR_DOWNCASE(state, pos, len) (rb_obj_freeze(str_downcase(rb_utf8_str_new((state)->ptr + pos, len))))
|
160
|
+
#define BUFFER_STR_UPCASE(state, pos, len) (rb_obj_freeze(str_upcase(rb_utf8_str_new((state)->ptr + pos, len))))
|
158
161
|
|
159
162
|
#define INC_BUFFER_POS(state) { \
|
160
163
|
BUFFER_POS(state)++; \
|
@@ -209,6 +212,12 @@ VALUE Parser_initialize(VALUE self, VALUE io) {
|
|
209
212
|
RB_GC_GUARD(value); \
|
210
213
|
}
|
211
214
|
|
215
|
+
#define SET_HEADER_UPCASE_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
|
216
|
+
VALUE value = BUFFER_STR_UPCASE(state, pos, len); \
|
217
|
+
rb_hash_aset(headers, key, value); \
|
218
|
+
RB_GC_GUARD(value); \
|
219
|
+
}
|
220
|
+
|
212
221
|
#define CONSUME_CRLF(state) { \
|
213
222
|
INC_BUFFER_POS(state); \
|
214
223
|
if (BUFFER_CUR(state) != '\n') goto bad_request; \
|
@@ -335,7 +344,7 @@ static inline int parse_method(struct parser_state *state, VALUE headers) {
|
|
335
344
|
}
|
336
345
|
}
|
337
346
|
done:
|
338
|
-
|
347
|
+
SET_HEADER_UPCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
|
339
348
|
return 1;
|
340
349
|
bad_request:
|
341
350
|
RAISE_BAD_REQUEST("Invalid method");
|
@@ -834,6 +843,7 @@ void Init_H1P() {
|
|
834
843
|
ID_read = rb_intern("read");
|
835
844
|
ID_readpartial = rb_intern("readpartial");
|
836
845
|
ID_to_i = rb_intern("to_i");
|
846
|
+
ID_upcase = rb_intern("upcase");
|
837
847
|
|
838
848
|
NUM_max_headers_read_length = INT2NUM(MAX_HEADERS_READ_LENGTH);
|
839
849
|
NUM_buffer_start = INT2NUM(0);
|
data/h1p.gemspec
CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.required_ruby_version = '>= 2.6'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake-compiler', '1.1.1'
|
22
|
-
s.add_development_dependency 'rake', '~>
|
23
|
-
s.add_development_dependency 'minitest', '~>5.
|
24
|
-
s.add_development_dependency 'minitest-reporters', '~>1.4.2'
|
22
|
+
s.add_development_dependency 'rake', '~>13.0.6'
|
23
|
+
s.add_development_dependency 'minitest', '~>5.14.4'
|
25
24
|
end
|
data/lib/h1p.rb
CHANGED
data/lib/h1p/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/test_h1p.rb
CHANGED
@@ -22,7 +22,7 @@ class H1PTest < MiniTest::Test
|
|
22
22
|
|
23
23
|
assert_equal(
|
24
24
|
{
|
25
|
-
':method' => '
|
25
|
+
':method' => 'GET',
|
26
26
|
':path' => '/',
|
27
27
|
':protocol' => 'http/1.1',
|
28
28
|
':rx' => msg.bytesize
|
@@ -38,7 +38,7 @@ class H1PTest < MiniTest::Test
|
|
38
38
|
|
39
39
|
assert_equal(
|
40
40
|
{
|
41
|
-
':method' => '
|
41
|
+
':method' => 'GET',
|
42
42
|
':path' => '/',
|
43
43
|
':protocol' => 'http/1.1',
|
44
44
|
':rx' => msg.bytesize
|
@@ -57,17 +57,17 @@ class H1PTest < MiniTest::Test
|
|
57
57
|
def test_method_case
|
58
58
|
@o << "GET / HTTP/1.1\r\n\r\n"
|
59
59
|
headers = @parser.parse_headers
|
60
|
-
assert_equal '
|
60
|
+
assert_equal 'GET', headers[':method']
|
61
61
|
|
62
62
|
reset_parser
|
63
63
|
@o << "post / HTTP/1.1\r\n\r\n"
|
64
64
|
headers = @parser.parse_headers
|
65
|
-
assert_equal '
|
65
|
+
assert_equal 'POST', headers[':method']
|
66
66
|
|
67
67
|
reset_parser
|
68
68
|
@o << "PoST / HTTP/1.1\r\n\r\n"
|
69
69
|
headers = @parser.parse_headers
|
70
|
-
assert_equal '
|
70
|
+
assert_equal 'POST', headers[':method']
|
71
71
|
end
|
72
72
|
|
73
73
|
def test_bad_method
|
@@ -80,7 +80,7 @@ class H1PTest < MiniTest::Test
|
|
80
80
|
|
81
81
|
reset_parser
|
82
82
|
@o << "#{'a' * max_length} / HTTP/1.1\r\n\r\n"
|
83
|
-
assert_equal '
|
83
|
+
assert_equal 'A' * max_length, @parser.parse_headers[':method']
|
84
84
|
|
85
85
|
reset_parser
|
86
86
|
@o << "#{'a' * (max_length + 1)} / HTTP/1.1\r\n\r\n"
|
@@ -245,7 +245,7 @@ class H1PTest < MiniTest::Test
|
|
245
245
|
@o << msg
|
246
246
|
headers = @parser.parse_headers
|
247
247
|
assert_equal({
|
248
|
-
':method' => '
|
248
|
+
':method' => 'GET',
|
249
249
|
':path' => '/foo',
|
250
250
|
':protocol' => 'http/1.1',
|
251
251
|
'bar' => 'baz',
|
@@ -534,7 +534,7 @@ class H1PTest < MiniTest::Test
|
|
534
534
|
client << msg
|
535
535
|
reply = client.read
|
536
536
|
assert_equal({
|
537
|
-
':method' => '
|
537
|
+
':method' => 'GET',
|
538
538
|
':path' => '/foo',
|
539
539
|
':protocol' => 'http/1.1',
|
540
540
|
'cookie' => 'abc=def',
|
@@ -560,7 +560,7 @@ class H1PTest < MiniTest::Test
|
|
560
560
|
|
561
561
|
headers = parser.parse_headers
|
562
562
|
assert_equal({
|
563
|
-
':method' => '
|
563
|
+
':method' => 'GET',
|
564
564
|
':path' => '/foo',
|
565
565
|
':protocol' => 'http/1.1',
|
566
566
|
'host' => 'bar',
|
@@ -572,7 +572,7 @@ class H1PTest < MiniTest::Test
|
|
572
572
|
request = +"GET /bar HTTP/1.1\r\nHost: baz\r\n\r\n"
|
573
573
|
headers = parser.parse_headers
|
574
574
|
assert_equal({
|
575
|
-
':method' => '
|
575
|
+
':method' => 'GET',
|
576
576
|
':path' => '/bar',
|
577
577
|
':protocol' => 'http/1.1',
|
578
578
|
'host' => 'baz',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: h1p
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -30,42 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 13.0.6
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 13.0.6
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 5.
|
47
|
+
version: 5.14.4
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 5.
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: minitest-reporters
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.4.2
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 1.4.2
|
54
|
+
version: 5.14.4
|
69
55
|
description:
|
70
56
|
email: sharon@noteflakes.com
|
71
57
|
executables: []
|
@@ -84,6 +70,7 @@ files:
|
|
84
70
|
- Rakefile
|
85
71
|
- TODO.md
|
86
72
|
- benchmarks/bm_http1_parser.rb
|
73
|
+
- examples/callable.rb
|
87
74
|
- examples/http_server.rb
|
88
75
|
- ext/h1p/extconf.rb
|
89
76
|
- ext/h1p/h1p.c
|
@@ -118,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
105
|
- !ruby/object:Gem::Version
|
119
106
|
version: '0'
|
120
107
|
requirements: []
|
121
|
-
rubygems_version: 3.1.
|
108
|
+
rubygems_version: 3.1.4
|
122
109
|
signing_key:
|
123
110
|
specification_version: 4
|
124
111
|
summary: H1P is a blocking HTTP/1 parser for Ruby
|