hatetepe 0.3.1 → 0.4.0
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/README.md +21 -16
- data/hatetepe.gemspec +1 -2
- data/lib/hatetepe/body.rb +5 -3
- data/lib/hatetepe/builder.rb +6 -3
- data/lib/hatetepe/cli.rb +27 -5
- data/lib/hatetepe/client.rb +157 -82
- data/lib/hatetepe/client/keep_alive.rb +58 -0
- data/lib/hatetepe/client/pipeline.rb +19 -0
- data/lib/hatetepe/connection.rb +42 -0
- data/lib/hatetepe/deferred_status_fix.rb +11 -0
- data/lib/hatetepe/message.rb +4 -4
- data/lib/hatetepe/parser.rb +3 -4
- data/lib/hatetepe/request.rb +19 -6
- data/lib/hatetepe/response.rb +11 -3
- data/lib/hatetepe/server.rb +115 -85
- data/lib/hatetepe/{app.rb → server/app.rb} +7 -2
- data/lib/hatetepe/server/keep_alive.rb +61 -0
- data/lib/hatetepe/server/pipeline.rb +24 -0
- data/lib/hatetepe/{proxy.rb → server/proxy.rb} +5 -11
- data/lib/hatetepe/version.rb +1 -1
- data/lib/rack/handler/hatetepe.rb +1 -4
- data/spec/integration/cli/start_spec.rb +75 -123
- data/spec/integration/client/keep_alive_spec.rb +74 -0
- data/spec/integration/server/keep_alive_spec.rb +99 -0
- data/spec/spec_helper.rb +41 -16
- data/spec/unit/app_spec.rb +16 -5
- data/spec/unit/builder_spec.rb +4 -4
- data/spec/unit/client/pipeline_spec.rb +40 -0
- data/spec/unit/client_spec.rb +355 -199
- data/spec/unit/connection_spec.rb +64 -0
- data/spec/unit/parser_spec.rb +3 -2
- data/spec/unit/proxy_spec.rb +9 -18
- data/spec/unit/rack_handler_spec.rb +2 -12
- data/spec/unit/server_spec.rb +154 -60
- metadata +31 -36
- data/.rspec +0 -1
- data/.travis.yml +0 -3
- data/.yardopts +0 -1
- data/lib/hatetepe/pipeline.rb +0 -27
@@ -3,7 +3,7 @@ require "rack"
|
|
3
3
|
|
4
4
|
Rack::STREAMING = "Rack::STREAMING"
|
5
5
|
|
6
|
-
|
6
|
+
class Hatetepe::Server
|
7
7
|
ASYNC_RESPONSE = [-1, {}, []].freeze
|
8
8
|
|
9
9
|
ERROR_RESPONSE = [500, {"Content-Type" => "text/html"},
|
@@ -39,7 +39,12 @@ module Hatetepe
|
|
39
39
|
|
40
40
|
response = ASYNC_RESPONSE
|
41
41
|
catch :async do
|
42
|
-
response =
|
42
|
+
response = begin
|
43
|
+
app.call env
|
44
|
+
rescue => ex
|
45
|
+
raise ex if ENV["RACK_ENV"] == "testing"
|
46
|
+
ERROR_RESPONSE
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
postprocess env, response
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Hatetepe::Server
|
2
|
+
class KeepAlive
|
3
|
+
attr_reader :app
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
m = case env["HTTP_CONNECTION"].to_s.downcase
|
11
|
+
when "close" then :close
|
12
|
+
when "keep-alive" then :keep_alive
|
13
|
+
else env["HTTP_VERSION"] =~ /^HTTP\/(0\.9|1\.0)$/ ? :close : :keep_alive
|
14
|
+
end
|
15
|
+
|
16
|
+
send :"call_and_#{m}", env
|
17
|
+
end
|
18
|
+
|
19
|
+
def call_and_close(env, response = nil)
|
20
|
+
req, conn = extract_request(env), extract_connection(env)
|
21
|
+
|
22
|
+
conn.processing_enabled = false
|
23
|
+
req.callback &conn.method(:close_connection_after_writing)
|
24
|
+
req.errback &conn.method(:close_connection_after_writing)
|
25
|
+
|
26
|
+
if response
|
27
|
+
response[1]["Connection"] = "close"
|
28
|
+
else
|
29
|
+
stream_start = env["stream.start"]
|
30
|
+
env["stream.start"] = proc do |res|
|
31
|
+
res[1]["Connection"] = "close"
|
32
|
+
stream_start.call res
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
response || app.call(env)
|
37
|
+
end
|
38
|
+
|
39
|
+
def call_and_keep_alive(env)
|
40
|
+
stream_start = env["stream.start"]
|
41
|
+
env["stream.start"] = proc do |res|
|
42
|
+
if res[1]["Connection"] == "close"
|
43
|
+
call_and_close env, res
|
44
|
+
else
|
45
|
+
res[1]["Connection"] = "keep-alive"
|
46
|
+
end
|
47
|
+
stream_start.call res
|
48
|
+
end
|
49
|
+
|
50
|
+
app.call env
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_request(env)
|
54
|
+
env["hatetepe.request"] || raise("env[hatetepe.request] not set")
|
55
|
+
end
|
56
|
+
|
57
|
+
def extract_connection(env)
|
58
|
+
env["hatetepe.connection"] || raise("env[hatetepe.connection] not set")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "em-synchrony"
|
2
|
+
|
3
|
+
class Hatetepe::Server
|
4
|
+
# TODO move specs from server_spec.rb to server/pipeline_spec.rb
|
5
|
+
class Pipeline
|
6
|
+
attr_reader :app
|
7
|
+
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
previous = env["hatetepe.connection"].requests[-2]
|
14
|
+
|
15
|
+
stream_start = env["stream.start"]
|
16
|
+
env["stream.start"] = proc do |response|
|
17
|
+
EM::Synchrony.sync previous if previous
|
18
|
+
stream_start.call response
|
19
|
+
end
|
20
|
+
|
21
|
+
app.call env
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,7 +2,7 @@ require "hatetepe/client"
|
|
2
2
|
require "hatetepe/request"
|
3
3
|
require "uri"
|
4
4
|
|
5
|
-
|
5
|
+
class Hatetepe::Server
|
6
6
|
class Proxy
|
7
7
|
attr_reader :app
|
8
8
|
|
@@ -23,7 +23,8 @@ module Hatetepe
|
|
23
23
|
|
24
24
|
env["proxy.callback"] ||= env["async.callback"]
|
25
25
|
|
26
|
-
cl = client || Client.start(:host => target.host,
|
26
|
+
cl = client || Hatetepe::Client.start(:host => target.host,
|
27
|
+
:port => target.port)
|
27
28
|
build_request(env, target).tap do |req|
|
28
29
|
cl << req
|
29
30
|
EM::Synchrony.sync req
|
@@ -40,15 +41,8 @@ module Hatetepe
|
|
40
41
|
|
41
42
|
uri = target.path + base.uri
|
42
43
|
host = "#{target.host}:#{target.port}"
|
43
|
-
headers = base.headers.merge(
|
44
|
-
|
45
|
-
"Host" => [base.headers["Host"], host].compact.join(", ")
|
46
|
-
})
|
47
|
-
|
48
|
-
Request.new(base.verb, uri, base.http_version).tap do |req|
|
49
|
-
req.headers = headers
|
50
|
-
req.body = base.body
|
51
|
-
end
|
44
|
+
headers = base.headers.merge("Host" => host)
|
45
|
+
Hatetepe::Request.new base.verb, uri, headers, base.body
|
52
46
|
end
|
53
47
|
end
|
54
48
|
end
|
data/lib/hatetepe/version.rb
CHANGED
@@ -15,10 +15,7 @@ module Rack
|
|
15
15
|
Signal.trap("TERM") { EM.stop }
|
16
16
|
|
17
17
|
EM.epoll
|
18
|
-
EM.synchrony
|
19
|
-
server = ::Hatetepe::Server.start options
|
20
|
-
yield server if block_given?
|
21
|
-
end
|
18
|
+
EM.synchrony { ::Hatetepe::Server.start options }
|
22
19
|
end
|
23
20
|
|
24
21
|
def self.valid_options
|
@@ -1,27 +1,18 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "hatetepe/cli"
|
3
|
-
require "socket"
|
4
3
|
|
5
|
-
describe "start command" do
|
6
|
-
def hook_event_loop(&block)
|
7
|
-
EM.spec_hooks << block
|
8
|
-
end
|
9
|
-
|
10
|
-
def add_stop_timer(timeout)
|
11
|
-
hook_event_loop do
|
12
|
-
EM.add_timer(timeout) { EM.stop }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
4
|
+
describe "The `hatetepe start' command" do
|
16
5
|
before do
|
17
|
-
|
6
|
+
ENV.delete "RACK_ENV"
|
7
|
+
|
8
|
+
$stderr = StringIO.new
|
18
9
|
|
19
10
|
FakeFS.activate!
|
20
|
-
File.open
|
21
|
-
f.write
|
11
|
+
File.open "config.ru", "w" do |f|
|
12
|
+
f.write 'run proc {|env| [200, {}, ["Hello world!"]] }'
|
22
13
|
end
|
23
|
-
File.open
|
24
|
-
f.write
|
14
|
+
File.open "config2.ru", "w" do |f|
|
15
|
+
f.write 'run proc {|env| [501, {}, ["Herp derp"]] }'
|
25
16
|
end
|
26
17
|
end
|
27
18
|
|
@@ -32,138 +23,99 @@ describe "start command" do
|
|
32
23
|
FakeFS::FileSystem.clear
|
33
24
|
end
|
34
25
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
$stderr.string.should include("127.0.0.1:3000")
|
43
|
-
end
|
44
|
-
|
45
|
-
it "answers HTTP requests" do
|
46
|
-
add_stop_timer 0.02
|
47
|
-
hook_event_loop do
|
48
|
-
request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
|
49
|
-
response = EM::Synchrony.sync(request)
|
50
|
-
|
51
|
-
response.response_header.status.should == 200
|
52
|
-
response.response_header["CONTENT_TYPE"].should == "text/plain"
|
53
|
-
response.response.should == "/"
|
54
|
-
end
|
55
|
-
Hatetepe::CLI.start %w{}
|
56
|
-
end
|
57
|
-
|
58
|
-
describe "--port option" do
|
59
|
-
it "changes the listen port" do
|
60
|
-
add_stop_timer 0.05
|
61
|
-
hook_event_loop do
|
62
|
-
Socket.tcp("127.0.0.1", 3001) {|*| }
|
26
|
+
describe "without options" do
|
27
|
+
it "starts a Hatetepe::Server with default options" do
|
28
|
+
command "" do
|
29
|
+
Socket.tcp("127.0.0.1", 3000) {|*| }
|
30
|
+
ENV["RACK_ENV"].should == "development"
|
31
|
+
$stderr.string.should include("config.ru", "127.0.0.1:3000", "development")
|
63
32
|
end
|
64
|
-
Hatetepe::CLI.start %w{--port=3001}
|
65
|
-
|
66
|
-
$stderr.string.should include(":3001")
|
67
33
|
end
|
68
34
|
|
69
|
-
it "
|
70
|
-
|
71
|
-
|
72
|
-
|
35
|
+
it "serves HTTP requests" do
|
36
|
+
command "" do
|
37
|
+
Hatetepe::Client.get("http://127.0.0.1:3000").tap do |response|
|
38
|
+
response.status.should equal(200)
|
39
|
+
response.body.read.should == "Hello world!"
|
40
|
+
end
|
73
41
|
end
|
74
|
-
Hatetepe::CLI.start %w{-p 3002}
|
75
|
-
|
76
|
-
$stderr.string.should include(":3002")
|
77
42
|
end
|
78
43
|
end
|
79
44
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
Hatetepe::CLI.start %w{--bind=127.0.0.2}
|
87
|
-
|
88
|
-
$stderr.string.should include("127.0.0.2:")
|
89
|
-
end
|
90
|
-
|
91
|
-
it "has an alias: -b" do
|
92
|
-
add_stop_timer 0.05
|
93
|
-
hook_event_loop do
|
94
|
-
Socket.tcp("127.0.0.3", 3000) {|*| }
|
45
|
+
["--port", "-p"].each do |opt|
|
46
|
+
describe "with #{opt} option" do
|
47
|
+
it "binds the Hatetepe::Server to the specified TCP port" do
|
48
|
+
command "#{opt} 3002" do
|
49
|
+
Socket.tcp("127.0.0.1", 3002) {|*| }
|
50
|
+
end
|
95
51
|
end
|
96
|
-
Hatetepe::CLI.start %w{-b 127.0.0.3}
|
97
|
-
|
98
|
-
$stderr.string.should include("127.0.0.3:")
|
99
52
|
end
|
100
53
|
end
|
101
54
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
response.response.should include("config2.ru")
|
55
|
+
["--bind", "-b"].each do |opt|
|
56
|
+
describe "with #{opt} option" do
|
57
|
+
it "binds the Hatetepe::Server to the specified TCP interface" do
|
58
|
+
command "#{opt} 127.0.0.2" do
|
59
|
+
Socket.tcp("127.0.0.2", 3000) {|*| }
|
60
|
+
end
|
109
61
|
end
|
110
|
-
Hatetepe::CLI.start %w{--rackup=config2.ru}
|
111
62
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
["--rackup", "-r"].each do |opt|
|
66
|
+
describe "with #{opt} option" do
|
67
|
+
it "loads the specified rackup (.ru) file" do
|
68
|
+
command "#{opt} config2.ru" do
|
69
|
+
Hatetepe::Client.get("http://127.0.0.1:3000").tap do |response|
|
70
|
+
response.status.should equal(501)
|
71
|
+
response.body.read.should == "Herp derp"
|
72
|
+
end
|
73
|
+
end
|
119
74
|
end
|
120
|
-
Hatetepe::CLI.start %w{-r config2.ru}
|
121
75
|
end
|
122
76
|
end
|
123
77
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
$stderr.string.should be_empty
|
132
|
-
end
|
133
|
-
|
134
|
-
it "has an alias: -q" do
|
135
|
-
pending
|
78
|
+
["--env", "-e"].each do |opt|
|
79
|
+
describe "with #{opt} option" do
|
80
|
+
it "boots the app in the specified environment" do
|
81
|
+
command "#{opt} herpderp" do
|
82
|
+
ENV["RACK_ENV"].should == "herpderp"
|
83
|
+
end
|
84
|
+
end
|
136
85
|
|
137
|
-
|
138
|
-
|
86
|
+
["dev", "devel", "develop"].each do |value|
|
87
|
+
it "expands #{value} to `development'" do
|
88
|
+
command "#{opt} #{value}" do
|
89
|
+
ENV["RACK_ENV"].should == "development"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
139
93
|
|
140
|
-
|
94
|
+
it "expands test to `testing'" do
|
95
|
+
command "#{opt} test" do
|
96
|
+
ENV["RACK_ENV"].should == "testing"
|
97
|
+
end
|
98
|
+
end
|
141
99
|
end
|
142
100
|
end
|
143
101
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
add_stop_timer 0.05
|
149
|
-
hook_event_loop do
|
150
|
-
request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
|
102
|
+
["--timeout", "-t"].each do |opt|
|
103
|
+
describe "with #{opt} option" do
|
104
|
+
let :client do
|
105
|
+
Hatetepe::Client.start :host => "127.0.0.1", :port => 3000
|
151
106
|
end
|
152
|
-
Hatetepe::CLI.start %w{--verbose}
|
153
107
|
|
154
|
-
|
155
|
-
|
108
|
+
it "times out a connection after the specified amount of seconds" do
|
109
|
+
command "#{opt} 0.5", 1 do
|
110
|
+
client.should_not be_closed
|
156
111
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
112
|
+
EM::Synchrony.sleep 0.45
|
113
|
+
client.should_not be_closed
|
114
|
+
|
115
|
+
EM::Synchrony.sleep 0.1
|
116
|
+
client.should be_closed_by_remote
|
117
|
+
end
|
163
118
|
end
|
164
|
-
Hatetepe::CLI.start %w{-V}
|
165
|
-
|
166
|
-
$stderr.string.split("\n").size.should > 10
|
167
119
|
end
|
168
120
|
end
|
169
121
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "hatetepe/cli"
|
2
|
+
require "hatetepe/client"
|
3
|
+
require "spec_helper"
|
4
|
+
require "stringio"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
describe Hatetepe::Client, "with Keep-Alive" do
|
8
|
+
before do
|
9
|
+
$stderr = StringIO.new
|
10
|
+
|
11
|
+
FakeFS.activate!
|
12
|
+
File.open "config.ru", "w" do |f|
|
13
|
+
f.write 'run proc {|env| [200,
|
14
|
+
{"Content_Type" => "text/plain"},
|
15
|
+
[env["HTTP_CONNECTION"]]] }'
|
16
|
+
end
|
17
|
+
File.open "config_close.ru", "w" do |f|
|
18
|
+
f.write 'run proc {|env| [200,
|
19
|
+
{"Content-Type" => "text/plain",
|
20
|
+
"Connection" => "close"},
|
21
|
+
[env["HTTP_CONNECTION"]]] }'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
$stderr = STDERR
|
27
|
+
|
28
|
+
FakeFS.deactivate!
|
29
|
+
FakeFS::FileSystem.clear
|
30
|
+
end
|
31
|
+
|
32
|
+
let :client do
|
33
|
+
Hatetepe::Client.start :host => "127.0.0.1", :port => 30001
|
34
|
+
end
|
35
|
+
|
36
|
+
it "keeps the connection open" do
|
37
|
+
command "-p 30001", 2 do
|
38
|
+
client
|
39
|
+
EM::Synchrony.sleep 1.95
|
40
|
+
client.should_not be_closed_by_self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "sends Connection: keep-alive" do
|
45
|
+
command "-p 30001" do
|
46
|
+
client.get("/").body.read.should == "keep-alive"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "and an obviously single request" do
|
51
|
+
it "sends Connection: close" do
|
52
|
+
command "-p 30001" do
|
53
|
+
Hatetepe::Client.get "http://127.0.0.1:30001/" do |response|
|
54
|
+
YAML.load(response.body.read).should == "close"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "closes the connection immediately after the response" do
|
60
|
+
command "-p 30001" do
|
61
|
+
#Hatetepe::Client.any_instance.should_receive :stop
|
62
|
+
Hatetepe::Client.get "http://127.0.0.1:30001/"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "closes the connection if the server tells it to" do
|
68
|
+
#pending "Server can't send Conn: close as its Keep-Alive middleware overwrites it"
|
69
|
+
command "-p 30001 -r config_close.ru" do
|
70
|
+
client.get "/"
|
71
|
+
client.should be_closed
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|