async-http 0.30.4 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/async-http.gemspec +2 -2
- data/lib/async/http/body/file.rb +1 -0
- data/lib/async/http/client.rb +1 -0
- data/lib/async/http/pool.rb +2 -2
- data/lib/async/http/protocol.rb +24 -0
- data/lib/async/http/protocol/http1.rb +19 -28
- data/lib/async/http/protocol/http1/client.rb +55 -0
- data/lib/async/http/protocol/http1/connection.rb +94 -0
- data/lib/async/http/protocol/http1/request.rb +45 -0
- data/lib/async/http/protocol/http1/response.rb +37 -0
- data/lib/async/http/protocol/http1/server.rb +72 -0
- data/lib/async/http/protocol/http10.rb +9 -24
- data/lib/async/http/protocol/http10/client.rb +36 -0
- data/lib/async/http/protocol/http10/server.rb +36 -0
- data/lib/async/http/protocol/http11.rb +9 -405
- data/lib/async/http/protocol/http11/client.rb +36 -0
- data/lib/async/http/protocol/http11/server.rb +36 -0
- data/lib/async/http/protocol/http2/client.rb +1 -0
- data/lib/async/http/protocol/http2/connection.rb +1 -1
- data/lib/async/http/protocol/http2/request.rb +1 -1
- data/lib/async/http/protocol/http2/server.rb +2 -1
- data/lib/async/http/reference.rb +4 -165
- data/lib/async/http/server.rb +1 -2
- data/lib/async/http/version.rb +1 -1
- metadata +16 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20c4728c088a68663d0f336b2326d3e988a342c5d1f1cd2890a12e43cefda133
|
4
|
+
data.tar.gz: da0503f98a16003b0a2c6606e4419ebb311365dac4be7ea685b18bb994b3613c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ecdd9b68abcaf50db65f02ab8f1db4e8ce1e4a00af980510df5d1323a0d4c3b2c135d85fe3683dcf02f5e818b364fe659c44ab5df25459b687ba2a50d8db9e2
|
7
|
+
data.tar.gz: 68536e0f1ea03914f0e4b34116e7a7f738c5fbc7cf2baf9f2d87f7dd6675329c6c48b444ece69cf8a977f4184a525df1fc14b25bb3f7cb3a85c4983519551eed
|
data/async-http.gemspec
CHANGED
@@ -17,9 +17,9 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
19
|
spec.add_dependency("async", "~> 1.6")
|
20
|
-
spec.add_dependency("async-io", "~> 1.
|
20
|
+
spec.add_dependency("async-io", "~> 1.16")
|
21
21
|
|
22
|
-
spec.add_dependency("http-protocol", "~> 0.
|
22
|
+
spec.add_dependency("http-protocol", "~> 0.5.0")
|
23
23
|
|
24
24
|
# spec.add_dependency("openssl")
|
25
25
|
|
data/lib/async/http/body/file.rb
CHANGED
data/lib/async/http/client.rb
CHANGED
data/lib/async/http/pool.rb
CHANGED
@@ -136,8 +136,8 @@ module Async
|
|
136
136
|
# This is a linear search... not idea, but simple for now.
|
137
137
|
@resources.each do |resource, count|
|
138
138
|
if count < resource.multiplex
|
139
|
-
# We want to use this resource... but is it
|
140
|
-
if resource.
|
139
|
+
# We want to use this resource... but is it connected?
|
140
|
+
if resource.connected?
|
141
141
|
@resources[resource] += 1
|
142
142
|
|
143
143
|
return resource
|
data/lib/async/http/protocol.rb
CHANGED
@@ -20,3 +20,27 @@
|
|
20
20
|
|
21
21
|
require_relative 'protocol/http1'
|
22
22
|
require_relative 'protocol/https'
|
23
|
+
|
24
|
+
module Async
|
25
|
+
module HTTP
|
26
|
+
# A protocol specifies a way in which to communicate with a remote peer.
|
27
|
+
module Protocol
|
28
|
+
# A protocol must implement the following interface:
|
29
|
+
# class Protocol
|
30
|
+
# def client(stream) -> Connection
|
31
|
+
# def server(stream) -> Connection
|
32
|
+
# end
|
33
|
+
|
34
|
+
# A connection must implement the following interface:
|
35
|
+
# class Connection
|
36
|
+
# def multiplex -> can invoke call 1 or more times simultaneously.
|
37
|
+
# def reusable? -> can be used again/persistent connection.
|
38
|
+
|
39
|
+
# def connected? -> Boolean
|
40
|
+
|
41
|
+
# def call(request) -> Response
|
42
|
+
# def each -> (yield(request) -> Response)
|
43
|
+
# end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -27,37 +27,28 @@ module Async
|
|
27
27
|
module HTTP
|
28
28
|
module Protocol
|
29
29
|
# A server that supports both HTTP1.0 and HTTP1.1 semantics by detecting the version of the request.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
"HTTP/1.1" => HTTP11,
|
34
|
-
}
|
35
|
-
|
36
|
-
def initialize(stream)
|
37
|
-
super(stream, HTTP11::CRLF)
|
38
|
-
end
|
39
|
-
|
40
|
-
class << self
|
41
|
-
def client(*args)
|
42
|
-
HTTP11.new(*args)
|
43
|
-
end
|
44
|
-
|
45
|
-
alias server new
|
46
|
-
end
|
47
|
-
|
48
|
-
def create_handler(version)
|
49
|
-
if klass = HANDLERS[version]
|
50
|
-
klass.server(@stream)
|
51
|
-
else
|
52
|
-
raise RuntimeError, "Unsupported protocol version #{version}"
|
53
|
-
end
|
30
|
+
module HTTP1
|
31
|
+
def self.client(*args)
|
32
|
+
HTTP11.client(*args)
|
54
33
|
end
|
55
34
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
create_handler(version).receive_requests(&block)
|
35
|
+
def self.server(*args)
|
36
|
+
HTTP11.server(*args)
|
60
37
|
end
|
38
|
+
#
|
39
|
+
# def create_handler(version)
|
40
|
+
# if klass = HANDLERS[version]
|
41
|
+
# klass.server(@stream)
|
42
|
+
# else
|
43
|
+
# raise RuntimeError, "Unsupported protocol version #{version}"
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def receive_requests(&block)
|
48
|
+
# method, path, version = self.peek_line.split(/\s+/, 3)
|
49
|
+
#
|
50
|
+
# create_handler(version).receive_requests(&block)
|
51
|
+
# end
|
61
52
|
end
|
62
53
|
end
|
63
54
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'http/protocol/http11/connection'
|
22
|
+
require_relative '../http1/connection'
|
23
|
+
|
24
|
+
module Async
|
25
|
+
module HTTP
|
26
|
+
module Protocol
|
27
|
+
module HTTP1
|
28
|
+
module Client
|
29
|
+
# Used by the client to send requests to the remote server.
|
30
|
+
def call(request)
|
31
|
+
Async.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
|
32
|
+
|
33
|
+
# We carefully interpret https://tools.ietf.org/html/rfc7230#section-6.3.1 to implement this correctly.
|
34
|
+
begin
|
35
|
+
self.write_request(request.authority, request.method, request.path, self.version, request.headers)
|
36
|
+
rescue
|
37
|
+
# If we fail to fully write the request and body, we can retry this request.
|
38
|
+
raise RequestFailed.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Once we start writing the body, we can't recover if the request fails. That's because the body might be generated dynamically, streaming, etc.
|
42
|
+
self.write_body(request.body)
|
43
|
+
|
44
|
+
return Response.new(self, request)
|
45
|
+
rescue
|
46
|
+
# This will ensure that #reusable? returns false.
|
47
|
+
@stream.close
|
48
|
+
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'request'
|
22
|
+
require_relative 'response'
|
23
|
+
|
24
|
+
require_relative '../../body/chunked'
|
25
|
+
require_relative '../../body/fixed'
|
26
|
+
|
27
|
+
module Async
|
28
|
+
module HTTP
|
29
|
+
module Protocol
|
30
|
+
module HTTP1
|
31
|
+
module Connection
|
32
|
+
CRLF = "\r\n"
|
33
|
+
|
34
|
+
attr :stream
|
35
|
+
|
36
|
+
def read_line
|
37
|
+
@stream.read_until(CRLF) or raise EOFError
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Async::Wrapper] the underlying non-blocking IO.
|
41
|
+
def hijack
|
42
|
+
@persistent = false
|
43
|
+
|
44
|
+
@stream.flush
|
45
|
+
|
46
|
+
return @stream.io
|
47
|
+
end
|
48
|
+
|
49
|
+
def peer
|
50
|
+
@stream.io
|
51
|
+
end
|
52
|
+
|
53
|
+
attr :count
|
54
|
+
|
55
|
+
def multiplex
|
56
|
+
1
|
57
|
+
end
|
58
|
+
|
59
|
+
# Can we use this connection to make requests?
|
60
|
+
def connected?
|
61
|
+
@stream.connected?
|
62
|
+
end
|
63
|
+
|
64
|
+
def reusable?
|
65
|
+
!@stream.closed?
|
66
|
+
# !(self.closed? || @stream.closed?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
Async.logger.debug(self) {"Closing connection"}
|
71
|
+
|
72
|
+
@stream.close
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_chunked_body
|
76
|
+
Body::Chunked.new(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_fixed_body(length)
|
80
|
+
Body::Fixed.new(@stream, length)
|
81
|
+
end
|
82
|
+
|
83
|
+
def read_tunnel_body
|
84
|
+
read_remainder_body
|
85
|
+
end
|
86
|
+
|
87
|
+
def read_remainder_body
|
88
|
+
Body::Remainder.new(@stream)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative '../request'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module HTTP
|
25
|
+
module Protocol
|
26
|
+
module HTTP1
|
27
|
+
class Request < Protocol::Request
|
28
|
+
def initialize(protocol)
|
29
|
+
super(*protocol.read_request)
|
30
|
+
|
31
|
+
@protocol = protocol
|
32
|
+
end
|
33
|
+
|
34
|
+
def hijack?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def hijack
|
39
|
+
@protocol.hijack
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative '../response'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module HTTP
|
25
|
+
module Protocol
|
26
|
+
module HTTP1
|
27
|
+
class Response < Protocol::Response
|
28
|
+
def initialize(protocol, request)
|
29
|
+
super(*protocol.read_response(request.method))
|
30
|
+
|
31
|
+
@protocol = protocol
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'http/protocol/http11/connection'
|
22
|
+
require_relative '../http1/connection'
|
23
|
+
|
24
|
+
module Async
|
25
|
+
module HTTP
|
26
|
+
module Protocol
|
27
|
+
module HTTP1
|
28
|
+
module Server
|
29
|
+
def next_request
|
30
|
+
# The default is true.
|
31
|
+
return nil unless @persistent
|
32
|
+
|
33
|
+
request = Request.new(self)
|
34
|
+
|
35
|
+
unless persistent?(request.headers)
|
36
|
+
@persistent = false
|
37
|
+
end
|
38
|
+
|
39
|
+
return request
|
40
|
+
rescue
|
41
|
+
# Bad Request
|
42
|
+
write_response(self.version, 400, {}, nil)
|
43
|
+
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
|
47
|
+
# Server loop.
|
48
|
+
def each(task: Task.current)
|
49
|
+
while request = next_request
|
50
|
+
response = yield(request, self)
|
51
|
+
|
52
|
+
return if @stream.closed?
|
53
|
+
|
54
|
+
if response
|
55
|
+
write_response(self.version, response.status, response.headers, response.body, request.head?)
|
56
|
+
else
|
57
|
+
# If the request failed to generate a response, it was an internal server error:
|
58
|
+
write_response(self.version, 500, {}, nil)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gracefully finish reading the request body if it was not already done so.
|
62
|
+
request.finish
|
63
|
+
|
64
|
+
# This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
|
65
|
+
task.yield
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|