tipi 0.31 → 0.32
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 +6 -0
- data/Gemfile.lock +3 -3
- data/examples/websocket_client.rb +1 -2
- data/examples/websocket_demo.rb +4 -2
- data/lib/tipi/config_dsl.rb +153 -0
- data/lib/tipi/http1_adapter.rb +32 -32
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +7 -4
- data/tipi.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: b0fd31b18e0c98a8beb780933f7adef48e0e4403a765cc64f37ef02515d94723
|
4
|
+
data.tar.gz: 1cc72a408cd8fe9b4d1528bd7b1200f3e7a71098596f5b18812d2d75b6120936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4fb34dbec4798561322546a90f8104f3e91bd806923ba6d73e3267c06ab05499104431bf15f1abb0984d014626a062267305370470d80a7c61776a2f0c0bd8b
|
7
|
+
data.tar.gz: b0b7d9ec9e0536acef6f9675d592d7c48123bf591468326178b96ef673f194c44a43dcd57ed7f860d78acfe27f71192378f328b5531efff73731cd4b3875c3ee
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tipi (0.
|
4
|
+
tipi (0.32)
|
5
5
|
http-2 (~> 0.10.0)
|
6
6
|
http_parser.rb (~> 0.6.0)
|
7
|
-
polyphony (~> 0.
|
7
|
+
polyphony (~> 0.45)
|
8
8
|
rack (>= 2.0.8, < 2.3.0)
|
9
9
|
websocket (~> 1.2.8)
|
10
10
|
|
@@ -24,7 +24,7 @@ GEM
|
|
24
24
|
builder
|
25
25
|
minitest (>= 5.0)
|
26
26
|
ruby-progressbar
|
27
|
-
polyphony (0.
|
27
|
+
polyphony (0.45.2)
|
28
28
|
rack (2.2.3)
|
29
29
|
rake (12.3.3)
|
30
30
|
ruby-progressbar (1.10.1)
|
data/examples/websocket_demo.rb
CHANGED
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tipi
|
4
|
+
module Configuration
|
5
|
+
class Interpreter
|
6
|
+
# make_blank_slate
|
7
|
+
|
8
|
+
def initialize(assembler)
|
9
|
+
@assembler = assembler
|
10
|
+
end
|
11
|
+
|
12
|
+
def gzip_response
|
13
|
+
@assembler.emit 'req = Tipi::GZip.wrap(req)'
|
14
|
+
end
|
15
|
+
|
16
|
+
def log(out)
|
17
|
+
@assembler.wrap_current_frame 'logger.log_request(req) do |req|'
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(&block)
|
21
|
+
assembler.emit_exception_handler &block
|
22
|
+
end
|
23
|
+
|
24
|
+
def match(pattern, &block)
|
25
|
+
@assembler.emit_conditional "if req.path =~ #{pattern.inspect}", &block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Assembler
|
30
|
+
def self.from_source(code)
|
31
|
+
new.from_source code
|
32
|
+
end
|
33
|
+
|
34
|
+
def from_source(code)
|
35
|
+
@stack = [new_frame]
|
36
|
+
@app_procs = {}
|
37
|
+
@interpreter = Interpreter.new self
|
38
|
+
@interpreter.instance_eval code
|
39
|
+
|
40
|
+
loop do
|
41
|
+
frame = @stack.pop
|
42
|
+
return assemble_app_proc(frame).join("\n") if @stack.empty?
|
43
|
+
|
44
|
+
@stack.last[:body] << assemble_frame(frame)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def new_frame
|
49
|
+
{
|
50
|
+
prelude: [],
|
51
|
+
body: []
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_frame(&block)
|
56
|
+
@stack.push new_frame
|
57
|
+
yield
|
58
|
+
ensure
|
59
|
+
frame = @stack.pop
|
60
|
+
emit assemble(frame)
|
61
|
+
end
|
62
|
+
|
63
|
+
def wrap_current_frame(head)
|
64
|
+
frame = @stack.pop
|
65
|
+
wrapper = new_frame
|
66
|
+
wrapper[:body] << head
|
67
|
+
@stack.push wrapper
|
68
|
+
@stack.push frame
|
69
|
+
end
|
70
|
+
|
71
|
+
def emit(code)
|
72
|
+
@stack.last[:body] << code
|
73
|
+
end
|
74
|
+
|
75
|
+
def emit_prelude(code)
|
76
|
+
@stack.last[:prelude] << code
|
77
|
+
end
|
78
|
+
|
79
|
+
def emit_exception_handler(&block)
|
80
|
+
proc_id = add_app_proc block
|
81
|
+
@stack.last[:rescue_proc_id] = proc_id
|
82
|
+
end
|
83
|
+
|
84
|
+
def emit_block(conditional, &block)
|
85
|
+
proc_id = add_app_proc block
|
86
|
+
@stack.last[:branched] = true
|
87
|
+
emit conditional
|
88
|
+
add_frame &block
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_app_proc(proc)
|
92
|
+
id = :"proc#{@app_procs.size}"
|
93
|
+
@app_procs[id] = proc
|
94
|
+
id
|
95
|
+
end
|
96
|
+
|
97
|
+
def assemble_frame(frame)
|
98
|
+
indent = 0
|
99
|
+
lines = []
|
100
|
+
emit_code lines, frame[:prelude], indent
|
101
|
+
if frame[:rescue_proc_id]
|
102
|
+
emit_code lines, 'begin', indent
|
103
|
+
indent += 1
|
104
|
+
end
|
105
|
+
emit_code lines, frame[:body], indent
|
106
|
+
if frame[:rescue_proc_id]
|
107
|
+
emit_code lines, 'rescue => e', indent
|
108
|
+
emit_code lines, " app_procs[#{frame[:rescue_proc_id].inspect}].call(req, e)", indent
|
109
|
+
emit_code lines, 'end', indent
|
110
|
+
indent -= 1
|
111
|
+
end
|
112
|
+
lines
|
113
|
+
end
|
114
|
+
|
115
|
+
def assemble_app_proc(frame)
|
116
|
+
indent = 0
|
117
|
+
lines = []
|
118
|
+
emit_code lines, frame[:prelude], indent
|
119
|
+
emit_code lines, 'proc do |req|', indent
|
120
|
+
emit_code lines, frame[:body], indent + 1
|
121
|
+
emit_code lines, 'end', indent
|
122
|
+
|
123
|
+
lines
|
124
|
+
end
|
125
|
+
|
126
|
+
def emit_code(lines, code, indent)
|
127
|
+
if code.is_a? Array
|
128
|
+
code.each { |l| emit_code lines, l, indent + 1 }
|
129
|
+
else
|
130
|
+
lines << (indent_line code, indent)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
@@indents = Hash.new { |h, k| h[k] = ' ' * k }
|
135
|
+
|
136
|
+
def indent_line(code, indent)
|
137
|
+
indent == 0 ? code : "#{ @@indents[indent] }#{code}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def assemble(code)
|
145
|
+
Tipi::Configuration::Assembler.from_source(code)
|
146
|
+
end
|
147
|
+
|
148
|
+
code = assemble <<~RUBY
|
149
|
+
gzip_response
|
150
|
+
log STDOUT
|
151
|
+
RUBY
|
152
|
+
|
153
|
+
puts code
|
data/lib/tipi/http1_adapter.rb
CHANGED
@@ -65,14 +65,9 @@ module Tipi
|
|
65
65
|
# callback
|
66
66
|
def consume_request
|
67
67
|
request = @requests_head
|
68
|
-
|
69
|
-
data = @conn.readpartial(8192)
|
68
|
+
@conn.read_loop do |data|
|
70
69
|
@parser << data
|
71
70
|
return if request.complete?
|
72
|
-
|
73
|
-
snooze
|
74
|
-
rescue EOFError
|
75
|
-
break
|
76
71
|
end
|
77
72
|
end
|
78
73
|
|
@@ -159,24 +154,27 @@ module Tipi
|
|
159
154
|
end
|
160
155
|
|
161
156
|
# response API
|
162
|
-
|
157
|
+
|
158
|
+
CRLF = "\r\n"
|
159
|
+
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
160
|
+
|
163
161
|
# Sends response including headers and body. Waits for the request to complete
|
164
162
|
# if not yet completed. The body is sent using chunked transfer encoding.
|
165
163
|
# @param body [String] response body
|
166
164
|
# @param headers
|
167
165
|
def respond(body, headers)
|
168
166
|
consume_request if @parsing
|
169
|
-
data =
|
167
|
+
data = collect_headers(headers, body)
|
170
168
|
if body
|
171
|
-
|
172
|
-
body
|
169
|
+
if @parser.http_minor == 0
|
170
|
+
data << body
|
173
171
|
else
|
174
|
-
|
172
|
+
data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
|
175
173
|
end
|
176
174
|
end
|
177
|
-
@conn
|
175
|
+
@conn.write(*data)
|
178
176
|
end
|
179
|
-
|
177
|
+
|
180
178
|
DEFAULT_HEADERS_OPTS = {
|
181
179
|
empty_response: false,
|
182
180
|
consume_request: true
|
@@ -188,7 +186,8 @@ module Tipi
|
|
188
186
|
# @param empty_response [boolean] whether a response body will be sent
|
189
187
|
# @return [void]
|
190
188
|
def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
|
191
|
-
|
189
|
+
data = collect_headers(headers, true)
|
190
|
+
@conn.write(*data)
|
192
191
|
end
|
193
192
|
|
194
193
|
# Sends a response body chunk. If no headers were sent, default headers are
|
@@ -198,9 +197,10 @@ module Tipi
|
|
198
197
|
# @param done [boolean] whether the response is completed
|
199
198
|
# @return [void]
|
200
199
|
def send_chunk(chunk, done: false)
|
201
|
-
data =
|
200
|
+
data = []
|
201
|
+
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
202
202
|
data << "0\r\n\r\n" if done
|
203
|
-
@conn
|
203
|
+
@conn.write(*data)
|
204
204
|
end
|
205
205
|
|
206
206
|
# Finishes the response to the current request. If no headers were sent,
|
@@ -215,30 +215,22 @@ module Tipi
|
|
215
215
|
end
|
216
216
|
|
217
217
|
private
|
218
|
-
|
219
|
-
# Formats response headers. If empty_response is true(thy),
|
220
|
-
# status code will default to 204, otherwise to 200.
|
218
|
+
|
219
|
+
# Formats response headers into an array. If empty_response is true(thy),
|
220
|
+
# the response status code will default to 204, otherwise to 200.
|
221
221
|
# @param headers [Hash] response headers
|
222
222
|
# @param empty_response [boolean] whether a response body will be sent
|
223
223
|
# @return [String] formatted response headers
|
224
|
-
def
|
224
|
+
def collect_headers(headers, body)
|
225
225
|
status = headers[':status'] || (body ? 200 : 204)
|
226
|
-
|
227
|
-
|
226
|
+
lines = [format_status_line(body, status)]
|
228
227
|
headers.each do |k, v|
|
229
228
|
next if k =~ /^:/
|
230
229
|
|
231
|
-
|
232
|
-
end
|
233
|
-
data << "\r\n"
|
234
|
-
end
|
235
|
-
|
236
|
-
def format_header_lines(key, value)
|
237
|
-
if value.is_a?(Array)
|
238
|
-
value.inject(+'') { |data, item| data << "#{key}: #{item}\r\n" }
|
239
|
-
else
|
240
|
-
"#{key}: #{value}\r\n"
|
230
|
+
collect_header_lines(lines, k, v)
|
241
231
|
end
|
232
|
+
lines << CRLF
|
233
|
+
lines
|
242
234
|
end
|
243
235
|
|
244
236
|
def format_status_line(body, status)
|
@@ -264,5 +256,13 @@ module Tipi
|
|
264
256
|
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
265
257
|
end
|
266
258
|
end
|
259
|
+
|
260
|
+
def collect_header_lines(lines, key, value)
|
261
|
+
if value.is_a?(Array)
|
262
|
+
value.inject(lines) { |lines, item| data << "#{key}: #{item}\r\n" }
|
263
|
+
else
|
264
|
+
lines << "#{key}: #{value}\r\n"
|
265
|
+
end
|
266
|
+
end
|
267
267
|
end
|
268
268
|
end
|
data/lib/tipi/version.rb
CHANGED
data/lib/tipi/websocket.rb
CHANGED
@@ -37,15 +37,18 @@ module Tipi
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def recv
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
if (msg = @reader.next)
|
41
|
+
return msg.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
@client.read_loop do |data|
|
44
45
|
@reader << data
|
45
46
|
if (msg = @reader.next)
|
46
47
|
break msg.to_s
|
47
48
|
end
|
48
49
|
end
|
50
|
+
|
51
|
+
nil
|
49
52
|
end
|
50
53
|
|
51
54
|
def send(data)
|
data/tipi.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
|
20
20
|
s.executables = ['tipi']
|
21
21
|
|
22
|
-
s.add_runtime_dependency 'polyphony', '~>0.
|
22
|
+
s.add_runtime_dependency 'polyphony', '~>0.45'
|
23
23
|
|
24
24
|
s.add_runtime_dependency 'http_parser.rb', '~>0.6.0'
|
25
25
|
s.add_runtime_dependency 'http-2', '~>0.10.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tipi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.32'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: polyphony
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.45'
|
20
20
|
type: :runtime
|
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: '0.
|
26
|
+
version: '0.45'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: http_parser.rb
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -200,6 +200,7 @@ files:
|
|
200
200
|
- examples/ws_page.html
|
201
201
|
- examples/wss_page.html
|
202
202
|
- lib/tipi.rb
|
203
|
+
- lib/tipi/config_dsl.rb
|
203
204
|
- lib/tipi/configuration.rb
|
204
205
|
- lib/tipi/handler.rb
|
205
206
|
- lib/tipi/http1_adapter.rb
|