tipi 0.35 → 0.38

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: '0381d5d135a88937862fe5e22d3f39d2dd6a9353af83a655c13c641af3dd0ec2'
4
- data.tar.gz: 17e20767ca22950023d44135fb2e20fdc469898fd2298e32158d791a111730cd
3
+ metadata.gz: 6a8c075ce0b769f014a20587dd061f17e32361f211aad5741f655dafe6687e37
4
+ data.tar.gz: d14c0fe026a7db1aa46edded54e5ee6f1a0f8598f626dd1aff342ad07b5cec39
5
5
  SHA512:
6
- metadata.gz: e15c3197e3ca5dff97071f34c6bb18e006bd338b0c182ae046af8cf9b0d22f7451f0230f2430c65400bc81999e1c269d5868db8bce0cec1843453cf00ae883aa
7
- data.tar.gz: a486df80bd50ff8069bc0abe1bca9df13e58ad8c0d84b7e24a86b284fb6fcfc4c679824692049fc27c1435d6c0598a45a6461a8b48fe4540722061399bf7c5e3
6
+ metadata.gz: e306783fc83d9d7ebd0678c4d68a13f6f9bd77e4b9d83fc84d745e5cc3f53eb27b69286fb1ba9f1ca54fe57358009a4c11dc9374c8a8d9046dc4330e359dc988
7
+ data.tar.gz: 789c3b4e1374cc57e04e3bce4aa8d12022cfd7e31c9cba4f5777302f57943da0a967f17f8cb2f91551357deac1bbd07fe514c2cb2e002331f525d341c6b7f0e9
data/CHANGELOG.md CHANGED
@@ -1,64 +1,80 @@
1
+ ## 0.38 2021-03-09
2
+
3
+ - Don't use chunked transfer encoding for non-streaming responses
4
+
5
+ ## 0.37.2 2021-03-08
6
+
7
+ - Fix header formatting when header value is an array
8
+
9
+ ## 0.37 2021-02-15
10
+
11
+ - Update upgrade mechanism to work with updated Qeweney API
12
+
13
+ ## 0.36 2021-02-12
14
+
15
+ - Use `Qeweney::Status` constants
16
+
1
17
  ## 0.35 2021-02-10
2
18
 
3
- * Extract Request class into separate [qna](https://github.com/digital-fabric/qna) gem
19
+ - Extract Request class into separate [qeweney](https://github.com/digital-fabric/qeweney) gem
4
20
 
5
21
  ## 0.34 2021-02-07
6
22
 
7
- * Implement digital fabric service and agents
8
- * Add multipart and urlencoded form data parsing
9
- * Improve request body reading behaviour
10
- * Add more `Request` information methods
11
- * Add access to connection for HTTP2 requests
12
- * Allow calling `Request#send_chunk` with empty chunk
13
- * Add support for handling protocol upgrades from within request handler
23
+ - Implement digital fabric service and agents
24
+ - Add multipart and urlencoded form data parsing
25
+ - Improve request body reading behaviour
26
+ - Add more `Request` information methods
27
+ - Add access to connection for HTTP2 requests
28
+ - Allow calling `Request#send_chunk` with empty chunk
29
+ - Add support for handling protocol upgrades from within request handler
14
30
 
15
31
  ## 0.33 2020-11-20
16
32
 
17
- * Update code for Polyphony 0.47.5
18
- * Add support for Rack::File body to Tipi::RackAdapter
33
+ - Update code for Polyphony 0.47.5
34
+ - Add support for Rack::File body to Tipi::RackAdapter
19
35
 
20
36
  ## 0.32 2020-08-14
21
37
 
22
- * Respond with array of strings instead of concatenating for HTTP 1
23
- * Use read_loop instead of readpartial
24
- * Fix http upgrade test
38
+ - Respond with array of strings instead of concatenating for HTTP 1
39
+ - Use read_loop instead of readpartial
40
+ - Fix http upgrade test
25
41
 
26
42
  ## 0.31 2020-07-28
27
43
 
28
- * Fix websocket server code
29
- * Implement configuration layer (WIP)
30
- * Improve performance of rack adapter
44
+ - Fix websocket server code
45
+ - Implement configuration layer (WIP)
46
+ - Improve performance of rack adapter
31
47
 
32
48
  ## 0.30 2020-07-15
33
49
 
34
- * Rename project to Tipi
35
- * Rearrange source code
36
- * Remove HTTP client code (to be developed eventually into a separate gem)
37
- * Fix header rendering in rack adapter (#2)
50
+ - Rename project to Tipi
51
+ - Rearrange source code
52
+ - Remove HTTP client code (to be developed eventually into a separate gem)
53
+ - Fix header rendering in rack adapter (#2)
38
54
 
39
55
  ## 0.29 2020-07-06
40
56
 
41
- * Use IO#read_loop
57
+ - Use IO#read_loop
42
58
 
43
59
  ## 0.28 2020-07-03
44
60
 
45
- * Update with API changes from Polyphony >= 0.41
61
+ - Update with API changes from Polyphony >= 0.41
46
62
 
47
63
  ## 0.27 2020-04-14
48
64
 
49
- * Remove modulation dependency
65
+ - Remove modulation dependency
50
66
 
51
67
  ## 0.26 2020-03-03
52
68
 
53
- * Fix `Server#listen`
69
+ - Fix `Server#listen`
54
70
 
55
71
  ## 0.25 2020-02-19
56
72
 
57
- * Ensure server socket is closed upon stopping loop
58
- * Fix `Request#format_header_lines`
73
+ - Ensure server socket is closed upon stopping loop
74
+ - Fix `Request#format_header_lines`
59
75
 
60
76
  ## 0.24 2020-01-08
61
77
 
62
- * Move HTTP to separate polyphony-http gem
78
+ - Move HTTP to separate polyphony-http gem
63
79
 
64
80
  For earlier changes look at the Polyphony changelog.
data/Gemfile.lock CHANGED
@@ -1,12 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.35)
4
+ tipi (0.38)
5
5
  http-2 (~> 0.10.0)
6
6
  http_parser.rb (~> 0.6.0)
7
7
  msgpack (~> 1.4.2)
8
- polyphony (~> 0.51.0)
9
- qna (~> 0.1)
8
+ polyphony (~> 0.52.0)
9
+ qeweney (~> 0.6)
10
10
  rack (>= 2.0.8, < 2.3.0)
11
11
  websocket (~> 1.2.8)
12
12
 
@@ -28,8 +28,8 @@ GEM
28
28
  minitest (>= 5.0)
29
29
  ruby-progressbar
30
30
  msgpack (1.4.2)
31
- polyphony (0.51.0)
32
- qna (0.1)
31
+ polyphony (0.52.0)
32
+ qeweney (0.7.5)
33
33
  escape_utils (~> 1.2.1)
34
34
  rack (2.2.3)
35
35
  rake (12.3.3)
data/TODO.md CHANGED
@@ -1,8 +1,6 @@
1
- # Digital Fabric
1
+ For immediate execution:
2
2
 
3
- Problems to fix:
4
3
 
5
- - Memory leak (in server? multi agent? multi client?)
6
4
 
7
5
  # Roadmap
8
6
 
@@ -14,7 +12,7 @@ Problems to fix:
14
12
  - https://gitlab.com/honeyryderchuck/http-2-next
15
13
  - Open an issue there, ask what's the difference between the two gems?
16
14
 
17
- ## 0.35
15
+ ## 0.38
18
16
 
19
17
  - Add more poly CLI commands and options:
20
18
 
@@ -26,7 +24,7 @@ Problems to fix:
26
24
  - set port to bind to
27
25
  - set forking process count
28
26
 
29
- ## 0.36 Working Sinatra application
27
+ ## 0.39 Working Sinatra application
30
28
 
31
29
  - app with database access (postgresql)
32
30
  - benchmarks!
data/e ADDED
File without changes
@@ -27,7 +27,8 @@ puts 'Listening on port 4411...'
27
27
 
28
28
  Tipi.serve('0.0.0.0', 4411, opts) do |req|
29
29
  if req.upgrade_protocol == 'websocket'
30
- ws_handler(Tipi::Websocket.new(req.adapter.conn, req.headers))
30
+ conn = req.upgrade_to_websocket
31
+ ws_handler(conn)
31
32
  else
32
33
  req.respond(HTML, 'Content-Type' => 'text/html')
33
34
  end
@@ -13,9 +13,17 @@ puts 'Listening on port 4411...'
13
13
 
14
14
  spin do
15
15
  Tipi.serve('0.0.0.0', 4411, opts) do |req|
16
- req.respond("Hello world!\n")
17
- rescue Exception => e
18
- p e
16
+ p path: req.path
17
+ if req.path == '/stream'
18
+ req.send_headers('Foo' => 'Bar')
19
+ sleep 1
20
+ req.send_chunk("foo\n")
21
+ sleep 1
22
+ req.send_chunk("bar\n")
23
+ req.finish
24
+ else
25
+ req.respond("Hello world!\n")
26
+ end
19
27
  end
20
28
  p 'done...'
21
29
  end.await
@@ -16,7 +16,17 @@ opts = {
16
16
  puts "pid: #{Process.pid}"
17
17
  puts 'Listening on port 1234...'
18
18
  Tipi.serve('0.0.0.0', 1234, opts) do |req|
19
- req.respond("Hello world!\n")
19
+ p path: req.path
20
+ if req.path == '/stream'
21
+ req.send_headers('Foo' => 'Bar')
22
+ sleep 1
23
+ req.send_chunk("foo\n")
24
+ sleep 1
25
+ req.send_chunk("bar\n")
26
+ req.finish
27
+ else
28
+ req.respond("Hello world!\n")
29
+ end
20
30
  # req.send_headers
21
31
  # req.send_chunk("Method: #{req.method}\n")
22
32
  # req.send_chunk("Path: #{req.path}\n")
@@ -171,7 +171,7 @@ module DigitalFabric
171
171
  rescue IOError, Errno::ECONNREFUSED, Errno::EPIPE
172
172
  # ignore
173
173
  rescue Polyphony::Terminate
174
- req.respond(nil, { ':status' => 503 }) if Fiber.current.graceful_shutdown?
174
+ req.respond(nil, { ':status' => Qeweney::Status::SERVICE_UNAVAILABLE }) if Fiber.current.graceful_shutdown?
175
175
  ensure
176
176
  @requests.delete(id)
177
177
  @long_running_requests.delete(id)
@@ -180,7 +180,7 @@ module DigitalFabric
180
180
  end
181
181
 
182
182
  def prepare_http_request(msg)
183
- req = QNA::Request.new(msg['headers'], RequestAdapter.new(self, msg))
183
+ req = Qeweney::Request.new(msg['headers'], RequestAdapter.new(self, msg))
184
184
  req.buffer_body_chunk(msg['body']) if msg['body']
185
185
  req.complete! if msg['complete']
186
186
  req
@@ -199,7 +199,7 @@ module DigitalFabric
199
199
  end
200
200
 
201
201
  def recv_ws_request(msg)
202
- req = QNA::Request.new(msg['headers'], RequestAdapter.new(self, msg))
202
+ req = Qeweney::Request.new(msg['headers'], RequestAdapter.new(self, msg))
203
203
  id = msg['id']
204
204
  @requests[id] = @long_running_requests[id] = spin do
205
205
  ws_request(req)
@@ -214,12 +214,12 @@ module DigitalFabric
214
214
 
215
215
  # default handler for HTTP request
216
216
  def http_request(req)
217
- req.respond(nil, { ':status': 501 })
217
+ req.respond(nil, { ':status': Qeweney::Status::SERVICE_UNAVAILABLE })
218
218
  end
219
219
 
220
220
  # default handler for WS request
221
221
  def ws_request(req)
222
- req.respond(nil, { ':status': 501 })
222
+ req.respond(nil, { ':status': Qeweney::Status::SERVICE_UNAVAILABLE })
223
223
  end
224
224
  end
225
225
  end
@@ -134,7 +134,7 @@ module DigitalFabric
134
134
  end
135
135
  end
136
136
  rescue => e
137
- req.respond("Error: #{e.inspect}", ':status' => 500)
137
+ req.respond("Error: #{e.inspect}", ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
138
138
  end
139
139
 
140
140
  # @return [Boolean] true if response is complete
@@ -150,16 +150,21 @@ module DigitalFabric
150
150
  headers = message['headers']
151
151
  body = message['body']
152
152
  done = message['complete']
153
- req.send_headers(headers) if headers && !req.headers_sent?
154
- req.send_chunk(body, done: done) if body or done
155
- done
153
+ if !req.headers_sent? && done
154
+ req.respond(body, headers|| {})
155
+ true
156
+ else
157
+ req.send_headers(headers) if headers && !req.headers_sent?
158
+ req.send_chunk(body, done: done) if body or done
159
+ done
160
+ end
156
161
  else
157
162
  # invalid message
158
163
  true
159
164
  end
160
165
  end
161
166
 
162
- HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => '101 Switching Protocols' }
167
+ HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => Qeweney::Status::SWITCHING_PROTOCOLS }
163
168
 
164
169
  def http_custom_upgrade(id, req, message)
165
170
  # send upgrade response
@@ -228,15 +233,15 @@ module DigitalFabric
228
233
  case response['kind']
229
234
  when Protocol::WS_RESPONSE
230
235
  headers = response['headers'] || {}
231
- status = headers[':status'] || 101
232
- if status != 101
236
+ status = headers[':status'] || Qeweney::Status::SWITCHING_PROTOCOLS
237
+ if status != Qeweney::Status::SWITCHING_PROTOCOLS
233
238
  req.respond(nil, headers)
234
239
  return
235
240
  end
236
241
  ws = Tipi::Websocket.new(req.adapter.conn, req.headers)
237
242
  run_websocket_connection(id, ws)
238
243
  else
239
- req.respond(nil, ':status' => 503)
244
+ req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
240
245
  end
241
246
  end
242
247
  end
@@ -34,7 +34,7 @@ module DigitalFabric
34
34
  when '/stream/stats'
35
35
  stream_stats(req)
36
36
  else
37
- req.respond('Invalid path', { ':status' => 404 })
37
+ req.respond('Invalid path', { ':status' => Qeweney::Status::NOT_FOUND })
38
38
  end
39
39
  ensure
40
40
  @current_request_count -= 1
@@ -95,7 +95,7 @@ module DigitalFabric
95
95
  return req.respond('pong') if req.query[:q] == 'ping'
96
96
 
97
97
  @counters[:errors] += 1
98
- return req.respond(nil, ':status' => 503)
98
+ return req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
99
99
  end
100
100
 
101
101
  agent.http_request(req)
@@ -105,7 +105,7 @@ module DigitalFabric
105
105
  @counters[:errors] += 1
106
106
  p e
107
107
  puts e.backtrace.join("\n")
108
- req.respond(e.inspect, ':status' => 500)
108
+ req.respond(e.inspect, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
109
109
  ensure
110
110
  @current_request_count -= 1
111
111
  req.adapter.conn.close if @shutdown
@@ -115,7 +115,7 @@ module DigitalFabric
115
115
  req.headers['x-request-id'] = SecureRandom.uuid
116
116
  conn = req.adapter.conn
117
117
  req.headers['x-forwarded-for'] = conn.peeraddr(false)[2]
118
- req.headers['x-forwarded-proto'] = conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
118
+ req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
119
119
  end
120
120
 
121
121
  def upgrade_request(req)
@@ -126,7 +126,7 @@ module DigitalFabric
126
126
  agent = find_agent(req)
127
127
  unless agent
128
128
  @counters[:errors] += 1
129
- return req.respond(nil, ':status' => 503)
129
+ return req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
130
130
  end
131
131
 
132
132
  agent.http_upgrade(req, protocol)
@@ -134,7 +134,9 @@ module DigitalFabric
134
134
  end
135
135
 
136
136
  def df_upgrade(req)
137
- return req.respond(nil, ':status' => 403) if req.headers['df-token'] != @token
137
+ if req.headers['df-token'] != @token
138
+ return req.respond(nil, ':status' => Qeweney::Status::FORBIDDEN)
139
+ end
138
140
 
139
141
  req.adapter.conn << Protocol.df_upgrade_response
140
142
  AgentProxy.new(self, req)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'http/parser'
4
4
  require_relative './http2_adapter'
5
- require 'qna/request'
5
+ require 'qeweney/request'
6
6
 
7
7
  module Tipi
8
8
  # HTTP1 protocol implementation
@@ -87,7 +87,10 @@ module Tipi
87
87
  headers = normalize_headers(headers)
88
88
  headers[':path'] = @parser.request_url
89
89
  headers[':method'] = @parser.http_method.downcase
90
- queue_request(QNA::Request.new(headers, self))
90
+ scheme = (proto = headers['x-forwarded-proto']) ?
91
+ proto.downcase : scheme_from_connection
92
+ headers[':scheme'] = scheme
93
+ queue_request(Qeweney::Request.new(headers, self))
91
94
  end
92
95
 
93
96
  def normalize_headers(headers)
@@ -131,6 +134,14 @@ module Tipi
131
134
  # protocols, notably WebSocket, can be specified by passing a hash to the
132
135
  # :upgrade option when starting a server:
133
136
  #
137
+ # def ws_handler(conn)
138
+ # conn << 'hi'
139
+ # msg = conn.recv
140
+ # conn << "You said #{msg}"
141
+ # conn << 'bye'
142
+ # conn.close
143
+ # end
144
+ #
134
145
  # opts = {
135
146
  # upgrade: {
136
147
  # websocket: Tipi::Websocket.handler(&method(:ws_handler))
@@ -154,7 +165,7 @@ module Tipi
154
165
 
155
166
  def upgrade_with_handler(handler, headers)
156
167
  @parser = @requests_head = @requests_tail = nil
157
- handler.(@conn, headers)
168
+ handler.(self, headers)
158
169
  true
159
170
  end
160
171
 
@@ -173,6 +184,14 @@ module Tipi
173
184
  ':authority' => headers['host']
174
185
  )
175
186
  end
187
+
188
+ def websocket_connection(req)
189
+ Tipi::Websocket.new(@conn, req.headers)
190
+ end
191
+
192
+ def scheme_from_connection
193
+ @conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
194
+ end
176
195
 
177
196
  # response API
178
197
 
@@ -185,30 +204,29 @@ module Tipi
185
204
  # @param headers
186
205
  def respond(body, headers)
187
206
  consume_request if @parsing
188
- data = format_headers(headers, body)
189
- if body
190
- if @parser.http_minor == 0
191
- data << body
192
- else
193
- data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
194
- end
195
- end
196
- @conn.write(data.join)
207
+ data = [format_headers(headers, body, false), body]
208
+
209
+ # if body
210
+ # if @parser.http_minor == 0
211
+ # data << body
212
+ # else
213
+ # # data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
214
+ # data << "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
215
+ # end
216
+ # end
217
+ # Polyphony.backend_sendv(@conn, data, 0)
218
+ @conn.write(*data)
197
219
  end
198
220
 
199
- DEFAULT_HEADERS_OPTS = {
200
- empty_response: false,
201
- consume_request: true
202
- }.freeze
203
-
204
221
  # Sends response headers. If empty_response is truthy, the response status
205
222
  # code will default to 204, otherwise to 200.
206
223
  # @param headers [Hash] response headers
207
224
  # @param empty_response [boolean] whether a response body will be sent
225
+ # @param chunked [boolean] whether to use chunked transfer encoding
208
226
  # @return [void]
209
- def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
210
- data = format_headers(headers, true)
211
- @conn.write(data.join)
227
+ def send_headers(headers, empty_response: false, chunked: true)
228
+ data = format_headers(headers, !empty_response, @parser.http_minor == 1 && chunked)
229
+ @conn.write(data)
212
230
  end
213
231
 
214
232
  # Sends a response body chunk. If no headers were sent, default headers are
@@ -221,7 +239,7 @@ module Tipi
221
239
  data = []
222
240
  data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
223
241
  data << "0\r\n\r\n" if done
224
- @conn.write(data.join)
242
+ @conn.write(data.join) unless data.empty?
225
243
  end
226
244
 
227
245
  # Finishes the response to the current request. If no headers were sent,
@@ -240,11 +258,13 @@ module Tipi
240
258
  # Formats response headers into an array. If empty_response is true(thy),
241
259
  # the response status code will default to 204, otherwise to 200.
242
260
  # @param headers [Hash] response headers
243
- # @param empty_response [boolean] whether a response body will be sent
261
+ # @param body [boolean] whether a response body will be sent
262
+ # @param chunked [boolean] whether to use chunked transfer encoding
244
263
  # @return [String] formatted response headers
245
- def format_headers(headers, body)
246
- status = headers[':status'] || (body ? 200 : 204)
247
- lines = [format_status_line(body, status)]
264
+ def format_headers(headers, body, chunked)
265
+ status = headers[':status']
266
+ status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
267
+ lines = format_status_line(body, status, chunked)
248
268
  headers.each do |k, v|
249
269
  next if k =~ /^:/
250
270
 
@@ -254,11 +274,11 @@ module Tipi
254
274
  lines
255
275
  end
256
276
 
257
- def format_status_line(body, status)
277
+ def format_status_line(body, status, chunked)
258
278
  if !body
259
279
  empty_status_line(status)
260
280
  else
261
- with_body_status_line(status, body)
281
+ with_body_status_line(status, body, chunked)
262
282
  end
263
283
  end
264
284
 
@@ -270,17 +290,17 @@ module Tipi
270
290
  end
271
291
  end
272
292
 
273
- def with_body_status_line(status, body)
274
- if @parser.http_minor == 0
275
- +"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
276
- else
293
+ def with_body_status_line(status, body, chunked)
294
+ if chunked
277
295
  +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
296
+ else
297
+ +"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
278
298
  end
279
299
  end
280
300
 
281
301
  def collect_header_lines(lines, key, value)
282
302
  if value.is_a?(Array)
283
- value.inject(lines) { |lines, item| data << "#{key}: #{item}\r\n" }
303
+ value.inject(lines) { |_, item| lines << "#{key}: #{item}\r\n" }
284
304
  else
285
305
  lines << "#{key}: #{value}\r\n"
286
306
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'http/2'
4
- require 'qna/request'
4
+ require 'qeweney/request'
5
5
 
6
6
  module Tipi
7
7
  # Manages an HTTP 2 stream
@@ -46,7 +46,7 @@ module Tipi
46
46
  end
47
47
 
48
48
  def on_headers(headers)
49
- @request = QNA::Request.new(headers.to_h, self)
49
+ @request = Qeweney::Request.new(headers.to_h, self)
50
50
  if @first
51
51
  @request.headers[':first'] = true
52
52
  @first = false
@@ -62,7 +62,7 @@ module Tipi
62
62
  @request.buffer_body_chunk(data)
63
63
  end
64
64
  end
65
-
65
+
66
66
  def on_half_close
67
67
  if @waiting_for_body_chunk
68
68
  @waiting_for_body_chunk = nil
@@ -103,7 +103,7 @@ module Tipi
103
103
 
104
104
  # response API
105
105
  def respond(chunk, headers)
106
- headers[':status'] ||= '200'
106
+ headers[':status'] ||= Qeweney::Status::OK
107
107
  @stream.headers(headers, end_stream: false)
108
108
  @stream.data(chunk, end_stream: true)
109
109
  @headers_sent = true
@@ -112,7 +112,7 @@ module Tipi
112
112
  def send_headers(headers, empty_response = false)
113
113
  return if @headers_sent
114
114
 
115
- headers[':status'] ||= (empty_response ? 204 : 200).to_s
115
+ headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
116
116
  @stream.headers(headers, end_stream: false)
117
117
  @headers_sent = true
118
118
  end
@@ -131,7 +131,7 @@ module Tipi
131
131
  if @headers_sent
132
132
  @stream.close
133
133
  else
134
- headers[':status'] ||= '204'
134
+ headers[':status'] ||= Qeweney::Status::NO_CONTENT
135
135
  @stream.headers(headers, end_stream: true)
136
136
  end
137
137
  end
data/lib/tipi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tipi
4
- VERSION = '0.35'
4
+ VERSION = '0.38'
5
5
  end
@@ -7,32 +7,17 @@ module Tipi
7
7
  # Websocket connection
8
8
  class Websocket
9
9
  def self.handler(&block)
10
- proc { |conn, headers|
11
- block.(new(conn, headers))
12
- }
13
- end
10
+ proc do |adapter, headers|
11
+ req = Qeweney::Request.new(headers, adapter)
12
+ websocket = req.upgrade_to_websocket
13
+ block.(websocket)
14
+ end
15
+ end
14
16
 
15
17
  def initialize(conn, headers)
16
18
  @conn = conn
17
19
  @headers = headers
18
- setup(headers)
19
- end
20
-
21
- S_WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
22
- UPGRADE_RESPONSE = <<~HTTP.gsub("\n", "\r\n")
23
- HTTP/1.1 101 Switching Protocols
24
- Upgrade: websocket
25
- Connection: Upgrade
26
- Sec-WebSocket-Accept: %<accept>s
27
-
28
- HTTP
29
-
30
- def setup(headers)
31
- key = headers['sec-websocket-key']
32
20
  @version = headers['sec-websocket-version'].to_i
33
- accept = Digest::SHA1.base64digest([key, S_WS_GUID].join)
34
- @conn << format(UPGRADE_RESPONSE, accept: accept)
35
-
36
21
  @reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
37
22
  end
38
23
 
@@ -64,8 +49,10 @@ module Tipi
64
49
  end
65
50
  end
66
51
 
52
+ OutgoingFrame = ::WebSocket::Frame::Outgoing::Server
53
+
67
54
  def send(data)
68
- frame = ::WebSocket::Frame::Outgoing::Server.new(
55
+ frame = OutgoingFrame.new(
69
56
  version: @version, data: data, type: :text
70
57
  )
71
58
  @conn << frame.to_s
@@ -60,8 +60,8 @@ class HTTP1ServerTest < MiniTest::Test
60
60
  connection << "GET / HTTP/1.0\r\n\r\n"
61
61
 
62
62
  response = connection.readpartial(8192)
63
- expected = <<~HTTP.chomp.http_lines
64
- HTTP/1.0 200
63
+ expected = <<~HTTP.chomp.http_lines.chomp
64
+ HTTP/1.1 200
65
65
  Content-Length: 13
66
66
 
67
67
  Hello, world!
@@ -78,14 +78,11 @@ class HTTP1ServerTest < MiniTest::Test
78
78
  connection << "GET / HTTP/1.1\r\n\r\n"
79
79
 
80
80
  response = connection.readpartial(8192)
81
- expected = <<~HTTP.http_lines
81
+ expected = <<~HTTP.http_lines.chomp
82
82
  HTTP/1.1 200
83
- Transfer-Encoding: chunked
83
+ Content-Length: 13
84
84
 
85
- d
86
85
  Hello, world!
87
- 0
88
-
89
86
  HTTP
90
87
  assert_equal(expected, response)
91
88
  end
@@ -98,26 +95,23 @@ class HTTP1ServerTest < MiniTest::Test
98
95
  connection << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
99
96
  response = connection.readpartial(8192)
100
97
  assert !connection.eof?
101
- assert_equal("HTTP/1.0 200\r\nContent-Length: 2\r\n\r\nHi", response)
98
+ assert_equal("HTTP/1.1 200\r\nContent-Length: 2\r\n\r\nHi", response)
102
99
 
103
100
  connection << "GET / HTTP/1.1\r\n\r\n"
104
101
  response = connection.readpartial(8192)
105
102
  assert !connection.eof?
106
- expected = <<~HTTP.http_lines
103
+ expected = <<~HTTP.http_lines.chomp
107
104
  HTTP/1.1 200
108
- Transfer-Encoding: chunked
105
+ Content-Length: 2
109
106
 
110
- 2
111
107
  Hi
112
- 0
113
-
114
108
  HTTP
115
109
  assert_equal(expected, response)
116
110
 
117
111
  connection << "GET / HTTP/1.0\r\n\r\n"
118
112
  response = connection.readpartial(8192)
119
113
  assert connection.eof?
120
- assert_equal("HTTP/1.0 200\r\nContent-Length: 2\r\n\r\nHi", response)
114
+ assert_equal("HTTP/1.1 200\r\nContent-Length: 2\r\n\r\nHi", response)
121
115
  end
122
116
 
123
117
  def test_pipelining_client
@@ -130,24 +124,17 @@ class HTTP1ServerTest < MiniTest::Test
130
124
  end
131
125
 
132
126
  connection << "GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\nFoo: bar\r\n\r\n"
133
- 2.times { snooze }
127
+ sleep 0.01
134
128
  response = connection.readpartial(8192)
135
129
 
136
- expected = <<~HTTP.http_lines
130
+ expected = <<~HTTP.http_lines.chomp
137
131
  HTTP/1.1 200
138
- Transfer-Encoding: chunked
139
-
140
- d
141
- Hello, world!
142
- 0
132
+ Content-Length: 13
143
133
 
144
- HTTP/1.1 200
145
- Transfer-Encoding: chunked
134
+ Hello, world!HTTP/1.1 200
135
+ Content-Length: 14
146
136
 
147
- e
148
137
  Hello, foobar!
149
- 0
150
-
151
138
  HTTP
152
139
  assert_equal(expected, response)
153
140
  end
@@ -172,22 +159,22 @@ class HTTP1ServerTest < MiniTest::Test
172
159
  6
173
160
  foobar
174
161
  HTTP
175
- 20.times { snooze }
162
+ sleep 0.01
176
163
  assert request
177
164
  assert_equal %w[foobar], chunks
178
165
  assert !request.complete?
179
166
 
180
167
  connection << "6\r\nbazbud\r\n"
181
- 20.times { snooze }
168
+ sleep 0.01
182
169
  assert_equal %w[foobar bazbud], chunks
183
170
  assert !request.complete?
184
171
 
185
172
  connection << "0\r\n\r\n"
186
- 20.times { snooze }
173
+ sleep 0.01
187
174
  assert_equal %w[foobar bazbud], chunks
188
175
  assert request.complete?
189
176
 
190
- 2.times { snooze }
177
+ sleep 0.01
191
178
 
192
179
  response = connection.readpartial(8192)
193
180
 
@@ -210,7 +197,8 @@ class HTTP1ServerTest < MiniTest::Test
210
197
 
211
198
  opts = {
212
199
  upgrade: {
213
- echo: lambda do |conn, _headers|
200
+ echo: lambda do |adapter, _headers|
201
+ conn = adapter.conn
214
202
  conn << <<~HTTP.http_lines
215
203
  HTTP/1.1 101 Switching Protocols
216
204
  Upgrade: echo
@@ -231,14 +219,11 @@ class HTTP1ServerTest < MiniTest::Test
231
219
  connection << "GET / HTTP/1.1\r\n\r\n"
232
220
  response = connection.readpartial(8192)
233
221
  assert !connection.eof?
234
- expected = <<~HTTP.http_lines
222
+ expected = <<~HTTP.http_lines.chomp
235
223
  HTTP/1.1 200
236
- Transfer-Encoding: chunked
224
+ Content-Length: 2
237
225
 
238
- 2
239
226
  Hi
240
- 0
241
-
242
227
  HTTP
243
228
  assert_equal(expected, response)
244
229
 
@@ -271,7 +256,7 @@ class HTTP1ServerTest < MiniTest::Test
271
256
  connection.close
272
257
  assert !done
273
258
 
274
- 12.times { snooze }
259
+ sleep 0.01
275
260
  assert done
276
261
  end
277
262
 
data/test/test_request.rb CHANGED
@@ -60,9 +60,9 @@ class RequestHeadersTest < MiniTest::Test
60
60
 
61
61
  connection << "GET /titi HTTP/1.1\r\nHost: blah.com\r\nFoo: bar\r\nhi: 1\r\nHi: 2\r\nhi: 3\r\n\r\n"
62
62
 
63
- snooze
63
+ sleep 0.01
64
64
 
65
- assert_kind_of QNA::Request, req
65
+ assert_kind_of Qeweney::Request, req
66
66
  assert_equal 'blah.com', req.headers['host']
67
67
  assert_equal 'bar', req.headers['foo']
68
68
  assert_equal ['1', '3', '2'], req.headers['hi']
@@ -78,7 +78,7 @@ class RequestHeadersTest < MiniTest::Test
78
78
  end
79
79
 
80
80
  connection << "GET /titi HTTP/1.1\nHost: blah.com\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
81
- snooze
81
+ sleep 0.01
82
82
  assert_equal 'blah.com', req.host
83
83
  end
84
84
 
@@ -90,7 +90,7 @@ class RequestHeadersTest < MiniTest::Test
90
90
  end
91
91
 
92
92
  connection << "GET /titi HTTP/1.1\nConnection: keep-alive\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
93
- snooze
93
+ sleep 0.01
94
94
  assert_equal 'keep-alive', req.connection
95
95
  end
96
96
 
@@ -102,7 +102,7 @@ class RequestHeadersTest < MiniTest::Test
102
102
  end
103
103
 
104
104
  connection << "GET /titi HTTP/1.1\nConnection: upgrade\nUpgrade: foobar\n\n"
105
- snooze
105
+ sleep 0.01
106
106
  assert_equal 'foobar', req.upgrade_protocol
107
107
  end
108
108
  end
data/tipi.gemspec CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.executables = ['tipi']
21
21
 
22
- s.add_runtime_dependency 'polyphony', '~>0.51.0'
23
- s.add_runtime_dependency 'qna', '~>0.1'
22
+ s.add_runtime_dependency 'polyphony', '~>0.52.0'
23
+ s.add_runtime_dependency 'qeweney', '~>0.6'
24
24
 
25
25
  s.add_runtime_dependency 'http_parser.rb', '~>0.6.0'
26
26
  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.35'
4
+ version: '0.38'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-10 00:00:00.000000000 Z
11
+ date: 2021-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.51.0
19
+ version: 0.52.0
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.51.0
26
+ version: 0.52.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: qna
28
+ name: qeweney
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '0.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '0.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: http_parser.rb
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +214,7 @@ files:
214
214
  - df/ws_page.html
215
215
  - docs/README.md
216
216
  - docs/tipi-logo.png
217
+ - e
217
218
  - examples/cuba.ru
218
219
  - examples/hanami-api.ru
219
220
  - examples/hello.ru
@@ -291,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
291
292
  - !ruby/object:Gem::Version
292
293
  version: '0'
293
294
  requirements: []
294
- rubygems_version: 3.1.4
295
+ rubygems_version: 3.0.8
295
296
  signing_key:
296
297
  specification_version: 4
297
298
  summary: Tipi - the All-in-one Web Server for Ruby Apps