hatetepe 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -1
- data/Gemfile.lock +46 -0
- data/Procfile +1 -0
- data/README.md +5 -3
- data/config.ru +7 -0
- data/hatetepe.gemspec +2 -1
- data/lib/hatetepe/client.rb +60 -7
- data/lib/hatetepe/version.rb +1 -1
- data/spec/unit/body_spec.rb +8 -8
- data/spec/unit/builder_spec.rb +9 -9
- data/spec/unit/client_spec.rb +47 -12
- data/spec/unit/parser_spec.rb +1 -1
- data/spec/unit/rack_handler_spec.rb +4 -4
- data/spec/unit/server_spec.rb +1 -1
- metadata +8 -5
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
hatetepe (0.5.1)
|
5
|
+
em-synchrony (~> 1.0)
|
6
|
+
eventmachine (~> 1.0.0.beta.4)
|
7
|
+
http_parser.rb (~> 0.6.0.beta.2)
|
8
|
+
rack
|
9
|
+
thor
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
awesome_print (1.1.0)
|
15
|
+
diff-lcs (1.2.4)
|
16
|
+
em-synchrony (1.0.3)
|
17
|
+
eventmachine (>= 1.0.0.beta.1)
|
18
|
+
eventmachine (1.0.3)
|
19
|
+
eventmachine (1.0.3-java)
|
20
|
+
http_parser.rb (0.6.0.beta.2)
|
21
|
+
http_parser.rb (0.6.0.beta.2-java)
|
22
|
+
kramdown (1.1.0)
|
23
|
+
rack (1.5.2)
|
24
|
+
rake (10.1.0)
|
25
|
+
rspec (2.14.1)
|
26
|
+
rspec-core (~> 2.14.0)
|
27
|
+
rspec-expectations (~> 2.14.0)
|
28
|
+
rspec-mocks (~> 2.14.0)
|
29
|
+
rspec-core (2.14.4)
|
30
|
+
rspec-expectations (2.14.1)
|
31
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
32
|
+
rspec-mocks (2.14.3)
|
33
|
+
thor (0.18.1)
|
34
|
+
yard (0.8.7)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
java
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
awesome_print
|
42
|
+
hatetepe!
|
43
|
+
kramdown
|
44
|
+
rake
|
45
|
+
rspec
|
46
|
+
yard
|
data/Procfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec hatetepe -p $PORT -b 0.0.0.0
|
data/README.md
CHANGED
@@ -182,13 +182,15 @@ Hatetepe is subject to an MIT-style license (see LICENSE file).
|
|
182
182
|
Roadmap
|
183
183
|
-------
|
184
184
|
|
185
|
-
- 0.
|
186
|
-
- Direct IO via EM.enable_proxy
|
185
|
+
- 0.6.0
|
186
|
+
- Direct recv<->send IO via EM.enable_proxy
|
187
|
+
- SSL/TLS
|
188
|
+
- HTTP proxying (in client and server)
|
189
|
+
- later
|
187
190
|
- Encoding support (ref. [github.com/tmm1/http_parser.rb#1](https://github.com/tmm1/http_parser.rb/pull/1))
|
188
191
|
- Optimize for performance
|
189
192
|
- Propagate connection errors to the app
|
190
193
|
|
191
|
-
|
192
194
|
Ideas
|
193
195
|
-----
|
194
196
|
|
data/config.ru
ADDED
data/hatetepe.gemspec
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
3
|
require "hatetepe/version"
|
4
|
+
require "date"
|
4
5
|
|
5
6
|
Gem::Specification.new do |s|
|
6
7
|
s.name = "hatetepe"
|
@@ -13,7 +14,7 @@ Gem::Specification.new do |s|
|
|
13
14
|
s.summary = %q{The HTTP toolkit}
|
14
15
|
#s.description = %q{TODO: write description}
|
15
16
|
|
16
|
-
s.add_dependency "http_parser.rb", "~> 0.
|
17
|
+
s.add_dependency "http_parser.rb", "~> 0.6.0.beta.2"
|
17
18
|
s.add_dependency "eventmachine", "~> 1.0.0.beta.4"
|
18
19
|
s.add_dependency "em-synchrony", "~> 1.0"
|
19
20
|
s.add_dependency "rack"
|
data/lib/hatetepe/client.rb
CHANGED
@@ -7,6 +7,13 @@ require "hatetepe/parser"
|
|
7
7
|
require "hatetepe/request"
|
8
8
|
require "hatetepe/version"
|
9
9
|
|
10
|
+
module Hatetepe
|
11
|
+
HatetepeError = Class.new(StandardError)
|
12
|
+
RequestError = Class.new(HatetepeError)
|
13
|
+
ClientError = Class.new(RequestError)
|
14
|
+
ServerError = Class.new(RequestError)
|
15
|
+
end
|
16
|
+
|
10
17
|
module Hatetepe::Client
|
11
18
|
include Hatetepe::Connection
|
12
19
|
|
@@ -39,12 +46,11 @@ module Hatetepe::Client
|
|
39
46
|
# @api semipublic
|
40
47
|
def initialize(config)
|
41
48
|
@config = CONFIG_DEFAULTS.merge(config)
|
49
|
+
@ssl_handshake_completed = EM::DefaultDeferrable.new
|
42
50
|
end
|
43
51
|
|
44
52
|
# Initializes the parser, request queue, and middleware pipe.
|
45
53
|
#
|
46
|
-
# TODO: Use +Rack::Builder+ for building the app pipe.
|
47
|
-
#
|
48
54
|
# @see EM::Connection#post_init
|
49
55
|
#
|
50
56
|
# @api semipublic
|
@@ -60,6 +66,12 @@ module Hatetepe::Client
|
|
60
66
|
|
61
67
|
self.comm_inactivity_timeout = config[:timeout]
|
62
68
|
self.pending_connect_timeout = config[:connect_timeout]
|
69
|
+
|
70
|
+
start_tls if config[:ssl]
|
71
|
+
end
|
72
|
+
|
73
|
+
def ssl_handshake_completed
|
74
|
+
EM::Synchrony.next_tick { @ssl_handshake_completed.succeed }
|
63
75
|
end
|
64
76
|
|
65
77
|
# Feeds response data into the parser.
|
@@ -101,6 +113,8 @@ module Hatetepe::Client
|
|
101
113
|
# @api public
|
102
114
|
def <<(request)
|
103
115
|
Fiber.new do
|
116
|
+
EM::Synchrony.sync(@ssl_handshake_completed) if config[:ssl]
|
117
|
+
|
104
118
|
response = @app.call(request)
|
105
119
|
|
106
120
|
if response && (request.verb == "HEAD" || response.status == 204)
|
@@ -108,7 +122,7 @@ module Hatetepe::Client
|
|
108
122
|
end
|
109
123
|
|
110
124
|
if !response
|
111
|
-
request.fail
|
125
|
+
request.fail
|
112
126
|
elsif response.failure?
|
113
127
|
request.fail(response)
|
114
128
|
else
|
@@ -133,11 +147,52 @@ module Hatetepe::Client
|
|
133
147
|
#
|
134
148
|
# @api public
|
135
149
|
def request(verb, uri, headers = {}, body = [])
|
136
|
-
|
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)
|
137
158
|
self << request
|
138
159
|
EM::Synchrony.sync(request)
|
139
160
|
end
|
140
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
|
191
|
+
end
|
192
|
+
|
193
|
+
response
|
194
|
+
end
|
195
|
+
|
141
196
|
# Gracefully stops the client.
|
142
197
|
#
|
143
198
|
# Waits for all requests to finish and then stops the client.
|
@@ -188,10 +243,8 @@ module Hatetepe::Client
|
|
188
243
|
# @api public
|
189
244
|
def self.request(verb, uri, headers = {}, body = [])
|
190
245
|
uri = URI(uri)
|
191
|
-
client = start(host: uri.host, port: uri.port)
|
246
|
+
client = start(host: uri.host, port: uri.port, ssl: uri.scheme == 'https')
|
192
247
|
client.request(verb, uri, headers, body)
|
193
|
-
ensure
|
194
|
-
client.stop
|
195
248
|
end
|
196
249
|
|
197
250
|
# Feeds the request into the builder and blocks while waiting for the
|
data/lib/hatetepe/version.rb
CHANGED
data/spec/unit/body_spec.rb
CHANGED
@@ -26,7 +26,7 @@ describe Hatetepe::Body do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
context "#sync" do
|
29
|
-
let(:conn) {
|
29
|
+
let(:conn) { double "conn", :paused? => true }
|
30
30
|
|
31
31
|
it "resumes the source connection if any" do
|
32
32
|
body.source = conn
|
@@ -43,7 +43,7 @@ describe Hatetepe::Body do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
context "#length" do
|
46
|
-
let(:length) {
|
46
|
+
let(:length) { double "length" }
|
47
47
|
|
48
48
|
it "forwards to io#length" do
|
49
49
|
body.stub :sync
|
@@ -79,7 +79,7 @@ describe Hatetepe::Body do
|
|
79
79
|
end
|
80
80
|
|
81
81
|
context "#pos" do
|
82
|
-
let(:pos) {
|
82
|
+
let(:pos) { double "pos" }
|
83
83
|
|
84
84
|
it "forwards to io#pos" do
|
85
85
|
body.io.stub :pos => pos
|
@@ -109,7 +109,7 @@ describe Hatetepe::Body do
|
|
109
109
|
|
110
110
|
context "#closed_write?" do
|
111
111
|
it "forwards to io#closed_write?" do
|
112
|
-
ret =
|
112
|
+
ret = double("return")
|
113
113
|
body.io.should_receive(:closed_write?) { ret }
|
114
114
|
|
115
115
|
body.closed_write?.should equal(ret)
|
@@ -143,7 +143,7 @@ describe Hatetepe::Body do
|
|
143
143
|
end
|
144
144
|
|
145
145
|
describe "without a block" do
|
146
|
-
let(:enumerator) {
|
146
|
+
let(:enumerator) { double }
|
147
147
|
|
148
148
|
before { body.stub(:to_enum).with(:each) { enumerator } }
|
149
149
|
|
@@ -167,7 +167,7 @@ describe Hatetepe::Body do
|
|
167
167
|
|
168
168
|
it "forwards to io#read" do
|
169
169
|
body.succeed
|
170
|
-
args, ret = [
|
170
|
+
args, ret = [double("arg#1"), double("arg#2")], double("ret")
|
171
171
|
|
172
172
|
body.io.should_receive(:read).with(*args) { ret }
|
173
173
|
body.read(*args).should equal(ret)
|
@@ -187,7 +187,7 @@ describe Hatetepe::Body do
|
|
187
187
|
|
188
188
|
it "forwards to io#gets" do
|
189
189
|
body.succeed
|
190
|
-
ret =
|
190
|
+
ret = double("ret")
|
191
191
|
|
192
192
|
body.io.should_receive(:gets) { ret }
|
193
193
|
body.gets.should equal(ret)
|
@@ -196,7 +196,7 @@ describe Hatetepe::Body do
|
|
196
196
|
|
197
197
|
context "#write(chunk)" do
|
198
198
|
it "forwards to io#write" do
|
199
|
-
arg, ret =
|
199
|
+
arg, ret = double("arg"), double("ret")
|
200
200
|
body.io.should_receive(:write).with(arg) { ret }
|
201
201
|
|
202
202
|
body.write(arg).should equal(ret)
|
data/spec/unit/builder_spec.rb
CHANGED
@@ -73,7 +73,7 @@ describe Hatetepe::Builder do
|
|
73
73
|
describe "#chunked?"
|
74
74
|
|
75
75
|
describe "#request(array)" do
|
76
|
-
let(:req) { [:get, "/foo", {"Key" => "value"},
|
76
|
+
let(:req) { [:get, "/foo", {"Key" => "value"}, double("body")] }
|
77
77
|
|
78
78
|
before { builder.send :initialize }
|
79
79
|
|
@@ -137,7 +137,7 @@ describe Hatetepe::Builder do
|
|
137
137
|
end
|
138
138
|
|
139
139
|
describe "#response(array)" do
|
140
|
-
let(:res) { [201, {"Key" => "value"},
|
140
|
+
let(:res) { [201, {"Key" => "value"}, double("body")] }
|
141
141
|
|
142
142
|
before { builder.send :initialize }
|
143
143
|
|
@@ -230,7 +230,7 @@ describe Hatetepe::Builder do
|
|
230
230
|
end
|
231
231
|
|
232
232
|
describe "#body(#each)" do
|
233
|
-
let(:body) { [
|
233
|
+
let(:body) { [double("chunk#1"), double("chunk#2")] }
|
234
234
|
|
235
235
|
it "calls #body_chunk for each element" do
|
236
236
|
builder.should_receive(:body_chunk).with body[0]
|
@@ -318,7 +318,7 @@ describe Hatetepe::Builder do
|
|
318
318
|
builder.complete
|
319
319
|
end
|
320
320
|
|
321
|
-
let(:hook) {
|
321
|
+
let(:hook) { double "hook", :call => nil }
|
322
322
|
|
323
323
|
it "calls the on_complete hooks" do
|
324
324
|
builder.on_complete << hook
|
@@ -333,8 +333,8 @@ describe Hatetepe::Builder do
|
|
333
333
|
end
|
334
334
|
|
335
335
|
describe "#write(data)" do
|
336
|
-
let(:hook) {
|
337
|
-
let(:data) {
|
336
|
+
let(:hook) { double "hook" }
|
337
|
+
let(:data) { double "data" }
|
338
338
|
|
339
339
|
before { builder.send :initialize }
|
340
340
|
|
@@ -346,10 +346,10 @@ describe Hatetepe::Builder do
|
|
346
346
|
end
|
347
347
|
|
348
348
|
describe "#error(message)" do
|
349
|
-
let(:hook1) {
|
350
|
-
let(:hook2) {
|
349
|
+
let(:hook1) { double "hook#1" }
|
350
|
+
let(:hook2) { double "hook#2" }
|
351
351
|
let(:message) { "error! error!" }
|
352
|
-
let(:exception) {
|
352
|
+
let(:exception) { double "exception" }
|
353
353
|
|
354
354
|
before do
|
355
355
|
builder.send :initialize
|
data/spec/unit/client_spec.rb
CHANGED
@@ -72,10 +72,10 @@ describe Hatetepe::Client do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
describe ".request" do
|
75
|
-
let(:client) {
|
76
|
-
let(:headers) {
|
77
|
-
let(:body) {
|
78
|
-
let(:res) {
|
75
|
+
let(:client) { double("client", request: res, stop: nil) }
|
76
|
+
let(:headers) { double("headers") }
|
77
|
+
let(:body) { double("body") }
|
78
|
+
let(:res) { double("response") }
|
79
79
|
let(:response) { Hatetepe::Client.request(:put, "/test", headers, body) }
|
80
80
|
|
81
81
|
before { Hatetepe::Client.stub(start: client) }
|
@@ -84,18 +84,13 @@ describe Hatetepe::Client do
|
|
84
84
|
client.should_receive(:request).with(:put, URI("/test"), headers, body)
|
85
85
|
response.should equal(res)
|
86
86
|
end
|
87
|
-
|
88
|
-
it "stops the client afterwards" do
|
89
|
-
client.should_receive(:stop)
|
90
|
-
response
|
91
|
-
end
|
92
87
|
end
|
93
88
|
|
94
89
|
describe "#request" do
|
95
90
|
let(:body) { [ "Hello,", " world!" ] }
|
96
91
|
let(:headers) { { "Content-Type" => "text/plain" } }
|
97
|
-
let(:request) {
|
98
|
-
let(:response) {
|
92
|
+
let(:request) { double("request") }
|
93
|
+
let(:response) { double("response") }
|
99
94
|
|
100
95
|
before do
|
101
96
|
client.stub(:<<)
|
@@ -125,6 +120,46 @@ describe Hatetepe::Client do
|
|
125
120
|
end
|
126
121
|
end
|
127
122
|
|
123
|
+
describe "#request!" do
|
124
|
+
subject do
|
125
|
+
proc { client.request!(:get, "/") }
|
126
|
+
end
|
127
|
+
|
128
|
+
let(:status) { 200 }
|
129
|
+
let(:response) { double("response", :status => status) }
|
130
|
+
|
131
|
+
before do
|
132
|
+
client.stub(:request).with(:get, "/", {}, []) { response }
|
133
|
+
end
|
134
|
+
|
135
|
+
it "forwards to #request" do
|
136
|
+
subject.call.should eq(response)
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "for a 4xx response" do
|
140
|
+
let(:status) { 404 }
|
141
|
+
|
142
|
+
it "raises a ClientError" do
|
143
|
+
subject.should raise_error(Hatetepe::ClientError)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "for a 5xx response" do
|
148
|
+
let(:status) { 502 }
|
149
|
+
|
150
|
+
it "raises a ServerError" do
|
151
|
+
subject.should raise_error(Hatetepe::ServerError)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "if no response could be received" do
|
156
|
+
it "raises a ServerError" do
|
157
|
+
client.stub(:request) { nil }
|
158
|
+
subject.should raise_error(Hatetepe::RequestError)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
128
163
|
describe "#<<" do
|
129
164
|
let :request do
|
130
165
|
Hatetepe::Request.new :head, "/test"
|
@@ -152,7 +187,7 @@ describe Hatetepe::Client do
|
|
152
187
|
let(:response) { nil }
|
153
188
|
|
154
189
|
it "fails the request" do
|
155
|
-
request.should_receive(:fail)
|
190
|
+
request.should_receive(:fail)
|
156
191
|
client << request
|
157
192
|
end
|
158
193
|
end
|
data/spec/unit/parser_spec.rb
CHANGED
@@ -2,14 +2,14 @@ require "spec_helper"
|
|
2
2
|
require "rack/handler/hatetepe"
|
3
3
|
|
4
4
|
describe Rack::Handler::Hatetepe do
|
5
|
-
let(:app) {
|
5
|
+
let(:app) { double "app" }
|
6
6
|
let(:options) {
|
7
7
|
{
|
8
|
-
:Host =>
|
9
|
-
:Port =>
|
8
|
+
:Host => double("host"),
|
9
|
+
:Port => double("port")
|
10
10
|
}
|
11
11
|
}
|
12
|
-
let(:server) {
|
12
|
+
let(:server) { double "server" }
|
13
13
|
|
14
14
|
describe ".run(app, options) {|server| ... }" do
|
15
15
|
before {
|
data/spec/unit/server_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hatetepe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-09-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: http_parser.rb
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
21
|
+
version: 0.6.0.beta.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.6.0.beta.2
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: eventmachine
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,10 +148,13 @@ extensions: []
|
|
148
148
|
extra_rdoc_files: []
|
149
149
|
files:
|
150
150
|
- Gemfile
|
151
|
+
- Gemfile.lock
|
151
152
|
- LICENSE
|
153
|
+
- Procfile
|
152
154
|
- README.md
|
153
155
|
- Rakefile
|
154
156
|
- bin/hatetepe
|
157
|
+
- config.ru
|
155
158
|
- examples/parallel_requests.rb
|
156
159
|
- hatetepe.gemspec
|
157
160
|
- lib/hatetepe.rb
|
@@ -205,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
208
|
version: '0'
|
206
209
|
requirements: []
|
207
210
|
rubyforge_project:
|
208
|
-
rubygems_version: 1.8.
|
211
|
+
rubygems_version: 1.8.23
|
209
212
|
signing_key:
|
210
213
|
specification_version: 3
|
211
214
|
summary: The HTTP toolkit
|