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