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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8eb4db3e433119acc5b10688d6a558de425f8f4b390bbd280055bd9429acde60
4
- data.tar.gz: 90f6dfc0ba6b99acb8b3d251845774f146641bbc922cd5b25658a7bf3fc97ef1
3
+ metadata.gz: b0fd31b18e0c98a8beb780933f7adef48e0e4403a765cc64f37ef02515d94723
4
+ data.tar.gz: 1cc72a408cd8fe9b4d1528bd7b1200f3e7a71098596f5b18812d2d75b6120936
5
5
  SHA512:
6
- metadata.gz: 80aa5c2464a8812b79dc6fa15c0230dde2bb839f66737b1fac03f8c7d51fbf1bc6ba3f7f1ad1bc6882f6ad8184ecb614b44dd43cdc67a403ae30dac541137579
7
- data.tar.gz: 8e77ba6c910898761ef63f435ad70e8bcd0f3f7ff14f5e86be2ea675c9387affa191a6ccc0c5f44565b6b8d1ea661606eb2e8a5c4cbb0ac4ed9119fa26828e27
6
+ metadata.gz: d4fb34dbec4798561322546a90f8104f3e91bd806923ba6d73e3267c06ab05499104431bf15f1abb0984d014626a062267305370470d80a7c61776a2f0c0bd8b
7
+ data.tar.gz: b0b7d9ec9e0536acef6f9675d592d7c48123bf591468326178b96ef673f194c44a43dcd57ed7f860d78acfe27f71192378f328b5531efff73731cd4b3875c3ee
@@ -1,3 +1,9 @@
1
+ ## 0.32 2020-08-14
2
+
3
+ * Respond with array of strings instead of concatenating for HTTP 1
4
+ * Use read_loop instead of readpartial
5
+ * Fix http upgrade test
6
+
1
7
  ## 0.31 2020-07-28
2
8
 
3
9
  * Fix websocket server code
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.31)
4
+ tipi (0.32)
5
5
  http-2 (~> 0.10.0)
6
6
  http_parser.rb (~> 0.6.0)
7
- polyphony (~> 0.44)
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.44.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)
@@ -26,8 +26,7 @@ class WebsocketClient
26
26
  end
27
27
 
28
28
  def receive
29
- loop do
30
- data = @socket.readpartial(8192)
29
+ @socket.read_loop do |data|
31
30
  @reader << data
32
31
  parsed = @reader.next
33
32
  return parsed if parsed
@@ -30,8 +30,10 @@ class WebsocketClient
30
30
  end
31
31
 
32
32
  def receive
33
- loop do
34
- data = @socket.readpartial(8192)
33
+ parsed = @reader.next
34
+ return parsed if parsed
35
+
36
+ @socket.read_loop do |data|
35
37
  @reader << data
36
38
  parsed = @reader.next
37
39
  return parsed if parsed
@@ -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
@@ -65,14 +65,9 @@ module Tipi
65
65
  # callback
66
66
  def consume_request
67
67
  request = @requests_head
68
- loop do
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 = format_headers(headers, body)
167
+ data = collect_headers(headers, body)
170
168
  if body
171
- data << if @parser.http_minor == 0
172
- body
169
+ if @parser.http_minor == 0
170
+ data << body
173
171
  else
174
- "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
172
+ data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
175
173
  end
176
174
  end
177
- @conn << data
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
- @conn << format_headers(headers, !opts[:empty_response])
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 = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
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 << data
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), the response
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 format_headers(headers, body)
224
+ def collect_headers(headers, body)
225
225
  status = headers[':status'] || (body ? 200 : 204)
226
- data = format_status_line(body, status)
227
-
226
+ lines = [format_status_line(body, status)]
228
227
  headers.each do |k, v|
229
228
  next if k =~ /^:/
230
229
 
231
- data << format_header_lines(k, v)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tipi
4
- VERSION = '0.31'
4
+ VERSION = '0.32'
5
5
  end
@@ -37,15 +37,18 @@ module Tipi
37
37
  end
38
38
 
39
39
  def recv
40
- loop do
41
- data = @client.readpartial(8192)
42
- break nil unless data
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)
@@ -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.44'
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.31'
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-07-28 00:00:00.000000000 Z
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.44'
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.44'
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