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 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"