tipi 0.31 → 0.32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|