h1p 0.2 → 0.3

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: 58f7bb5105298c97edf87c91b8038ccc50ac6732417b981331a39cf56711da9b
4
- data.tar.gz: d5745229262d866ed3564eede8b52706b14cb791652886be9f0a3ec2bc44eaf4
3
+ metadata.gz: 3c4b5a2d00d0bb28e75e75a1bb5c5c0bf4ef060d837e33606408d92bcb70deca
4
+ data.tar.gz: 73332efbff1047e8f5e3a65b6a718a187b332b405f943f6d53fc9acdf6b55b7e
5
5
  SHA512:
6
- metadata.gz: d64c4623aa5c1cffd019222f6b75e3ec07da72997a5118490e07437ee0b0449371da96d89f4755182703fc71bc0974f415e6c9eecffa1f173c3bcb59f894d376
7
- data.tar.gz: 9df79da840b37ecacb1df7818a661a33560970b5c07e4b82250e29466b868c6087848b527a8123a1373ead55ec1a37a4626e1a702c04037a4515e66ac6220476
6
+ metadata.gz: 5b1616a34cf1f43c1dece307cabccd882f4805824a54e58a70e476fc37db0738542f260cf22a88a8c8d7e8fb310cdc3a67d3f4e50a22285ecdd4a08b43fbb46c
7
+ data.tar.gz: ca10eb8b6c764f15c5f816068e84719cb7b242d6e5cd521fb941995cc2b8fca7c48abe5e0791ba0d06d7331a3601adbf860373a550a132a13fb073c6684c66f8
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
- ## 0.42 2021-08-16
1
+ ## 0.3 2022-02-03
2
+
3
+ - Add support for parsing HTTP responses (#1)
4
+ - Put state directly in parser struct
2
5
 
3
6
  ## 0.2 2021-08-20
4
7
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- h1p (0.2)
4
+ h1p (0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # H1P - a blocking HTTP/1 parser for Ruby
2
2
 
3
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)
4
+ [![H1P Test](https://github.com/digital-fabric/h1p/workflows/Tests/badge.svg)](https://github.com/digital-fabric/h1p/actions?query=workflow%3ATests)
5
5
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/h1p/blob/master/LICENSE)
6
6
 
7
7
  H1P is a blocking/synchronous HTTP/1 parser for Ruby with a simple and intuitive
@@ -23,6 +23,7 @@ The H1P was originally written as part of
23
23
  - Simple, blocking/synchronous API
24
24
  - Zero dependencies
25
25
  - Transport-agnostic
26
+ - Parses both HTTP request and HTTP response
26
27
  - Support for chunked encoding
27
28
  - Support for both `LF` and `CRLF` line breaks
28
29
  - Track total incoming traffic
@@ -41,15 +42,21 @@ You can then run `bundle install` to install it. Otherwise, just run `gem instal
41
42
 
42
43
  ## Usage
43
44
 
44
- Start by creating an instance of H1P::Parser, passing a connection instance:
45
+ Start by creating an instance of `H1P::Parser`, passing a connection instance and the parsing mode:
45
46
 
46
47
  ```ruby
47
48
  require 'h1p'
48
49
 
49
- parser = H1P::Parser.new(conn)
50
+ parser = H1P::Parser.new(conn, :server)
50
51
  ```
51
52
 
52
- To read the next request from the connection, call `#parse_headers`:
53
+ In order to parse HTTP responses, change the mode to `:client`:
54
+
55
+ ```ruby
56
+ parser = H1P::Parser.new(conn, :client)
57
+ ```
58
+
59
+ To read the next message from the connection, call `#parse_headers`:
53
60
 
54
61
  ```ruby
55
62
  loop do
@@ -65,13 +72,21 @@ headers. In case the client has closed the connection, `#parse_headers` will
65
72
  return `nil` (see the guard clause above).
66
73
 
67
74
  In addition to the header keys and values, the resulting hash also contains the
68
- following "pseudo-headers":
75
+ following "pseudo-headers" (in server mode):
69
76
 
70
77
  - `:method`: the HTTP method (in upper case)
71
78
  - `:path`: the request target
72
79
  - `:protocol`: the protocol used (either `'http/1.0'` or `'http/1.1'`)
73
80
  - `:rx`: the total bytes read by the parser
74
81
 
82
+ In client mode, the following pseudo-headers will be present:
83
+
84
+ - `:protocol`: the protocol used (either `'http/1.0'` or `'http/1.1'`)
85
+ - `:status': the HTTP status as an integer
86
+ - `:status_message`: the HTTP status message
87
+ - `:rx`: the total bytes read by the parser
88
+
89
+
75
90
  The header keys are always lower-cased. Consider the following HTTP request:
76
91
 
77
92
  ```
@@ -101,24 +116,24 @@ where the value is an array containing the corresponding values. For example,
101
116
  multiple `Cookie` headers will appear in the hash as a single `"cookie"` entry,
102
117
  e.g. `{ "cookie" => ['a=1', 'b=2'] }`
103
118
 
104
- ### Handling of invalid requests
119
+ ### Handling of invalid message
105
120
 
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
121
+ When an invalid message is encountered, the parser will raise a `H1P::Error`
122
+ exception. An incoming message may be considered invalid if an invalid character
123
+ has been encountered at any point in parsing the message, or if any of the
109
124
  tokens have an invalid length. You can consult the limits used by the parser
110
125
  [here](https://github.com/digital-fabric/h1p/blob/main/ext/h1p/limits.rb).
111
126
 
112
- ### Reading the request body
127
+ ### Reading the message body
113
128
 
114
- To read the request body use `#read_body`:
129
+ To read the message body use `#read_body`:
115
130
 
116
131
  ```ruby
117
132
  # read entire body
118
133
  body = parser.read_body
119
134
  ```
120
135
 
121
- The H1P parser knows how to read both request bodies with a specified
136
+ The H1P parser knows how to read both message bodies with a specified
122
137
  `Content-Length` and request bodies in chunked encoding. The method call will
123
138
  return when the entire body has been read. If the body is incomplete or has
124
139
  invalid formatting, the parser will raise a `H1P::Error` exception.
@@ -211,8 +226,7 @@ performance.
211
226
  Here are some of the features and enhancements planned for H1P:
212
227
 
213
228
  - 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
229
+ - Add ability to splice the message body into an arbitrary fd
216
230
  (Polyphony-specific)
217
231
  - Improve performance
218
232
 
data/Rakefile CHANGED
@@ -12,5 +12,5 @@ task :recompile => [:clean, :compile]
12
12
  task :default => [:compile, :test]
13
13
 
14
14
  task :test do
15
- exec 'ruby test/test_h1p.rb'
15
+ exec 'ruby test/run.rb'
16
16
  end
@@ -36,7 +36,7 @@ end
36
36
  def benchmark_other_http1_parser(iterations)
37
37
  STDOUT << "http_parser.rb: "
38
38
  require 'http_parser.rb'
39
-
39
+
40
40
  i, o = IO.pipe
41
41
  parser = Http::Parser.new
42
42
  done = false
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\nUser-Agent: foobar\r\n\r\n" +
4
+ "GET /bar HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\nUser-Agent: foobar\r\n\r\n"
5
+
6
+ def measure_time_and_allocs
7
+ 4.times { GC.start }
8
+ GC.disable
9
+
10
+ t0 = Time.now
11
+ a0 = object_count
12
+ yield
13
+ t1 = Time.now
14
+ a1 = object_count
15
+ [t1 - t0, a1 - a0]
16
+ ensure
17
+ GC.enable
18
+ end
19
+
20
+ def object_count
21
+ count = ObjectSpace.count_objects
22
+ count[:TOTAL] - count[:FREE]
23
+ end
24
+
25
+ def benchmark_other_http1_parser(iterations)
26
+ STDOUT << "http_parser.rb: "
27
+ require 'http_parser.rb'
28
+
29
+ i, o = IO.pipe
30
+ parser = Http::Parser.new
31
+ done = false
32
+ queue = nil
33
+ rx = 0
34
+ req_count = 0
35
+ parser.on_headers_complete = proc do |h|
36
+ h[':method'] = parser.http_method
37
+ h[':path'] = parser.request_url
38
+ h[':rx'] = rx
39
+ queue << h
40
+ end
41
+ parser.on_message_complete = proc { done = true }
42
+
43
+ writer = Thread.new do
44
+ iterations.times { o << HTTP_REQUEST }
45
+ o.close
46
+ end
47
+
48
+ elapsed, allocated = measure_time_and_allocs do
49
+ queue = []
50
+ done = false
51
+ rx = 0
52
+ loop do
53
+ data = i.readpartial(4096) rescue nil
54
+ break unless data
55
+
56
+ rx += data.bytesize
57
+ parser << data
58
+ while (req = queue.shift)
59
+ req_count += 1
60
+ end
61
+ end
62
+ end
63
+ puts(format('count: %d, elapsed: %f, allocated: %d (%f/req), rate: %f ips', req_count, elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
64
+ end
65
+
66
+ def benchmark_h1p_parser(iterations)
67
+ STDOUT << "H1P parser: "
68
+ require_relative '../lib/h1p'
69
+ i, o = IO.pipe
70
+ parser = H1P::Parser.new(i)
71
+ req_count = 0
72
+
73
+ writer = Thread.new do
74
+ iterations.times { o << HTTP_REQUEST }
75
+ o.close
76
+ end
77
+
78
+ elapsed, allocated = measure_time_and_allocs do
79
+ while (headers = parser.parse_headers)
80
+ req_count += 1
81
+ end
82
+ end
83
+ puts(format('count: %d, elapsed: %f, allocated: %d (%f/req), rate: %f ips', req_count, elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
84
+ end
85
+
86
+ def fork_benchmark(method, iterations)
87
+ pid = fork do
88
+ send(method, iterations)
89
+ rescue Exception => e
90
+ p e
91
+ p e.backtrace
92
+ exit!
93
+ end
94
+ Process.wait(pid)
95
+ end
96
+
97
+ x = 100000
98
+ fork_benchmark(:benchmark_other_http1_parser, x)
99
+ fork_benchmark(:benchmark_h1p_parser, x)
100
+
101
+ # benchmark_h1p_parser(x)
data/examples/callable.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/setup'
4
4
  require 'h1p'
5
5
 
6
6
  data = ['GET ', '/foo', " HTTP/1.1\r\n", "\r\n"]
7
- parser = H1P::Parser.new(proc { data.shift })
7
+ parser = H1P::Parser.new(proc { data.shift }, :server)
8
8
 
9
9
  headers = parser.parse_headers
10
10
  p headers
@@ -10,13 +10,13 @@ trap('SIGINT') { exit! }
10
10
 
11
11
  def handle_client(conn)
12
12
  Thread.new do
13
- parser = H1P::Parser.new(conn)
13
+ parser = H1P::Parser.new(conn, :server)
14
14
  loop do
15
15
  headers = parser.parse_headers
16
16
  break unless headers
17
17
 
18
18
  req_body = parser.read_body
19
-
19
+
20
20
  p headers: headers
21
21
  p body: req_body
22
22