h1p 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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