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 +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +4 -2
- data/examples/routing_server.rb +34 -0
- data/lib/tipi.rb +4 -0
- data/lib/tipi/digital_fabric/agent.rb +2 -2
- data/lib/tipi/http1_adapter.rb +2 -2
- data/lib/tipi/http2_stream.rb +2 -2
- data/lib/tipi/version.rb +1 -1
- data/test/test_request.rb +1 -1
- data/tipi.gemspec +1 -1
- metadata +17 -17
- data/lib/tipi/request.rb +0 -239
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0381d5d135a88937862fe5e22d3f39d2dd6a9353af83a655c13c641af3dd0ec2'
|
4
|
+
data.tar.gz: 17e20767ca22950023d44135fb2e20fdc469898fd2298e32158d791a111730cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e15c3197e3ca5dff97071f34c6bb18e006bd338b0c182ae046af8cf9b0d22f7451f0230f2430c65400bc81999e1c269d5868db8bce0cec1843453cf00ae883aa
|
7
|
+
data.tar.gz: a486df80bd50ff8069bc0abe1bca9df13e58ad8c0d84b7e24a86b284fb6fcfc4c679824692049fc27c1435d6c0598a45a6461a8b48fe4540722061399bf7c5e3
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tipi (0.
|
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
@@ -180,7 +180,7 @@ module DigitalFabric
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def prepare_http_request(msg)
|
183
|
-
req =
|
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 =
|
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)
|
data/lib/tipi/http1_adapter.rb
CHANGED
@@ -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)
|
data/lib/tipi/http2_stream.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'http/2'
|
4
|
-
|
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
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
|
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.
|
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-
|
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
|