rity 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
- - Logging
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 => :test
4
+ task :default => :spec
5
5
 
6
- require "rake/testtask"
7
- Rake::TestTask.new :test do |t|
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
@@ -5,34 +5,34 @@ require "rity/request"
5
5
 
6
6
  module Rity
7
7
  class Connection < EM::Connection
8
- def initialize(app)
9
- @app = app
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
- request.callback &@responder.method(:resume)
19
+ @requests.last.callback &@responder.method(:resume)
20
20
  end
21
21
 
22
22
  p.on_header do |name, value|
23
- request.add_header(name, value)
23
+ @requests.last.add_header(name, value)
24
24
  end
25
25
 
26
26
  p.on_headers_complete do
27
- request.precall
27
+ @requests.last.precall
28
28
  end
29
29
 
30
30
  p.on_body_chunk do |chunk|
31
- request.add_body_chunk(chunk)
31
+ @requests.last.add_body_chunk(chunk)
32
32
  end
33
33
 
34
34
  p.on_complete do
35
- request.call
35
+ @requests.last.call
36
36
  end
37
37
  end
38
38
 
@@ -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
 
@@ -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, app
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
@@ -1,3 +1,3 @@
1
1
  module Rity
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/myapp.rb CHANGED
@@ -6,7 +6,7 @@ require "awesome_print"
6
6
 
7
7
  class MyApp
8
8
  def call(env)
9
- raise "Error, Error!"
9
+ #raise "Error, Error!"
10
10
 
11
11
  response = [200, {"Content-Type" => "text/html"}, ["Hello!"]]
12
12
  return response
@@ -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")
@@ -0,0 +1,6 @@
1
+ require "bundler"
2
+ Bundler.setup :default
3
+
4
+ require "rity"
5
+ require "rspec"
6
+ require "awesome_print"
@@ -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.1
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-07 00:00:00 Z
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: -1038596497
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: -1038596497
141
+ hash: 34399981
127
142
  segments:
128
143
  - 0
129
144
  version: "0"