rity 0.0.3 → 0.1.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/.yardopts +1 -0
- data/Gemfile +7 -0
- data/README.md +3 -0
- data/Rakefile +5 -1
- data/config.ru +102 -0
- data/lib/rity.rb +4 -0
- data/lib/rity/body.rb +29 -0
- data/lib/rity/cli.rb +1 -1
- data/lib/rity/connection.rb +48 -54
- data/lib/rity/request.rb +29 -54
- data/lib/rity/version.rb +1 -1
- data/myapp.rb +23 -13
- data/spec/unit/body_spec.rb +78 -0
- data/spec/unit/connection_spec.rb +155 -86
- data/spec/unit/request_spec.rb +74 -103
- metadata +15 -17
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- LICENSE
|
data/Gemfile
CHANGED
@@ -4,3 +4,10 @@ gemspec
|
|
4
4
|
|
5
5
|
gem "rake"
|
6
6
|
gem "awesome_print"
|
7
|
+
|
8
|
+
gem "yard"
|
9
|
+
gem "rdiscount"
|
10
|
+
|
11
|
+
gem "em-synchrony", :git => "git://github.com/lgierth/em-synchrony",
|
12
|
+
:branch => "immediately-return-from-sync"
|
13
|
+
gem "em-http-request", :git => "git://github.com/igrigorik/em-http-request"
|
data/README.md
CHANGED
@@ -7,8 +7,11 @@ TODO
|
|
7
7
|
----
|
8
8
|
|
9
9
|
- Proxying via EM.enable_proxy
|
10
|
+
- Investigate side effects of sending 500 for errors
|
10
11
|
- Rack Handler
|
11
12
|
- Support for keep-alive connections
|
12
13
|
- Investigate MVM support in JRuby/Rubinius/MRI
|
13
14
|
- Support for SPDY
|
14
15
|
- Investigate preforking and letting multiple EventMachine loops listen on a shared socket
|
16
|
+
- Support for X-Sendfile header
|
17
|
+
- Deamonizing & dropping privileges
|
data/Rakefile
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require "bundler"
|
2
|
-
Bundler.setup :default
|
2
|
+
Bundler.setup :default, :development
|
3
3
|
|
4
4
|
task :default => :spec
|
5
5
|
|
6
6
|
require "rspec/core/rake_task"
|
7
7
|
RSpec::Core::RakeTask.new :spec
|
8
8
|
|
9
|
+
require "bundler/gem_helper"
|
9
10
|
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
require "yard"
|
13
|
+
YARD::Rake::YardocTask.new
|
data/config.ru
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# streaming response body
|
3
|
+
#
|
4
|
+
|
5
|
+
run proc {|env|
|
6
|
+
EM.add_timer(1) {
|
7
|
+
env["stream.start"].call 200, "Content-Type" => "image/png"
|
8
|
+
EM.add_periodic_timer(1) { env["stream.send"].call "\n" }
|
9
|
+
}
|
10
|
+
EM.add_timer(10) { env["stream.close"].call }
|
11
|
+
[-1, {}, []]
|
12
|
+
|
13
|
+
# or instantly:
|
14
|
+
EM.add_periodic_timer(1) { env["stream.send"].call "\n" }
|
15
|
+
EM.add_timer(10) { env["stream.close"].call }
|
16
|
+
[200, {"Content-Type" => "image/png"}, Rack::STREAMING]
|
17
|
+
}
|
18
|
+
|
19
|
+
#
|
20
|
+
# async response (though all responses are async by default, anyway)
|
21
|
+
#
|
22
|
+
|
23
|
+
run proc {|env|
|
24
|
+
Fiber.new {
|
25
|
+
do_something
|
26
|
+
env["async.callback"].call [201, {"Content-Type" => "text/plain"}, ["Great!"]]
|
27
|
+
}.resume
|
28
|
+
|
29
|
+
[-1, {}, []]
|
30
|
+
}
|
31
|
+
|
32
|
+
#
|
33
|
+
# streaming request body
|
34
|
+
#
|
35
|
+
|
36
|
+
run proc {|env|
|
37
|
+
# will return when the input is write-closed
|
38
|
+
env["rack.input"].receive {|chunk| }
|
39
|
+
|
40
|
+
# same here
|
41
|
+
env["rack.input"].sync
|
42
|
+
File.open("input.dat", "w") {|f| f << env["rack.input"] }
|
43
|
+
|
44
|
+
[201, {"The" => "headers"}, []]
|
45
|
+
}
|
46
|
+
|
47
|
+
#
|
48
|
+
# proxying
|
49
|
+
#
|
50
|
+
|
51
|
+
class MySimpleProxy
|
52
|
+
def initialize(env)
|
53
|
+
@env = env
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(response)
|
57
|
+
@env["async.callback"].call response
|
58
|
+
|
59
|
+
# or modify the ouput via streaming:
|
60
|
+
@env["stream.start"].call *response[0..1]
|
61
|
+
response[2].each do |chunk|
|
62
|
+
@env["stream.send"].call chunk.upcase
|
63
|
+
end
|
64
|
+
@env["stream.close"].call
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class MyProxy
|
69
|
+
def initialize(env)
|
70
|
+
@env = env
|
71
|
+
end
|
72
|
+
|
73
|
+
def call(response)
|
74
|
+
@env["proxy.start_reverse"].call *response[0..1]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class MyApp
|
79
|
+
def headers(env)
|
80
|
+
shard = sharding_func.call env["REQUEST_URI"]
|
81
|
+
# env["rity.callback"] = MySimpleProxy.new(env)
|
82
|
+
env["proxy.callback"] = MyProxy.new(env)
|
83
|
+
env["proxy.start"].call shard.uri, "X-Sharding-Func" => "skip"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
run MyApp.new
|
88
|
+
|
89
|
+
# or in short:
|
90
|
+
|
91
|
+
run proc {|env|
|
92
|
+
shard = sharding_func.call(env["REQUEST_URI"])
|
93
|
+
# if no block is passed to proxy.start, it will use EM's native proxying to
|
94
|
+
# pass the response to the client
|
95
|
+
env["proxy.start"].call shard.uri, "Additional" => "headers" do |response|
|
96
|
+
@env["stream.start"].call *response[0..1]
|
97
|
+
response[2].each do |chunk|
|
98
|
+
@env["stream.send"].call chunk.upcase
|
99
|
+
end
|
100
|
+
@env["stream.close"].call
|
101
|
+
end
|
102
|
+
}
|
data/lib/rity.rb
CHANGED
data/lib/rity/body.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "em-synchrony"
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Rity
|
6
|
+
class Body < StringIO
|
7
|
+
include EM::Deferrable
|
8
|
+
|
9
|
+
def close_write
|
10
|
+
super
|
11
|
+
succeed
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(str)
|
15
|
+
super
|
16
|
+
@receiver.call str if @receiver
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive(&block)
|
20
|
+
string.each_line &block
|
21
|
+
@receiver = block
|
22
|
+
sync
|
23
|
+
end
|
24
|
+
|
25
|
+
def sync
|
26
|
+
EM::Synchrony.sync self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rity/cli.rb
CHANGED
data/lib/rity/connection.rb
CHANGED
@@ -1,78 +1,72 @@
|
|
1
1
|
require "eventmachine"
|
2
|
+
require "em-synchrony"
|
2
3
|
require "hatetepe"
|
3
|
-
|
4
4
|
require "rity/request"
|
5
5
|
|
6
6
|
module Rity
|
7
|
-
|
7
|
+
module Connection
|
8
8
|
attr_accessor :app, :log
|
9
|
-
attr_reader :requests, :parser, :builder, :responder
|
10
9
|
|
11
10
|
def post_init
|
12
|
-
@
|
11
|
+
@parser = Hatetepe::Parser.new
|
12
|
+
@builder = Hatetepe::Builder.new
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
p.on_header do |name, value|
|
23
|
-
@requests.last.add_header(name, value)
|
24
|
-
end
|
25
|
-
|
26
|
-
p.on_headers_complete do
|
27
|
-
@requests.last.precall
|
28
|
-
end
|
29
|
-
|
30
|
-
p.on_body_chunk do |chunk|
|
31
|
-
@requests.last.add_body_chunk(chunk)
|
32
|
-
end
|
33
|
-
|
34
|
-
p.on_complete do
|
35
|
-
@requests.last.call
|
36
|
-
request = nil
|
37
|
-
end
|
38
|
-
|
39
|
-
p.on_error do |e|
|
40
|
-
if request
|
41
|
-
request.error(e)
|
42
|
-
else
|
43
|
-
raise(e)
|
44
|
-
end
|
45
|
-
end
|
14
|
+
(previous = EM::DefaultDeferrable.new).succeed
|
15
|
+
request = nil
|
16
|
+
|
17
|
+
@parser.on_response { close_connection }
|
18
|
+
|
19
|
+
@parser.on_request do |verb, uri|
|
20
|
+
previous = request if request
|
21
|
+
request = Request.new(@app, verb, uri)
|
46
22
|
end
|
47
23
|
|
48
|
-
@
|
49
|
-
|
24
|
+
@parser.on_header do |name, value|
|
25
|
+
request.add_header name, value
|
26
|
+
end
|
27
|
+
|
28
|
+
@parser.on_headers_complete do
|
29
|
+
prev, req = previous, request
|
50
30
|
|
51
|
-
|
52
|
-
|
31
|
+
req.env["stream.start"] = proc do |response|
|
32
|
+
EM::Synchrony.sync prev
|
33
|
+
|
34
|
+
@builder.response response[0..1]
|
35
|
+
req.env["stream.send"] = proc do |chunk|
|
36
|
+
@builder.body chunk
|
37
|
+
end
|
38
|
+
|
39
|
+
req.env["stream.close"] = proc do
|
40
|
+
@builder.complete
|
41
|
+
req.succeed
|
42
|
+
close_connection_after_writing if req == request
|
43
|
+
end
|
53
44
|
end
|
54
45
|
|
55
|
-
|
46
|
+
req.process
|
56
47
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
48
|
+
|
49
|
+
@parser.on_body_chunk do |chunk|
|
50
|
+
request.add_body_chunk chunk
|
51
|
+
end
|
52
|
+
|
53
|
+
@parser.on_complete do
|
54
|
+
request.close_body
|
55
|
+
end
|
56
|
+
|
57
|
+
@builder.on_write do |data|
|
58
|
+
send_data data
|
63
59
|
end
|
64
60
|
end
|
65
61
|
|
66
62
|
def receive_data(data)
|
67
63
|
@parser << data
|
68
|
-
rescue
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
def error(e)
|
64
|
+
rescue Hatetepe::ParserError
|
65
|
+
close_connection
|
66
|
+
rescue Exception => ex
|
73
67
|
if log
|
74
|
-
log.error
|
75
|
-
log <<
|
68
|
+
log.error ex.message
|
69
|
+
log << ex.backtrace.map {|line| " #{line}" }.join("\n") + "\n"
|
76
70
|
end
|
77
71
|
close_connection
|
78
72
|
end
|
data/lib/rity/request.rb
CHANGED
@@ -1,77 +1,52 @@
|
|
1
|
-
require "
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
|
5
|
-
require "rack"
|
6
|
-
require "async-rack"
|
1
|
+
require "eventmachine"
|
2
|
+
require "em-synchrony"
|
3
|
+
require "rity/body"
|
7
4
|
|
8
5
|
module Rity
|
9
6
|
class Request
|
10
7
|
include EM::Deferrable
|
11
8
|
|
12
|
-
attr_reader :
|
13
|
-
attr_reader :app, :env, :response
|
9
|
+
attr_reader :app, :env
|
14
10
|
|
15
|
-
def initialize(app, verb,
|
16
|
-
@app = app
|
17
|
-
|
11
|
+
def initialize(app, verb, uri)
|
12
|
+
@app, @env = app, {
|
13
|
+
"rack.input" => Body.new,
|
18
14
|
"REQUEST_METHOD" => verb,
|
19
|
-
"REQUEST_URI" =>
|
20
|
-
"
|
21
|
-
"
|
15
|
+
"REQUEST_URI" => uri,
|
16
|
+
"async.callback" => method(:postprocess),
|
17
|
+
# "proxy.start" => &method(:proxy_start),
|
18
|
+
# "proxy.callback" => &method(:proxy_start_reverse),
|
19
|
+
# "proxy.start_reverse" => &method(:proxy_start_reverse),
|
22
20
|
}
|
23
21
|
end
|
24
22
|
|
25
|
-
def log=(log)
|
26
|
-
@log = log
|
27
|
-
@env["rack.logger"] = log
|
28
|
-
end
|
29
|
-
|
30
|
-
def add_body_chunk(chunk)
|
31
|
-
env["rack.input"] << chunk
|
32
|
-
end
|
33
|
-
|
34
23
|
def add_header(name, value)
|
35
24
|
key = "HTTP_" + name.upcase.gsub("-", "_")
|
36
|
-
|
25
|
+
env[key] = value
|
37
26
|
end
|
38
27
|
|
39
|
-
def
|
40
|
-
|
41
|
-
rescue_errors { app.precall(env) }
|
42
|
-
end.resume if app.respond_to?(:precall)
|
43
|
-
end
|
44
|
-
|
45
|
-
def call
|
46
|
-
env["rack.input"].close
|
47
|
-
env["rack.input"].rewind
|
48
|
-
|
49
|
-
Fiber.new do
|
50
|
-
rescue_errors { postcall(app.call(env)) }
|
51
|
-
end.resume
|
28
|
+
def add_body_chunk(chunk)
|
29
|
+
env["rack.input"].write chunk
|
52
30
|
end
|
53
31
|
|
54
|
-
def
|
55
|
-
|
56
|
-
@response = response
|
57
|
-
succeed
|
32
|
+
def close_body
|
33
|
+
env["rack.input"].close_write
|
58
34
|
end
|
59
35
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
postcall [500, {"Content-Type" => "text/html"},
|
66
|
-
["<h1>#{Hatetepe::STATUS_CODES[500]}</h1>"]]
|
36
|
+
def process
|
37
|
+
Fiber.new {
|
38
|
+
postprocess app.call(env)
|
39
|
+
}.resume
|
67
40
|
end
|
68
41
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
42
|
+
def postprocess(response)
|
43
|
+
return if response[0] < 0
|
44
|
+
|
45
|
+
env["stream.start"].call response[0..1]
|
46
|
+
return if response[2] == Rack::STREAMING
|
47
|
+
|
48
|
+
response[2].each {|chunk| env["stream.send"].call chunk }
|
49
|
+
env["stream.close"].call
|
75
50
|
end
|
76
51
|
end
|
77
52
|
end
|
data/lib/rity/version.rb
CHANGED
data/myapp.rb
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
require "bundler"
|
2
2
|
Bundler.setup :default
|
3
3
|
|
4
|
-
require "
|
4
|
+
require "logger"
|
5
|
+
require "eventmachine"
|
6
|
+
require "em-synchrony"
|
7
|
+
require "rity/new_connection"
|
5
8
|
require "awesome_print"
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
[-1, {}, []]
|
17
|
-
end
|
10
|
+
address, port = "127.0.0.1", 3000
|
11
|
+
|
12
|
+
app = proc {|env|
|
13
|
+
[200, {"Content-Type" => "text/html"}, ["You requested: #{env["REQUEST_URI"]}"]]
|
14
|
+
}
|
15
|
+
|
16
|
+
log = Logger.new($stderr)
|
17
|
+
log.formatter = proc do |severity, time, progname, message|
|
18
|
+
"[#{time}] #{severity}: #{message}\n"
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
log.info("Binding to #{address}:#{port}")
|
22
|
+
|
23
|
+
EM.synchrony do
|
24
|
+
trap("INT") { EM.stop }
|
25
|
+
trap("TERM") { EM.stop }
|
26
|
+
|
27
|
+
EM.epoll
|
28
|
+
|
29
|
+
EM.start_server address, port, Rity::Connection, app, log
|
30
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rity::Body do
|
4
|
+
let(:body) { Rity::Body.new }
|
5
|
+
|
6
|
+
it "is deferrable" do
|
7
|
+
body.should respond_to(:callback)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#close_write" do
|
11
|
+
it "closes the body for writing" do
|
12
|
+
body.close_write
|
13
|
+
expect { body.write "" }.to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "succeeds the deferrable" do
|
17
|
+
body.should_receive :succeed
|
18
|
+
body.close_write
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#write" do
|
23
|
+
it "writes data to the IO" do
|
24
|
+
body.write "asdf"
|
25
|
+
body.string.should == "asdf"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "yields the data to the receiver block if any" do
|
29
|
+
str = nil
|
30
|
+
Fiber.new {
|
31
|
+
body.receive {|s| str = s }
|
32
|
+
}.resume
|
33
|
+
|
34
|
+
body.write "asdf"
|
35
|
+
str.should == "asdf"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#receive" do
|
40
|
+
it "yields each line that has already been written" do
|
41
|
+
body.write "as\ndf"
|
42
|
+
lines = []
|
43
|
+
Fiber.new {
|
44
|
+
body.receive {|l| lines << l }
|
45
|
+
}.resume
|
46
|
+
|
47
|
+
body.close_write
|
48
|
+
lines.should == ["as\n", "df"]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "saves the receiver block for future writes" do
|
52
|
+
chunks = []
|
53
|
+
Fiber.new {
|
54
|
+
body.receive {|c| chunks << c }
|
55
|
+
}.resume
|
56
|
+
|
57
|
+
body.write "as"
|
58
|
+
chunks.should == ["as"]
|
59
|
+
body.write "df"
|
60
|
+
chunks.should == ["as", "df"]
|
61
|
+
body.close_write
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#sync" do
|
66
|
+
it "waits until the body is closed for writing" do
|
67
|
+
closed = false
|
68
|
+
Fiber.new {
|
69
|
+
body.sync
|
70
|
+
expect { body.write "" }.to raise_error
|
71
|
+
closed = true
|
72
|
+
}.resume
|
73
|
+
|
74
|
+
body.close_write
|
75
|
+
closed.should be_true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,112 +1,181 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Rity::Connection do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
let(:conn) { Class.new { include Rity::Connection }.new }
|
5
|
+
let(:app) { mock "app" }
|
6
|
+
let(:log) { mock "log" }
|
7
|
+
let(:parser) { Hatetepe::Parser.new }
|
8
|
+
let(:builder) { Hatetepe::Builder.new }
|
9
|
+
let(:requests) { [] }
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
before do
|
12
|
+
Hatetepe::Parser.stub :new => parser
|
13
|
+
Hatetepe::Builder.stub :new => builder
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@conn.receive_data("asdfg")
|
15
|
+
Rity::Request.singleton_class.send :alias_method, :spec_original_new, :new
|
16
|
+
Rity::Request.stub :new do |*args|
|
17
|
+
requests << request = Rity::Request.spec_original_new(*args)
|
18
|
+
request
|
19
|
+
end
|
20
|
+
|
21
|
+
conn.post_init
|
22
22
|
end
|
23
23
|
|
24
|
-
it "
|
25
|
-
|
26
|
-
|
24
|
+
it "can have an app and a logger" do
|
25
|
+
conn.app, conn.log = app, log
|
26
|
+
conn.app.should == app
|
27
|
+
conn.log.should == log
|
27
28
|
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
describe "#receive_data" do
|
31
|
+
it "feeds the parser with data" do
|
32
|
+
parser.should_receive(:<<).with "asdf"
|
33
|
+
conn.receive_data "asdf"
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
it "closes the connection if parsing fails" do
|
37
|
+
parser.stub(:<<) {|data| raise Hatetepe::ParserError, "parser error" }
|
38
|
+
conn.should_receive :close_connection
|
39
|
+
|
40
|
+
conn.receive_data "asdf"
|
41
|
+
end
|
35
42
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
it "adds each parsed header to the request" do
|
47
|
-
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
48
|
-
@conn.requests.last.should_receive(:add_header).with("Asd", "123")
|
49
|
-
@conn.parser.on_header[0].call("Asd", "123")
|
50
|
-
end
|
51
|
-
|
52
|
-
it "calls the request's #precall method when headers are finished" do
|
53
|
-
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
54
|
-
@conn.requests.last.should_receive(:precall)
|
55
|
-
@conn.parser.on_headers_complete[0].call
|
56
|
-
end
|
57
|
-
|
58
|
-
it "adds each parsed body chunk to the request" do
|
59
|
-
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
60
|
-
@conn.requests.last.should_receive(:add_body_chunk).with("asdf")
|
61
|
-
@conn.parser.on_body_chunk[0].call("asdf")
|
43
|
+
it "logs and closes the connection if other errors happen" do
|
44
|
+
parser.stub(:<<) {|data| raise "some other error" }
|
45
|
+
conn.should_receive :close_connection
|
46
|
+
|
47
|
+
conn.log = log
|
48
|
+
log.should_receive(:error).with "some other error"
|
49
|
+
log.should_receive(:<<)
|
50
|
+
|
51
|
+
conn.receive_data "asdf"
|
52
|
+
end
|
62
53
|
end
|
63
54
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
55
|
+
describe "the parser" do
|
56
|
+
it "closes the connection if a response comes in" do
|
57
|
+
conn.should_receive :close_connection
|
58
|
+
parser.on_response[0].call
|
59
|
+
end
|
60
|
+
|
61
|
+
it "creates a request object for each incoming request" do
|
62
|
+
parser.on_request[0].call "GET", "/"
|
63
|
+
requests[0].env["REQUEST_METHOD"].should == "GET"
|
64
|
+
requests[0].env["REQUEST_URI"].should == "/"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "adds each header to the request object" do
|
68
|
+
parser.on_request[0].call "GET", "/"
|
69
|
+
requests[0].should_receive(:add_header).with "Key", "value"
|
70
|
+
parser.on_header[0].call "Key", "value"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "processes the request when the headers are complete" do
|
74
|
+
parser.on_request[0].call "GET", "/"
|
75
|
+
requests[0].should_receive :process
|
76
|
+
parser.on_headers_complete[0].call
|
77
|
+
end
|
78
|
+
|
79
|
+
it "adds the stream.start proc to the request before processing" do
|
80
|
+
parser.on_request[0].call "GET", "/"
|
81
|
+
requests[0].stub :process
|
82
|
+
parser.on_headers_complete[0].call
|
83
|
+
requests[0].env["stream.start"].should respond_to(:call)
|
84
|
+
end
|
68
85
|
end
|
69
86
|
|
70
|
-
describe "
|
71
|
-
|
72
|
-
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
73
|
-
response = stub("response")
|
74
|
-
@conn.requests[0].stub(:response => response)
|
75
|
-
|
76
|
-
@conn.builder.should_receive(:response).with(response)
|
77
|
-
@conn.write_responses
|
78
|
-
end
|
87
|
+
describe "response stream" do
|
88
|
+
let(:env) { requests[0].env }
|
79
89
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
90
|
+
before do
|
91
|
+
conn.stub :send_data
|
92
|
+
|
93
|
+
parser.on_request[0].call "GET", "/"
|
94
|
+
requests[0].stub :process
|
95
|
+
parser.on_headers_complete[0].call
|
84
96
|
end
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
98
|
+
describe "stream.start" do
|
99
|
+
it "waits until the previous request is finished" do
|
100
|
+
expected_responses = []
|
101
|
+
2.times do |i|
|
102
|
+
response = stub "response##{i}"
|
103
|
+
response.stub :[] => response
|
104
|
+
expected_responses << response
|
105
|
+
end
|
106
|
+
|
107
|
+
actual_responses = []
|
108
|
+
builder.stub :response do |response|
|
109
|
+
actual_responses << response
|
110
|
+
end
|
111
|
+
|
112
|
+
parser.on_request[0].call "GET", "/another"
|
113
|
+
requests[1].stub :process
|
114
|
+
parser.on_headers_complete[0].call
|
115
|
+
|
116
|
+
Fiber.new {
|
117
|
+
requests[1].env["stream.start"].call expected_responses[1]
|
118
|
+
}.resume
|
119
|
+
Fiber.new {
|
120
|
+
requests[0].env["stream.start"].call expected_responses[0]
|
121
|
+
}.resume
|
122
|
+
|
123
|
+
requests[0].env["stream.close"].call
|
124
|
+
actual_responses.should == expected_responses
|
125
|
+
end
|
90
126
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
127
|
+
it "passes response status and headers to the builder" do
|
128
|
+
response = [200, {"Key" => "value"}, []]
|
129
|
+
builder.should_receive(:response).with response[0..1]
|
130
|
+
requests[0].env["stream.start"].call response
|
131
|
+
end
|
95
132
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
133
|
+
it "sets stream.send and stream.close" do
|
134
|
+
env["stream.send"].should be_nil
|
135
|
+
env["stream.close"].should be_nil
|
136
|
+
|
137
|
+
env["stream.start"].call [200, {}, []]
|
138
|
+
env["stream.send"].should respond_to(:call)
|
139
|
+
env["stream.close"].should respond_to(:call)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "stream.send" do
|
144
|
+
it "passes a body chunk to the builder" do
|
145
|
+
env["stream.start"].call [200, {}, []]
|
146
|
+
|
147
|
+
builder.should_receive(:body).with "asdf"
|
148
|
+
env["stream.send"].call "asdf"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "stream.close" do
|
153
|
+
before do
|
154
|
+
env["stream.start"].call [200, {}, []]
|
155
|
+
conn.stub :close_connection_after_writing
|
156
|
+
end
|
101
157
|
|
102
|
-
|
158
|
+
it "completes building the response" do
|
159
|
+
builder.should_receive :complete
|
160
|
+
env["stream.close"].call
|
161
|
+
end
|
103
162
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
163
|
+
it "finishes the request" do
|
164
|
+
requests[0].should_receive :succeed
|
165
|
+
env["stream.close"].call
|
166
|
+
end
|
108
167
|
|
109
|
-
|
168
|
+
it "closes the connection" do
|
169
|
+
conn.should_receive :close_connection_after_writing
|
170
|
+
env["stream.close"].call
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "the builder" do
|
176
|
+
it "writes to the connection" do
|
177
|
+
conn.should_receive(:send_data).with "asdf"
|
178
|
+
builder.on_write[0].call "asdf"
|
110
179
|
end
|
111
180
|
end
|
112
181
|
end
|
data/spec/unit/request_spec.rb
CHANGED
@@ -1,132 +1,103 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Rity::Request do
|
4
|
-
|
5
|
-
|
6
|
-
@request = Rity::Request.new(@app, "GET", "/")
|
7
|
-
end
|
4
|
+
let(:app) { mock "app" }
|
5
|
+
let(:request) { Rity::Request.new app, "GET", "/" }
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
describe "#initialize" do
|
8
|
+
it "sets the app" do
|
9
|
+
request.app.should == app
|
10
|
+
end
|
11
|
+
|
12
|
+
it "initializes the env hash" do
|
13
|
+
request.env["REQUEST_METHOD"].should == "GET"
|
14
|
+
request.env["REQUEST_URI"].should == "/"
|
15
|
+
request.env["rack.input"].should be_kind_of(Rity::Body)
|
16
|
+
request.env["async.callback"].should == request.method(:postprocess)
|
17
|
+
end
|
12
18
|
end
|
13
19
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
describe "#add_header" do
|
21
|
+
it "adds a header to the env hash" do
|
22
|
+
request.add_header "Content-Type", "text/html"
|
23
|
+
request.env["HTTP_CONTENT_TYPE"].should == "text/html"
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
describe "#add_body_chunk" do
|
28
|
+
it "adds a chunk to the body input" do
|
29
|
+
request.env["rack.input"].should_receive(:write).with "asdf"
|
30
|
+
request.add_body_chunk "asdf"
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@request.response.should == response
|
34
|
+
describe "#close_body" do
|
35
|
+
it "closes the body input for writing" do
|
36
|
+
request.env["rack.input"].should_receive(:close_write)
|
37
|
+
request.close_body
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
|
-
|
36
|
-
response
|
37
|
-
async_response = [200, {}, "okokok"]
|
38
|
-
@app.should_receive(:call).with(@request.env).and_return(response)
|
41
|
+
describe "#process" do
|
42
|
+
let(:response) { [-1, {}, []] }
|
39
43
|
|
40
|
-
@
|
44
|
+
before { @outer_fiber = Fiber.current }
|
41
45
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@app.singleton_class.send(:define_method, :call) do |env|
|
51
|
-
inner_fiber = Fiber.current
|
46
|
+
it "calls the app in a separate fiber" do
|
47
|
+
app.should_receive :call do |env|
|
48
|
+
request.env.should == env
|
49
|
+
@outer_fiber.should_not == Fiber.current
|
50
|
+
response
|
51
|
+
end
|
52
|
+
|
53
|
+
request.process
|
52
54
|
end
|
53
|
-
@request.call
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
it "hands the response to post-processing in the same fiber" do
|
57
|
+
app.stub :call => response
|
58
|
+
request.should_receive :postprocess do |resp|
|
59
|
+
response.should == resp
|
60
|
+
@outer_fiber.should_not == Fiber.current
|
61
|
+
end
|
62
|
+
|
63
|
+
request.process
|
61
64
|
end
|
62
|
-
@request.should_receive(:succeed)
|
63
|
-
@request.call
|
64
|
-
|
65
|
-
@request.response[0].should == 500
|
66
|
-
@request.response[1].should == {"Content-Type" => "text/html"}
|
67
|
-
@request.response[2].should == ["<h1>Internal Server Error</h1>"]
|
68
65
|
end
|
69
66
|
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
describe "#postprocess" do
|
68
|
+
let :response do
|
69
|
+
[200, {"Content-Type" => "text/html"}, ["Hello", "World!"]]
|
73
70
|
end
|
74
|
-
@request.should_receive(:succeed)
|
75
|
-
@request.precall
|
76
71
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
called = false
|
84
|
-
request = @request
|
85
|
-
@app.singleton_class.send(:define_method, :precall) do |env|
|
86
|
-
env.should == request.env
|
87
|
-
called = true
|
72
|
+
before do
|
73
|
+
request.env.merge!({
|
74
|
+
"stream.start" => mock("stream.start"),
|
75
|
+
"stream.send" => mock("stream.send"),
|
76
|
+
"stream.close" => mock("stream.close")
|
77
|
+
})
|
88
78
|
end
|
89
79
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@request.precall
|
96
|
-
called.should be_true
|
97
|
-
end
|
98
|
-
|
99
|
-
it "calls the app's #precall method in a separate fiber" do
|
100
|
-
outer_fiber = Fiber.current
|
101
|
-
inner_fiber = nil
|
102
|
-
@app.singleton_class.send(:define_method, :precall) do |env|
|
103
|
-
inner_fiber = Fiber.current
|
80
|
+
it "ignores responses with a status < 0" do
|
81
|
+
response[0] = -1
|
82
|
+
request.env["stream.start"].should_not_receive :call
|
83
|
+
|
84
|
+
request.postprocess response
|
104
85
|
end
|
105
|
-
@request.precall
|
106
|
-
|
107
|
-
outer_fiber.should_not == inner_fiber
|
108
|
-
end
|
109
|
-
|
110
|
-
it "adds headers to the env hash" do
|
111
|
-
@request.add_header("Content-Type", "text/html; charset=utf-8")
|
112
|
-
@request.env["HTTP_CONTENT_TYPE"].should == "text/html; charset=utf-8"
|
113
|
-
end
|
114
|
-
|
115
|
-
it "adds data to the body" do
|
116
|
-
@request.add_body_chunk "asdf"
|
117
|
-
@request.env["rack.input"].string.should == "asdf"
|
118
86
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
@request.add_body_chunk "asdf"
|
125
|
-
@app.should_receive(:call) do |env|
|
126
|
-
env["rack.input"].closed_write?.should be_true
|
127
|
-
env["rack.input"].pos.should == 0
|
87
|
+
it "only sends response line and headers for streaming responses" do
|
88
|
+
response[2] = Rack::STREAMING
|
89
|
+
request.env["stream.start"].should_receive(:call).with response[0..1]
|
90
|
+
|
91
|
+
request.postprocess response
|
128
92
|
end
|
129
93
|
|
130
|
-
|
94
|
+
it "sends the response and closes the stream" do
|
95
|
+
request.env["stream.start"].should_receive(:call).with response[0..1]
|
96
|
+
request.env["stream.send"].should_receive(:call).with response[2][0]
|
97
|
+
request.env["stream.send"].should_receive(:call).with response[2][1]
|
98
|
+
request.env["stream.close"].should_receive :call
|
99
|
+
|
100
|
+
request.postprocess response
|
101
|
+
end
|
131
102
|
end
|
132
103
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: rity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0
|
5
|
+
version: 0.1.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Lars Gierth
|
@@ -10,10 +10,11 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-19 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
17
|
+
prerelease: false
|
17
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
18
19
|
none: false
|
19
20
|
requirements:
|
@@ -21,10 +22,10 @@ dependencies:
|
|
21
22
|
- !ruby/object:Gem::Version
|
22
23
|
version: "0"
|
23
24
|
type: :runtime
|
24
|
-
prerelease: false
|
25
25
|
version_requirements: *id001
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: em-synchrony
|
28
|
+
prerelease: false
|
28
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
29
30
|
none: false
|
30
31
|
requirements:
|
@@ -32,10 +33,10 @@ dependencies:
|
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: "0"
|
34
35
|
type: :runtime
|
35
|
-
prerelease: false
|
36
36
|
version_requirements: *id002
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: hatetepe
|
39
|
+
prerelease: false
|
39
40
|
requirement: &id003 !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
41
42
|
requirements:
|
@@ -43,10 +44,10 @@ dependencies:
|
|
43
44
|
- !ruby/object:Gem::Version
|
44
45
|
version: "0"
|
45
46
|
type: :runtime
|
46
|
-
prerelease: false
|
47
47
|
version_requirements: *id003
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: rack
|
50
|
+
prerelease: false
|
50
51
|
requirement: &id004 !ruby/object:Gem::Requirement
|
51
52
|
none: false
|
52
53
|
requirements:
|
@@ -54,10 +55,10 @@ dependencies:
|
|
54
55
|
- !ruby/object:Gem::Version
|
55
56
|
version: "0"
|
56
57
|
type: :runtime
|
57
|
-
prerelease: false
|
58
58
|
version_requirements: *id004
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: async-rack
|
61
|
+
prerelease: false
|
61
62
|
requirement: &id005 !ruby/object:Gem::Requirement
|
62
63
|
none: false
|
63
64
|
requirements:
|
@@ -65,10 +66,10 @@ dependencies:
|
|
65
66
|
- !ruby/object:Gem::Version
|
66
67
|
version: "0"
|
67
68
|
type: :runtime
|
68
|
-
prerelease: false
|
69
69
|
version_requirements: *id005
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: thor
|
72
|
+
prerelease: false
|
72
73
|
requirement: &id006 !ruby/object:Gem::Requirement
|
73
74
|
none: false
|
74
75
|
requirements:
|
@@ -76,10 +77,10 @@ dependencies:
|
|
76
77
|
- !ruby/object:Gem::Version
|
77
78
|
version: "0"
|
78
79
|
type: :runtime
|
79
|
-
prerelease: false
|
80
80
|
version_requirements: *id006
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rspec
|
83
|
+
prerelease: false
|
83
84
|
requirement: &id007 !ruby/object:Gem::Requirement
|
84
85
|
none: false
|
85
86
|
requirements:
|
@@ -87,10 +88,10 @@ dependencies:
|
|
87
88
|
- !ruby/object:Gem::Version
|
88
89
|
version: "0"
|
89
90
|
type: :development
|
90
|
-
prerelease: false
|
91
91
|
version_requirements: *id007
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: fakefs
|
94
|
+
prerelease: false
|
94
95
|
requirement: &id008 !ruby/object:Gem::Requirement
|
95
96
|
none: false
|
96
97
|
requirements:
|
@@ -98,10 +99,10 @@ dependencies:
|
|
98
99
|
- !ruby/object:Gem::Version
|
99
100
|
version: "0"
|
100
101
|
type: :development
|
101
|
-
prerelease: false
|
102
102
|
version_requirements: *id008
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: em-http-request
|
105
|
+
prerelease: false
|
105
106
|
requirement: &id009 !ruby/object:Gem::Requirement
|
106
107
|
none: false
|
107
108
|
requirements:
|
@@ -109,7 +110,6 @@ dependencies:
|
|
109
110
|
- !ruby/object:Gem::Version
|
110
111
|
version: "0"
|
111
112
|
type: :development
|
112
|
-
prerelease: false
|
113
113
|
version_requirements: *id009
|
114
114
|
description: Rity is a lightweight Ruby webserver that runs inside an EventMachine loop and puts each request into a fiber.
|
115
115
|
email:
|
@@ -122,12 +122,15 @@ extra_rdoc_files: []
|
|
122
122
|
|
123
123
|
files:
|
124
124
|
- .rspec
|
125
|
+
- .yardopts
|
125
126
|
- Gemfile
|
126
127
|
- LICENSE
|
127
128
|
- README.md
|
128
129
|
- Rakefile
|
129
130
|
- bin/rity
|
131
|
+
- config.ru
|
130
132
|
- lib/rity.rb
|
133
|
+
- lib/rity/body.rb
|
131
134
|
- lib/rity/cli.rb
|
132
135
|
- lib/rity/connection.rb
|
133
136
|
- lib/rity/request.rb
|
@@ -136,6 +139,7 @@ files:
|
|
136
139
|
- rity.gemspec
|
137
140
|
- spec/integration/start_spec.rb
|
138
141
|
- spec/spec_helper.rb
|
142
|
+
- spec/unit/body_spec.rb
|
139
143
|
- spec/unit/connection_spec.rb
|
140
144
|
- spec/unit/request_spec.rb
|
141
145
|
homepage: https://github.com/lgierth/rity
|
@@ -151,18 +155,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
155
|
requirements:
|
152
156
|
- - ">="
|
153
157
|
- !ruby/object:Gem::Version
|
154
|
-
hash: -888247411
|
155
|
-
segments:
|
156
|
-
- 0
|
157
158
|
version: "0"
|
158
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
160
|
none: false
|
160
161
|
requirements:
|
161
162
|
- - ">="
|
162
163
|
- !ruby/object:Gem::Version
|
163
|
-
hash: -888247411
|
164
|
-
segments:
|
165
|
-
- 0
|
166
164
|
version: "0"
|
167
165
|
requirements: []
|
168
166
|
|