tipi 0.35 → 0.38

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: '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