rity 0.0.1 → 0.0.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/.rspec +1 -0
- data/README.md +3 -2
- data/Rakefile +3 -5
- data/lib/rity/connection.rb +10 -10
- data/lib/rity/request.rb +8 -1
- data/lib/rity/server.rb +8 -1
- data/lib/rity/version.rb +1 -1
- data/myapp.rb +1 -1
- data/rity.gemspec +2 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/connection_spec.rb +112 -0
- data/spec/unit/request_spec.rb +132 -0
- metadata +19 -4
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/README.md
CHANGED
@@ -6,10 +6,11 @@ Rity is a lightweight Ruby webserver that runs inside an EventMachine loop and p
|
|
6
6
|
TODO
|
7
7
|
----
|
8
8
|
|
9
|
-
- Tests (!)
|
10
9
|
- Command Line Interface
|
11
|
-
-
|
10
|
+
- Let Request handle parsing and building errors
|
11
|
+
- More robust error handling in general
|
12
12
|
- Proxying via EM.enable_proxy
|
13
13
|
- Rack Handler
|
14
14
|
- Support for keep-alive connections
|
15
15
|
- Investigate MVM support in JRuby/Rubinius/MRI
|
16
|
+
- Support for SPDY
|
data/Rakefile
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
require "bundler"
|
2
2
|
Bundler.setup :default
|
3
3
|
|
4
|
-
task :default => :
|
4
|
+
task :default => :spec
|
5
5
|
|
6
|
-
require "
|
7
|
-
|
8
|
-
t.test_files = FileList["test/*_test.rb"]
|
9
|
-
end
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
RSpec::Core::RakeTask.new :spec
|
10
8
|
|
11
9
|
Bundler::GemHelper.install_tasks
|
data/lib/rity/connection.rb
CHANGED
@@ -5,34 +5,34 @@ require "rity/request"
|
|
5
5
|
|
6
6
|
module Rity
|
7
7
|
class Connection < EM::Connection
|
8
|
-
|
9
|
-
|
10
|
-
@requests = []
|
11
|
-
end
|
8
|
+
attr_accessor :app, :log
|
9
|
+
attr_reader :requests, :parser, :builder, :responder
|
12
10
|
|
13
11
|
def post_init
|
14
|
-
request = nil
|
12
|
+
@requests, request = [], nil
|
13
|
+
|
15
14
|
@parser = Hatetepe::Parser.new do |p|
|
16
15
|
p.on_request do |verb, url|
|
17
16
|
request = Request.new(@app, verb, url)
|
17
|
+
request.log = log
|
18
18
|
@requests.push(request)
|
19
|
-
|
19
|
+
@requests.last.callback &@responder.method(:resume)
|
20
20
|
end
|
21
21
|
|
22
22
|
p.on_header do |name, value|
|
23
|
-
|
23
|
+
@requests.last.add_header(name, value)
|
24
24
|
end
|
25
25
|
|
26
26
|
p.on_headers_complete do
|
27
|
-
|
27
|
+
@requests.last.precall
|
28
28
|
end
|
29
29
|
|
30
30
|
p.on_body_chunk do |chunk|
|
31
|
-
|
31
|
+
@requests.last.add_body_chunk(chunk)
|
32
32
|
end
|
33
33
|
|
34
34
|
p.on_complete do
|
35
|
-
|
35
|
+
@requests.last.call
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
data/lib/rity/request.rb
CHANGED
@@ -9,6 +9,7 @@ module Rity
|
|
9
9
|
class Request
|
10
10
|
include EM::Deferrable
|
11
11
|
|
12
|
+
attr_reader :log
|
12
13
|
attr_reader :app, :env, :response
|
13
14
|
|
14
15
|
def initialize(app, verb, url)
|
@@ -17,10 +18,15 @@ module Rity
|
|
17
18
|
"REQUEST_METHOD" => verb,
|
18
19
|
"REQUEST_URI" => url,
|
19
20
|
"rack.input" => StringIO.new,
|
20
|
-
"async.callback" => method(:postcall)
|
21
|
+
"async.callback" => method(:postcall),
|
21
22
|
}
|
22
23
|
end
|
23
24
|
|
25
|
+
def log=(log)
|
26
|
+
@log = log
|
27
|
+
@env["rack.logger"] = log
|
28
|
+
end
|
29
|
+
|
24
30
|
def add_body_chunk(chunk)
|
25
31
|
env["rack.input"] << chunk
|
26
32
|
end
|
@@ -48,6 +54,7 @@ module Rity
|
|
48
54
|
def postcall(response)
|
49
55
|
return if @response || response[0] < 0
|
50
56
|
@response = response
|
57
|
+
log.info("#{response[0]} - #{env["REQUEST_METHOD"]} #{env["REQUEST_URI"]}") if log
|
51
58
|
succeed
|
52
59
|
end
|
53
60
|
|
data/lib/rity/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "eventmachine"
|
2
2
|
require "em-synchrony"
|
3
|
+
require "logger"
|
3
4
|
|
4
5
|
require "rity/connection"
|
5
6
|
|
@@ -13,7 +14,13 @@ module Rity
|
|
13
14
|
EM.epoll
|
14
15
|
|
15
16
|
puts "Binding to #{address}:#{port}"
|
16
|
-
EM.start_server address, port, Connection
|
17
|
+
EM.start_server address, port, Connection do |conn|
|
18
|
+
conn.app = app
|
19
|
+
conn.log = Logger.new(STDERR)
|
20
|
+
conn.log.formatter = proc do |severity, time, progname, message|
|
21
|
+
"[#{time}] #{severity}: #{message}\n"
|
22
|
+
end
|
23
|
+
end
|
17
24
|
end
|
18
25
|
end
|
19
26
|
end
|
data/lib/rity/version.rb
CHANGED
data/myapp.rb
CHANGED
data/rity.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_dependency "rack"
|
19
19
|
s.add_dependency "async-rack"
|
20
20
|
s.add_dependency "thor"
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec"
|
21
23
|
|
22
24
|
s.files = `git ls-files`.split("\n") - [".gitignore"]
|
23
25
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rity::Connection do
|
4
|
+
before do
|
5
|
+
@conn = Rity::Connection.new("blah")
|
6
|
+
@app = stub("app")
|
7
|
+
@conn.app = @app
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has an app, requests and a logger" do
|
11
|
+
@conn.app.should == @app
|
12
|
+
@conn.should have(0).requests
|
13
|
+
|
14
|
+
log = stub("log")
|
15
|
+
@conn.log = log
|
16
|
+
@conn.log.should == log
|
17
|
+
end
|
18
|
+
|
19
|
+
it "passes incoming data to the parser" do
|
20
|
+
@conn.parser.should_receive(:<<).with("asdfg")
|
21
|
+
@conn.receive_data("asdfg")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "closes the connection if parsing fails" do
|
25
|
+
@conn.should_receive(:close_connection)
|
26
|
+
@conn.receive_data("this will definitely fail!")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "creates a new request as soon as headers are parsed" do
|
30
|
+
request, log = stub("request", :callback => nil), stub("log")
|
31
|
+
@conn.log = log
|
32
|
+
|
33
|
+
Rity::Request.should_receive(:new).with(@conn.app, "GET", "/").and_return(request)
|
34
|
+
request.should_receive(:log=).with(log)
|
35
|
+
|
36
|
+
@conn.parser.on_request[0].call("GET", "/")
|
37
|
+
@conn.requests[0].should == request
|
38
|
+
end
|
39
|
+
|
40
|
+
it "adds the responder as request's callback" do
|
41
|
+
@conn.responder.should_receive(:resume)
|
42
|
+
@conn.parser.on_request[0].call("GET", "/")
|
43
|
+
@conn.requests[0].succeed
|
44
|
+
end
|
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")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "calls the request's #call method when the whole message is parsed" do
|
65
|
+
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
66
|
+
@conn.requests.last.should_receive(:call)
|
67
|
+
@conn.parser.on_complete[0].call()
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "the responder" do
|
71
|
+
it "pushes the request's response to the builder" do
|
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.responder.resume
|
78
|
+
end
|
79
|
+
|
80
|
+
it "doesn't push the response until it's ready" do
|
81
|
+
@conn.requests.push(Rity::Request.new(nil, nil, nil))
|
82
|
+
@conn.builder.should_not_receive(:response)
|
83
|
+
@conn.responder.resume
|
84
|
+
end
|
85
|
+
|
86
|
+
it "pushes responses in the order the resp. requests came in" do
|
87
|
+
request1, request2 = mock("request1"), mock("request2")
|
88
|
+
response1, response2 = stub("response1"), stub("response2")
|
89
|
+
@conn.requests.push(request1, request2)
|
90
|
+
|
91
|
+
request1.stub(:response => nil)
|
92
|
+
request2.stub(:response => response2)
|
93
|
+
@conn.builder.should_not_receive(:response)
|
94
|
+
@conn.responder.resume
|
95
|
+
|
96
|
+
request1.stub(:response => response1)
|
97
|
+
request2.stub(:response => nil)
|
98
|
+
@conn.builder.rspec_reset
|
99
|
+
@conn.builder.should_receive(:response).with(response1)
|
100
|
+
@conn.responder.resume
|
101
|
+
|
102
|
+
@conn.requests.should_not include(request1)
|
103
|
+
|
104
|
+
request2.stub(:response => response2)
|
105
|
+
@conn.builder.rspec_reset
|
106
|
+
@conn.builder.should_receive(:response).with(response2)
|
107
|
+
@conn.responder.resume
|
108
|
+
|
109
|
+
@conn.requests.should be_empty
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rity::Request do
|
4
|
+
before do
|
5
|
+
@app = proc {|env|}
|
6
|
+
@request = Rity::Request.new(@app, "GET", "/")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "is deferrable" do
|
10
|
+
@request.should respond_to(:callback)
|
11
|
+
@request.should respond_to(:succeed)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "initializes the env hash" do
|
15
|
+
@request.env["REQUEST_METHOD"].should == "GET"
|
16
|
+
@request.env["REQUEST_URI"].should == "/"
|
17
|
+
StringIO.should === @request.env["rack.input"]
|
18
|
+
@request.env["async.callback"].should respond_to(:call)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "puts the logger into the env hash" do
|
22
|
+
log = stub("log")
|
23
|
+
@request.log = log
|
24
|
+
@request.env["rack.logger"].should == log
|
25
|
+
end
|
26
|
+
|
27
|
+
it "calls the app and stores its response" do
|
28
|
+
response = [303, {"Content-Type" => "text/html"}, ["foo bar"]]
|
29
|
+
@app.should_receive(:call).with(@request.env).and_return(response)
|
30
|
+
@request.should_receive(:succeed)
|
31
|
+
@request.call
|
32
|
+
@request.response.should == response
|
33
|
+
end
|
34
|
+
|
35
|
+
it "fetches async responses" do
|
36
|
+
response = [-1, {}, []]
|
37
|
+
async_response = [200, {}, "okokok"]
|
38
|
+
@app.should_receive(:call).with(@request.env).and_return(response)
|
39
|
+
|
40
|
+
@request.call
|
41
|
+
|
42
|
+
@request.should_receive(:succeed)
|
43
|
+
@request.env["async.callback"].call(async_response)
|
44
|
+
@request.response.should == async_response
|
45
|
+
end
|
46
|
+
|
47
|
+
it "calls the app in a separate fiber" do
|
48
|
+
outer_fiber = Fiber.current
|
49
|
+
inner_fiber = nil
|
50
|
+
@app.singleton_class.send(:define_method, :call) do |env|
|
51
|
+
inner_fiber = Fiber.current
|
52
|
+
end
|
53
|
+
@request.call
|
54
|
+
|
55
|
+
outer_fiber.should_not == inner_fiber
|
56
|
+
end
|
57
|
+
|
58
|
+
it "rescues errors in app's #call" do
|
59
|
+
@app.should_receive(:call) do
|
60
|
+
raise "error"
|
61
|
+
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
|
+
end
|
69
|
+
|
70
|
+
it "rescues errors in app's #precall" do
|
71
|
+
@app.should_receive(:precall) do
|
72
|
+
raise "error"
|
73
|
+
end
|
74
|
+
@request.should_receive(:succeed)
|
75
|
+
@request.precall
|
76
|
+
|
77
|
+
@request.response[0].should == 500
|
78
|
+
@request.response[1].should == {"Content-Type" => "text/html"}
|
79
|
+
@request.response[2].should == ["<h1>Internal Server Error</h1>"]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "calls the app's #precall method if it exists" do
|
83
|
+
called = false
|
84
|
+
request = @request
|
85
|
+
@app.singleton_class.send(:define_method, :precall) do |env|
|
86
|
+
env.should == request.env
|
87
|
+
called = true
|
88
|
+
end
|
89
|
+
|
90
|
+
@app.stub(:respond_to? => false)
|
91
|
+
@request.precall
|
92
|
+
called.should be_false
|
93
|
+
|
94
|
+
@app.stub(:respond_to? => true)
|
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
|
104
|
+
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
|
+
|
119
|
+
@request.add_body_chunk "foo"
|
120
|
+
@request.env["rack.input"].string.should == "asdffoo"
|
121
|
+
end
|
122
|
+
|
123
|
+
it "rewinds the body and closes its input before calling the app" do
|
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
|
128
|
+
end
|
129
|
+
|
130
|
+
@request.call
|
131
|
+
end
|
132
|
+
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.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Lars Gierth
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-09 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
@@ -78,6 +78,17 @@ dependencies:
|
|
78
78
|
type: :runtime
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: *id006
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *id007
|
81
92
|
description: Rity is a lightweight Ruby webserver that runs inside an EventMachine loop and puts each request into a fiber.
|
82
93
|
email:
|
83
94
|
- lars.gierth@gmail.com
|
@@ -88,6 +99,7 @@ extensions: []
|
|
88
99
|
extra_rdoc_files: []
|
89
100
|
|
90
101
|
files:
|
102
|
+
- .rspec
|
91
103
|
- Gemfile
|
92
104
|
- LICENSE
|
93
105
|
- README.md
|
@@ -101,6 +113,9 @@ files:
|
|
101
113
|
- lib/rity/version.rb
|
102
114
|
- myapp.rb
|
103
115
|
- rity.gemspec
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/unit/connection_spec.rb
|
118
|
+
- spec/unit/request_spec.rb
|
104
119
|
homepage: https://github.com/lgierth/rity
|
105
120
|
licenses: []
|
106
121
|
|
@@ -114,7 +129,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
129
|
requirements:
|
115
130
|
- - ">="
|
116
131
|
- !ruby/object:Gem::Version
|
117
|
-
hash:
|
132
|
+
hash: 34399981
|
118
133
|
segments:
|
119
134
|
- 0
|
120
135
|
version: "0"
|
@@ -123,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
138
|
requirements:
|
124
139
|
- - ">="
|
125
140
|
- !ruby/object:Gem::Version
|
126
|
-
hash:
|
141
|
+
hash: 34399981
|
127
142
|
segments:
|
128
143
|
- 0
|
129
144
|
version: "0"
|