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 CHANGED
@@ -1,4 +1,6 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
+
3
+ ruby "1.9.3"
2
4
 
3
5
  gemspec
4
6
 
@@ -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
@@ -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.5.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
 
@@ -0,0 +1,7 @@
1
+ Hatetepe::Server::CONFIG_DEFAULTS[:app] = [ Hatetepe::Server::KeepAlive, Hatetepe::Server::RackApp ]
2
+
3
+ use Rack::ContentLength
4
+
5
+ run proc {|_|
6
+ [200, {'Content-Type' => 'text/html'}, ['hello, world']]
7
+ }
@@ -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.5.3"
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"
@@ -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(nil, self)
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
- request = Hatetepe::Request.new(verb, URI(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)
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
@@ -1,3 +1,3 @@
1
1
  module Hatetepe
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -26,7 +26,7 @@ describe Hatetepe::Body do
26
26
  end
27
27
 
28
28
  context "#sync" do
29
- let(:conn) { stub "conn", :paused? => true }
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) { stub "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) { stub "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 = stub("return")
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) { stub }
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 = [stub("arg#1"), stub("arg#2")], stub("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 = stub("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 = stub("arg"), stub("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)
@@ -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"}, stub("body")] }
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"}, stub("body")] }
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) { [stub("chunk#1"), stub("chunk#2")] }
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) { stub "hook", :call => nil }
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) { stub "hook" }
337
- let(:data) { stub "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) { stub "hook#1" }
350
- let(:hook2) { stub "hook#2" }
349
+ let(:hook1) { double "hook#1" }
350
+ let(:hook2) { double "hook#2" }
351
351
  let(:message) { "error! error!" }
352
- let(:exception) { stub "exception" }
352
+ let(:exception) { double "exception" }
353
353
 
354
354
  before do
355
355
  builder.send :initialize
@@ -72,10 +72,10 @@ describe Hatetepe::Client do
72
72
  end
73
73
 
74
74
  describe ".request" do
75
- let(:client) { stub("client", request: res, stop: nil) }
76
- let(:headers) { stub("headers") }
77
- let(:body) { stub("body") }
78
- let(:res) { stub("response") }
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) { stub("request") }
98
- let(:response) { stub("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).with(nil, client)
190
+ request.should_receive(:fail)
156
191
  client << request
157
192
  end
158
193
  end
@@ -68,7 +68,7 @@ describe Hatetepe::Parser do
68
68
  end
69
69
 
70
70
  let(:block) {
71
- stub("block").tap {|blk|
71
+ double("block").tap {|blk|
72
72
  blk.stub :to_proc => proc {|*args| blk.call *args }
73
73
  }
74
74
  }
@@ -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) { stub "app" }
5
+ let(:app) { double "app" }
6
6
  let(:options) {
7
7
  {
8
- :Host => stub("host"),
9
- :Port => stub("port")
8
+ :Host => double("host"),
9
+ :Port => double("port")
10
10
  }
11
11
  }
12
- let(:server) { stub "server" }
12
+ let(:server) { double "server" }
13
13
 
14
14
  describe ".run(app, options) {|server| ... }" do
15
15
  before {
@@ -39,7 +39,7 @@ describe Hatetepe::Server, "(public API)" do
39
39
  end
40
40
 
41
41
  let :app do
42
- stub("app", :call => nil)
42
+ double("app", :call => nil)
43
43
  end
44
44
 
45
45
  let :http_request do
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.1
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: 2012-09-06 00:00:00.000000000 Z
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.5.3
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.5.3
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.24
211
+ rubygems_version: 1.8.23
209
212
  signing_key:
210
213
  specification_version: 3
211
214
  summary: The HTTP toolkit