httpkit 0.6.0.pre.3 → 0.6.0.pre.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|