uv-rays 1.2.3 → 1.3.0

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