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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f5437880d127e446a1e89f0160196101c064596
4
- data.tar.gz: 17ee53fe763b9ae8d53caebcfa02c692f80f4da7
3
+ metadata.gz: dcf2dd4cbeb83a76c6f218cc4c0ccc55ed05d755
4
+ data.tar.gz: 2f1e98b94414be0f86f22b5297b38e45c9cc8882
5
5
  SHA512:
6
- metadata.gz: 3596ce722dac73675539be1a0c2336da15b116a5205d13e4ccd86916fbbddeb49c685496acda002db1691e2eb7b91782e4b94a0c3ef1a3132faa364c96fef68c
7
- data.tar.gz: a96be731ac57037fac6a69f43ac169b32e21f70d7e669c7db29a06172688e1b43bb9561fbca983e3ce26d8c3a035c381550307cff3361552c6e71f0a43f6f80f
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
- def post_init
35
- puts "-- someone connected to the echo server!"
36
- end
37
-
38
- def on_read data, *args
39
- write ">>>you sent: #{data}"
40
- close_connection if data =~ /quit/i
41
- end
42
-
43
- def on_close
44
- puts "-- someone disconnected from the echo server!"
45
- end
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
- # Note that this will block current thread.
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
- loop = Libuv::Loop.current # Get the current event loop
69
- raise ThreadError, "There is no Libuv Loop running on the current thread" if loop.nil?
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 loop, server, port, klass, *args
74
+ UV::TcpServer.new thread, server, port, klass, *args
73
75
  end
74
76
 
75
77
  def self.attach_server(sock, handler, *args)
76
- loop = Libuv::Loop.current # Get the current event loop
77
- raise ThreadError, "There is no Libuv Loop running on the current thread" if loop.nil?
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 loop, sd, sd, klass, *args
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'.freeze
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
- @input = ''
25
- @input.force_encoding(@encoding)
26
- @indicator.force_encoding(@encoding) if @indicator.is_a?(String)
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 = ''.force_encoding(@encoding)
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'.freeze
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
- @input.force_encoding(@encoding)
151
- @delimiter.force_encoding(@encoding) if @delimiter.is_a?(String)
152
- @indicator.force_encoding(@encoding) if @indicator.is_a?(String)
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
- @input.force_encoding(@encoding)
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
@@ -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.loop.lookup(server).then(
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 loop
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 = @loop.file(filename, File::RDONLY)
80
- file.progress do # File is open and available for reading
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
- @loop = tcp.loop
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
- @loop = Libuv::Loop.current || Libuv::Loop.default
130
+ @reactor = reactor
131
131
  @server = server
132
132
  @port = port
133
- @transport = @loop.tcp
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
- @loop = Libuv::Loop.current || @loop
149
+ @reactor = reactor
150
150
 
151
- @transport = @loop.tcp
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
- @loop = Libuv::Loop.current || Libuv::Loop.default
164
- @transport = @loop.udp
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
- @loop.lookup(recipient_address).then do |result|
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('') do |result, (key, value)|
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('') { |result, (k, v)| result << encode_param(k, v) + ';' }
124
+ cookie.inject(String.new) { |result, (k, v)| result << encode_param(k, v) + ';' }
123
125
  else
124
126
  cookie
125
127
  end
@@ -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