uv-rays 1.3.8 → 2.0.1
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/README.md +14 -15
- data/lib/faraday/adapter/libuv.rb +80 -0
- data/lib/uv-rays.rb +8 -6
- data/lib/uv-rays/abstract_tokenizer.rb +7 -5
- data/lib/uv-rays/buffered_tokenizer.rb +24 -14
- data/lib/uv-rays/connection.rb +14 -14
- data/lib/uv-rays/http/encoding.rb +4 -2
- data/lib/uv-rays/http/parser.rb +14 -2
- data/lib/uv-rays/http/request.rb +4 -3
- data/lib/uv-rays/http_endpoint.rb +17 -14
- data/lib/uv-rays/scheduler.rb +26 -22
- data/lib/uv-rays/scheduler/cron.rb +2 -0
- data/lib/uv-rays/scheduler/time.rb +3 -1
- data/lib/uv-rays/tcp_server.rb +3 -2
- data/lib/uv-rays/version.rb +3 -1
- data/spec/connection_spec.rb +16 -41
- data/spec/http_endpoint_spec.rb +41 -120
- data/spec/scheduler_spec.rb +21 -45
- data/uv-rays.gemspec +15 -13
- metadata +52 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dcf2dd4cbeb83a76c6f218cc4c0ccc55ed05d755
|
4
|
+
data.tar.gz: 2f1e98b94414be0f86f22b5297b38e45c9cc8882
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc24a2d715a2325b8f531bd4823cf73e7ed9e54dcb6ee31eeacc830b3af1df2cd70b4280e68409a61507e5fbfcbf6aa9576e9d59b56f9afe9f07939550759f78
|
7
|
+
data.tar.gz: 3be2b91df14b7be0b7fbe7f2c50c08fbaf45ff56e586b50db736a87a12c02c411e27f21852ac2baa0aaee3e7df296cb03d981043c9f7178e6fd509dbd75b1a69
|
data/README.md
CHANGED
@@ -27,26 +27,25 @@ Run `gem install uv-rays` to install
|
|
27
27
|
Here's a fully-functional echo server written with UV-Rays:
|
28
28
|
|
29
29
|
```ruby
|
30
|
-
|
31
30
|
require 'uv-rays'
|
32
31
|
|
33
32
|
module EchoServer
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
33
|
+
def on_connect(socket)
|
34
|
+
@ip, @port = socket.peername
|
35
|
+
logger.info "-- #{@ip}:#{@port} connected"
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_read(data, socket)
|
39
|
+
write ">>>you sent: #{data}"
|
40
|
+
close_connection if data =~ /quit/i
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_close
|
44
|
+
puts "-- #{@ip}:#{@port} disconnected"
|
45
|
+
end
|
46
46
|
end
|
47
47
|
|
48
|
-
|
49
|
-
Libuv::Loop.default.run {
|
48
|
+
reactor {
|
50
49
|
UV.start_server "127.0.0.1", 8081, EchoServer
|
51
50
|
}
|
52
51
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'uv-rays'
|
3
|
+
|
4
|
+
|
5
|
+
module Faraday
|
6
|
+
class Adapter < Middleware
|
7
|
+
register_middleware libuv: :Libuv
|
8
|
+
|
9
|
+
class Libuv < Faraday::Adapter
|
10
|
+
def initialize(app, connection_options = {})
|
11
|
+
@connection_options = connection_options
|
12
|
+
super(app)
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
super
|
17
|
+
|
18
|
+
opts = {}
|
19
|
+
if env[:url].scheme == 'https' && ssl = env[:ssl]
|
20
|
+
tls_opts = opts[:tls_options] = {}
|
21
|
+
|
22
|
+
# opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true)
|
23
|
+
# TODO:: Need to provide verify callbacks
|
24
|
+
|
25
|
+
tls_opts[:cert_chain] = ssl[:ca_path] if ssl[:ca_path]
|
26
|
+
tls_opts[:client_ca] = ssl[:ca_file] if ssl[:ca_file]
|
27
|
+
#tls_opts[:client_cert] = ssl[:client_cert] if ssl[:client_cert]
|
28
|
+
#tls_opts[:client_key] = ssl[:client_key] if ssl[:client_key]
|
29
|
+
#tls_opts[:certificate] = ssl[:certificate] if ssl[:certificate]
|
30
|
+
tls_opts[:private_key] = ssl[:private_key] if ssl[:private_key]
|
31
|
+
end
|
32
|
+
|
33
|
+
if (req = env[:request])
|
34
|
+
opts[:inactivity_timeout] = req[:timeout] if req[:timeout]
|
35
|
+
end
|
36
|
+
|
37
|
+
error = nil
|
38
|
+
thread = reactor
|
39
|
+
if thread.running?
|
40
|
+
error = perform_request(env, opts)
|
41
|
+
else
|
42
|
+
# Pretty much here for testing
|
43
|
+
thread.run {
|
44
|
+
error = perform_request(env, opts)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Re-raise the error out of the event loop
|
49
|
+
# Really this is only required for tests as this will always run on the reactor
|
50
|
+
raise error if error
|
51
|
+
@app.call env
|
52
|
+
rescue ::CoroutineRejection => err
|
53
|
+
if err.value == :timeout
|
54
|
+
raise Error::TimeoutError, err
|
55
|
+
else
|
56
|
+
raise Error::ConnectionFailed, err
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# TODO: support streaming requests
|
61
|
+
def read_body(env)
|
62
|
+
env[:body].respond_to?(:read) ? env[:body].read : env[:body]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def perform_request(env, opts)
|
67
|
+
conn = ::UV::HttpEndpoint.new(env[:url].to_s, opts.merge!(@connection_options))
|
68
|
+
resp = co conn.request(env[:method].to_s.downcase.to_sym,
|
69
|
+
headers: env[:request_headers],
|
70
|
+
path: "/#{env[:url].to_s.split('/', 4)[-1]}",
|
71
|
+
keepalive: false,
|
72
|
+
body: read_body(env))
|
73
|
+
|
74
|
+
save_response(env, resp.status.to_i, resp.body, resp) #, resp.reason_phrase)
|
75
|
+
nil
|
76
|
+
rescue Exception => e
|
77
|
+
e
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/uv-rays.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'libuv'
|
2
4
|
|
3
5
|
|
@@ -65,21 +67,21 @@ module UV
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def self.start_server(server, port, handler, *args)
|
68
|
-
|
69
|
-
raise ThreadError, "There is no Libuv
|
70
|
+
thread = reactor # Get the reactor running on this thread
|
71
|
+
raise ThreadError, "There is no Libuv reactor running on the current thread" if thread.nil?
|
70
72
|
|
71
73
|
klass = klass_from_handler(InboundConnection, handler, *args)
|
72
|
-
UV::TcpServer.new
|
74
|
+
UV::TcpServer.new thread, server, port, klass, *args
|
73
75
|
end
|
74
76
|
|
75
77
|
def self.attach_server(sock, handler, *args)
|
76
|
-
|
77
|
-
raise ThreadError, "There is no Libuv
|
78
|
+
thread = reactor # Get the reactor running on this thread
|
79
|
+
raise ThreadError, "There is no Libuv reactor running on the current thread" if thread.nil?
|
78
80
|
|
79
81
|
klass = klass_from_handler(InboundConnection, handler, *args)
|
80
82
|
sd = sock.respond_to?(:fileno) ? sock.fileno : sock
|
81
83
|
|
82
|
-
UV::TcpServer.new
|
84
|
+
UV::TcpServer.new thread, sd, sd, klass, *args
|
83
85
|
end
|
84
86
|
|
85
87
|
def self.open_datagram_socket(handler, server = nil, port = nil, *args)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
1
2
|
|
2
3
|
module UV
|
3
4
|
|
@@ -6,7 +7,7 @@ module UV
|
|
6
7
|
# callback based system for application level tokenization without
|
7
8
|
# the heavy lifting.
|
8
9
|
class AbstractTokenizer
|
9
|
-
DEFAULT_ENCODING = 'ASCII-8BIT'
|
10
|
+
DEFAULT_ENCODING = 'ASCII-8BIT'
|
10
11
|
|
11
12
|
attr_accessor :callback, :indicator, :size_limit, :verbose
|
12
13
|
|
@@ -21,9 +22,10 @@ module UV
|
|
21
22
|
raise ArgumentError, 'no indicator provided' unless @indicator
|
22
23
|
raise ArgumentError, 'no callback provided' unless @callback
|
23
24
|
|
24
|
-
|
25
|
-
@
|
26
|
-
|
25
|
+
reset
|
26
|
+
if @indicator.is_a?(String)
|
27
|
+
@indicator = String.new(@indicator).encode(@encoding).freeze
|
28
|
+
end
|
27
29
|
end
|
28
30
|
|
29
31
|
# Extract takes an arbitrary string of input data and returns an array of
|
@@ -101,7 +103,7 @@ module UV
|
|
101
103
|
|
102
104
|
|
103
105
|
def reset
|
104
|
-
@input =
|
106
|
+
@input = String.new.force_encoding(@encoding)
|
105
107
|
end
|
106
108
|
end
|
107
109
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
1
2
|
|
2
3
|
# BufferedTokenizer takes a delimiter upon instantiation.
|
3
4
|
# It allows input to be spoon-fed from some outside source which receives
|
@@ -15,7 +16,7 @@
|
|
15
16
|
# end
|
16
17
|
module UV
|
17
18
|
class BufferedTokenizer
|
18
|
-
DEFAULT_ENCODING = 'ASCII-8BIT'
|
19
|
+
DEFAULT_ENCODING = 'ASCII-8BIT'
|
19
20
|
|
20
21
|
attr_accessor :delimiter, :indicator, :size_limit, :verbose
|
21
22
|
|
@@ -30,11 +31,8 @@ module UV
|
|
30
31
|
@encoding = options[:encoding] || DEFAULT_ENCODING
|
31
32
|
|
32
33
|
if @delimiter
|
33
|
-
@delimiter.force_encoding(@encoding) if @delimiter.is_a?(String)
|
34
|
-
@indicator.force_encoding(@encoding) if @indicator.is_a?(String)
|
35
34
|
@extract_method = method(:delimiter_extract)
|
36
35
|
elsif @indicator && @msg_length
|
37
|
-
@indicator.force_encoding(@encoding) if @indicator.is_a?(String)
|
38
36
|
@extract_method = method(:length_extract)
|
39
37
|
else
|
40
38
|
raise ArgumentError, 'no delimiter provided'
|
@@ -88,7 +86,7 @@ module UV
|
|
88
86
|
messages = @input.split(@delimiter, -1)
|
89
87
|
|
90
88
|
if @indicator
|
91
|
-
@input = messages.pop ||
|
89
|
+
@input = messages.pop || empty_string
|
92
90
|
entities = []
|
93
91
|
messages.each do |msg|
|
94
92
|
res = msg.split(@indicator, -1)
|
@@ -96,7 +94,7 @@ module UV
|
|
96
94
|
end
|
97
95
|
else
|
98
96
|
entities = messages
|
99
|
-
@input = entities.pop ||
|
97
|
+
@input = entities.pop || empty_string
|
100
98
|
end
|
101
99
|
|
102
100
|
check_buffer_limits
|
@@ -111,7 +109,7 @@ module UV
|
|
111
109
|
messages = @input.split(@indicator, -1)
|
112
110
|
messages.shift # discard junk data
|
113
111
|
|
114
|
-
last = messages.pop ||
|
112
|
+
last = messages.pop || empty_string
|
115
113
|
|
116
114
|
# Select messages of the right size then remove junk data
|
117
115
|
messages.select! { |msg| msg.length >= @msg_length ? true : false }
|
@@ -146,15 +144,27 @@ module UV
|
|
146
144
|
end
|
147
145
|
|
148
146
|
def init_buffer
|
149
|
-
@input =
|
150
|
-
|
151
|
-
|
152
|
-
|
147
|
+
@input = empty_string
|
148
|
+
|
149
|
+
if @delimiter.is_a?(String)
|
150
|
+
@delimiter = String.new(@delimiter).encode(@encoding).freeze
|
151
|
+
end
|
152
|
+
|
153
|
+
if @indicator.is_a?(String)
|
154
|
+
@indicator = String.new(@indicator).encode(@encoding).freeze
|
155
|
+
end
|
153
156
|
end
|
154
157
|
|
155
|
-
def reset(value =
|
156
|
-
@input = value
|
157
|
-
|
158
|
+
def reset(value = nil)
|
159
|
+
@input = String.new(value || '').force_encoding(@encoding)
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
|
166
|
+
def empty_string
|
167
|
+
String.new.force_encoding(@encoding)
|
158
168
|
end
|
159
169
|
end
|
160
170
|
end
|
data/lib/uv-rays/connection.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module UV
|
3
4
|
def self.try_connect(tcp, handler, server, port)
|
@@ -13,12 +14,12 @@ module UV
|
|
13
14
|
tcp.start_read
|
14
15
|
end
|
15
16
|
else
|
16
|
-
tcp.
|
17
|
+
tcp.reactor.lookup(server, wait: false).then(
|
17
18
|
proc { |result|
|
18
19
|
UV.try_connect(tcp, handler, result[0][0], port)
|
19
20
|
},
|
20
21
|
proc { |failure|
|
21
|
-
# TODO:: Log error on
|
22
|
+
# TODO:: Log error on reactor
|
22
23
|
handler.on_close
|
23
24
|
}
|
24
25
|
)
|
@@ -64,7 +65,7 @@ module UV
|
|
64
65
|
|
65
66
|
class TcpConnection < Connection
|
66
67
|
def write(data)
|
67
|
-
@transport.write(data)
|
68
|
+
@transport.write(data, wait: :promise)
|
68
69
|
end
|
69
70
|
|
70
71
|
def close_connection(after_writing = false)
|
@@ -76,9 +77,8 @@ module UV
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def stream_file(filename, type = :raw)
|
79
|
-
file = @
|
80
|
-
|
81
|
-
file.send_file(@transport, type).finally do
|
80
|
+
file = @reactor.file(filename, File::RDONLY) do # File is open and available for reading
|
81
|
+
file.send_file(@transport, type, wait: :promise).finally do
|
82
82
|
file.close
|
83
83
|
end
|
84
84
|
end
|
@@ -105,7 +105,7 @@ module UV
|
|
105
105
|
def initialize(tcp)
|
106
106
|
super()
|
107
107
|
|
108
|
-
@
|
108
|
+
@reactor = tcp.reactor
|
109
109
|
@transport = tcp
|
110
110
|
@transport.finally method(:on_close)
|
111
111
|
@transport.progress method(:on_read)
|
@@ -127,10 +127,10 @@ module UV
|
|
127
127
|
def initialize(server, port)
|
128
128
|
super()
|
129
129
|
|
130
|
-
@
|
130
|
+
@reactor = reactor
|
131
131
|
@server = server
|
132
132
|
@port = port
|
133
|
-
@transport = @
|
133
|
+
@transport = @reactor.tcp
|
134
134
|
|
135
135
|
::UV.try_connect(@transport, self, @server, @port)
|
136
136
|
end
|
@@ -146,9 +146,9 @@ module UV
|
|
146
146
|
end
|
147
147
|
|
148
148
|
def reconnect(server = nil, port = nil)
|
149
|
-
@
|
149
|
+
@reactor = reactor
|
150
150
|
|
151
|
-
@transport = @
|
151
|
+
@transport = @reactor.tcp
|
152
152
|
@server = server || @server
|
153
153
|
@port = port || @port
|
154
154
|
|
@@ -160,8 +160,8 @@ module UV
|
|
160
160
|
def initialize(server = nil, port = nil)
|
161
161
|
super()
|
162
162
|
|
163
|
-
@
|
164
|
-
@transport = @
|
163
|
+
@reactor = reactor
|
164
|
+
@transport = @reactor.udp
|
165
165
|
@transport.progress method(:on_read)
|
166
166
|
|
167
167
|
if not server.nil?
|
@@ -182,7 +182,7 @@ module UV
|
|
182
182
|
else
|
183
183
|
# Async DNS resolution
|
184
184
|
# Note:: send here will chain the promise
|
185
|
-
@
|
185
|
+
@reactor.lookup(recipient_address).then do |result|
|
186
186
|
@transport.send result[0][0], recipient_port, data
|
187
187
|
end
|
188
188
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
1
3
|
module UV
|
2
4
|
module Http
|
3
5
|
module Encoding
|
@@ -105,7 +107,7 @@ module UV
|
|
105
107
|
end
|
106
108
|
|
107
109
|
def encode_headers(head)
|
108
|
-
head.inject(
|
110
|
+
head.inject(String.new) do |result, (key, value)|
|
109
111
|
# Munge keys from foo-bar-baz to Foo-Bar-Baz
|
110
112
|
key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
|
111
113
|
result << case key
|
@@ -119,7 +121,7 @@ module UV
|
|
119
121
|
|
120
122
|
def encode_cookie(cookie)
|
121
123
|
if cookie.is_a? Hash
|
122
|
-
cookie.inject(
|
124
|
+
cookie.inject(String.new) { |result, (k, v)| result << encode_param(k, v) + ';' }
|
123
125
|
else
|
124
126
|
cookie
|
125
127
|
end
|
data/lib/uv-rays/http/parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module UV
|
2
4
|
module Http
|
3
5
|
class Headers < Hash
|
@@ -7,6 +9,9 @@ module UV
|
|
7
9
|
# The status code (as an integer)
|
8
10
|
attr_accessor :status
|
9
11
|
|
12
|
+
# The text after the status code
|
13
|
+
attr_accessor :reason_phrase
|
14
|
+
|
10
15
|
# Cookies at the time of the request
|
11
16
|
attr_accessor :cookies
|
12
17
|
|
@@ -57,12 +62,14 @@ module UV
|
|
57
62
|
# Parser Callbacks:
|
58
63
|
def on_message_begin(parser)
|
59
64
|
@headers = Headers.new
|
60
|
-
@body =
|
65
|
+
@body = String.new
|
61
66
|
@chunked = false
|
62
67
|
@close_connection = false
|
63
68
|
end
|
64
69
|
|
65
70
|
def on_status(parser, data)
|
71
|
+
@headers.reason_phrase = data
|
72
|
+
|
66
73
|
# Different HTTP versions have different defaults
|
67
74
|
if @state.http_minor == 0
|
68
75
|
@close_connection = true
|
@@ -88,6 +95,11 @@ module UV
|
|
88
95
|
# If chunked we'll buffer streaming data for notification
|
89
96
|
@chunked = data == 'chunked'
|
90
97
|
|
98
|
+
end
|
99
|
+
|
100
|
+
current = @headers[@header]
|
101
|
+
if current
|
102
|
+
@headers[@header] = "#{current}, #{data}"
|
91
103
|
else
|
92
104
|
@headers[@header] = data
|
93
105
|
end
|
@@ -134,7 +146,7 @@ module UV
|
|
134
146
|
def eof
|
135
147
|
return if @request.nil?
|
136
148
|
|
137
|
-
if @headers_complete && @headers[:'Content-Length'].nil?
|
149
|
+
if @headers_complete && (@headers[:'Content-Length'].nil? || @request.method == :head)
|
138
150
|
on_message_complete(nil)
|
139
151
|
else
|
140
152
|
# Reject if this is a partial response
|