h1p 0.6 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|