hatetepe 0.5.1 → 0.5.2
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.
- 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
|