httpkit 0.6.0.pre.3 → 0.6.0.pre.5
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 +15 -0
- data/.rspec +0 -1
- data/.travis.yml +5 -4
- data/Gemfile +0 -2
- data/Gemfile.devtools +27 -22
- data/README.md +11 -13
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/reek.yml +11 -5
- data/config/rubocop.yml +47 -5
- data/examples/echo_server.rb +2 -2
- data/examples/getting_started.rb +16 -10
- data/httpkit.gemspec +5 -4
- data/lib/httpkit.rb +15 -5
- data/lib/httpkit/body.rb +44 -33
- data/lib/httpkit/client.rb +52 -23
- data/lib/httpkit/client/body_handler.rb +21 -0
- data/lib/httpkit/client/{keep_alive.rb → keep_alive_handler.rb} +1 -1
- data/lib/httpkit/client/mandatory_handler.rb +29 -0
- data/lib/httpkit/client/{timeouts.rb → timeouts_handler.rb} +1 -1
- data/lib/httpkit/connection/eventmachine.rb +4 -4
- data/lib/httpkit/request.rb +46 -10
- data/lib/httpkit/response.rb +37 -5
- data/lib/httpkit/serializer.rb +33 -25
- data/lib/httpkit/server.rb +14 -19
- data/lib/httpkit/server/body_handler.rb +25 -0
- data/lib/httpkit/server/{keep_alive.rb → keep_alive_handler.rb} +22 -13
- data/lib/httpkit/server/mandatory_handler.rb +23 -0
- data/lib/httpkit/server/{timeouts.rb → timeouts_handler.rb} +1 -1
- data/lib/httpkit/support/handler_manager.rb +8 -5
- data/lib/httpkit/support/message.rb +28 -15
- data/lib/httpkit/version.rb +1 -1
- data/spec/integration/keep_alive_spec.rb +6 -7
- data/spec/integration/smoke_spec.rb +4 -4
- data/spec/integration/streaming_spec.rb +2 -3
- data/spec/integration/timeouts_spec.rb +6 -6
- data/spec/shared/integration/server_client_pair.rb +1 -1
- data/spec/spec_helper.rb +3 -2
- data/spec/support/handler.rb +1 -1
- data/spec/support/helper.rb +6 -4
- data/spec/unit/body_spec.rb +6 -0
- data/spec/unit/client/keep_alive_handler_spec.rb +6 -0
- data/spec/unit/client/mandatory_handler_spec.rb +31 -0
- data/spec/unit/client/timeouts_handler_spec.rb +6 -0
- data/spec/unit/client_spec.rb +83 -34
- data/spec/unit/connection/eventmachine_spec.rb +12 -13
- data/spec/unit/httpkit_spec.rb +65 -24
- data/spec/unit/promise_spec.rb +1 -1
- data/spec/unit/request_spec.rb +2 -10
- data/spec/unit/response_spec.rb +7 -15
- data/spec/unit/serializer_spec.rb +83 -0
- data/spec/unit/server/{keep_alive_spec.rb → keep_alive_handler_spec.rb} +5 -2
- data/spec/unit/server/mandatory_handler_spec.rb +30 -0
- data/spec/unit/server/timeouts_handler_spec.rb +6 -0
- data/spec/unit/server_spec.rb +26 -32
- data/spec/unit/support/handler_manager_spec.rb +38 -7
- data/spec/unit/support/message_spec.rb +45 -20
- metadata +57 -36
- data/lib/httpkit/serializer/encoding.rb +0 -43
data/lib/httpkit/client.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module HTTPkit
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
4
6
|
class Client
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
DoubleRequestError = Class.new(Error) do
|
8
|
+
attr_reader :client, :request
|
9
|
+
|
10
|
+
def initialize(client, request)
|
11
|
+
@client, @request = client, request
|
12
|
+
super(sprintf('%s %s already has sequence number %d',
|
13
|
+
request.http_method, request.uri, sequence))
|
14
|
+
end
|
15
|
+
|
16
|
+
def sequence
|
17
|
+
request.sequence(client)
|
18
|
+
end
|
19
|
+
end
|
9
20
|
|
10
21
|
include Support::HandlerManager::Setup
|
11
22
|
include Connection::Status
|
@@ -14,9 +25,16 @@ module HTTPkit
|
|
14
25
|
Connection::EventMachine.start_client(config, self)
|
15
26
|
end
|
16
27
|
|
28
|
+
def self.request(*args)
|
29
|
+
uri = URI(args[1])
|
30
|
+
client = start(address: uri.host, port: uri.port)
|
31
|
+
client.request(*args)
|
32
|
+
end
|
33
|
+
|
17
34
|
attr_reader :config
|
18
35
|
|
19
36
|
def initialize(config, connection)
|
37
|
+
@sequence = 0
|
20
38
|
@config = config
|
21
39
|
@requests = []
|
22
40
|
|
@@ -32,12 +50,8 @@ module HTTPkit
|
|
32
50
|
def perform(request)
|
33
51
|
served = Promise.new
|
34
52
|
|
35
|
-
|
36
|
-
|
37
|
-
else
|
38
|
-
@requests << [request, served]
|
39
|
-
perform!(request)
|
40
|
-
end
|
53
|
+
request.sequence(self, @sequence += 1)
|
54
|
+
perform!(request, served).resume if validate(request, served)
|
41
55
|
|
42
56
|
served
|
43
57
|
end
|
@@ -46,39 +60,49 @@ module HTTPkit
|
|
46
60
|
request, served = find_request
|
47
61
|
|
48
62
|
if request
|
63
|
+
# XXX: fulfillment should happen after invoke(:receive), so that all
|
64
|
+
# handlers have run when the application code resumes
|
49
65
|
served.fulfill(response)
|
50
|
-
@handlers.
|
66
|
+
request, response = @handlers.invoke(:receive, request, response)
|
51
67
|
response.closed { finish(request) }
|
52
68
|
end
|
53
69
|
end
|
54
70
|
|
55
71
|
def finish(request)
|
56
|
-
@requests.delete_if
|
57
|
-
|
72
|
+
@requests.delete_if do |(other)|
|
73
|
+
request.sequence(self) == other.sequence(self)
|
74
|
+
end
|
75
|
+
@handlers.invoke(:finish, request)
|
58
76
|
end
|
59
77
|
|
60
78
|
def teardown(reason)
|
61
79
|
@requests.each do |_, served|
|
62
80
|
served.reject(reason)
|
63
|
-
|
64
|
-
|
65
|
-
end
|
81
|
+
next unless (response = served.value)
|
82
|
+
response.reject_closed(reason)
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
69
86
|
private
|
70
87
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
88
|
+
def validate(request, served)
|
89
|
+
if closed?
|
90
|
+
served.reject(@connection.closed.reason)
|
91
|
+
false
|
92
|
+
elsif request.sequence(self) != @sequence
|
93
|
+
served.reject(DoubleRequestError.new(self, request))
|
94
|
+
false
|
95
|
+
else
|
96
|
+
true
|
97
|
+
end
|
74
98
|
end
|
75
99
|
|
76
|
-
def perform!(request)
|
100
|
+
def perform!(request, served)
|
77
101
|
Fiber.new do
|
78
|
-
|
79
|
-
@
|
102
|
+
request, _ = @handlers.invoke(:perform, request)
|
103
|
+
@requests << [request, served]
|
80
104
|
@connection.serialize(request)
|
81
|
-
end
|
105
|
+
end
|
82
106
|
end
|
83
107
|
|
84
108
|
def find_request
|
@@ -90,5 +114,10 @@ module HTTPkit
|
|
90
114
|
@connection.on_message = method(:receive)
|
91
115
|
@connection.closed.then(method(:teardown), method(:teardown))
|
92
116
|
end
|
117
|
+
|
118
|
+
def setup_handlers
|
119
|
+
(@config[:handlers] ||= []).push(MandatoryHandler.new, BodyHandler.new)
|
120
|
+
super
|
121
|
+
end
|
93
122
|
end
|
94
123
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module HTTPkit
|
4
|
+
class Client::BodyHandler
|
5
|
+
def perform(request)
|
6
|
+
request = request.with_headers(request.body_headers)
|
7
|
+
|
8
|
+
yield banana_request(request)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def banana_request(request)
|
14
|
+
if !request.body_included?
|
15
|
+
request.with_body('')
|
16
|
+
else
|
17
|
+
request
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module HTTPkit
|
4
|
+
class Client::MandatoryHandler
|
5
|
+
USER_AGENT = 'User-Agent'.freeze
|
6
|
+
USER_AGENT_VALUE = "httpkit/#{HTTPkit::VERSION}".freeze
|
7
|
+
HOST = 'Host'.freeze
|
8
|
+
HOST_VALUE = '%s:%d'.freeze
|
9
|
+
|
10
|
+
def setup(config, _, _)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform(request)
|
15
|
+
yield request.with_headers(missing_headers(request))
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def missing_headers(request)
|
21
|
+
headers.reject { |k, _| request.headers.key?(k) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def headers
|
25
|
+
host = sprintf(HOST_VALUE, @config[:address], @config[:port])
|
26
|
+
{ USER_AGENT => USER_AGENT_VALUE, HOST => host }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -24,7 +24,6 @@ module HTTPkit
|
|
24
24
|
|
25
25
|
def serialize(message)
|
26
26
|
serializer = Serializer.new(message, method(:send_data))
|
27
|
-
serializer.setup_body
|
28
27
|
serializer.serialize
|
29
28
|
end
|
30
29
|
|
@@ -33,12 +32,13 @@ module HTTPkit
|
|
33
32
|
# p data
|
34
33
|
@parser << data
|
35
34
|
rescue => ex
|
35
|
+
# puts [ex.message, *ex.backtrace].join("\n\t")
|
36
36
|
close(ex)
|
37
37
|
end
|
38
38
|
|
39
39
|
# def send_data(data)
|
40
|
-
#
|
41
|
-
# p data
|
40
|
+
# p [__id__, :send, data]
|
41
|
+
# # p data
|
42
42
|
# super
|
43
43
|
# end
|
44
44
|
|
@@ -61,7 +61,7 @@ module HTTPkit
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def on_body(chunk)
|
64
|
-
@message.body.
|
64
|
+
@message.body.write(chunk)
|
65
65
|
end
|
66
66
|
|
67
67
|
def on_message_complete
|
data/lib/httpkit/request.rb
CHANGED
@@ -1,19 +1,55 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module HTTPkit
|
4
|
-
Request = Struct.new(:http_method, :uri, :headers, :body, :http_version)
|
5
|
-
|
6
4
|
class Request
|
7
|
-
private :http_method=, :uri=, :headers=, :body=
|
8
|
-
|
9
5
|
include Support::Message
|
6
|
+
include Adamantium
|
7
|
+
|
8
|
+
attr_reader :http_method, :uri, :headers, :body, :http_version
|
9
|
+
|
10
|
+
def initialize(http_method, uri, headers = {}, body = '',
|
11
|
+
http_version = 1.1)
|
12
|
+
@http_method = http_method
|
13
|
+
# @uri = URI('http://' + uri)
|
14
|
+
@uri = URI.join('http:///', uri).request_uri
|
15
|
+
@headers = headers
|
16
|
+
@body = Body.build(body)
|
17
|
+
@http_version = http_version
|
18
|
+
@sequences = SequenceHash.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_headers(new_headers)
|
22
|
+
with(2, headers.merge(new_headers))
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_body(new_body)
|
26
|
+
with(3, Body.build(new_body))
|
27
|
+
end
|
10
28
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
URI.join('http:///', uri).request_uri,
|
16
|
-
headers, Body.build(body), 1.1)
|
29
|
+
def with(index, argument)
|
30
|
+
args = [http_method, uri, headers, body, http_version]
|
31
|
+
args[index] = argument
|
32
|
+
new_self(args)
|
17
33
|
end
|
34
|
+
|
35
|
+
def new_self(args)
|
36
|
+
other = self.class.new(*args)
|
37
|
+
@sequences.each { |(obj, id)| other.sequence(obj, id) }
|
38
|
+
other
|
39
|
+
end
|
40
|
+
|
41
|
+
def sequence(obj, id = nil)
|
42
|
+
@sequences[obj.object_id] ||= id
|
43
|
+
end
|
44
|
+
|
45
|
+
def body_included?
|
46
|
+
body_present?
|
47
|
+
end
|
48
|
+
|
49
|
+
def body_present?
|
50
|
+
!body.length_known? || !body.length.zero?
|
51
|
+
end
|
52
|
+
|
53
|
+
SequenceHash = Class.new(Hash) { include Adamantium::Mutable }
|
18
54
|
end
|
19
55
|
end
|
data/lib/httpkit/response.rb
CHANGED
@@ -1,15 +1,46 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module HTTPkit
|
4
|
-
Response = Struct.new(:status, :headers, :body, :http_version)
|
5
|
-
|
6
4
|
class Response
|
7
|
-
|
5
|
+
NOT_INCLUDED = [204, 304]
|
6
|
+
NOT_PRESENT = [205]
|
8
7
|
|
9
8
|
include Support::Message
|
9
|
+
include Adamantium
|
10
|
+
|
11
|
+
attr_reader :status, :headers, :body, :http_version
|
12
|
+
|
13
|
+
def initialize(status, headers = {}, body = '', http_version = 1.1)
|
14
|
+
@status = status
|
15
|
+
@headers = headers
|
16
|
+
@body = Body.build(body)
|
17
|
+
@http_version = http_version
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_headers(new_headers)
|
21
|
+
with(1, headers.merge(new_headers))
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_body(new_body)
|
25
|
+
with(2, Body.build(new_body))
|
26
|
+
end
|
27
|
+
|
28
|
+
def with(index, argument)
|
29
|
+
args = [status, headers, body, http_version]
|
30
|
+
args[index] = argument
|
31
|
+
new_self(args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_self(args)
|
35
|
+
self.class.new(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def body_included?
|
39
|
+
!informational? && !NOT_INCLUDED.include?(status)
|
40
|
+
end
|
10
41
|
|
11
|
-
def
|
12
|
-
|
42
|
+
def body_present?
|
43
|
+
!informational? && !NOT_PRESENT.include?(status)
|
13
44
|
end
|
14
45
|
|
15
46
|
def status_name
|
@@ -42,6 +73,7 @@ module HTTPkit
|
|
42
73
|
|
43
74
|
UNKNOWN_STATUS = 'Unknown Status'.freeze
|
44
75
|
|
76
|
+
# TODO: double-check with RFC7230
|
45
77
|
STATUS_NAMES =
|
46
78
|
{ 100 => 'Continue'.freeze,
|
47
79
|
101 => 'Switching Protocols'.freeze,
|
data/lib/httpkit/serializer.rb
CHANGED
@@ -1,48 +1,36 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
module HTTPkit
|
4
|
+
# Streaming HTTP message serializer.
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
#
|
6
8
|
class Serializer
|
7
9
|
REQUEST_LINE = "%s %s HTTP/%.1f\r\n".freeze
|
8
10
|
RESPONSE_LINE = "HTTP/%.1f %d %s\r\n".freeze
|
9
11
|
HEADER = "%s: %s\r\n".freeze
|
12
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
|
13
|
+
CHUNKED = 'chunked'.freeze
|
14
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
10
15
|
CRLF = "\r\n".freeze
|
11
16
|
|
12
|
-
include Encoding
|
13
|
-
|
14
|
-
extend Forwardable
|
15
|
-
def_delegator :@message, :headers
|
16
|
-
def_delegator :@message, :body
|
17
|
-
|
18
17
|
def initialize(message, writer)
|
19
|
-
@message = message
|
20
|
-
@writer = writer
|
18
|
+
@message, @writer = message, writer
|
21
19
|
end
|
22
20
|
|
23
21
|
def serialize
|
24
|
-
write(
|
22
|
+
write(first_line)
|
25
23
|
write(header_block)
|
26
|
-
|
27
24
|
write_body
|
28
25
|
end
|
29
26
|
|
30
|
-
def setup_body
|
31
|
-
if body.closed.pending?
|
32
|
-
setup_chunked_encoding
|
33
|
-
setup_chunked_streaming
|
34
|
-
else
|
35
|
-
setup_identity_encoding
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
27
|
private
|
40
28
|
|
41
29
|
def write(*data)
|
42
|
-
@writer.call(data.join
|
30
|
+
@writer.call(data.join)
|
43
31
|
end
|
44
32
|
|
45
|
-
def
|
33
|
+
def first_line
|
46
34
|
if Request === @message
|
47
35
|
request_line
|
48
36
|
else
|
@@ -61,15 +49,35 @@ module HTTPkit
|
|
61
49
|
end
|
62
50
|
|
63
51
|
def header_block
|
64
|
-
headers
|
52
|
+
@message.headers
|
53
|
+
.reduce('') { |a, e| a << header_line(*e) } + CRLF
|
65
54
|
end
|
66
55
|
|
67
56
|
def header_line(key, value)
|
68
57
|
if value.to_s.empty?
|
69
58
|
''
|
70
59
|
else
|
71
|
-
sprintf(HEADER, key,
|
60
|
+
sprintf(HEADER, key,
|
61
|
+
value.respond_to?(:join) ? value.join(', ') : value)
|
72
62
|
end
|
73
63
|
end
|
64
|
+
|
65
|
+
def write_body
|
66
|
+
body = @message.body
|
67
|
+
if !body.length_known?
|
68
|
+
body.each { |chunk| write_chunk(chunk) }
|
69
|
+
write_chunk!('')
|
70
|
+
else
|
71
|
+
write(body)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def write_chunk(chunk)
|
76
|
+
write_chunk!(chunk) unless chunk.empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_chunk!(chunk)
|
80
|
+
write(chunk.bytesize.to_s(16), CRLF, chunk, CRLF)
|
81
|
+
end
|
74
82
|
end
|
75
83
|
end
|