tipi 0.34 → 0.35

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: 4cb2e4515c63ad20d116bf1aecb175db3b927bb2bf07d71b66fe90517fa5ab14
4
- data.tar.gz: fbddc482cd4172ad87e07c55718770051f9659f7efbfbd48c5eca41fee667ce7
3
+ metadata.gz: '0381d5d135a88937862fe5e22d3f39d2dd6a9353af83a655c13c641af3dd0ec2'
4
+ data.tar.gz: 17e20767ca22950023d44135fb2e20fdc469898fd2298e32158d791a111730cd
5
5
  SHA512:
6
- metadata.gz: f7a5ff1142ddcb938e1c78b16bdc27fb466f07e90a68306d39176557ca5913bf24a6e37a866d2a193a18d3f63171efcc580843ae5d34750e11d4653b7e601b65
7
- data.tar.gz: 4b8c357af526b53a687161b38edf7c7d3ca086c39958f7e202060cb2551b1f2e27c7d041fcbdafd8840c159e8f25303b63f4a1bd8e1af99f43ed3f709ad122c5
6
+ metadata.gz: e15c3197e3ca5dff97071f34c6bb18e006bd338b0c182ae046af8cf9b0d22f7451f0230f2430c65400bc81999e1c269d5868db8bce0cec1843453cf00ae883aa
7
+ data.tar.gz: a486df80bd50ff8069bc0abe1bca9df13e58ad8c0d84b7e24a86b284fb6fcfc4c679824692049fc27c1435d6c0598a45a6461a8b48fe4540722061399bf7c5e3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.35 2021-02-10
2
+
3
+ * Extract Request class into separate [qna](https://github.com/digital-fabric/qna) gem
4
+
1
5
  ## 0.34 2021-02-07
2
6
 
3
7
  * Implement digital fabric service and agents
data/Gemfile.lock CHANGED
@@ -1,12 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.34)
5
- escape_utils (~> 1.2.1)
4
+ tipi (0.35)
6
5
  http-2 (~> 0.10.0)
7
6
  http_parser.rb (~> 0.6.0)
8
7
  msgpack (~> 1.4.2)
9
8
  polyphony (~> 0.51.0)
9
+ qna (~> 0.1)
10
10
  rack (>= 2.0.8, < 2.3.0)
11
11
  websocket (~> 1.2.8)
12
12
 
@@ -29,6 +29,8 @@ GEM
29
29
  ruby-progressbar
30
30
  msgpack (1.4.2)
31
31
  polyphony (0.51.0)
32
+ qna (0.1)
33
+ escape_utils (~> 1.2.1)
32
34
  rack (2.2.3)
33
35
  rake (12.3.3)
34
36
  ruby-progressbar (1.10.1)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ puts "pid: #{Process.pid}"
12
+ puts 'Listening on port 4411...'
13
+
14
+ app = Tipi.route do |r|
15
+ r.root do
16
+ r.redirect '/hello'
17
+ end
18
+ r.on 'hello' do
19
+ r.get 'world' do
20
+ r.respond 'Hello world'
21
+ end
22
+ r.get do
23
+ r.respond 'Hello'
24
+ end
25
+ r.post do
26
+ puts 'Someone said Hello'
27
+ r.redirect '/'
28
+ end
29
+ end
30
+ end
31
+
32
+ spin do
33
+ Tipi.serve('0.0.0.0', 4411, opts, &app)
34
+ end.await
data/lib/tipi.rb CHANGED
@@ -49,5 +49,9 @@ module Tipi
49
49
  klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
50
50
  klass.new(socket, opts)
51
51
  end
52
+
53
+ def route(&block)
54
+ proc { |req| req.route(&block) }
55
+ end
52
56
  end
53
57
  end
@@ -180,7 +180,7 @@ module DigitalFabric
180
180
  end
181
181
 
182
182
  def prepare_http_request(msg)
183
- req = Tipi::Request.new(msg['headers'], RequestAdapter.new(self, msg))
183
+ req = QNA::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 = Tipi::Request.new(msg['headers'], RequestAdapter.new(self, msg))
202
+ req = QNA::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)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'http/parser'
4
- require_relative './request'
5
4
  require_relative './http2_adapter'
5
+ require 'qna/request'
6
6
 
7
7
  module Tipi
8
8
  # HTTP1 protocol implementation
@@ -87,7 +87,7 @@ 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(Request.new(headers, self))
90
+ queue_request(QNA::Request.new(headers, self))
91
91
  end
92
92
 
93
93
  def normalize_headers(headers)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'http/2'
4
- require_relative './request'
4
+ require 'qna/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 = Request.new(headers.to_h, self)
49
+ @request = QNA::Request.new(headers.to_h, self)
50
50
  if @first
51
51
  @request.headers[':first'] = true
52
52
  @first = false
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.34'
4
+ VERSION = '0.35'
5
5
  end
data/test/test_request.rb CHANGED
@@ -62,7 +62,7 @@ class RequestHeadersTest < MiniTest::Test
62
62
 
63
63
  snooze
64
64
 
65
- assert_kind_of Tipi::Request, req
65
+ assert_kind_of QNA::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']
data/tipi.gemspec CHANGED
@@ -20,12 +20,12 @@ Gem::Specification.new do |s|
20
20
  s.executables = ['tipi']
21
21
 
22
22
  s.add_runtime_dependency 'polyphony', '~>0.51.0'
23
+ s.add_runtime_dependency 'qna', '~>0.1'
23
24
 
24
25
  s.add_runtime_dependency 'http_parser.rb', '~>0.6.0'
25
26
  s.add_runtime_dependency 'http-2', '~>0.10.0'
26
27
  s.add_runtime_dependency 'rack', '>=2.0.8', '<2.3.0'
27
28
  s.add_runtime_dependency 'websocket', '~>1.2.8'
28
- s.add_runtime_dependency 'escape_utils', '~>1.2.1'
29
29
 
30
30
  # for digital fabric
31
31
  s.add_runtime_dependency 'msgpack', '~>1.4.2'
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.34'
4
+ version: '0.35'
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-07 00:00:00.000000000 Z
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.51.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: qna
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: http_parser.rb
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,20 +100,6 @@ dependencies:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
102
  version: 1.2.8
89
- - !ruby/object:Gem::Dependency
90
- name: escape_utils
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: 1.2.1
96
- type: :runtime
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: 1.2.1
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: msgpack
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -235,6 +235,7 @@ files:
235
235
  - examples/rack_server.rb
236
236
  - examples/rack_server_https.rb
237
237
  - examples/rack_server_https_forked.rb
238
+ - examples/routing_server.rb
238
239
  - examples/websocket_client.rb
239
240
  - examples/websocket_demo.rb
240
241
  - examples/websocket_secure_server.rb
@@ -257,7 +258,6 @@ files:
257
258
  - lib/tipi/http2_adapter.rb
258
259
  - lib/tipi/http2_stream.rb
259
260
  - lib/tipi/rack_adapter.rb
260
- - lib/tipi/request.rb
261
261
  - lib/tipi/version.rb
262
262
  - lib/tipi/websocket.rb
263
263
  - test/coverage.rb
data/lib/tipi/request.rb DELETED
@@ -1,239 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'escape_utils'
5
-
6
- module Tipi
7
- module RequestInfoInstanceMethods
8
- def host
9
- @headers['host']
10
- end
11
-
12
- def connection
13
- @headers['connection']
14
- end
15
-
16
- def upgrade_protocol
17
- connection == 'upgrade' && @headers['upgrade']&.downcase
18
- end
19
-
20
- def protocol
21
- @protocol ||= @adapter.protocol
22
- end
23
-
24
- def method
25
- @method ||= @headers[':method']
26
- end
27
-
28
- def scheme
29
- @scheme ||= @headers[':scheme']
30
- end
31
-
32
- def uri
33
- @uri ||= URI.parse(@headers[':path'] || '')
34
- end
35
-
36
- def path
37
- @path ||= uri.path
38
- end
39
-
40
- def query_string
41
- @query_string ||= uri.query
42
- end
43
-
44
- def query
45
- return @query if @query
46
-
47
- @query = (q = uri.query) ? split_query_string(q) : {}
48
- end
49
-
50
- def split_query_string(query)
51
- query.split('&').each_with_object({}) do |kv, h|
52
- k, v = kv.split('=')
53
- h[k.to_sym] = URI.decode_www_form_component(v)
54
- end
55
- end
56
-
57
- def request_id
58
- @headers['x-request-id']
59
- end
60
-
61
- def forwarded_for
62
- @headers['x-forwarded-for']
63
- end
64
- end
65
-
66
- module RequestInfoClassMethods
67
- def parse_form_data(body, headers)
68
- case (content_type = headers['content-type'])
69
- when /multipart\/form\-data; boundary=([^\s]+)/
70
- boundary = "--#{Regexp.last_match(1)}"
71
- parse_multipart_form_data(body, boundary)
72
- when 'application/x-www-form-urlencoded'
73
- parse_urlencoded_form_data(body)
74
- else
75
- raise "Unsupported form data content type: #{content_type}"
76
- end
77
- end
78
-
79
- def parse_multipart_form_data(body, boundary)
80
- parts = body.split(boundary)
81
- parts.each_with_object({}) do |p, h|
82
- next if p.empty? || p == "--\r\n"
83
-
84
- # remove post-boundary \r\n
85
- p.slice!(0, 2)
86
- parse_multipart_form_data_part(p, h)
87
- end
88
- end
89
-
90
- def parse_multipart_form_data_part(part, hash)
91
- body, headers = parse_multipart_form_data_part_headers(part)
92
- disposition = headers['content-disposition'] || ''
93
-
94
- name = (disposition =~ /name="([^"]+)"/) ? Regexp.last_match(1) : nil
95
- filename = (disposition =~ /filename="([^"]+)"/) ? Regexp.last_match(1) : nil
96
-
97
- if filename
98
- hash[name] = { filename: filename, content_type: headers['content-type'], data: body }
99
- else
100
- hash[name] = body
101
- end
102
- end
103
-
104
- def parse_multipart_form_data_part_headers(part)
105
- headers = {}
106
- while true
107
- idx = part.index("\r\n")
108
- break unless idx
109
-
110
- header = part[0, idx]
111
- part.slice!(0, idx + 2)
112
- break if header.empty?
113
-
114
- next unless header =~ /^([^\:]+)\:\s?(.+)$/
115
-
116
- headers[Regexp.last_match(1).downcase] = Regexp.last_match(2)
117
- end
118
- # remove trailing \r\n
119
- part.slice!(part.size - 2, 2)
120
- [part, headers]
121
- end
122
-
123
- PARAMETER_RE = /^(.+)=(.*)$/.freeze
124
- MAX_PARAMETER_NAME_SIZE = 256
125
- MAX_PARAMETER_VALUE_SIZE = 2**20 # 1MB
126
-
127
- def parse_urlencoded_form_data(body)
128
- body.force_encoding(UTF_8) unless body.encoding == Encoding::UTF_8
129
- body.split('&').each_with_object({}) do |i, m|
130
- raise 'Invalid parameter format' unless i =~ PARAMETER_RE
131
-
132
- k = Regexp.last_match(1)
133
- raise 'Invalid parameter size' if k.size > MAX_PARAMETER_NAME_SIZE
134
-
135
- v = Regexp.last_match(2)
136
- raise 'Invalid parameter size' if v.size > MAX_PARAMETER_VALUE_SIZE
137
-
138
- m[EscapeUtils.unescape_uri(k)] = EscapeUtils.unescape_uri(v)
139
- end
140
- end
141
- end
142
-
143
- # HTTP request
144
- class Request
145
- include RequestInfoInstanceMethods
146
- extend RequestInfoClassMethods
147
-
148
- attr_reader :headers, :adapter
149
- attr_accessor :__next__
150
-
151
- def initialize(headers, adapter)
152
- @headers = headers
153
- @adapter = adapter
154
- end
155
-
156
- def buffer_body_chunk(chunk)
157
- @buffered_body_chunks ||= []
158
- @buffered_body_chunks << chunk
159
- end
160
-
161
- def next_chunk
162
- if @buffered_body_chunks
163
- chunk = @buffered_body_chunks.shift
164
- @buffered_body_chunks = nil if @buffered_body_chunks.empty?
165
- return chunk
166
- end
167
-
168
- @message_complete ? nil : @adapter.get_body_chunk
169
- end
170
-
171
- def each_chunk
172
- if @buffered_body_chunks
173
- while (chunk = @buffered_body_chunks.shift)
174
- yield chunk
175
- end
176
- @buffered_body_chunks = nil
177
- end
178
- while !@message_complete && (chunk = @adapter.get_body_chunk)
179
- yield chunk
180
- end
181
- end
182
-
183
- def complete!(keep_alive = nil)
184
- @message_complete = true
185
- @keep_alive = keep_alive
186
- end
187
-
188
- def complete?
189
- @message_complete
190
- end
191
-
192
- def consume
193
- @adapter.consume_request
194
- end
195
-
196
- def keep_alive?
197
- @keep_alive
198
- end
199
-
200
- def read
201
- buf = @buffered_body_chunks ? @buffered_body_chunks.join : nil
202
- while (chunk = @adapter.get_body_chunk)
203
- (buf ||= +'') << chunk
204
- end
205
- @buffered_body_chunks = nil
206
- buf
207
- end
208
- alias_method :body, :read
209
-
210
- def respond(body, headers = {})
211
- @adapter.respond(body, headers)
212
- @headers_sent = true
213
- end
214
-
215
- def send_headers(headers = {}, empty_response = false)
216
- return if @headers_sent
217
-
218
- @headers_sent = true
219
- @adapter.send_headers(headers, empty_response: empty_response)
220
- end
221
-
222
- def send_chunk(body, done: false)
223
- send_headers({}) unless @headers_sent
224
-
225
- @adapter.send_chunk(body, done: done)
226
- end
227
- alias_method :<<, :send_chunk
228
-
229
- def finish
230
- send_headers({}) unless @headers_sent
231
-
232
- @adapter.finish
233
- end
234
-
235
- def headers_sent?
236
- @headers_sent
237
- end
238
- end
239
- end