uv-rays 1.3.8 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|