tipi 0.31 → 0.32

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