h1p 0.6 → 1.0
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 +8 -0
- data/Gemfile.lock +5 -5
- data/README.md +23 -6
- data/ext/h1p/h1p.c +22 -5
- data/h1p.gemspec +2 -2
- data/lib/h1p/version.rb +1 -1
- data/test/test_h1p.rb +41 -7
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d2493b6fc11d7deb1231f9a2d0bf1c6dc59b00a01065da7086e8c65326f74df
|
4
|
+
data.tar.gz: e30bf2c5a3213a4744a66a412e2db34a2b26b05baa8073409a18c7e0bd314de3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2adc85f461d3a41e7e67f9c199be486b85fd46407e60455329ac065a5779c60d62328a049091bb9d4b7d0be2f1610db38a8744c35f7071b06991ec837235a981
|
7
|
+
data.tar.gz: 926acbd38590f74666e8d774b6f54d3b610cbf0570495727049b2c2ba628a80888d30e2e0bd83ac26b3897c9a0875be42ce336a439a9e07a2dfb8160aebc8441
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
h1p (0
|
4
|
+
h1p (1.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
minitest (5.
|
9
|
+
minitest (5.18.0)
|
10
10
|
rake (13.0.6)
|
11
|
-
rake-compiler (1.2.
|
11
|
+
rake-compiler (1.2.3)
|
12
12
|
rake
|
13
13
|
|
14
14
|
PLATFORMS
|
@@ -16,9 +16,9 @@ PLATFORMS
|
|
16
16
|
|
17
17
|
DEPENDENCIES
|
18
18
|
h1p!
|
19
|
-
minitest (~> 5.
|
19
|
+
minitest (~> 5.18.0)
|
20
20
|
rake (~> 13.0.6)
|
21
|
-
rake-compiler (= 1.2.
|
21
|
+
rake-compiler (= 1.2.3)
|
22
22
|
|
23
23
|
BUNDLED WITH
|
24
24
|
2.2.26
|
data/README.md
CHANGED
@@ -230,15 +230,22 @@ arbitrary IO instances. To write a response with or without a body, use
|
|
230
230
|
|
231
231
|
```ruby
|
232
232
|
H1P.send_response(socket, { 'Some-Header' => 'header value'}, 'foobar')
|
233
|
-
|
233
|
+
# HTTP/1.1 200 OK
|
234
|
+
# Some-Header: header value
|
235
|
+
#
|
236
|
+
# foobar
|
234
237
|
|
235
238
|
# The :protocol pseudo header sets the protocol in the status line:
|
236
239
|
H1P.send_response(socket, { ':protocol' => 'HTTP/0.9' })
|
237
|
-
|
240
|
+
# HTTP/0.9 200 OK
|
241
|
+
#
|
242
|
+
#
|
238
243
|
|
239
244
|
# The :status pseudo header sets the response status:
|
240
245
|
H1P.send_response(socket, { ':status' => '418 I\'m a teapot' })
|
241
|
-
|
246
|
+
# HTTP/1.1 418 I'm a teapot
|
247
|
+
#
|
248
|
+
#
|
242
249
|
```
|
243
250
|
|
244
251
|
To send responses using chunked transfer encoding use
|
@@ -246,7 +253,13 @@ To send responses using chunked transfer encoding use
|
|
246
253
|
|
247
254
|
```ruby
|
248
255
|
H1P.send_chunked_response(socket, {}, "foobar")
|
249
|
-
|
256
|
+
# HTTP/1.1 200 OK
|
257
|
+
# Transfer-Encoding: chunked
|
258
|
+
# 6
|
259
|
+
# foobar
|
260
|
+
# 0
|
261
|
+
#
|
262
|
+
#
|
250
263
|
```
|
251
264
|
|
252
265
|
You can also call `H1P.send_chunked_response` with a block that provides the
|
@@ -263,10 +276,14 @@ To send individual chunks use `H1P.send_body_chunk`:
|
|
263
276
|
|
264
277
|
```ruby
|
265
278
|
H1P.send_body_chunk(socket, 'foo')
|
266
|
-
|
279
|
+
# 3
|
280
|
+
# foo
|
281
|
+
#
|
267
282
|
|
268
283
|
H1P.send_body_chunk(socket, nil)
|
269
|
-
|
284
|
+
# 0
|
285
|
+
#
|
286
|
+
#
|
270
287
|
```
|
271
288
|
|
272
289
|
## Parser Design
|
data/ext/h1p/h1p.c
CHANGED
@@ -22,6 +22,7 @@ ID ID_call;
|
|
22
22
|
ID ID_downcase;
|
23
23
|
ID ID_eof_p;
|
24
24
|
ID ID_eq;
|
25
|
+
ID ID_join;
|
25
26
|
ID ID_read_method;
|
26
27
|
ID ID_read;
|
27
28
|
ID ID_readpartial;
|
@@ -56,6 +57,7 @@ VALUE STR_transfer_encoding_capitalized;
|
|
56
57
|
|
57
58
|
VALUE STR_CRLF;
|
58
59
|
VALUE STR_EMPTY_CHUNK;
|
60
|
+
VALUE STR_COMMA_SPACE;
|
59
61
|
|
60
62
|
VALUE SYM_backend_read;
|
61
63
|
VALUE SYM_backend_recv;
|
@@ -1176,12 +1178,24 @@ void send_response_write_status_line(send_response_ctx *ctx, VALUE protocol, VAL
|
|
1176
1178
|
ctx->buffer_len += partlen + 2;
|
1177
1179
|
}
|
1178
1180
|
|
1181
|
+
inline static VALUE format_comma_separated_header_values(VALUE array) {
|
1182
|
+
return rb_funcall(array, ID_join, 1, STR_COMMA_SPACE);
|
1183
|
+
}
|
1184
|
+
|
1179
1185
|
int send_response_write_header(VALUE key, VALUE val, VALUE arg) {
|
1180
1186
|
if (TYPE(key) != T_STRING) key = rb_funcall(key, ID_to_s, 0);
|
1181
1187
|
char *keyptr = RSTRING_PTR(key);
|
1182
1188
|
if (RSTRING_LEN(key) < 1 || keyptr[0] == ':') return 0;
|
1183
1189
|
|
1184
|
-
|
1190
|
+
switch (TYPE(val)) {
|
1191
|
+
case T_STRING:
|
1192
|
+
break;
|
1193
|
+
case T_ARRAY:
|
1194
|
+
val = format_comma_separated_header_values(val);
|
1195
|
+
break;
|
1196
|
+
default:
|
1197
|
+
val = rb_funcall(val, ID_to_s, 0);
|
1198
|
+
}
|
1185
1199
|
unsigned int keylen = RSTRING_LEN(key);
|
1186
1200
|
char *valptr = RSTRING_PTR(val);
|
1187
1201
|
unsigned int vallen = RSTRING_LEN(val);
|
@@ -1236,10 +1250,11 @@ VALUE H1P_send_response(int argc,VALUE *argv, VALUE self) {
|
|
1236
1250
|
|
1237
1251
|
bodyptr = RSTRING_PTR(body);
|
1238
1252
|
bodylen = RSTRING_LEN(body);
|
1239
|
-
rb_hash_aset(headers, STR_content_length_capitalized, INT2FIX(bodylen));
|
1253
|
+
// rb_hash_aset(headers, STR_content_length_capitalized, INT2FIX(bodylen));
|
1240
1254
|
}
|
1241
1255
|
|
1242
1256
|
rb_hash_foreach(headers, send_response_write_header, (VALUE)&ctx);
|
1257
|
+
send_response_write_header(STR_content_length_capitalized, INT2FIX(bodylen), (VALUE)&ctx);
|
1243
1258
|
|
1244
1259
|
char *endptr = ctx.buffer_ptr + ctx.buffer_len;
|
1245
1260
|
endptr[0] = '\r';
|
@@ -1309,8 +1324,8 @@ VALUE H1P_send_chunked_response(VALUE self, VALUE io, VALUE headers) {
|
|
1309
1324
|
if (status == Qnil) status = STR_pseudo_status_default;
|
1310
1325
|
send_response_write_status_line(&ctx, protocol, status);
|
1311
1326
|
|
1312
|
-
rb_hash_aset(headers, STR_transfer_encoding_capitalized, STR_chunked);
|
1313
1327
|
rb_hash_foreach(headers, send_response_write_header, (VALUE)&ctx);
|
1328
|
+
send_response_write_header(STR_transfer_encoding_capitalized, STR_chunked, (VALUE)&ctx);
|
1314
1329
|
|
1315
1330
|
ctx.buffer_ptr[ctx.buffer_len] = '\r';
|
1316
1331
|
ctx.buffer_ptr[ctx.buffer_len + 1] = '\n';
|
@@ -1376,6 +1391,7 @@ void Init_H1P(void) {
|
|
1376
1391
|
ID_downcase = rb_intern("downcase");
|
1377
1392
|
ID_eof_p = rb_intern("eof?");
|
1378
1393
|
ID_eq = rb_intern("==");
|
1394
|
+
ID_join = rb_intern("join");
|
1379
1395
|
ID_read_method = rb_intern("__read_method__");
|
1380
1396
|
ID_read = rb_intern("read");
|
1381
1397
|
ID_readpartial = rb_intern("readpartial");
|
@@ -1404,8 +1420,9 @@ void Init_H1P(void) {
|
|
1404
1420
|
GLOBAL_STR(STR_transfer_encoding, "transfer-encoding");
|
1405
1421
|
GLOBAL_STR(STR_transfer_encoding_capitalized, "Transfer-Encoding");
|
1406
1422
|
|
1407
|
-
GLOBAL_STR(STR_CRLF,
|
1408
|
-
GLOBAL_STR(STR_EMPTY_CHUNK,
|
1423
|
+
GLOBAL_STR(STR_CRLF, "\r\n");
|
1424
|
+
GLOBAL_STR(STR_EMPTY_CHUNK, "0\r\n\r\n");
|
1425
|
+
GLOBAL_STR(STR_COMMA_SPACE, ", ");
|
1409
1426
|
|
1410
1427
|
SYM_backend_read = ID2SYM(ID_backend_read);
|
1411
1428
|
SYM_backend_recv = ID2SYM(ID_backend_recv);
|
data/h1p.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
s.required_ruby_version = '>= 2.7'
|
20
20
|
|
21
|
-
s.add_development_dependency 'rake-compiler', '1.2.
|
21
|
+
s.add_development_dependency 'rake-compiler', '1.2.3'
|
22
22
|
s.add_development_dependency 'rake', '~>13.0.6'
|
23
|
-
s.add_development_dependency 'minitest', '~>5.
|
23
|
+
s.add_development_dependency 'minitest', '~>5.18.0'
|
24
24
|
end
|
data/lib/h1p/version.rb
CHANGED
data/test/test_h1p.rb
CHANGED
@@ -9,30 +9,38 @@ class SendResponseTest < MiniTest::Test
|
|
9
9
|
H1P.send_response(o, { ':status' => '418 I\'m a teapot' })
|
10
10
|
o.close
|
11
11
|
response = i.read
|
12
|
-
assert_equal "HTTP/1.1 418 I'm a teapot\r\n\r\n", response
|
12
|
+
assert_equal "HTTP/1.1 418 I'm a teapot\r\nContent-Length: 0\r\n\r\n", response
|
13
13
|
|
14
14
|
i, o = IO.pipe
|
15
15
|
count = H1P.send_response(o, { ':protocol' => 'HTTP/1.0' })
|
16
16
|
o.close
|
17
17
|
response = i.read
|
18
|
-
assert_equal "HTTP/1.0 200 OK\r\n\r\n", response
|
19
|
-
assert_equal "HTTP/1.0 200 OK\r\n\r\n".bytesize, count
|
18
|
+
assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n", response
|
19
|
+
assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n".bytesize, count
|
20
20
|
end
|
21
21
|
|
22
22
|
def test_send_response_string_headers
|
23
23
|
i, o = IO.pipe
|
24
|
-
H1P.send_response(o, { 'Foo' => 'Bar', '
|
24
|
+
H1P.send_response(o, { 'Foo' => 'Bar', 'X-Blah' => '123' })
|
25
25
|
o.close
|
26
26
|
response = i.read
|
27
|
-
assert_equal "HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length:
|
27
|
+
assert_equal "HTTP/1.1 200 OK\r\nFoo: Bar\r\nX-Blah: 123\r\nContent-Length: 0\r\n\r\n", response
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_send_response_non_string_headers
|
31
31
|
i, o = IO.pipe
|
32
|
-
H1P.send_response(o, { :Foo => 'Bar', '
|
32
|
+
H1P.send_response(o, { :Foo => 'Bar', 'X-Blah' => 123 })
|
33
33
|
o.close
|
34
34
|
response = i.read
|
35
|
-
assert_equal "HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length:
|
35
|
+
assert_equal "HTTP/1.1 200 OK\r\nFoo: Bar\r\nX-Blah: 123\r\nContent-Length: 0\r\n\r\n", response
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_send_response_multiple_header_values
|
39
|
+
i, o = IO.pipe
|
40
|
+
H1P.send_response(o, { :Foo => ['Bar', 'Baz'], 'X-Blah' => 123 })
|
41
|
+
o.close
|
42
|
+
response = i.read
|
43
|
+
assert_equal "HTTP/1.1 200 OK\r\nFoo: Bar, Baz\r\nX-Blah: 123\r\nContent-Length: 0\r\n\r\n", response
|
36
44
|
end
|
37
45
|
|
38
46
|
def test_send_response_with_body
|
@@ -43,6 +51,15 @@ class SendResponseTest < MiniTest::Test
|
|
43
51
|
assert_equal "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nfoobar", response
|
44
52
|
end
|
45
53
|
|
54
|
+
def test_send_response_with_frozen_headers_hash
|
55
|
+
i, o = IO.pipe
|
56
|
+
h = {Foo: 'bar'}.freeze
|
57
|
+
H1P.send_response(o, h, 'foo')
|
58
|
+
o.close
|
59
|
+
response = i.read
|
60
|
+
assert_equal "HTTP/1.1 200 OK\r\nFoo: bar\r\nContent-Length: 3\r\n\r\nfoo", response
|
61
|
+
end
|
62
|
+
|
46
63
|
def test_send_response_with_big_body
|
47
64
|
i, o = IO.pipe
|
48
65
|
body = "abcdefg" * 10000
|
@@ -111,4 +128,21 @@ class SendChunkedResponseTest < MiniTest::Test
|
|
111
128
|
assert_equal "HTTP/1.1 200 OK\r\nFoo: bar\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo\r\n3\r\nbar\r\n3\r\nbaz\r\n0\r\n\r\n", response
|
112
129
|
assert_equal len, response.bytesize
|
113
130
|
end
|
131
|
+
|
132
|
+
def test_send_chunked_response_with_frozen_headers_hash
|
133
|
+
isrc, osrc = IO.pipe
|
134
|
+
osrc << 'foobarbaz'
|
135
|
+
osrc.close
|
136
|
+
|
137
|
+
i, o = IO.pipe
|
138
|
+
h = { 'Foo' => 'bar' }.freeze
|
139
|
+
len = H1P.send_chunked_response(o, h) do
|
140
|
+
isrc.read(3)
|
141
|
+
end
|
142
|
+
o.close
|
143
|
+
|
144
|
+
response = i.read
|
145
|
+
assert_equal "HTTP/1.1 200 OK\r\nFoo: bar\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo\r\n3\r\nbar\r\n3\r\nbaz\r\n0\r\n\r\n", response
|
146
|
+
assert_equal len, response.bytesize
|
147
|
+
end
|
114
148
|
end
|
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: '1.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.
|
19
|
+
version: 1.2.3
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.
|
26
|
+
version: 1.2.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 5.
|
47
|
+
version: 5.18.0
|
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.
|
54
|
+
version: 5.18.0
|
55
55
|
description:
|
56
56
|
email: sharon@noteflakes.com
|
57
57
|
executables: []
|