tp2 0.11.2 → 0.12
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/.rubocop.yml +196 -0
- data/CHANGELOG.md +9 -0
- data/TODO.md +6 -1
- data/bin/tp2 +19 -18
- data/lib/tp2/{http1_adapter.rb → http1_connection.rb} +32 -69
- data/lib/tp2/logger.rb +39 -2
- data/lib/tp2/server.rb +2 -2
- data/lib/tp2/version.rb +1 -1
- data/lib/tp2.rb +4 -4
- data/test/helper.rb +6 -0
- data/test/test_http1_adapter.rb +2 -8
- data/test/test_server.rb +27 -8
- data/tp2.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3f8224d39ef6a0c94d38882aa4fbde1692de6e777803060eb5adc96e56f3504
|
4
|
+
data.tar.gz: 572011895d001d3f132416ad669191fc75291d7c0b0c01fbf5dce619da6bf22c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8faf10f9c9da6ef061c3203cd4795d0d9fa01a2ecf7ce788abd89fbdc251a126f760c08424e41b4a5e0a7f10c2ecd7a64b56b152b441049637a9a6ac3f34db4b
|
7
|
+
data.tar.gz: 111856fa3064194dc93a98c197adeb9c512c232bc8ca4aea73b9f24542a866360f086cb2c1ba94cee068df3bb8967fdb565104b58604a7c8c3555f6184f07700
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.2
|
3
|
+
RubyInterpreters:
|
4
|
+
- ruby
|
5
|
+
Exclude:
|
6
|
+
- "**/*.gemspec"
|
7
|
+
- "test/**/*.rb"
|
8
|
+
- "examples/**/*.rb"
|
9
|
+
- "Gemfile*"
|
10
|
+
# Style/LambdaCall:
|
11
|
+
# Enabled: false
|
12
|
+
# Style/ModuleFunction:
|
13
|
+
# Enabled: false
|
14
|
+
# Style/RegexpLiteral:
|
15
|
+
# Enabled: false
|
16
|
+
|
17
|
+
# Naming/MemoizedInstanceVariableName:
|
18
|
+
# Enabled: false
|
19
|
+
|
20
|
+
# Style/Alias:
|
21
|
+
# EnforcedStyle: prefer_alias_method
|
22
|
+
|
23
|
+
# Style/SpecialGlobalVars:
|
24
|
+
# Enabled: false
|
25
|
+
|
26
|
+
# Style/ClassAndModuleChildren:
|
27
|
+
# Enabled: false
|
28
|
+
|
29
|
+
# Metrics/AbcSize:
|
30
|
+
# Enabled: false
|
31
|
+
|
32
|
+
# Style/MixinUsage:
|
33
|
+
# Enabled: false
|
34
|
+
|
35
|
+
# Style/MultilineBlockChain:
|
36
|
+
# Enabled: false
|
37
|
+
|
38
|
+
# Lint/RescueException:
|
39
|
+
# Enabled: false
|
40
|
+
|
41
|
+
# Lint/InheritException:
|
42
|
+
# Enabled: false
|
43
|
+
|
44
|
+
Style/NumericPredicate:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
# Style/TrivialAccessors:
|
48
|
+
# Enabled: false
|
49
|
+
|
50
|
+
# Lint/MissingSuper:
|
51
|
+
# Enabled: false
|
52
|
+
|
53
|
+
# Style/GlobalVars:
|
54
|
+
# Exclude:
|
55
|
+
# - lib/polyphony/auto_run.rb
|
56
|
+
# - lib/polyphony/extensions/core.rb
|
57
|
+
# - examples/**/*.rb
|
58
|
+
|
59
|
+
Layout/HashAlignment:
|
60
|
+
Enabled: false
|
61
|
+
EnforcedColonStyle: table
|
62
|
+
EnforcedHashRocketStyle: table
|
63
|
+
|
64
|
+
# Naming/AccessorMethodName:
|
65
|
+
# Exclude:
|
66
|
+
# - lib/polyphony/extensions/fiber.rb
|
67
|
+
# - examples/**/*.rb
|
68
|
+
|
69
|
+
# Naming/MethodName:
|
70
|
+
# Exclude:
|
71
|
+
# - test/test_signal.rb
|
72
|
+
|
73
|
+
# Lint/SuppressedException:
|
74
|
+
# Exclude:
|
75
|
+
# - examples/**/*.rb
|
76
|
+
|
77
|
+
Metrics/MethodLength:
|
78
|
+
Max: 14
|
79
|
+
# Exclude:
|
80
|
+
# - lib/polyphony/extensions/io.rb
|
81
|
+
# - lib/polyphony/extensions/fiber.rb
|
82
|
+
# - lib/polyphony/extensions/thread.rb
|
83
|
+
# - lib/polyphony/adapters/open3.rb
|
84
|
+
# - test/**/*.rb
|
85
|
+
# - examples/**/*.rb
|
86
|
+
|
87
|
+
# Metrics/ModuleLength:
|
88
|
+
# Exclude:
|
89
|
+
# - lib/polyphony/core/global_api.rb
|
90
|
+
# - examples/**/*.rb
|
91
|
+
|
92
|
+
# Metrics/ClassLength:
|
93
|
+
# Exclude:
|
94
|
+
# - lib/polyphony/extensions/io.rb
|
95
|
+
# - lib/polyphony/extensions/fiber.rb
|
96
|
+
# - lib/polyphony/extensions/object.rb
|
97
|
+
# - lib/polyphony/extensions/thread.rb
|
98
|
+
# - test/**/*.rb
|
99
|
+
# - examples/**/*.rb
|
100
|
+
|
101
|
+
# Metrics/CyclomaticComplexity:
|
102
|
+
# Exclude:
|
103
|
+
# - lib/polyphony/extensions/fiber.rb
|
104
|
+
|
105
|
+
# Metrics/PerceivedComplexity:
|
106
|
+
# Exclude:
|
107
|
+
# - lib/polyphony/extensions/fiber.rb
|
108
|
+
|
109
|
+
# Style/RegexpLiteral:
|
110
|
+
# Enabled: false
|
111
|
+
|
112
|
+
Style/RescueModifier:
|
113
|
+
Enabled: false
|
114
|
+
# Style/Documentation:
|
115
|
+
# Exclude:
|
116
|
+
# - test/**/*.rb
|
117
|
+
# - examples/**/*.rb
|
118
|
+
# - lib/polyphony/adapters/**/*.rb
|
119
|
+
|
120
|
+
# Style/FormatString:
|
121
|
+
# Exclude:
|
122
|
+
# - test/**/*.rb
|
123
|
+
# - examples/**/*.rb
|
124
|
+
|
125
|
+
# Style/FormatStringToken:
|
126
|
+
# Exclude:
|
127
|
+
# - test/**/*.rb
|
128
|
+
# - examples/**/*.rb
|
129
|
+
|
130
|
+
Naming/MethodParameterName:
|
131
|
+
Enabled: false
|
132
|
+
|
133
|
+
# Security/MarshalLoad:
|
134
|
+
# Exclude:
|
135
|
+
# - examples/**/*.rb
|
136
|
+
|
137
|
+
# Lint/ShadowedArgument:
|
138
|
+
# Exclude:
|
139
|
+
# - lib/polyphony/extensions/fiber.rb
|
140
|
+
|
141
|
+
# Style/HashEachMethods:
|
142
|
+
# Enabled: true
|
143
|
+
|
144
|
+
# Style/HashTransformKeys:
|
145
|
+
# Enabled: true
|
146
|
+
|
147
|
+
# Style/HashTransformValues:
|
148
|
+
# Enabled: true
|
149
|
+
|
150
|
+
# Layout/EmptyLinesAroundAttributeAccessor:
|
151
|
+
# Enabled: true
|
152
|
+
|
153
|
+
# Layout/SpaceAroundMethodCallOperator:
|
154
|
+
# Enabled: true
|
155
|
+
|
156
|
+
# Lint/DeprecatedOpenSSLConstant:
|
157
|
+
# Enabled: true
|
158
|
+
|
159
|
+
# Lint/MixedRegexpCaptureTypes:
|
160
|
+
# Enabled: true
|
161
|
+
|
162
|
+
# Lint/RaiseException:
|
163
|
+
# Enabled: true
|
164
|
+
|
165
|
+
# Lint/StructNewOverride:
|
166
|
+
# Enabled: true
|
167
|
+
|
168
|
+
Style/NegatedIf:
|
169
|
+
Enabled: false
|
170
|
+
# Style/NegatedWhile:
|
171
|
+
# Enabled: false
|
172
|
+
|
173
|
+
# Style/CombinableLoops:
|
174
|
+
# Enabled: false
|
175
|
+
|
176
|
+
# Style/InfiniteLoop:
|
177
|
+
# Enabled: false
|
178
|
+
|
179
|
+
# Style/RedundantReturn:
|
180
|
+
# Enabled: false
|
181
|
+
|
182
|
+
# Style/ExponentialNotation:
|
183
|
+
# Enabled: true
|
184
|
+
|
185
|
+
# Style/RedundantRegexpCharacterClass:
|
186
|
+
# Enabled: true
|
187
|
+
|
188
|
+
# Style/RedundantRegexpEscape:
|
189
|
+
# Enabled: true
|
190
|
+
|
191
|
+
# Style/SlicingWithRange:
|
192
|
+
# Enabled: true
|
193
|
+
|
194
|
+
# Style/RaiseArgs:
|
195
|
+
# Exclude:
|
196
|
+
# - lib/polyphony/extensions/fiber.rb
|
data/CHANGELOG.md
CHANGED
data/TODO.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
- Add failing tests for request bombs (requests with lots of bytes)
|
2
|
-
- Add
|
2
|
+
- Add test for `ProtocolError` handling
|
3
|
+
- Add limits with tests for:
|
3
4
|
- total request size (pre body)
|
4
5
|
- line size
|
5
6
|
- request method size
|
@@ -12,3 +13,7 @@
|
|
12
13
|
|
13
14
|
HEADER_RE = /^\s([^\s^\:]{1, #{MAX_HEADER_KEY_BYTES}}).../
|
14
15
|
```
|
16
|
+
|
17
|
+
- Benchmark parsing with UM::Stream (current approach), against parsing with strscan
|
18
|
+
|
19
|
+
- Add pluggable logging. Move default logging format outside and pass a callable.
|
data/bin/tp2
CHANGED
@@ -1,46 +1,47 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require 'tp2'
|
5
|
+
require 'optparse'
|
6
6
|
|
7
7
|
opts = {
|
8
8
|
banner: true,
|
9
|
-
|
9
|
+
logger: true
|
10
10
|
}
|
11
11
|
|
12
12
|
parser = OptionParser.new do |o|
|
13
|
-
o.banner =
|
13
|
+
o.banner = 'Usage: tp2 [options]'
|
14
14
|
|
15
|
-
o.on(
|
15
|
+
o.on('-c', '--config CONFIG_FILE', String, 'TP2 config file to use (default: app.rb)') do
|
16
16
|
opts[:app_type] = :tp2
|
17
17
|
opts[:app_location] = it
|
18
|
-
|
19
|
-
o.on(
|
18
|
+
end
|
19
|
+
o.on('-r', '--rack RACK_FILE', String, 'Rack config file to use (default: config.ru)') do
|
20
20
|
opts[:app_type] = :rack
|
21
21
|
opts[:app_location] = it
|
22
|
-
|
23
|
-
o.on(
|
22
|
+
end
|
23
|
+
o.on('-d', '--dir PATH', String, 'Static files at path') do
|
24
24
|
opts[:app_type] = :static
|
25
25
|
opts[:app_location] = it
|
26
|
-
|
26
|
+
end
|
27
27
|
|
28
|
-
o.on(
|
28
|
+
o.on('-b', '--bind BIND', String,
|
29
|
+
'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
|
29
30
|
opts[:bind] ||= []
|
30
31
|
opts[:bind] << it
|
31
|
-
|
32
|
+
end
|
32
33
|
|
33
|
-
o.on(
|
34
|
+
o.on('-s', '--silent', 'Silent mode') do
|
34
35
|
opts[:banner] = nil
|
35
|
-
opts[:
|
36
|
-
|
36
|
+
opts[:logger] = nil
|
37
|
+
end
|
37
38
|
|
38
|
-
o.on(
|
39
|
+
o.on('-h', '--help', 'Show this help message') do
|
39
40
|
puts o
|
40
41
|
exit
|
41
42
|
end
|
42
43
|
|
43
|
-
o.on(
|
44
|
+
o.on('-v', '--version', 'Show version') do
|
44
45
|
puts "TP2 version #{TP2::VERSION}"
|
45
46
|
exit
|
46
47
|
end
|
@@ -59,7 +60,7 @@ opts[:location] = ARGV.shift || '.'
|
|
59
60
|
def check_app_file(opts, fn, type)
|
60
61
|
fn = File.join(opts[:location], fn)
|
61
62
|
return false if !File.file?(fn)
|
62
|
-
|
63
|
+
|
63
64
|
opts[:app_type] = type
|
64
65
|
opts[:app_location] = File.expand_path(fn)
|
65
66
|
end
|
@@ -4,7 +4,7 @@ require 'qeweney'
|
|
4
4
|
require 'stringio'
|
5
5
|
|
6
6
|
module TP2
|
7
|
-
class
|
7
|
+
class HTTP1Connection
|
8
8
|
attr_reader :fd
|
9
9
|
|
10
10
|
def initialize(machine, fd, opts, &app)
|
@@ -20,30 +20,21 @@ module TP2
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def run
|
23
|
-
|
23
|
+
loop do
|
24
24
|
@done = nil
|
25
25
|
@response_headers = nil
|
26
26
|
persist = serve_request
|
27
27
|
break if !persist
|
28
28
|
end
|
29
|
-
rescue
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
when SystemCallError
|
34
|
-
@opts[:log]&.log("Encountered #{e.class}, closing connection")
|
35
|
-
when ProtocolError
|
36
|
-
@opts[:log]&.log("Protocol error (#{e.message}), closing connection")
|
37
|
-
when StandardError
|
38
|
-
@opts[:log]&.log("Unhandled exception #{e.class}: #{e.message}, closing connection")
|
39
|
-
@opts[:log]&.log(e.backtrace.join("\n"))
|
40
|
-
else
|
41
|
-
raise
|
42
|
-
end
|
29
|
+
rescue UM::Terminate, SystemCallError
|
30
|
+
# we're done, we don't care about broken socket
|
31
|
+
rescue StandardError => e
|
32
|
+
@opts[:logger]&.call(e, nil)
|
43
33
|
ensure
|
44
34
|
@machine.close_async(@fd)
|
45
35
|
end
|
46
36
|
|
37
|
+
# Returns true if connection should persist
|
47
38
|
def serve_request
|
48
39
|
headers = parse_headers
|
49
40
|
return false if !headers
|
@@ -51,6 +42,9 @@ module TP2
|
|
51
42
|
request = Qeweney::Request.new(headers, self)
|
52
43
|
@app.call(request)
|
53
44
|
persist_connection?(headers)
|
45
|
+
rescue ProtocolError
|
46
|
+
# TODO: add hook for testing protocol errors
|
47
|
+
false
|
54
48
|
end
|
55
49
|
|
56
50
|
def get_body(req)
|
@@ -65,7 +59,7 @@ module TP2
|
|
65
59
|
read(1 << 20, nil, false)
|
66
60
|
end
|
67
61
|
|
68
|
-
def get_body_chunk(req,
|
62
|
+
def get_body_chunk(req, _buffered_only = false)
|
69
63
|
headers = req.headers
|
70
64
|
content_length = headers['content-length']
|
71
65
|
if content_length
|
@@ -110,7 +104,7 @@ module TP2
|
|
110
104
|
else
|
111
105
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
112
106
|
end
|
113
|
-
|
107
|
+
@opts[:logger]&.call(request, headers)
|
114
108
|
@done = true
|
115
109
|
@response_headers = headers
|
116
110
|
end
|
@@ -125,7 +119,6 @@ module TP2
|
|
125
119
|
def send_headers(request, headers, empty_response: false, chunked: true)
|
126
120
|
formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
|
127
121
|
request.tx_incr(formatted_headers.bytesize)
|
128
|
-
#
|
129
122
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
130
123
|
@response_headers = headers
|
131
124
|
end
|
@@ -148,10 +141,10 @@ module TP2
|
|
148
141
|
|
149
142
|
request.tx_incr(data.bytesize)
|
150
143
|
@machine.send(@fd, data, data.bytesize, SEND_FLAGS)
|
151
|
-
if done
|
152
|
-
|
153
|
-
|
154
|
-
|
144
|
+
return if @done || !done
|
145
|
+
|
146
|
+
@opts[:logger]&.call(request, @response_headers)
|
147
|
+
@done = true
|
155
148
|
end
|
156
149
|
|
157
150
|
# Finishes the response to the current request. If no headers were sent,
|
@@ -160,10 +153,10 @@ module TP2
|
|
160
153
|
def finish(request)
|
161
154
|
request.tx_incr(EMPTY_CHUNK_LEN)
|
162
155
|
@machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
|
163
|
-
if
|
164
|
-
|
165
|
-
|
166
|
-
|
156
|
+
return if @done
|
157
|
+
|
158
|
+
@opts[:logger]&.call(request, @response_headers)
|
159
|
+
@done = true
|
167
160
|
end
|
168
161
|
|
169
162
|
def respond_with_static_file(req, path, opts, cache_headers)
|
@@ -175,17 +168,17 @@ module TP2
|
|
175
168
|
opts[:headers] = cache_headers
|
176
169
|
end
|
177
170
|
|
178
|
-
maxlen = opts[:max_len] ||
|
171
|
+
maxlen = opts[:max_len] || 65_536
|
179
172
|
buf = String.new(capacity: maxlen)
|
180
173
|
headers_sent = nil
|
181
|
-
|
174
|
+
loop do
|
182
175
|
res = @machine.read(fd, buf, maxlen, 0)
|
183
176
|
if res < maxlen && !headers_sent
|
184
177
|
return respond(req, buf, opts[:headers])
|
185
178
|
elsif res == 0
|
186
179
|
return finish(req)
|
187
180
|
end
|
188
|
-
|
181
|
+
|
189
182
|
if !headers_sent
|
190
183
|
send_headers(req, opts[:headers])
|
191
184
|
headers_sent = true
|
@@ -204,34 +197,8 @@ module TP2
|
|
204
197
|
|
205
198
|
private
|
206
199
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
request_headers = request.headers
|
211
|
-
uri = full_uri(request_headers)
|
212
|
-
|
213
|
-
str = format(
|
214
|
-
"%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d",
|
215
|
-
client_ip: request.forwarded_for || '?',
|
216
|
-
method: request_headers[':method'].upcase,
|
217
|
-
uri: uri,
|
218
|
-
status: @status,
|
219
|
-
tx: request_headers[':tx']
|
220
|
-
)
|
221
|
-
@opts[:log].log(str)
|
222
|
-
end
|
223
|
-
|
224
|
-
def full_uri(headers)
|
225
|
-
format(
|
226
|
-
"%s://%s%s",
|
227
|
-
headers['x_forwarded_proto'] || 'http',
|
228
|
-
headers['host'],
|
229
|
-
headers[':path']
|
230
|
-
)
|
231
|
-
end
|
232
|
-
|
233
|
-
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
|
234
|
-
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
|
200
|
+
RE_REQUEST_LINE = %r{^([a-z]+)\s+([^\s]+)\s+(http/[0-9.]{1,3})}i
|
201
|
+
RE_HEADER_LINE = /^([a-z0-9-]+):\s+(.+)/i
|
235
202
|
MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
|
236
203
|
MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
|
237
204
|
MAX_CHUNK_SIZE_LEN = 16
|
@@ -241,11 +208,9 @@ module TP2
|
|
241
208
|
|
242
209
|
def persist_connection?(headers)
|
243
210
|
connection = headers['connection']&.downcase
|
244
|
-
if headers[':protocol'] == 'http/1.1'
|
245
|
-
|
246
|
-
|
247
|
-
return connection && connection != 'close'
|
248
|
-
end
|
211
|
+
return connection != 'close' if headers[':protocol'] == 'http/1.1'
|
212
|
+
|
213
|
+
connection && connection != 'close'
|
249
214
|
end
|
250
215
|
|
251
216
|
def parse_headers
|
@@ -253,7 +218,7 @@ module TP2
|
|
253
218
|
headers = get_request_line(buf)
|
254
219
|
return nil if !headers
|
255
220
|
|
256
|
-
|
221
|
+
loop do
|
257
222
|
line = @stream.get_line(buf, MAX_HEADER_LINE_LEN)
|
258
223
|
break if line.nil? || line.empty?
|
259
224
|
|
@@ -281,7 +246,7 @@ module TP2
|
|
281
246
|
end
|
282
247
|
|
283
248
|
def get_body_chunked_encoding(headers)
|
284
|
-
buf = String.new(capacity:
|
249
|
+
buf = String.new(capacity: 65_536)
|
285
250
|
while read_chunk(headers, buf)
|
286
251
|
end
|
287
252
|
|
@@ -290,9 +255,7 @@ module TP2
|
|
290
255
|
|
291
256
|
def read(len, buf = nil, raise_on_eof = true)
|
292
257
|
str = @stream.get_string(buf, len)
|
293
|
-
if !str && raise_on_eof
|
294
|
-
raise ProtocolError, 'Missing data'
|
295
|
-
end
|
258
|
+
raise ProtocolError, 'Missing data' if !str && raise_on_eof
|
296
259
|
|
297
260
|
str
|
298
261
|
end
|
@@ -319,7 +282,7 @@ module TP2
|
|
319
282
|
request.headers[':protocol'] == 'http/1.1'
|
320
283
|
end
|
321
284
|
|
322
|
-
INTERNAL_HEADER_REGEXP =
|
285
|
+
INTERNAL_HEADER_REGEXP = /^:/
|
323
286
|
|
324
287
|
# Formats response headers into an array. If empty_response is true(thy),
|
325
288
|
# the response status code will default to 204, otherwise to 200.
|
data/lib/tp2/logger.rb
CHANGED
@@ -2,15 +2,52 @@
|
|
2
2
|
|
3
3
|
module TP2
|
4
4
|
class Logger
|
5
|
-
def initialize(machine, fd =
|
5
|
+
def initialize(machine, fd = $stdout.fileno, **opts)
|
6
6
|
@machine = machine
|
7
7
|
@fd = fd
|
8
8
|
@opts = opts
|
9
9
|
end
|
10
10
|
|
11
11
|
def log(str)
|
12
|
-
str = format(
|
12
|
+
str = format(
|
13
|
+
"%<stamp>s %<msg>s\n",
|
14
|
+
stamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
|
15
|
+
msg: str
|
16
|
+
)
|
13
17
|
@machine.write_async(@fd, str)
|
14
18
|
end
|
19
|
+
|
20
|
+
def call(request, _response_headers)
|
21
|
+
if request.is_a?(Exception)
|
22
|
+
e = request
|
23
|
+
log("Error: #{e.inspect}: #{e.backtrace.inspect}")
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
log(format_request_log_line(request))
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_request_log_line(request)
|
31
|
+
request_headers = request.headers
|
32
|
+
uri = full_uri(request_headers)
|
33
|
+
|
34
|
+
format(
|
35
|
+
'%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d',
|
36
|
+
client_ip: request.forwarded_for || '?',
|
37
|
+
method: request_headers[':method'].upcase,
|
38
|
+
uri: uri,
|
39
|
+
status: @status,
|
40
|
+
tx: request_headers[':tx']
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def full_uri(headers)
|
45
|
+
format(
|
46
|
+
'%<scheme>s://%<host>s%<path>s',
|
47
|
+
scheme: headers['x_forwarded_proto'] || 'http',
|
48
|
+
host: headers['host'],
|
49
|
+
path: headers[':path']
|
50
|
+
)
|
51
|
+
end
|
15
52
|
end
|
16
53
|
end
|
data/lib/tp2/server.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'tp2/
|
3
|
+
require 'tp2/http1_connection'
|
4
4
|
require 'tp2/request_extensions'
|
5
5
|
require 'tp2/rack_adapter'
|
6
6
|
|
@@ -102,7 +102,7 @@ module TP2
|
|
102
102
|
|
103
103
|
def accept_incoming(listen_fd)
|
104
104
|
@machine.accept_each(listen_fd) do |fd|
|
105
|
-
conn =
|
105
|
+
conn = HTTP1Connection.new(@machine, fd, @opts, &@app)
|
106
106
|
f = @machine.spin(conn) do
|
107
107
|
it.run
|
108
108
|
ensure
|
data/lib/tp2/version.rb
CHANGED
data/lib/tp2.rb
CHANGED
@@ -22,7 +22,7 @@ module TP2
|
|
22
22
|
" / \\ https://github.com/noteflakes/tp2\n" +
|
23
23
|
"⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
|
24
24
|
)
|
25
|
-
|
25
|
+
|
26
26
|
class << self
|
27
27
|
def run(opts = nil, &app)
|
28
28
|
if @in_run
|
@@ -37,7 +37,7 @@ module TP2
|
|
37
37
|
machine = UM.new
|
38
38
|
|
39
39
|
machine.puts(TP2::BANNER) if opts[:banner]
|
40
|
-
opts[:
|
40
|
+
opts[:logger] = opts[:logger] && TP2::Logger.new(machine, **opts)
|
41
41
|
|
42
42
|
server = Server.new(machine, opts, &app)
|
43
43
|
|
@@ -50,7 +50,7 @@ module TP2
|
|
50
50
|
|
51
51
|
def config(opts = nil, &app)
|
52
52
|
return @config if !opts && !app
|
53
|
-
|
53
|
+
|
54
54
|
@config = opts || {}
|
55
55
|
@config[:app] = app if app
|
56
56
|
end
|
@@ -66,7 +66,7 @@ module TP2
|
|
66
66
|
# waits for signal from queue, then terminates given fiber
|
67
67
|
# to be done
|
68
68
|
def watch_for_int_signal(machine, queue, fiber)
|
69
|
-
|
69
|
+
machine.shift(queue)
|
70
70
|
machine.schedule(fiber, UM::Terminate.new)
|
71
71
|
end
|
72
72
|
end
|
data/test/helper.rb
CHANGED
data/test/test_http1_adapter.rb
CHANGED
@@ -2,13 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative './helper'
|
4
4
|
|
5
|
-
class
|
6
|
-
def crlf_lines
|
7
|
-
chomp.gsub("\n", "\r\n").chomp
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class HTTP1AdapterTest < Minitest::Test
|
5
|
+
class HTTP1ConnectionTest < Minitest::Test
|
12
6
|
def make_socket_pair
|
13
7
|
port = 10000 + rand(30000)
|
14
8
|
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
@@ -31,7 +25,7 @@ class HTTP1AdapterTest < Minitest::Test
|
|
31
25
|
@reqs = []
|
32
26
|
@hook = nil
|
33
27
|
@app = ->(req) { @hook&.call(req); @reqs << req }
|
34
|
-
@adapter = TP2::
|
28
|
+
@adapter = TP2::HTTP1Connection.new(@machine, @s_fd, {}, &@app)
|
35
29
|
end
|
36
30
|
|
37
31
|
def teardown
|
data/test/test_server.rb
CHANGED
@@ -25,19 +25,25 @@ class ServerTest < Minitest::Test
|
|
25
25
|
def setup
|
26
26
|
@machine = UM.new
|
27
27
|
@port = 10000 + rand(30000)
|
28
|
-
@
|
29
|
-
@
|
30
|
-
|
31
|
-
rescue STOP
|
32
|
-
ensure
|
33
|
-
@server_done = true
|
34
|
-
end
|
28
|
+
@opts = { bind: "127.0.0.1:#{@port}" }
|
29
|
+
@server = TP2::Server.new(@machine, @opts) { @app&.call(it) }
|
30
|
+
@f_server = @machine.spin { run_server }
|
35
31
|
|
32
|
+
# let server spin and listen to incoming connections
|
36
33
|
@machine.sleep(0.01)
|
34
|
+
|
37
35
|
@client_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
38
36
|
@machine.connect(@client_fd, '127.0.0.1', @port)
|
39
37
|
end
|
40
38
|
|
39
|
+
def run_server
|
40
|
+
@server.run
|
41
|
+
rescue STOP
|
42
|
+
# ignore
|
43
|
+
ensure
|
44
|
+
@server_done = true
|
45
|
+
end
|
46
|
+
|
41
47
|
def teardown
|
42
48
|
@machine.close(@client_fd) rescue nil
|
43
49
|
@machine.schedule(@f_server, STOP.new)
|
@@ -75,7 +81,7 @@ class ServerTest < Minitest::Test
|
|
75
81
|
req.respond("method: #{req.method}")
|
76
82
|
}
|
77
83
|
|
78
|
-
write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\
|
84
|
+
write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHmet /bar HTTP/1.1\r\n\r\n"
|
79
85
|
|
80
86
|
response = read_client_side
|
81
87
|
expected = "HTTP/1.1 200\r\nContent-Length: 11\r\n\r\nmethod: getHTTP/1.1 200\r\nContent-Length: 14\r\n\r\nmethod: schmet"
|
@@ -240,4 +246,17 @@ class ServerTest < Minitest::Test
|
|
240
246
|
body = @bodies.shift
|
241
247
|
assert_equal 'barbaz', body
|
242
248
|
end
|
249
|
+
|
250
|
+
def test_logging
|
251
|
+
reqs = []
|
252
|
+
@opts[:logger] = ->(req, _h) { reqs << req }
|
253
|
+
@app = ->(req) { req.respond('Hello, world!', {}) }
|
254
|
+
|
255
|
+
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
256
|
+
response = read_client_side
|
257
|
+
expected = "HTTP/1.1 200\r\nContent-Length: 13\r\n\r\nHello, world!"
|
258
|
+
assert_equal(expected, response)
|
259
|
+
|
260
|
+
assert_equal 1, reqs.size
|
261
|
+
end
|
243
262
|
end
|
data/tp2.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.required_ruby_version = '>= 3.4'
|
21
21
|
s.executables = ['tp2']
|
22
22
|
|
23
|
-
s.add_dependency 'uringmachine', '~> 0.
|
23
|
+
s.add_dependency 'uringmachine', '~> 0.15'
|
24
24
|
s.add_dependency 'qeweney', '~> 0.21'
|
25
25
|
s.add_dependency 'rack', '~> 3.1.15'
|
26
26
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tp2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.12'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - "~>"
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '0.
|
18
|
+
version: '0.15'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '0.
|
25
|
+
version: '0.15'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: qeweney
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -60,6 +60,7 @@ extra_rdoc_files:
|
|
60
60
|
files:
|
61
61
|
- ".github/workflows/test.yml"
|
62
62
|
- ".gitignore"
|
63
|
+
- ".rubocop.yml"
|
63
64
|
- CHANGELOG.md
|
64
65
|
- Gemfile
|
65
66
|
- README.md
|
@@ -70,7 +71,7 @@ files:
|
|
70
71
|
- examples/rack.ru
|
71
72
|
- examples/simple.rb
|
72
73
|
- lib/tp2.rb
|
73
|
-
- lib/tp2/
|
74
|
+
- lib/tp2/http1_connection.rb
|
74
75
|
- lib/tp2/logger.rb
|
75
76
|
- lib/tp2/rack_adapter.rb
|
76
77
|
- lib/tp2/request_extensions.rb
|