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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fb3573e65df46350986981759a454c8bc2c4166901a46dd827fdc17a27b08b5
4
- data.tar.gz: afc10ead82e3286f61fa2bee1739499f275b75a386ed3e3401988805470c9b3a
3
+ metadata.gz: 58f7bb5105298c97edf87c91b8038ccc50ac6732417b981331a39cf56711da9b
4
+ data.tar.gz: d5745229262d866ed3564eede8b52706b14cb791652886be9f0a3ec2bc44eaf4
5
5
  SHA512:
6
- metadata.gz: 72ba304cda888fcfb1c8afaee1a03b94ddb15fde0110c303886e4036125fb5b464d214ebb731ea91dac792070c9cc3d5eccb789f667c8a590fae01afb88d5665
7
- data.tar.gz: 31cd6c6d7bb6695d338596b56be808cd1fed99cc76f11ee5f38fbdf7ff5d1206960a7134932ac82e9ca9c19116a20ee99db8f2a002fb76c1997134ac4d432dae
6
+ metadata.gz: d64c4623aa5c1cffd019222f6b75e3ec07da72997a5118490e07437ee0b0449371da96d89f4755182703fc71bc0974f415e6c9eecffa1f173c3bcb59f894d376
7
+ data.tar.gz: 9df79da840b37ecacb1df7818a661a33560970b5c07e4b82250e29466b868c6087848b527a8123a1373ead55ec1a37a4626e1a702c04037a4515e66ac6220476
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## 0.42 2021-08-16
2
2
 
3
+ ## 0.2 2021-08-20
4
+
5
+ - Use uppercase for HTTP method
6
+
3
7
  ## 0.1 2021-08-19
4
8
 
5
9
  - Import code from Tipi
data/Gemfile.lock CHANGED
@@ -1,33 +1,24 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- h1p (0.1)
4
+ h1p (0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ansi (1.5.0)
10
- builder (3.2.4)
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.11.3)
28
- minitest-reporters (~> 1.4.2)
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.1.4
24
+ 2.2.26
data/README.md CHANGED
@@ -1,5 +1,222 @@
1
- # h1p
1
+ # H1P - a blocking HTTP/1 parser for Ruby
2
2
 
3
- h1p
3
+ [![Gem Version](https://badge.fury.io/rb/h1p.svg)](http://rubygems.org/gems/h1p)
4
+ [![Modulation Test](https://github.com/digital-fabric/h1p/workflows/Tests/badge.svg)](https://github.com/digital-fabric/h1p/actions?query=workflow%3ATests)
5
+ [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/h1p/blob/master/LICENSE)
4
6
 
5
- HTTP 1.1 parser for Ruby
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
- require 'bundler/setup'
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 = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
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 << "tipi parser: "
56
- require_relative '../lib/tipi_ext'
69
+ STDOUT << "H1P parser: "
70
+ require_relative '../lib/h1p'
57
71
  i, o = IO.pipe
58
- reader = proc { |len| i.readpartial(len) }
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 = 500000
82
- # fork_benchmark(:benchmark_other_http1_parser, x)
83
- # fork_benchmark(:benchmark_tipi_http1_parser, x)
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)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'h1p'
5
+
6
+ data = ['GET ', '/foo', " HTTP/1.1\r\n", "\r\n"]
7
+ parser = H1P::Parser.new(proc { data.shift })
8
+
9
+ headers = parser.parse_headers
10
+ p headers
@@ -1,41 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'tipi'
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 10080...'
7
+ puts 'Listening on port 1234...'
13
8
 
14
- # GC.disable
15
- # Thread.current.backend.idle_gc_period = 60
9
+ trap('SIGINT') { exit! }
16
10
 
17
- spin_loop(interval: 10) { p Thread.backend.stats }
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
- spin_loop(interval: 10) do
20
- GC.compact
21
- end
18
+ req_body = parser.read_body
19
+
20
+ p headers: headers
21
+ p body: req_body
22
22
 
23
- spin do
24
- Tipi.serve('0.0.0.0', 10080, opts) do |req|
25
- if req.path == '/stream'
26
- req.send_headers('Foo' => 'Bar')
27
- sleep 1
28
- req.send_chunk("foo\n")
29
- sleep 1
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
- p 'done...'
41
- end.await
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
- SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
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', '~>12.3.3'
23
- s.add_development_dependency 'minitest', '~>5.11.3'
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'h1p_ext'
3
+ require_relative './h1p_ext'
4
4
 
5
5
  unless Object.const_defined?('Polyphony')
6
6
  class IO
data/lib/h1p/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module H1P
4
- VERSION = '0.1'
4
+ VERSION = '0.2'
5
5
  end
data/test/helper.rb CHANGED
@@ -5,7 +5,6 @@ require 'bundler/setup'
5
5
  require 'fileutils'
6
6
 
7
7
  require 'minitest/autorun'
8
- require 'minitest/reporters'
9
8
 
10
9
  module Minitest::Assertions
11
10
  def assert_in_range exp_range, act
data/test/test_h1p.rb CHANGED
@@ -22,7 +22,7 @@ class H1PTest < MiniTest::Test
22
22
 
23
23
  assert_equal(
24
24
  {
25
- ':method' => 'get',
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' => 'get',
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 'get', headers[':method']
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 'post', headers[':method']
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 'post', headers[':method']
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 'a' * max_length, @parser.parse_headers[':method']
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' => 'get',
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' => 'get',
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' => 'get',
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' => 'get',
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.1'
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-19 00:00:00.000000000 Z
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: 12.3.3
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: 12.3.3
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.11.3
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.11.3
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.6
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