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 +4 -4
- data/lib/uv-rays.rb +1 -1
- data/lib/uv-rays/http/{response.rb → parser.rb} +55 -31
- data/lib/uv-rays/http/request.rb +69 -37
- data/lib/uv-rays/http_endpoint.rb +128 -198
- data/lib/uv-rays/version.rb +1 -1
- data/spec/http_endpoint_spec.rb +238 -45
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbe9019bb0b5b548daf337f12f4f6d6eed42d3d6
|
4
|
+
data.tar.gz: 1b5779c34179033260c79ec5afeb088c470a8180
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e14c928bf63dbd3ebfd230dbf2dfcff281328e7e6376d551838ba0f3a3fd7c8d5891e8ab5b54ca913267b5053f26cbe53b31062b0cfc7ec40acf556fd9384e2
|
7
|
+
data.tar.gz: e3aad461e23c4e60a52fbea8114a8e54b7fcb94f327f08ca6db810f5c8a41e3aa9a607bfb4185f7136f2bbd78dda3a6cfdd7216db04651ff2790f957f1035e42
|
data/lib/uv-rays.rb
CHANGED
@@ -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/
|
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
|
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
|
-
|
33
|
+
attr_reader :request
|
25
34
|
|
26
35
|
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
114
|
-
bod = @body
|
115
|
-
|
116
|
-
# Clean up memory
|
117
|
-
@request = nil
|
118
|
-
@body = nil
|
120
|
+
@headers.body = @body
|
119
121
|
|
120
|
-
|
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 @
|
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
|
data/lib/uv-rays/http/request.rb
CHANGED
@@ -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
|
27
|
-
super(endpoint.
|
26
|
+
def initialize(endpoint, options)
|
27
|
+
super(endpoint.thread, endpoint.thread.defer)
|
28
28
|
|
29
29
|
@options = options
|
30
30
|
@endpoint = endpoint
|
31
|
-
@
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
47
|
+
execute(@transport)
|
48
|
+
false
|
45
49
|
else
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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 '
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
:keepalive => true
|
50
|
-
}
|
45
|
+
def post_init(*args)
|
46
|
+
end
|
51
47
|
|
52
|
-
|
53
|
-
|
48
|
+
def on_connect(transport) # user to define
|
49
|
+
@client.connection_ready
|
50
|
+
end
|
54
51
|
|
55
|
-
|
56
|
-
|
57
|
-
|
52
|
+
def on_close # user to define
|
53
|
+
req = @request
|
54
|
+
@request = nil
|
55
|
+
@client.connection_closed(req)
|
56
|
+
end
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
76
|
-
@
|
77
|
-
@
|
78
|
-
@
|
79
|
-
@
|
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
|
-
|
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 = @
|
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 =
|
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
|
109
|
-
request.then(proc { |
|
110
|
-
|
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
|
-
|
116
|
-
|
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
|
-
|
130
|
+
response
|
131
|
+
}, proc { |err|
|
132
|
+
close_connection
|
133
|
+
next_request
|
134
|
+
::Libuv::Q.reject(@thread, err)
|
125
135
|
})
|
126
136
|
|
127
|
-
|
128
|
-
|
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
|
143
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
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
|
-
|
180
|
-
|
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
|
215
|
-
@
|
216
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
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
|
-
|
243
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
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
|
-
|
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
|
283
|
-
@
|
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
|
291
|
-
@timer.
|
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
|
300
|
-
|
301
|
-
|
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
|
data/lib/uv-rays/version.rb
CHANGED
data/spec/http_endpoint_spec.rb
CHANGED
@@ -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[:
|
110
|
-
expect(@response
|
111
|
-
expect(@response
|
112
|
-
expect(@response
|
113
|
-
expect(@response
|
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
|
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(:
|
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(:
|
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[:
|
147
|
-
expect(@response
|
148
|
-
expect(@response
|
149
|
-
expect(@response
|
150
|
-
expect(@response
|
151
|
-
|
152
|
-
expect(@response2[:
|
153
|
-
expect(@response2
|
154
|
-
expect(@response2
|
155
|
-
expect(@response2
|
156
|
-
expect(@response2
|
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[:
|
184
|
-
expect(@response
|
185
|
-
expect(@response
|
186
|
-
expect(@response
|
187
|
-
expect(@response
|
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',
|
201
|
-
server = UV::HttpEndpoint.new 'http://127.0.0.1:
|
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[:
|
219
|
-
expect(@response
|
220
|
-
expect(@response
|
221
|
-
expect(@response
|
222
|
-
expect(@response
|
223
|
-
|
224
|
-
expect(@response2[:
|
225
|
-
expect(@response2
|
226
|
-
expect(@response2
|
227
|
-
expect(@response2
|
228
|
-
expect(@response2
|
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',
|
244
|
-
server = UV::HttpEndpoint.new 'http://127.0.0.1:
|
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[:
|
260
|
-
expect(@response
|
261
|
-
expect(@response
|
262
|
-
expect(@response
|
263
|
-
expect(@response
|
264
|
-
expect(@response
|
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.
|
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-
|
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
|