hatetepe 0.5.2 → 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -4
- data/Gemfile.devtools +55 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -192
- data/Rakefile +3 -2
- data/bin/hatetepe +35 -2
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +103 -0
- data/config/rubocop.yml +58 -0
- data/config/yardstick.yml +2 -0
- data/hatetepe.gemspec +23 -27
- data/lib/hatetepe/client/keep_alive.rb +59 -0
- data/lib/hatetepe/client/timeouts.rb +19 -0
- data/lib/hatetepe/client.rb +54 -302
- data/lib/hatetepe/connection/eventmachine.rb +61 -0
- data/lib/hatetepe/connection/status.rb +28 -0
- data/lib/hatetepe/errors.rb +7 -0
- data/lib/hatetepe/promise.rb +86 -0
- data/lib/hatetepe/request.rb +15 -39
- data/lib/hatetepe/response.rb +82 -22
- data/lib/hatetepe/serializer/encoding.rb +58 -0
- data/lib/hatetepe/serializer.rb +61 -0
- data/lib/hatetepe/server/keep_alive.rb +53 -13
- data/lib/hatetepe/server/timeouts.rb +17 -0
- data/lib/hatetepe/server.rb +37 -85
- data/lib/hatetepe/support/handlers.rb +19 -0
- data/lib/hatetepe/support/keep_alive.rb +14 -0
- data/lib/hatetepe/support/message.rb +40 -0
- data/lib/hatetepe/version.rb +3 -1
- data/lib/hatetepe.rb +29 -7
- data/spec/integration/error_handling_spec.rb +7 -0
- data/spec/integration/keep_alive_spec.rb +106 -0
- data/spec/integration/smoke_spec.rb +21 -0
- data/spec/integration/streaming_spec.rb +61 -0
- data/spec/integration/timeouts_spec.rb +82 -0
- data/spec/shared/integration/server_client_pair.rb +26 -0
- data/spec/spec_helper.rb +41 -10
- data/spec/support/handler.rb +55 -0
- data/spec/support/helper.rb +74 -0
- data/spec/unit/client_spec.rb +115 -156
- data/spec/unit/connection/eventmachine_spec.rb +146 -0
- data/spec/unit/request_spec.rb +35 -0
- data/spec/unit/response_spec.rb +42 -0
- data/spec/unit/server_spec.rb +65 -100
- data/spec/unit/support/keep_alive_spec.rb +52 -0
- data/spec/unit/support/message_spec.rb +41 -0
- metadata +68 -103
- data/Gemfile.lock +0 -46
- data/LICENSE +0 -19
- data/Procfile +0 -1
- data/config.ru +0 -7
- data/examples/parallel_requests.rb +0 -32
- data/lib/hatetepe/body.rb +0 -182
- data/lib/hatetepe/builder.rb +0 -171
- data/lib/hatetepe/cli.rb +0 -61
- data/lib/hatetepe/connection.rb +0 -73
- data/lib/hatetepe/events.rb +0 -35
- data/lib/hatetepe/message.rb +0 -13
- data/lib/hatetepe/parser.rb +0 -83
- data/lib/hatetepe/server/pipeline.rb +0 -20
- data/lib/hatetepe/server/rack_app.rb +0 -39
- data/lib/rack/handler/hatetepe.rb +0 -33
- data/spec/integration/cli/start_spec.rb +0 -113
- data/spec/integration/client/keep_alive_spec.rb +0 -23
- data/spec/integration/client/timeout_spec.rb +0 -97
- data/spec/integration/server/keep_alive_spec.rb +0 -27
- data/spec/integration/server/timeout_spec.rb +0 -51
- data/spec/unit/body_spec.rb +0 -205
- data/spec/unit/builder_spec.rb +0 -372
- data/spec/unit/connection_spec.rb +0 -62
- data/spec/unit/events_spec.rb +0 -96
- data/spec/unit/parser_spec.rb +0 -209
- data/spec/unit/rack_handler_spec.rb +0 -60
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Hatetepe
|
4
|
+
class Client::KeepAlive
|
5
|
+
CONNECTION = 'Connection'.freeze
|
6
|
+
|
7
|
+
include Support::KeepAlive
|
8
|
+
|
9
|
+
def initialize(config, client, connection)
|
10
|
+
@config, @client = config, client
|
11
|
+
@connection = connection
|
12
|
+
@close = nil
|
13
|
+
@requests = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(request)
|
17
|
+
previous_request = current_request
|
18
|
+
@requests << request
|
19
|
+
|
20
|
+
if @close.nil?
|
21
|
+
await_connection_header(request)
|
22
|
+
elsif !@close
|
23
|
+
await_previous_response(previous_request) if previous_request
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive(request, response)
|
28
|
+
@requests.delete(request)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def current_request
|
34
|
+
@requests.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def await_connection_header(request)
|
38
|
+
if current_request == request
|
39
|
+
# first response has the relevant Connection header
|
40
|
+
assume_connection_header
|
41
|
+
else
|
42
|
+
# subsequent requests wait for first response
|
43
|
+
current_request.served.sync
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def assume_connection_header
|
48
|
+
current_request.served.then do |response|
|
49
|
+
@close = close_connection?(response.http_version,
|
50
|
+
response.headers[CONNECTION])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def await_previous_response(request)
|
55
|
+
response = request.served.sync
|
56
|
+
response.finished.sync
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Hatetepe
|
4
|
+
# @see EM.heartbeat_interval
|
5
|
+
class Client::Timeouts
|
6
|
+
def initialize(config, client, connection)
|
7
|
+
@config, @client = config, client
|
8
|
+
@connection = connection
|
9
|
+
|
10
|
+
@config[:timeout] ||= 2.0
|
11
|
+
@config[:connect_timeout] ||= 2.0
|
12
|
+
end
|
13
|
+
|
14
|
+
def post_init
|
15
|
+
@connection.comm_inactivity_timeout = @config[:timeout]
|
16
|
+
@connection.pending_connect_timeout = @config[:connect_timeout]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/hatetepe/client.rb
CHANGED
@@ -1,327 +1,79 @@
|
|
1
|
-
|
2
|
-
require "eventmachine"
|
3
|
-
|
4
|
-
require "hatetepe/builder"
|
5
|
-
require "hatetepe/connection"
|
6
|
-
require "hatetepe/parser"
|
7
|
-
require "hatetepe/request"
|
8
|
-
require "hatetepe/version"
|
1
|
+
# encoding: utf-8
|
9
2
|
|
10
3
|
module Hatetepe
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
ServerError = Class.new(RequestError)
|
15
|
-
end
|
16
|
-
|
17
|
-
module Hatetepe::Client
|
18
|
-
include Hatetepe::Connection
|
19
|
-
|
20
|
-
# @api private
|
21
|
-
Job = Struct.new(:fiber, :request, :sent, :response)
|
22
|
-
|
23
|
-
# The default configuration.
|
24
|
-
#
|
25
|
-
# @api public
|
26
|
-
CONFIG_DEFAULTS = {
|
27
|
-
:timeout => 5,
|
28
|
-
:connect_timeout => 5
|
29
|
-
}
|
30
|
-
|
31
|
-
# The configuration for this Client instance.
|
32
|
-
#
|
33
|
-
# @api public
|
34
|
-
attr_reader :config
|
35
|
-
|
36
|
-
# The pipe of middleware and request transmission/response reception.
|
37
|
-
#
|
38
|
-
# @api private
|
39
|
-
attr_reader :app
|
40
|
-
|
41
|
-
# Initializes a new Client instance.
|
42
|
-
#
|
43
|
-
# @param [Hash] config
|
44
|
-
# Configuration values that overwrite the defaults.
|
45
|
-
#
|
46
|
-
# @api semipublic
|
47
|
-
def initialize(config)
|
48
|
-
@config = CONFIG_DEFAULTS.merge(config)
|
49
|
-
@ssl_handshake_completed = EM::DefaultDeferrable.new
|
50
|
-
end
|
51
|
-
|
52
|
-
# Initializes the parser, request queue, and middleware pipe.
|
53
|
-
#
|
54
|
-
# @see EM::Connection#post_init
|
55
|
-
#
|
56
|
-
# @api semipublic
|
57
|
-
def post_init
|
58
|
-
@builder, @parser = Hatetepe::Builder.new, Hatetepe::Parser.new
|
59
|
-
@builder.on_write << method(:send_data)
|
60
|
-
# @builder.on_write {|data| p "|--> #{data}" }
|
61
|
-
@parser.on_response << method(:receive_response)
|
62
|
-
|
63
|
-
@queue = []
|
64
|
-
|
65
|
-
@app = proc {|request| send_request(request) }
|
4
|
+
class Client
|
5
|
+
include Support::Handlers
|
6
|
+
include Connection::Status
|
66
7
|
|
67
|
-
|
68
|
-
self.pending_connect_timeout = config[:connect_timeout]
|
8
|
+
attr_reader :config
|
69
9
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def ssl_handshake_completed
|
74
|
-
EM::Synchrony.next_tick { @ssl_handshake_completed.succeed }
|
75
|
-
end
|
76
|
-
|
77
|
-
# Feeds response data into the parser.
|
78
|
-
#
|
79
|
-
# @see EM::Connection#receive_data
|
80
|
-
#
|
81
|
-
# @param [String] data
|
82
|
-
# The received data that's gonna be fed into the parser.
|
83
|
-
#
|
84
|
-
# @api semipublic
|
85
|
-
def receive_data(data)
|
86
|
-
# p "|<-- #{data}"
|
87
|
-
@parser << data
|
88
|
-
end
|
89
|
-
|
90
|
-
# Aborts all outstanding requests.
|
91
|
-
#
|
92
|
-
# @see EM::Connection#unbind
|
93
|
-
#
|
94
|
-
# @api semipublic
|
95
|
-
def unbind(reason)
|
96
|
-
super
|
97
|
-
@queue.each {|job| job.fiber.resume(:kill) }
|
98
|
-
end
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
@requests = []
|
99
13
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# response, depending on whether the response indicates success (100-399)
|
105
|
-
# or failure (400-599).
|
106
|
-
#
|
107
|
-
# The request will +#fail+ with a +nil+ response if the connection was
|
108
|
-
# closed for whatever reason.
|
109
|
-
#
|
110
|
-
# TODO find out if there are more cases where the response body
|
111
|
-
# should automatically be closed.
|
112
|
-
#
|
113
|
-
# @api public
|
114
|
-
def <<(request)
|
115
|
-
Fiber.new do
|
116
|
-
EM::Synchrony.sync(@ssl_handshake_completed) if config[:ssl]
|
14
|
+
setup_connection
|
15
|
+
setup_handlers
|
16
|
+
notify_handlers(:post_init)
|
17
|
+
end
|
117
18
|
|
118
|
-
|
19
|
+
def request(http_method, uri)
|
20
|
+
request = Request.new(http_method, uri)
|
21
|
+
request.finished.fulfill
|
22
|
+
perform(request)
|
23
|
+
request.served.sync
|
24
|
+
end
|
119
25
|
|
120
|
-
|
121
|
-
|
122
|
-
end
|
26
|
+
def perform(request)
|
27
|
+
@requests << request
|
123
28
|
|
124
|
-
|
125
|
-
request
|
126
|
-
|
127
|
-
request.fail(response)
|
128
|
-
else
|
129
|
-
request.succeed(response)
|
29
|
+
fiber = Fiber.new do
|
30
|
+
notify_handlers(:perform, request)
|
31
|
+
@connection.serialize(request)
|
130
32
|
end
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
# Builds a +Request+, sends it, and blocks while waiting for the response.
|
135
|
-
#
|
136
|
-
# @param [Symbol, String] verb
|
137
|
-
# The HTTP method verb, e.g. +:get+ or +"PUT"+.
|
138
|
-
# @param [String, URI] uri
|
139
|
-
# The request URI.
|
140
|
-
# @param [Hash] headers (optional)
|
141
|
-
# The request headers.
|
142
|
-
# @param [#each] body (optional)
|
143
|
-
# A request body object whose +#each+ method yields objects that respond
|
144
|
-
# to +#to_s+.
|
145
|
-
#
|
146
|
-
# @return [Hatetepe::Response, nil]
|
147
|
-
#
|
148
|
-
# @api public
|
149
|
-
def request(verb, uri, headers = {}, body = [])
|
150
|
-
uri = URI(uri)
|
151
|
-
uri.scheme ||= @config[:ssl] ? 'http' : 'https'
|
152
|
-
uri.host ||= @config[:host]
|
153
|
-
uri.port ||= @config[:port]
|
154
|
-
|
155
|
-
headers['Host'] ||= "#{uri.host}:#{uri.port}"
|
156
|
-
|
157
|
-
request = Hatetepe::Request.new(verb, URI(uri.to_s), headers, body)
|
158
|
-
self << request
|
159
|
-
EM::Synchrony.sync(request)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Like +#request+, but raises errors for 4xx and 5xx responses.
|
163
|
-
#
|
164
|
-
# @param [Symbol, String] verb
|
165
|
-
# The HTTP method verb, e.g. +:get+ or +"PUT"+.
|
166
|
-
# @param [String, URI] uri
|
167
|
-
# The request URI.
|
168
|
-
# @param [Hash] headers (optional)
|
169
|
-
# The request headers.
|
170
|
-
# @param [#each] body (optional)
|
171
|
-
# A request body object whose +#each+ method yields objects that respond
|
172
|
-
# to +#to_s+.
|
173
|
-
#
|
174
|
-
# @return [Hatetepe]::Response, nil]
|
175
|
-
#
|
176
|
-
# @raise [Hatetepe::ClientError]
|
177
|
-
# If the server responded with a 4xx status code.
|
178
|
-
# @raise [Hatetepe::ServerError]
|
179
|
-
# If the server responded with a 5xx status code.
|
180
|
-
# @raise [Hatetepe::RequestError]
|
181
|
-
# If the client failed to receive any response at all.
|
182
|
-
def request!(verb, uri, headers = {}, body = [])
|
183
|
-
response = request(verb, uri, headers, body)
|
184
|
-
|
185
|
-
if response.nil?
|
186
|
-
raise Hatetepe::RequestError
|
187
|
-
elsif response.status >= 500
|
188
|
-
raise Hatetepe::ServerError
|
189
|
-
elsif response.status >= 400
|
190
|
-
raise Hatetepe::ClientError
|
33
|
+
fiber.resume
|
191
34
|
end
|
192
35
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
# Gracefully stops the client.
|
197
|
-
#
|
198
|
-
# Waits for all requests to finish and then stops the client.
|
199
|
-
#
|
200
|
-
# @api public
|
201
|
-
def stop
|
202
|
-
wait
|
203
|
-
stop!
|
204
|
-
end
|
205
|
-
|
206
|
-
# Immediately stops the client by closing the connection.
|
207
|
-
#
|
208
|
-
# This will lead to EventMachine's event loop calling {#unbind}, which fail
|
209
|
-
# all outstanding requests.
|
210
|
-
#
|
211
|
-
# @see #unbind
|
212
|
-
#
|
213
|
-
# @api public
|
214
|
-
def stop!
|
215
|
-
close_connection
|
216
|
-
end
|
217
|
-
|
218
|
-
# Blocks until the last request has finished receiving its response.
|
219
|
-
#
|
220
|
-
# Returns immediately if there are no outstanding requests.
|
221
|
-
#
|
222
|
-
# @api public
|
223
|
-
def wait
|
224
|
-
if job = @queue.last
|
225
|
-
EM::Synchrony.sync(job.request)
|
226
|
-
EM::Synchrony.sync(job.response.body) if job.response
|
36
|
+
def close
|
37
|
+
@connection.close
|
227
38
|
end
|
228
|
-
end
|
229
39
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
#
|
235
|
-
# @return [Hatetepe::Client]
|
236
|
-
# The new Client instance.
|
237
|
-
#
|
238
|
-
# @api public
|
239
|
-
def self.start(config)
|
240
|
-
EM.connect(config[:host], config[:port], self, config)
|
241
|
-
end
|
242
|
-
|
243
|
-
# @api public
|
244
|
-
def self.request(verb, uri, headers = {}, body = [])
|
245
|
-
uri = URI(uri)
|
246
|
-
client = start(host: uri.host, port: uri.port, ssl: uri.scheme == 'https')
|
247
|
-
client.request(verb, uri, headers, body)
|
248
|
-
end
|
249
|
-
|
250
|
-
# Feeds the request into the builder and blocks while waiting for the
|
251
|
-
# response to arrive.
|
252
|
-
#
|
253
|
-
# Supports the request bit of HTTP pipelining by waiting until the previous
|
254
|
-
# request has been sent.
|
255
|
-
#
|
256
|
-
# @param [Hatetepe::Request] request
|
257
|
-
# The request that's gonna be sent.
|
258
|
-
#
|
259
|
-
# @return [Hatetepe::Response, nil]
|
260
|
-
# The received response or +nil+ if the connection has been closed before
|
261
|
-
# receiving a response.
|
262
|
-
#
|
263
|
-
# @api private
|
264
|
-
def send_request(request)
|
265
|
-
previous = @queue.last
|
266
|
-
current = Job.new(Fiber.current, request, false)
|
267
|
-
@queue << current
|
40
|
+
def receive(response)
|
41
|
+
unless (request = correlate(response))
|
42
|
+
raise ClientError, 'Unable to correlate with request'
|
43
|
+
end
|
268
44
|
|
269
|
-
|
270
|
-
|
271
|
-
return if Fiber.yield == :kill
|
45
|
+
setup_cleanup(request, response)
|
46
|
+
notify_handlers(:receive, request, response)
|
272
47
|
end
|
273
48
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
return if Fiber.yield == :kill
|
49
|
+
def teardown(reason)
|
50
|
+
while (request = @requests.shift)
|
51
|
+
if (response = request.served.value)
|
52
|
+
response.finished.reject(reason)
|
53
|
+
else
|
54
|
+
request.served.reject(reason)
|
55
|
+
end
|
56
|
+
end
|
283
57
|
end
|
284
58
|
|
285
|
-
|
286
|
-
@queue.delete(current)
|
287
|
-
current.response
|
288
|
-
end
|
59
|
+
private
|
289
60
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
#
|
297
|
-
# @param [Hatetepe::Response] response
|
298
|
-
# The incoming response
|
299
|
-
#
|
300
|
-
# @raise [RuntimeError]
|
301
|
-
# There is no request that's waiting for a response.
|
302
|
-
#
|
303
|
-
# @api private
|
304
|
-
def receive_response(response)
|
305
|
-
query = proc {|j| j.response.nil? }
|
61
|
+
def setup_connection
|
62
|
+
@connection =
|
63
|
+
EM.connect(@config[:address], @config[:port], Connection::EventMachine)
|
64
|
+
@connection.parse(method(:receive))
|
65
|
+
@connection.closed.then(method(:teardown))
|
66
|
+
end
|
306
67
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
raise "Received response but didn't expect one: #{response.status}"
|
68
|
+
def correlate(response)
|
69
|
+
@requests
|
70
|
+
.detect { |request| request.served.pending? }
|
71
|
+
.tap { |request| request.served.fulfill(response) if request }
|
312
72
|
end
|
313
|
-
end
|
314
73
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
].each do |verb|
|
319
|
-
define_method(verb) do |uri, headers = {}, body = []|
|
320
|
-
request(verb, uri, headers, body)
|
321
|
-
end
|
74
|
+
def setup_cleanup(request, response)
|
75
|
+
callback = proc { @requests.delete(request) }
|
76
|
+
response.finished.then(callback, callback)
|
322
77
|
end
|
323
78
|
end
|
324
|
-
|
325
|
-
include VerbMethods
|
326
|
-
extend VerbMethods
|
327
79
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Hatetepe
|
4
|
+
module Connection
|
5
|
+
class EventMachine < EM::Connection
|
6
|
+
include Promise::Attribute
|
7
|
+
promise :closed
|
8
|
+
|
9
|
+
def initialize(callback = nil)
|
10
|
+
@parser = HTTP::Parser.new(self)
|
11
|
+
callback.call(self) if callback
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(block)
|
15
|
+
@parse = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize(message)
|
19
|
+
serializer = Serializer.new(message, method(:send_data))
|
20
|
+
serializer.serialize
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive_data(data)
|
24
|
+
# ap data
|
25
|
+
@parser << data
|
26
|
+
# rescue => error
|
27
|
+
# $stderr.puts(error.to_s)
|
28
|
+
# @message.finished.reject(error) if @message
|
29
|
+
# close_connection
|
30
|
+
end
|
31
|
+
|
32
|
+
# def send_data(data)
|
33
|
+
# ap data
|
34
|
+
# super
|
35
|
+
# end
|
36
|
+
|
37
|
+
def close
|
38
|
+
@reason ||= :self
|
39
|
+
close_connection_after_writing
|
40
|
+
end
|
41
|
+
|
42
|
+
def unbind(reason)
|
43
|
+
closed.fulfill(@reason || reason)
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_headers_complete(_)
|
47
|
+
@message = Support::Message.build(@parser)
|
48
|
+
@parse.call(@message)
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_body(chunk)
|
52
|
+
@message.body << chunk
|
53
|
+
@message.finished.progress(chunk)
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_message_complete
|
57
|
+
@message.finished.fulfill
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Hatetepe
|
4
|
+
module Connection
|
5
|
+
module Status
|
6
|
+
def closed?
|
7
|
+
@connection.closed.fulfilled?
|
8
|
+
end
|
9
|
+
|
10
|
+
def closed_by_timeout?
|
11
|
+
closed? && @connection.closed.value == Errno::ETIMEDOUT
|
12
|
+
end
|
13
|
+
|
14
|
+
def closed_by_self?
|
15
|
+
closed? && @connection.closed.value == :self
|
16
|
+
end
|
17
|
+
|
18
|
+
def closed_by_remote?
|
19
|
+
closed? && !closed_by_timeout? && !closed_by_self?
|
20
|
+
end
|
21
|
+
|
22
|
+
# XXX this exists only to support spec/integration/timeout_spec
|
23
|
+
def closed_reason
|
24
|
+
@connection.closed.value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Hatetepe
|
4
|
+
class Promise < ::Promise
|
5
|
+
include ::Promise::Progress
|
6
|
+
|
7
|
+
def fulfill(value = nil)
|
8
|
+
super(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def reject(reason = nil)
|
12
|
+
super(reason)
|
13
|
+
end
|
14
|
+
|
15
|
+
def sync
|
16
|
+
if fulfilled?
|
17
|
+
return value
|
18
|
+
elsif rejected?
|
19
|
+
return reason
|
20
|
+
end
|
21
|
+
|
22
|
+
sync!
|
23
|
+
end
|
24
|
+
|
25
|
+
def sync!
|
26
|
+
fiber = Fiber.current
|
27
|
+
resume = proc do |arg|
|
28
|
+
EM.next_tick { fiber.resume(arg) }
|
29
|
+
# fiber.resume(arg)
|
30
|
+
end
|
31
|
+
|
32
|
+
self.then(resume, resume)
|
33
|
+
Fiber.yield
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def defer(callback, arg)
|
39
|
+
EM.next_tick { safe_dispatch(callback, arg) }
|
40
|
+
# safe_dispatch(callback, arg)
|
41
|
+
end
|
42
|
+
|
43
|
+
def safe_dispatch(callback, arg)
|
44
|
+
callback.dispatch(arg)
|
45
|
+
# rescue => error
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
|
49
|
+
module Attribute
|
50
|
+
def self.included(klass)
|
51
|
+
klass.extend(ClassMethods)
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
self.class.send(:promises).map { |name| send(name) }
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def get_or_set_promise(ivar)
|
61
|
+
if instance_variable_defined?(ivar)
|
62
|
+
instance_variable_get(ivar)
|
63
|
+
else
|
64
|
+
instance_variable_set(ivar, Promise.new)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module ClassMethods
|
69
|
+
private
|
70
|
+
|
71
|
+
def promises
|
72
|
+
@promises ||= []
|
73
|
+
end
|
74
|
+
|
75
|
+
def promise(name)
|
76
|
+
promises << name
|
77
|
+
define_promise(name, "@#{name}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def define_promise(name, ivar)
|
81
|
+
define_method(name) { get_or_set_promise(ivar) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/hatetepe/request.rb
CHANGED
@@ -1,45 +1,21 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
3
|
module Hatetepe
|
4
|
-
class Request
|
5
|
-
include
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
self.verb = verb
|
12
|
-
@uri = uri
|
13
|
-
super headers, body, http_version
|
14
|
-
end
|
15
|
-
|
16
|
-
def verb=(verb)
|
17
|
-
@verb = verb.to_s.upcase
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_a
|
21
|
-
[verb, uri, headers, body, http_version]
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_h
|
25
|
-
{
|
26
|
-
"rack.version" => [1, 0],
|
27
|
-
"hatetepe.request" => self,
|
28
|
-
"rack.input" => body,
|
29
|
-
"REQUEST_METHOD" => verb.dup,
|
30
|
-
"REQUEST_URI" => uri.dup
|
31
|
-
}.tap do |hsh|
|
32
|
-
headers.each do |key, value|
|
33
|
-
key = key.upcase.gsub /[^A-Z]/, "_"
|
34
|
-
key = "HTTP_#{key}" unless key =~ /^CONTENT_(TYPE|LENGTH)$/
|
35
|
-
hsh[key] = value.dup
|
36
|
-
end
|
4
|
+
class Request
|
5
|
+
include Promise::Attribute
|
6
|
+
promise :finished
|
7
|
+
promise :served
|
8
|
+
|
9
|
+
attr_reader :http_method, :uri, :headers, :body
|
10
|
+
attr_accessor :http_version
|
37
11
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
12
|
+
# TODO: URI.join is really slow
|
13
|
+
def initialize(http_method, uri, headers = {}, body = '')
|
14
|
+
# @http_method, @uri = http_method, URI('http://' + uri)
|
15
|
+
@http_method, @uri = http_method, URI.join('http:///', uri).request_uri
|
16
|
+
@headers, @body = headers, body
|
17
|
+
@http_version = 1.1
|
18
|
+
super()
|
43
19
|
end
|
44
20
|
end
|
45
21
|
end
|