uv-rays 1.2.3 → 1.3.0

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: 4e0ed8546ca888cff20ded4605bd07311bcce602
4
- data.tar.gz: 9752390a4161b472d39fb9a3d5d34abb72ff690d
3
+ metadata.gz: bbe9019bb0b5b548daf337f12f4f6d6eed42d3d6
4
+ data.tar.gz: 1b5779c34179033260c79ec5afeb088c470a8180
5
5
  SHA512:
6
- metadata.gz: b03214fac41e0522af1674774306d7c2ae705f62a376ec6175b49927ae29887d29dfdd33f5b128aa4b09bc3422a5fcca8e195172130583bb7597001a99501fe1
7
- data.tar.gz: 1fb5c2a2cd90ecbb2856826e8d3bdcf1c2ff87621d50f393176a4dd06d6f7f33680a5082550f70b68513a266839138f4b06b55f8d331189fb5450be550037700
6
+ metadata.gz: 7e14c928bf63dbd3ebfd230dbf2dfcff281328e7e6376d551838ba0f3a3fd7c8d5891e8ab5b54ca913267b5053f26cbe53b31062b0cfc7ec40acf556fd9384e2
7
+ data.tar.gz: e3aad461e23c4e60a52fbea8114a8e54b7fcb94f327f08ca6db810f5c8a41e3aa9a607bfb4185f7136f2bbd78dda3a6cfdd7216db04651ff2790f957f1035e42
@@ -24,7 +24,7 @@ require 'http-parser' # Parses HTTP request / responses
24
24
  require 'addressable/uri' # URI parser
25
25
  require 'uv-rays/http/encoding'
26
26
  require 'uv-rays/http/request'
27
- require 'uv-rays/http/response'
27
+ require 'uv-rays/http/parser'
28
28
  require 'uv-rays/http_endpoint'
29
29
 
30
30
 
@@ -11,35 +11,42 @@ module UV
11
11
  attr_accessor :cookies
12
12
 
13
13
  attr_accessor :keep_alive
14
+
15
+ attr_accessor :body
16
+
17
+ def to_s
18
+ "HTTP#{http_version} #{status} - keep alive: #{keep_alive}\nheaders: #{super}\nbody: #{body}"
19
+ end
20
+
21
+ alias_method :inspect, :to_s
14
22
  end
15
23
 
16
- class Response
24
+ class Parser
17
25
  def initialize
18
26
  @parser = ::HttpParser::Parser.new(self)
19
27
  @state = ::HttpParser::Parser.new_instance
20
28
  @state.type = :response
29
+ @headers = nil
21
30
  end
22
31
 
23
32
 
24
- attr_accessor :request
33
+ attr_reader :request
25
34
 
26
35
 
27
- # Called on connection disconnect
28
- def reset!
36
+ def new_request(request)
37
+ @headers = nil
38
+ @request = request
39
+ @headers_complete = false
29
40
  @state.reset!
30
41
  end
31
42
 
32
- # Socket level data is forwarded as it arrives
33
- def receive(data)
34
- # Returns true if error
43
+ def received(data)
35
44
  if @parser.parse(@state, data)
36
45
  if @request
37
46
  @request.reject(@state.error)
38
47
  @request = nil
48
+ @response = nil
39
49
  return true
40
- #else # silently fail here
41
- # p 'parse error and no request..'
42
- # p @state.error
43
50
  end
44
51
  end
45
52
 
@@ -52,6 +59,7 @@ module UV
52
59
  @headers = Headers.new
53
60
  @body = ''
54
61
  @chunked = false
62
+ @close_connection = false
55
63
  end
56
64
 
57
65
  def on_status(parser, data)
@@ -86,50 +94,66 @@ module UV
86
94
  end
87
95
 
88
96
  def on_headers_complete(parser)
89
- headers = @headers
90
- @headers = nil
91
- @header = nil
97
+ @headers_complete = true
92
98
 
93
99
  # https://github.com/joyent/http-parser indicates we should extract
94
100
  # this information here
95
- headers.http_version = @state.http_version
96
- headers.status = @state.http_status
97
- headers.cookies = @request.cookies_hash
98
- headers.keep_alive = !@close_connection
101
+ @headers.http_version = @state.http_version
102
+ @headers.status = @state.http_status
103
+ @headers.cookies = @request.cookies_hash
104
+ @headers.keep_alive = !@close_connection
99
105
 
100
106
  # User code may throw an error
101
107
  # Errors will halt the processing and return a PAUSED error
102
- @request.set_headers(headers)
108
+ @request.set_headers(@headers)
103
109
  end
104
110
 
105
111
  def on_body(parser, data)
106
- @body << data
107
- # TODO:: What if it is a chunked response body?
108
- # We should buffer complete chunks
109
- @request.notify(data)
112
+ if @request.streaming?
113
+ @request.notify(data)
114
+ else
115
+ @body << data
116
+ end
110
117
  end
111
118
 
112
119
  def on_message_complete(parser)
113
- req = @request
114
- bod = @body
115
-
116
- # Clean up memory
117
- @request = nil
118
- @body = nil
120
+ @headers.body = @body
119
121
 
120
- req.resolve(self, bod)
122
+ if @request.resolve(@headers)
123
+ cleanup
124
+ else
125
+ req = @request
126
+ cleanup
127
+ new_request(req)
128
+ end
121
129
  end
122
130
 
123
131
  # We need to flush the response on disconnect if content-length is undefined
124
132
  # As per the HTTP spec
133
+ attr_accessor :reason
125
134
  def eof
126
- if @headers.nil? && @request.headers && @request.headers[:'Content-Length'].nil?
135
+ return if @request.nil?
136
+
137
+ if @headers_complete && @headers[:'Content-Length'].nil?
127
138
  on_message_complete(nil)
128
139
  else
129
140
  # Reject if this is a partial response
130
- @request.reject(:partial_response)
141
+ @request.reject(@reason || :partial_response)
142
+ cleanup
131
143
  end
132
144
  end
145
+
146
+
147
+ private
148
+
149
+
150
+ def cleanup
151
+ @request = nil
152
+ @body = nil
153
+ @headers = nil
154
+ @reason = nil
155
+ @headers_complete = false
156
+ end
133
157
  end
134
158
  end
135
159
  end
@@ -1,3 +1,5 @@
1
+ require 'rubyntlm'
2
+
1
3
  module UV
2
4
  module Http
3
5
  class Request < ::Libuv::Q::DeferredPromise
@@ -9,9 +11,7 @@ module UV
9
11
  CRLF="\r\n"
10
12
 
11
13
 
12
- attr_reader :path
13
- attr_reader :method
14
- attr_reader :headers, :options
14
+ attr_reader :path, :method, :options
15
15
 
16
16
 
17
17
  def cookies_hash
@@ -23,35 +23,33 @@ module UV
23
23
  end
24
24
 
25
25
 
26
- def initialize(endpoint, options, using_ntlm)
27
- super(endpoint.loop, endpoint.loop.defer)
26
+ def initialize(endpoint, options)
27
+ super(endpoint.thread, endpoint.thread.defer)
28
28
 
29
29
  @options = options
30
30
  @endpoint = endpoint
31
- @ntlm_retries = using_ntlm ? 0 : nil
31
+ @ntlm_creds = options[:ntlm]
32
32
 
33
33
  @path = options[:path]
34
34
  @method = options[:method]
35
35
  @uri = "#{endpoint.scheme}#{encode_host(endpoint.host, endpoint.port)}#{@path}"
36
+
37
+ @error = proc { |reason| reject(reason) }
36
38
  end
37
39
 
38
- def resolve(response, body)
39
- if @ntlm_retries == 0 && @headers.status == 401 && @headers[:"WWW-Authenticate"]
40
- @options[:headers][:Authorization] = @endpoint.ntlm_auth_header(@headers[:"WWW-Authenticate"])
40
+
41
+
42
+ def resolve(response, parser = nil)
43
+ if response.status == 401 && @ntlm_creds && @ntlm_retries == 0 && response[:"WWW-Authenticate"]
44
+ @options[:headers][:Authorization] = ntlm_auth_header(response[:"WWW-Authenticate"])
41
45
  @ntlm_retries += 1
42
46
 
43
- response.request = self
44
- execute(*@exec_params)
47
+ execute(@transport)
48
+ false
45
49
  else
46
- if @ntlm_retries == 1
47
- @endpoint.clear_ntlm_header
48
- end
49
-
50
- @exec_params = nil
51
- @defer.resolve({
52
- headers: @headers,
53
- body: body
54
- })
50
+ @transport = nil
51
+ @defer.resolve(response)
52
+ true
55
53
  end
56
54
  end
57
55
 
@@ -59,9 +57,15 @@ module UV
59
57
  @defer.reject(reason)
60
58
  end
61
59
 
62
- def execute(transport, error)
60
+ def execute(transport)
61
+ # configure ntlm request headers
62
+ if @options[:ntlm]
63
+ @options[:headers] ||= {}
64
+ @options[:headers][:Authorization] ||= ntlm_auth_header
65
+ end
66
+
63
67
  head, body = build_request, @options[:body]
64
- @exec_params = [transport, error]
68
+ @transport = transport
65
69
 
66
70
  @endpoint.middleware.each do |m|
67
71
  head, body = m.request(self, head, body) if m.respond_to?(:request)
@@ -94,23 +98,23 @@ module UV
94
98
 
95
99
  if body
96
100
  request_header << body
97
- transport.write(request_header).catch error
101
+ transport.write(request_header).catch @error
98
102
  elsif file
99
- transport.write(request_header).catch error
103
+ transport.write(request_header).catch @error
100
104
 
101
105
  # Send file
102
106
  fileRef = @endpoint.loop.file file, File::RDONLY
103
107
  fileRef.progress do
104
108
  # File is open and available for reading
105
109
  pSend = fileRef.send_file(transport, :raw)
106
- pSend.catch error
110
+ pSend.catch @error
107
111
  pSend.finally do
108
112
  fileRef.close
109
113
  end
110
114
  end
111
- fileRef.catch error
115
+ fileRef.catch @error
112
116
  else
113
- transport.write(request_header).catch error
117
+ transport.write(request_header).catch @error
114
118
  end
115
119
  end
116
120
 
@@ -119,19 +123,17 @@ module UV
119
123
  end
120
124
 
121
125
  def set_headers(head)
122
- @headers = head
123
- if not @headers_callback.nil?
124
- @headers_callback.call(@headers)
125
- end
126
+ @headers_callback.call(head) if @headers_callback
126
127
  end
127
128
 
129
+
130
+
128
131
  def on_headers(callback, &blk)
129
- callback ||= blk
130
- if @headers.nil?
131
- @headers_callback = callback
132
- else
133
- callback.call(@headers)
134
- end
132
+ @headers_callback = callback
133
+ end
134
+
135
+ def streaming?
136
+ @options[:streaming]
135
137
  end
136
138
 
137
139
 
@@ -173,6 +175,36 @@ module UV
173
175
 
174
176
  head
175
177
  end
178
+
179
+ def ntlm_auth_header(challenge = nil)
180
+ if @ntlm_auth && challenge.nil?
181
+ return @ntlm_auth
182
+ elsif challenge
183
+ scheme, param_str = parse_ntlm_challenge_header(challenge)
184
+ if param_str.nil?
185
+ @ntlm_auth = nil
186
+ return ntlm_auth_header(@ntlm_creds)
187
+ else
188
+ t2 = Net::NTLM::Message.decode64(param_str)
189
+ t3 = t2.response(@ntlm_creds, ntlmv2: true)
190
+ @ntlm_auth = "NTLM #{t3.encode64}"
191
+ return @ntlm_auth
192
+ end
193
+ else
194
+ @ntlm_retries = 0
195
+ domain = @ntlm_creds[:domain]
196
+ t1 = Net::NTLM::Message::Type1.new()
197
+ t1.domain = domain if domain
198
+ @ntlm_auth = "NTLM #{t1.encode64}"
199
+ return @ntlm_auth
200
+ end
201
+ end
202
+
203
+ def parse_ntlm_challenge_header(challenge)
204
+ scheme, param_str = challenge.scan(/\A(\S+)(?:\s+(.*))?\z/)[0]
205
+ return nil if scheme.nil?
206
+ return scheme, param_str
207
+ end
176
208
  end
177
209
  end
178
210
  end
@@ -1,5 +1,4 @@
1
- require 'rubyntlm'
2
-
1
+ require 'uri'
3
2
 
4
3
  module UV
5
4
  class CookieJar
@@ -28,69 +27,81 @@ module UV
28
27
  end
29
28
  end # CookieJar
30
29
 
30
+ class HttpEndpoint
31
+ class Connection < OutboundConnection
32
+ def initialize(host, port, tls, client)
33
+ @client = client
34
+ @request = nil
35
+ super(host, port)
36
+ use_tls(client.tls_options) if tls
37
+ end
31
38
 
32
- class HttpEndpoint < OutboundConnection
33
- TRANSFER_ENCODING="TRANSFER_ENCODING".freeze
34
- CONTENT_ENCODING="CONTENT_ENCODING".freeze
35
- CONTENT_LENGTH="CONTENT_LENGTH".freeze
36
- CONTENT_TYPE="CONTENT_TYPE".freeze
37
- LAST_MODIFIED="LAST_MODIFIED".freeze
38
- KEEP_ALIVE="CONNECTION".freeze
39
- LOCATION="LOCATION".freeze
40
- HOST="HOST".freeze
41
- ETAG="ETAG".freeze
42
- CRLF="\r\n".freeze
43
- HTTPS="https://".freeze
44
- HTTP="http://".freeze
39
+ attr_accessor :request
45
40
 
41
+ def on_read(data, *args) # user to define
42
+ @client.data_received(data)
43
+ end
46
44
 
47
- @@defaults = {
48
- :path => '/',
49
- :keepalive => true
50
- }
45
+ def post_init(*args)
46
+ end
51
47
 
52
- attr_reader :scheme, :host, :port, :using_tls, :loop, :cookiejar
53
- attr_reader :inactivity_timeout
48
+ def on_connect(transport) # user to define
49
+ @client.connection_ready
50
+ end
54
51
 
55
- def initialize(uri, options = {})
56
- @inactivity_timeout = options[:inactivity_timeout] ||= 10000 # default connection inactivity (post-setup) timeout
57
- @ntlm_creds = options[:ntlm]
52
+ def on_close # user to define
53
+ req = @request
54
+ @request = nil
55
+ @client.connection_closed(req)
56
+ end
58
57
 
59
- uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
60
- @https = uri.scheme == "https"
61
- uri.port ||= (@https ? 443 : 80)
62
- @scheme = @https ? HTTPS : HTTP
58
+ def close_connection(request = nil)
59
+ if request.is_a? Http::Request
60
+ @request = request
61
+ super(:after_writing)
62
+ else
63
+ super(request)
64
+ end
65
+ end
66
+ end
63
67
 
64
68
 
65
- @loop = Libuv::Loop.current || Libuv::Loop.default
66
- @host = uri.host
67
- @port = uri.port
68
- #@transport = @loop.tcp
69
+ @@defaults = {
70
+ :path => '/',
71
+ :keepalive => true
72
+ }
69
73
 
70
- # State flags
71
- @closed = true
72
- @closing = false
73
- @connecting = false
74
74
 
75
- # Current requests
76
- @pending_requests = []
77
- @staging_request = nil
78
- @waiting_response = nil
79
- @cookiejar = CookieJar.new
75
+ def initialize(host, options = {})
76
+ @queue = []
77
+ @parser = Http::Parser.new
78
+ @thread = Libuv::Loop.current || Libuv::Loop.default
79
+ @connection = nil
80
80
 
81
- # Callback methods
81
+ @options = @@defaults.merge(options)
82
+ @tls_options = options[:tls_options] || {}
83
+ @inactivity_timeout = options[:inactivity_timeout] || 10000
82
84
  @idle_timeout_method = method(:idle_timeout)
83
85
 
84
- # Manages the tokenising of response from the input stream
85
- @response = Http::Response.new
86
-
87
- # Timeout timer
88
86
  if @inactivity_timeout > 0
89
- @timer = @loop.timer
87
+ @timer = @thread.timer
90
88
  end
89
+
90
+ uri = URI.parse host
91
+ @port = uri.port
92
+ @host = uri.host
93
+ @scheme = uri.scheme
94
+ @tls = @scheme == 'https'
95
+ @cookiejar = CookieJar.new
96
+ @middleware = []
91
97
  end
92
98
 
93
99
 
100
+ attr_reader :inactivity_timeout, :thread
101
+ attr_reader :tls_options, :port, :host, :tls, :scheme
102
+ attr_reader :cookiejar, :middleware
103
+
104
+
94
105
  def get options = {}, &blk; request(:get, options, &blk); end
95
106
  def head options = {}, &blk; request(:head, options, &blk); end
96
107
  def delete options = {}, &blk; request(:delete, options, &blk); end
@@ -101,205 +112,124 @@ module UV
101
112
 
102
113
 
103
114
  def request(method, options = {}, &blk)
104
- options = @@defaults.merge(options)
115
+ options = @options.merge(options)
105
116
  options[:method] = method
106
117
 
107
118
  # Setup the request with callbacks
108
- request = Http::Request.new(self, options, @ntlm_creds)
109
- request.then(proc { |result|
110
- @waiting_response = nil
111
-
112
- if @closed || result[:headers].keep_alive
113
- next_request
119
+ request = Http::Request.new(self, options)
120
+ request.then(proc { |response|
121
+ if response.keep_alive
122
+ restart_timer
114
123
  else
115
- @closing = true
116
- @transport.close
124
+
125
+ close_connection
117
126
  end
118
127
 
119
- result
120
- }, proc { |err|
121
- @waiting_response = nil
122
128
  next_request
123
129
 
124
- ::Libuv::Q.reject(@loop, err)
130
+ response
131
+ }, proc { |err|
132
+ close_connection
133
+ next_request
134
+ ::Libuv::Q.reject(@thread, err)
125
135
  })
126
136
 
127
- ##
128
- # TODO:: Add response middleware here
129
- request.then blk if blk
130
-
131
- # Add to pending requests and schedule using the breakpoint
132
- @pending_requests << request
133
- if !@waiting_response && !@staging_request
137
+ @queue.unshift(request)
138
+ if @queue.length == 1 && @parser.request.nil?
134
139
  next_request
135
140
  end
136
141
 
137
- # return the request
138
142
  request
139
143
  end
140
144
 
141
-
142
- def next_request
143
- @staging_request = @pending_requests.shift
144
- process_request unless @staging_request.nil?
145
- end
146
-
147
- def process_request
148
- if @closed && !@connecting
149
- @transport = @loop.tcp
150
-
151
- @connecting = @staging_request
152
- ::UV.try_connect(@transport, self, @host, @port)
153
- elsif !@closing
154
- try_send
145
+ # Callbacks
146
+ def connection_ready
147
+ if @queue.length > 0
148
+ restart_timer
149
+ next_request
150
+ else
151
+ close_connection
155
152
  end
156
153
  end
157
154
 
155
+ def connection_closed(request)
156
+ # We may have closed a previous connection
157
+ if request.nil? || request == @parser.request
158
+ @connection = nil
159
+ stop_timer
158
160
 
159
- def on_connect(transport)
160
- @connecting = false
161
- @closed = false
162
-
163
- # start tls if connection is encrypted
164
- use_tls() if @https
165
-
166
- # Update timeouts
167
- stop_timer
168
- if @inactivity_timeout > 0
169
- @timer.progress @idle_timeout_method
170
- @timer.start @inactivity_timeout
161
+ @parser.eof
171
162
  end
172
-
173
- # Kick off pending requests
174
- @response.reset!
175
- try_send # we only connect if there is a request waiting
176
163
  end
177
164
 
178
-
179
- def on_close
180
- @closed = true
181
- @ntlm_auth = nil
182
- clear_staging = @connecting == @staging_request
183
- @connecting = false
184
- stop_timer
185
-
186
- # On close may be called before on data
187
- @loop.next_tick do
188
- if @closing
189
- @closing = false
190
- @connecting = false
191
-
192
- if @staging_request
193
- process_request
194
- else
195
- next_request
196
- end
197
- else
198
- if clear_staging
199
- @staging_request.reject(:connection_refused)
200
- elsif @waiting_response
201
- # Flush any processing request
202
- @response.eof if @response.request
203
-
204
- # Reject any requests waiting on a response
205
- @waiting_response.reject(:disconnected)
206
- elsif @staging_request
207
- # Try reconnect
208
- process_request
209
- end
210
- end
211
- end
165
+ def data_received(data)
166
+ restart_timer
167
+ close_connection if @parser.received(data)
212
168
  end
213
169
 
214
- def try_send
215
- @waiting_response = @staging_request
216
- @response.request = @staging_request
217
- @staging_request = nil
218
-
219
- if @ntlm_creds
220
- opts = @waiting_response.options
221
- opts[:headers] ||= {}
222
- opts = opts[:headers]
223
- opts[:Authorization] = ntlm_auth_header
170
+ def cancel_all
171
+ @queue.each do |request|
172
+ request.reject(:cancelled)
224
173
  end
225
-
226
- @timer.again if @inactivity_timeout > 0
227
- @waiting_response.execute(@transport, proc { |err|
228
- @transport.close
229
- @waiting_response.reject(err)
230
- })
174
+ if @parser.request
175
+ @parser.request.reject(:cancelled)
176
+ @parser.eof
177
+ end
178
+ @queue = []
179
+ close_connection
231
180
  end
232
181
 
233
182
 
234
- def middleware
235
- # TODO:: allow for middle ware
236
- []
237
- end
183
+ private
238
184
 
239
- def on_read(data, *args)
240
- @timer.again if @inactivity_timeout > 0
241
185
 
242
- # returns true on error
243
- # Response rejects the request
244
- if @response.receive(data)
245
- @transport.close
246
- end
247
- end
186
+ def next_request
187
+ return if @parser.request || @queue.length == 0
248
188
 
249
- def close_connection(after_writing = false)
250
- stop_timer
251
- reqs = @pending_requests
252
- @pending_requests.clear
253
- reqs.each do |request|
254
- request.reject(:close_connection)
255
- end
256
- super(after_writing) if @transport
257
- end
189
+ if @connection
190
+ req = @queue.pop
191
+ @connection.request = req
192
+ @parser.new_request(req)
258
193
 
259
- def ntlm_auth_header(challenge = nil)
260
- if @ntlm_auth && challenge.nil?
261
- return @ntlm_auth
262
- elsif challenge
263
- scheme, param_str = parse_ntlm_challenge_header(challenge)
264
- if param_str.nil?
265
- @ntlm_auth = nil
266
- return ntlm_auth_header(@ntlm_creds)
267
- else
268
- t2 = Net::NTLM::Message.decode64(param_str)
269
- t3 = t2.response(@ntlm_creds, ntlmv2: true)
270
- @ntlm_auth = "NTLM #{t3.encode64}"
271
- return @ntlm_auth
272
- end
194
+ req.execute(@connection)
273
195
  else
274
- domain = @ntlm_creds[:domain]
275
- t1 = Net::NTLM::Message::Type1.new()
276
- t1.domain = domain if domain
277
- @ntlm_auth = "NTLM #{t1.encode64}"
278
- return @ntlm_auth
196
+ new_connection
279
197
  end
280
198
  end
281
199
 
282
- def clear_ntlm_header
283
- @ntlm_auth = nil
200
+ def new_connection
201
+ if @queue.length > 0 && @connection.nil?
202
+ @connection = Connection.new(@host, @port, @tls, self)
203
+ start_timer
204
+ end
205
+ @connection
284
206
  end
285
207
 
208
+ def close_connection
209
+ return if @connection.nil?
210
+ @connection.close_connection(@parser.request)
211
+ stop_timer
212
+ @connection = nil
213
+ end
286
214
 
287
- protected
288
215
 
216
+ def start_timer
217
+ return if @timer.nil?
218
+ @timer.progress @idle_timeout_method
219
+ @timer.start @inactivity_timeout
220
+ end
289
221
 
290
- def idle_timeout
291
- @timer.stop
292
- @transport.close
222
+ def restart_timer
223
+ @timer.again unless @timer.nil?
293
224
  end
294
225
 
295
226
  def stop_timer
296
227
  @timer.stop unless @timer.nil?
297
228
  end
298
229
 
299
- def parse_ntlm_challenge_header(challenge)
300
- scheme, param_str = challenge.scan(/\A(\S+)(?:\s+(.*))?\z/)[0]
301
- return nil if scheme.nil?
302
- return scheme, param_str
230
+ def idle_timeout
231
+ @parser.reason = :timeout
232
+ close_connection
303
233
  end
304
234
  end
305
235
  end
@@ -1,3 +1,3 @@
1
1
  module UV
2
- VERSION = '1.2.3'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -66,6 +66,81 @@ module OldServer
66
66
  end
67
67
  end
68
68
 
69
+ module WeirdServer
70
+ def post_init
71
+ @parser = ::HttpParser::Parser.new(self)
72
+ @state = ::HttpParser::Parser.new_instance
73
+ @state.type = :request
74
+ end
75
+
76
+ def on_message_complete(parser)
77
+ write("HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\nnolength")
78
+ close_connection(:after_writing)
79
+ end
80
+
81
+ def on_read(data, connection)
82
+ if @parser.parse(@state, data)
83
+ p 'parse error'
84
+ p @state.error
85
+ end
86
+ end
87
+ end
88
+
89
+ module BrokenServer
90
+ @@req = 0
91
+
92
+ def post_init
93
+ @parser = ::HttpParser::Parser.new(self)
94
+ @state = ::HttpParser::Parser.new_instance
95
+ @state.type = :request
96
+ end
97
+
98
+ def on_message_complete(parser)
99
+ if @@req == 0
100
+ @state = ::HttpParser::Parser.new_instance
101
+ write("HTTP/1.1 401 Unauthorized\r\nContent-type: text/ht")
102
+ close_connection(:after_writing)
103
+ else
104
+ write("HTTP/1.1 200 OK\r\nContent-type: text/html\r\nContent-length: 3\r\n\r\nyes")
105
+ end
106
+ @@req += 1
107
+ end
108
+
109
+ def on_read(data, connection)
110
+ if @parser.parse(@state, data)
111
+ p 'parse error'
112
+ p @state.error
113
+ end
114
+ end
115
+ end
116
+
117
+ module SlowServer
118
+ @@req = 0
119
+
120
+ def post_init
121
+ @parser = ::HttpParser::Parser.new(self)
122
+ @state = ::HttpParser::Parser.new_instance
123
+ @state.type = :request
124
+ end
125
+
126
+ def on_message_complete(parser)
127
+ if @@req == 0
128
+ @state = ::HttpParser::Parser.new_instance
129
+ write("HTTP/1.1 200 OK\r\nContent-")
130
+ else
131
+ write("HTTP/1.1 200 OK\r\nContent-type: text/html\r\nContent-length: 3\r\n\r\nokg")
132
+ end
133
+ @@req += 1
134
+ end
135
+
136
+ def on_read(data, connection)
137
+ if @parser.parse(@state, data)
138
+ p 'parse error'
139
+ p @state.error
140
+ end
141
+ end
142
+ end
143
+
69
144
 
70
145
  describe UV::HttpEndpoint do
71
146
  before :each do
@@ -106,13 +181,45 @@ describe UV::HttpEndpoint do
106
181
  }
107
182
 
108
183
  expect(@general_failure).to eq([])
109
- expect(@response[:headers][:"Content-type"]).to eq('text/html')
110
- expect(@response[:headers].http_version).to eq('1.1')
111
- expect(@response[:headers].status).to eq(200)
112
- expect(@response[:headers].cookies).to eq({})
113
- expect(@response[:headers].keep_alive).to eq(true)
184
+ expect(@response[:"Content-type"]).to eq('text/html')
185
+ expect(@response.http_version).to eq('1.1')
186
+ expect(@response.status).to eq(200)
187
+ expect(@response.cookies).to eq({})
188
+ expect(@response.keep_alive).to eq(true)
114
189
 
115
- expect(@response[:body]).to eq('y')
190
+ expect(@response.body).to eq('y')
191
+ end
192
+
193
+ it "should return the response when no length is given and the connection is closed" do
194
+ # I've seen IoT devices do this (projector screen controllers etc)
195
+ @loop.run { |logger|
196
+ logger.progress do |level, errorid, error|
197
+ begin
198
+ @general_failure << "Log called: #{level}: #{errorid}\n#{error.message}\n#{error.backtrace.join("\n")}\n"
199
+ rescue Exception
200
+ @general_failure << 'error in logger'
201
+ end
202
+ end
203
+
204
+ tcp = UV.start_server '127.0.0.1', 3250, WeirdServer
205
+ server = UV::HttpEndpoint.new 'http://127.0.0.1:3250'
206
+
207
+ request = server.get(:path => '/')
208
+ request.then(proc { |response|
209
+ @response = response
210
+ tcp.close
211
+ @loop.stop
212
+ }, @request_failure)
213
+ }
214
+
215
+ expect(@general_failure).to eq([])
216
+ expect(@response[:"Content-type"]).to eq('text/html')
217
+ expect(@response.http_version).to eq('1.1')
218
+ expect(@response.status).to eq(200)
219
+ expect(@response.cookies).to eq({})
220
+ expect(@response.keep_alive).to eq(true)
221
+
222
+ expect(@response.body).to eq('nolength')
116
223
  end
117
224
 
118
225
  it "should send multiple requests on the same connection" do
@@ -128,13 +235,13 @@ describe UV::HttpEndpoint do
128
235
  tcp = UV.start_server '127.0.0.1', 3250, HttpServer
129
236
  server = UV::HttpEndpoint.new 'http://127.0.0.1:3250'
130
237
 
131
- request = server.get(:path => '/')
238
+ request = server.get(path: '/', req: 1)
132
239
  request.then(proc { |response|
133
240
  @response = response
134
241
  #@loop.stop
135
242
  }, @request_failure)
136
243
 
137
- request2 = server.get(:path => '/')
244
+ request2 = server.get(path: '/', req: 2)
138
245
  request2.then(proc { |response|
139
246
  @response2 = response
140
247
  tcp.close
@@ -143,17 +250,17 @@ describe UV::HttpEndpoint do
143
250
  }
144
251
 
145
252
  expect(@general_failure).to eq([])
146
- expect(@response[:headers][:"Content-type"]).to eq('text/html')
147
- expect(@response[:headers].http_version).to eq('1.1')
148
- expect(@response[:headers].status).to eq(200)
149
- expect(@response[:headers].cookies).to eq({})
150
- expect(@response[:headers].keep_alive).to eq(true)
151
-
152
- expect(@response2[:headers][:"Content-type"]).to eq('text/html')
153
- expect(@response2[:headers].http_version).to eq('1.1')
154
- expect(@response2[:headers].status).to eq(200)
155
- expect(@response2[:headers].cookies).to eq({})
156
- expect(@response2[:headers].keep_alive).to eq(true)
253
+ expect(@response[:"Content-type"]).to eq('text/html')
254
+ expect(@response.http_version).to eq('1.1')
255
+ expect(@response.status).to eq(200)
256
+ expect(@response.cookies).to eq({})
257
+ expect(@response.keep_alive).to eq(true)
258
+
259
+ expect(@response2[:"Content-type"]).to eq('text/html')
260
+ expect(@response2.http_version).to eq('1.1')
261
+ expect(@response2.status).to eq(200)
262
+ expect(@response2.cookies).to eq({})
263
+ expect(@response2.keep_alive).to eq(true)
157
264
  end
158
265
  end
159
266
 
@@ -180,11 +287,11 @@ describe UV::HttpEndpoint do
180
287
  }
181
288
 
182
289
  expect(@general_failure).to eq([])
183
- expect(@response[:headers][:"Content-type"]).to eq('text/html')
184
- expect(@response[:headers].http_version).to eq('1.0')
185
- expect(@response[:headers].status).to eq(200)
186
- expect(@response[:headers].cookies).to eq({})
187
- expect(@response[:headers].keep_alive).to eq(false)
290
+ expect(@response[:"Content-type"]).to eq('text/html')
291
+ expect(@response.http_version).to eq('1.0')
292
+ expect(@response.status).to eq(200)
293
+ expect(@response.cookies).to eq({})
294
+ expect(@response.keep_alive).to eq(false)
188
295
  end
189
296
 
190
297
  it "should send multiple requests" do
@@ -197,8 +304,8 @@ describe UV::HttpEndpoint do
197
304
  end
198
305
  end
199
306
 
200
- tcp = UV.start_server '127.0.0.1', 3250, OldServer
201
- server = UV::HttpEndpoint.new 'http://127.0.0.1:3250'
307
+ tcp = UV.start_server '127.0.0.1', 3251, OldServer
308
+ server = UV::HttpEndpoint.new 'http://127.0.0.1:3251'
202
309
 
203
310
  request = server.get(:path => '/')
204
311
  request.then(proc { |response|
@@ -215,17 +322,17 @@ describe UV::HttpEndpoint do
215
322
  }
216
323
 
217
324
  expect(@general_failure).to eq([])
218
- expect(@response[:headers][:"Content-type"]).to eq('text/html')
219
- expect(@response[:headers].http_version).to eq('1.0')
220
- expect(@response[:headers].status).to eq(200)
221
- expect(@response[:headers].cookies).to eq({})
222
- expect(@response[:headers].keep_alive).to eq(false)
223
-
224
- expect(@response2[:headers][:"Content-type"]).to eq('text/html')
225
- expect(@response2[:headers].http_version).to eq('1.0')
226
- expect(@response2[:headers].status).to eq(200)
227
- expect(@response2[:headers].cookies).to eq({})
228
- expect(@response2[:headers].keep_alive).to eq(false)
325
+ expect(@response[:"Content-type"]).to eq('text/html')
326
+ expect(@response.http_version).to eq('1.0')
327
+ expect(@response.status).to eq(200)
328
+ expect(@response.cookies).to eq({})
329
+ expect(@response.keep_alive).to eq(false)
330
+
331
+ expect(@response2[:"Content-type"]).to eq('text/html')
332
+ expect(@response2.http_version).to eq('1.0')
333
+ expect(@response2.status).to eq(200)
334
+ expect(@response2.cookies).to eq({})
335
+ expect(@response2.keep_alive).to eq(false)
229
336
  end
230
337
  end
231
338
 
@@ -240,8 +347,8 @@ describe UV::HttpEndpoint do
240
347
  end
241
348
  end
242
349
 
243
- tcp = UV.start_server '127.0.0.1', 3250, NTLMServer
244
- server = UV::HttpEndpoint.new 'http://127.0.0.1:3250', ntlm: {
350
+ tcp = UV.start_server '127.0.0.1', 3252, NTLMServer
351
+ server = UV::HttpEndpoint.new 'http://127.0.0.1:3252', ntlm: {
245
352
  user: 'username',
246
353
  password: 'password',
247
354
  domain: 'domain'
@@ -256,12 +363,98 @@ describe UV::HttpEndpoint do
256
363
  }
257
364
 
258
365
  expect(@general_failure).to eq([])
259
- expect(@response[:headers][:"Content-type"]).to eq('text/html')
260
- expect(@response[:headers].http_version).to eq('1.1')
261
- expect(@response[:headers].status).to eq(200)
262
- expect(@response[:headers].cookies).to eq({})
263
- expect(@response[:headers].keep_alive).to eq(true)
264
- expect(@response[:body]).to eq('y')
366
+ expect(@response[:"Content-type"]).to eq('text/html')
367
+ expect(@response.http_version).to eq('1.1')
368
+ expect(@response.status).to eq(200)
369
+ expect(@response.cookies).to eq({})
370
+ expect(@response.keep_alive).to eq(true)
371
+ expect(@response.body).to eq('y')
372
+ end
373
+ end
374
+
375
+ describe 'when things go wrong' do
376
+ it "should reconnect after connection dropped and continue sending requests" do
377
+ @loop.run { |logger|
378
+ logger.progress do |level, errorid, error|
379
+ begin
380
+ @general_failure << "Log called: #{level}: #{errorid}\n#{error.message}\n#{error.backtrace.join("\n")}\n"
381
+ rescue Exception
382
+ @general_failure << 'error in logger'
383
+ end
384
+ end
385
+
386
+ tcp = UV.start_server '127.0.0.1', 6353, BrokenServer
387
+ server = UV::HttpEndpoint.new 'http://127.0.0.1:6353'
388
+
389
+ @response = nil
390
+ request = server.get(:path => '/')
391
+ request.then(proc { |response|
392
+ @response = response
393
+ #@loop.stop
394
+ }, proc { |error|
395
+ @error = error
396
+ })
397
+
398
+ request2 = server.get(:path => '/')
399
+ request2.then(proc { |response|
400
+ @response2 = response
401
+ tcp.close
402
+ @loop.stop
403
+ }, @request_failure)
404
+ }
405
+
406
+ expect(@general_failure).to eq([])
407
+ expect(@response).to eq(nil)
408
+ expect(@error).to eq(:partial_response)
409
+
410
+ expect(@response2[:"Content-type"]).to eq('text/html')
411
+ expect(@response2.http_version).to eq('1.1')
412
+ expect(@response2.status).to eq(200)
413
+ expect(@response2.cookies).to eq({})
414
+ expect(@response2.keep_alive).to eq(true)
415
+ expect(@response2.body).to eq('yes')
416
+ end
417
+
418
+ it "should reconnect after timeout and continue sending requests" do
419
+ @loop.run { |logger|
420
+ logger.progress do |level, errorid, error|
421
+ begin
422
+ @general_failure << "Log called: #{level}: #{errorid}\n#{error.message}\n#{error.backtrace.join("\n")}\n"
423
+ rescue Exception
424
+ @general_failure << 'error in logger'
425
+ end
426
+ end
427
+
428
+ tcp = UV.start_server '127.0.0.1', 6363, SlowServer
429
+ server = UV::HttpEndpoint.new 'http://127.0.0.1:6363', inactivity_timeout: 500
430
+
431
+ @response = nil
432
+ request = server.get(:path => '/')
433
+ request.then(proc { |response|
434
+ @response = response
435
+ #@loop.stop
436
+ }, proc { |error|
437
+ @error = error
438
+ })
439
+
440
+ request2 = server.get(:path => '/')
441
+ request2.then(proc { |response|
442
+ @response2 = response
443
+ tcp.close
444
+ @loop.stop
445
+ }, @request_failure)
446
+ }
447
+
448
+ expect(@general_failure).to eq([])
449
+ expect(@response).to eq(nil)
450
+ expect(@error).to eq(:timeout)
451
+
452
+ expect(@response2[:"Content-type"]).to eq('text/html')
453
+ expect(@response2.http_version).to eq('1.1')
454
+ expect(@response2.status).to eq(200)
455
+ expect(@response2.cookies).to eq({})
456
+ expect(@response2.keep_alive).to eq(true)
457
+ expect(@response2.body).to eq('okg')
265
458
  end
266
459
  end
267
460
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uv-rays
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen von Takach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-19 00:00:00.000000000 Z
11
+ date: 2016-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: libuv
@@ -180,8 +180,8 @@ files:
180
180
  - lib/uv-rays/buffered_tokenizer.rb
181
181
  - lib/uv-rays/connection.rb
182
182
  - lib/uv-rays/http/encoding.rb
183
+ - lib/uv-rays/http/parser.rb
183
184
  - lib/uv-rays/http/request.rb
184
- - lib/uv-rays/http/response.rb
185
185
  - lib/uv-rays/http_endpoint.rb
186
186
  - lib/uv-rays/scheduler.rb
187
187
  - lib/uv-rays/scheduler/cron.rb