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 +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
|