tipi 0.34 → 0.35

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