rity 0.0.2 → 0.0.3

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 CHANGED
@@ -6,11 +6,9 @@ Rity is a lightweight Ruby webserver that runs inside an EventMachine loop and p
6
6
  TODO
7
7
  ----
8
8
 
9
- - Command Line Interface
10
- - Let Request handle parsing and building errors
11
- - More robust error handling in general
12
9
  - Proxying via EM.enable_proxy
13
10
  - Rack Handler
14
11
  - Support for keep-alive connections
15
12
  - Investigate MVM support in JRuby/Rubinius/MRI
16
13
  - Support for SPDY
14
+ - Investigate preforking and letting multiple EventMachine loops listen on a shared socket
@@ -1,4 +1,3 @@
1
1
  require "rity/connection"
2
2
  require "rity/request"
3
- require "rity/server"
4
3
  require "rity/version"
@@ -1,9 +1,17 @@
1
1
  require "rity"
2
2
  require "thor"
3
3
 
4
+ require "eventmachine"
5
+ require "em-synchrony"
6
+ require "rack"
7
+ require "logger"
8
+
4
9
  module Rity
5
10
  class CLI < Thor
6
- map "--version" => :version, "-v" => :version
11
+ map "--version" => :version
12
+ map "-v" => :version
13
+
14
+ default_task :start
7
15
 
8
16
  desc :version, "Print version information"
9
17
  def version
@@ -11,9 +19,41 @@ module Rity
11
19
  end
12
20
 
13
21
  desc :start, "Start an instance of Rity"
22
+ method_option :bind, :aliases => "-b", :type => :string,
23
+ :banner => "Bind to the specified TCP interface (default: 127.0.0.1)"
24
+ method_option :port, :aliases => "-p", :type => :numeric,
25
+ :banner => "Bind to the specified port (default: 3000)"
26
+ method_option :rackup, :aliases => "-r", :type => :string,
27
+ :banner => "Load specified rackup (.ru) file (default: config.ru)"
28
+ method_option :quiet, :aliases => "-q", :type => :boolean,
29
+ :banner => "Don't log to stderr"
14
30
  def start
15
- server = Rity::Server.new
16
- server.start
31
+ address = options[:bind] || "127.0.0.1"
32
+ port = options[:port] || 3000
33
+
34
+ rackup = options[:rackup] || "config.ru"
35
+ app = Rack::Builder.parse_file(rackup)[0]
36
+
37
+ unless options[:quiet]
38
+ log = Logger.new($stderr)
39
+ log.formatter = proc do |severity, time, progname, message|
40
+ "[#{time}] #{severity}: #{message}\n"
41
+ end
42
+
43
+ log.info("Binding to #{address}:#{port}")
44
+ end
45
+
46
+ EM.synchrony do
47
+ trap("INT") { EM.stop }
48
+ trap("TERM") { EM.stop }
49
+
50
+ EM.epoll
51
+
52
+ EM.start_server(address, port, Connection) do |conn|
53
+ conn.app = app
54
+ conn.log = log if defined? log
55
+ end
56
+ end
17
57
  end
18
58
  end
19
59
  end
@@ -16,7 +16,7 @@ module Rity
16
16
  request = Request.new(@app, verb, url)
17
17
  request.log = log
18
18
  @requests.push(request)
19
- @requests.last.callback &@responder.method(:resume)
19
+ @requests.last.callback &method(:write_responses)
20
20
  end
21
21
 
22
22
  p.on_header do |name, value|
@@ -33,6 +33,15 @@ module Rity
33
33
 
34
34
  p.on_complete do
35
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
36
45
  end
37
46
  end
38
47
 
@@ -42,22 +51,29 @@ module Rity
42
51
  b.on_complete do
43
52
  close_connection_after_writing if @requests.empty?
44
53
  end
54
+
55
+ b.on_error &method(:error)
45
56
  end
46
-
47
- @responder = Fiber.new do
48
- loop do
49
- while @requests[0] && @requests[0].response
50
- request = @requests.shift
51
- @builder.response(request.response)
52
- end
53
- Fiber.yield
54
- end
57
+ end
58
+
59
+ def write_responses
60
+ while requests[0] && requests[0].response
61
+ request = requests.shift
62
+ builder.response(request.response)
55
63
  end
56
64
  end
57
65
 
58
66
  def receive_data(data)
59
67
  @parser << data
60
- rescue Exception
68
+ rescue Exception => e
69
+ error(e)
70
+ end
71
+
72
+ def error(e)
73
+ if log
74
+ log.error(e.message)
75
+ log << e.backtrace.join("\n") + "\n"
76
+ end
61
77
  close_connection
62
78
  end
63
79
  end
@@ -54,16 +54,23 @@ module Rity
54
54
  def postcall(response)
55
55
  return if @response || response[0] < 0
56
56
  @response = response
57
- log.info("#{response[0]} - #{env["REQUEST_METHOD"]} #{env["REQUEST_URI"]}") if log
58
57
  succeed
59
58
  end
60
59
 
60
+ def error(e)
61
+ if log
62
+ log.error(e.message)
63
+ log << e.backtrace.join("\n") + "\n"
64
+ end
65
+ postcall [500, {"Content-Type" => "text/html"},
66
+ ["<h1>#{Hatetepe::STATUS_CODES[500]}</h1>"]]
67
+ end
68
+
61
69
  def rescue_errors
62
70
  begin
63
71
  yield
64
- rescue Exception
65
- postcall([500, {"Content-Type" => "text/html"},
66
- ["<h1>#{Hatetepe::STATUS_CODES[500]}</h1>"]])
72
+ rescue Exception => e
73
+ error(e)
67
74
  end
68
75
  end
69
76
  end
@@ -1,3 +1,3 @@
1
1
  module Rity
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency "thor"
21
21
 
22
22
  s.add_development_dependency "rspec"
23
+ s.add_development_dependency "fakefs"
24
+ s.add_development_dependency "em-http-request"
23
25
 
24
26
  s.files = `git ls-files`.split("\n") - [".gitignore"]
25
27
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,140 @@
1
+ require "spec_helper"
2
+ require "rity/cli"
3
+ require "socket"
4
+ require "em-synchrony/em-http"
5
+
6
+ describe "start command" do
7
+ def hook_event_loop(&block)
8
+ EM.spec_hooks << block
9
+ end
10
+
11
+ def add_stop_timer(timeout)
12
+ hook_event_loop do
13
+ EM.add_timer(timeout) { EM.stop }
14
+ end
15
+ end
16
+
17
+ before do
18
+ $stderr = StringIO.new ""
19
+
20
+ FakeFS.activate!
21
+ File.open("config.ru", "w") do |f|
22
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, [e["REQUEST_URI"]]] }}
23
+ end
24
+ File.open("config2.ru", "w") do |f|
25
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, ["config2.ru loaded"]] }}
26
+ end
27
+ end
28
+
29
+ after do
30
+ $stderr = STDERR
31
+
32
+ FakeFS.deactivate!
33
+ FakeFS::FileSystem.clear
34
+ end
35
+
36
+ it "starts an instance of Rity" do
37
+ add_stop_timer 0.01
38
+ hook_event_loop do
39
+ Socket.tcp("127.0.0.1", 3000) {|*| }
40
+ end
41
+ Rity::CLI.start %w{}
42
+
43
+ $stderr.string.should include("127.0.0.1:3000")
44
+ end
45
+
46
+ it "answers HTTP requests" do
47
+ add_stop_timer 0.02
48
+ hook_event_loop do
49
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
50
+ response = EM::Synchrony.sync(request)
51
+
52
+ response.response_header.status.should == 200
53
+ response.response_header["CONTENT_TYPE"].should == "text/plain"
54
+ response.response.should == "/"
55
+ end
56
+ Rity::CLI.start %w{}
57
+ end
58
+
59
+ describe "--port option" do
60
+ it "changes the listen port" do
61
+ add_stop_timer 0.01
62
+ hook_event_loop do
63
+ Socket.tcp("127.0.0.1", 3001) {|*| }
64
+ end
65
+ Rity::CLI.start %w{--port=3001}
66
+
67
+ $stderr.string.should include(":3001")
68
+ end
69
+
70
+ it "has an alias: -p" do
71
+ add_stop_timer 0.01
72
+ hook_event_loop do
73
+ Socket.tcp("127.0.0.1", 3002) {|*| }
74
+ end
75
+ Rity::CLI.start %w{-p 3002}
76
+
77
+ $stderr.string.should include(":3002")
78
+ end
79
+ end
80
+
81
+ describe "--bind option" do
82
+ it "changes the listen interface" do
83
+ add_stop_timer 0.01
84
+ hook_event_loop do
85
+ Socket.tcp("127.0.0.2", 3000) {|*| }
86
+ end
87
+ Rity::CLI.start %w{--bind=127.0.0.2}
88
+
89
+ $stderr.string.should include("127.0.0.2:")
90
+ end
91
+
92
+ it "has an alias: -b" do
93
+ add_stop_timer 0.01
94
+ hook_event_loop do
95
+ Socket.tcp("127.0.0.3", 3000) {|*| }
96
+ end
97
+ Rity::CLI.start %w{-b 127.0.0.3}
98
+
99
+ $stderr.string.should include("127.0.0.3:")
100
+ end
101
+ end
102
+
103
+ describe "--rackup option" do
104
+ it "changes the rackup file that'll be loaded" do
105
+ add_stop_timer 0.01
106
+ hook_event_loop do
107
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
108
+ response = EM::Synchrony.sync(request)
109
+ response.response.should include("config2.ru")
110
+ end
111
+ Rity::CLI.start %w{--rackup=config2.ru}
112
+ end
113
+
114
+ it "has an alias: -r" do
115
+ add_stop_timer 0.01
116
+ hook_event_loop do
117
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
118
+ response = EM::Synchrony.sync(request)
119
+ response.response.should include("config2.ru")
120
+ end
121
+ Rity::CLI.start %w{-r config2.ru}
122
+ end
123
+ end
124
+
125
+ describe "--quiet option" do
126
+ it "discards all output" do
127
+ add_stop_timer 0.01
128
+ Rity::CLI.start %w{--quiet}
129
+
130
+ $stderr.string.should be_empty
131
+ end
132
+
133
+ it "has an alias: -q" do
134
+ add_stop_timer 0.01
135
+ Rity::CLI.start %w{-q}
136
+
137
+ $stderr.string.should be_empty
138
+ end
139
+ end
140
+ end
@@ -3,4 +3,40 @@ Bundler.setup :default
3
3
 
4
4
  require "rity"
5
5
  require "rspec"
6
- require "awesome_print"
6
+ require "fakefs"
7
+
8
+ begin
9
+ require "awesome_print"
10
+ rescue LoadError; end
11
+
12
+ require "em-synchrony"
13
+
14
+ RSpec.configure do |config|
15
+ config.before do
16
+ EM.class_eval do
17
+ @spec_hooks = []
18
+ class << self
19
+ attr_reader :spec_hooks
20
+ def synchrony_with_hooks(blk = nil, tail = nil, &block)
21
+ synchrony_without_hooks do
22
+ (blk || block).call
23
+ @spec_hooks.each {|sh| sh.call }
24
+ end
25
+ end
26
+ alias_method :synchrony_without_hooks, :synchrony
27
+ alias_method :synchrony, :synchrony_with_hooks
28
+ end
29
+ end
30
+ end
31
+
32
+ config.after do
33
+ EM.class_eval do
34
+ @spec_hooks = nil
35
+ class << self
36
+ remove_method :spec_hooks
37
+ alias_method :synchrony, :synchrony_without_hooks
38
+ remove_method :synchrony_with_hooks
39
+ end
40
+ end
41
+ end
42
+ end
@@ -38,7 +38,7 @@ describe Rity::Connection do
38
38
  end
39
39
 
40
40
  it "adds the responder as request's callback" do
41
- @conn.responder.should_receive(:resume)
41
+ @conn.should_receive(:write_responses)
42
42
  @conn.parser.on_request[0].call("GET", "/")
43
43
  @conn.requests[0].succeed
44
44
  end
@@ -74,13 +74,13 @@ describe Rity::Connection do
74
74
  @conn.requests[0].stub(:response => response)
75
75
 
76
76
  @conn.builder.should_receive(:response).with(response)
77
- @conn.responder.resume
77
+ @conn.write_responses
78
78
  end
79
79
 
80
80
  it "doesn't push the response until it's ready" do
81
81
  @conn.requests.push(Rity::Request.new(nil, nil, nil))
82
82
  @conn.builder.should_not_receive(:response)
83
- @conn.responder.resume
83
+ @conn.write_responses
84
84
  end
85
85
 
86
86
  it "pushes responses in the order the resp. requests came in" do
@@ -91,20 +91,20 @@ describe Rity::Connection do
91
91
  request1.stub(:response => nil)
92
92
  request2.stub(:response => response2)
93
93
  @conn.builder.should_not_receive(:response)
94
- @conn.responder.resume
94
+ @conn.write_responses
95
95
 
96
96
  request1.stub(:response => response1)
97
97
  request2.stub(:response => nil)
98
98
  @conn.builder.rspec_reset
99
99
  @conn.builder.should_receive(:response).with(response1)
100
- @conn.responder.resume
100
+ @conn.write_responses
101
101
 
102
102
  @conn.requests.should_not include(request1)
103
103
 
104
104
  request2.stub(:response => response2)
105
105
  @conn.builder.rspec_reset
106
106
  @conn.builder.should_receive(:response).with(response2)
107
- @conn.responder.resume
107
+ @conn.write_responses
108
108
 
109
109
  @conn.requests.should be_empty
110
110
  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.2
5
+ version: 0.0.3
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-09 00:00:00 Z
13
+ date: 2011-06-12 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: eventmachine
@@ -89,6 +89,28 @@ dependencies:
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: *id007
92
+ - !ruby/object:Gem::Dependency
93
+ name: fakefs
94
+ requirement: &id008 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: *id008
103
+ - !ruby/object:Gem::Dependency
104
+ name: em-http-request
105
+ requirement: &id009 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: "0"
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: *id009
92
114
  description: Rity is a lightweight Ruby webserver that runs inside an EventMachine loop and puts each request into a fiber.
93
115
  email:
94
116
  - lars.gierth@gmail.com
@@ -109,10 +131,10 @@ files:
109
131
  - lib/rity/cli.rb
110
132
  - lib/rity/connection.rb
111
133
  - lib/rity/request.rb
112
- - lib/rity/server.rb
113
134
  - lib/rity/version.rb
114
135
  - myapp.rb
115
136
  - rity.gemspec
137
+ - spec/integration/start_spec.rb
116
138
  - spec/spec_helper.rb
117
139
  - spec/unit/connection_spec.rb
118
140
  - spec/unit/request_spec.rb
@@ -129,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
129
151
  requirements:
130
152
  - - ">="
131
153
  - !ruby/object:Gem::Version
132
- hash: 34399981
154
+ hash: -888247411
133
155
  segments:
134
156
  - 0
135
157
  version: "0"
@@ -138,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
160
  requirements:
139
161
  - - ">="
140
162
  - !ruby/object:Gem::Version
141
- hash: 34399981
163
+ hash: -888247411
142
164
  segments:
143
165
  - 0
144
166
  version: "0"
@@ -1,27 +0,0 @@
1
- require "eventmachine"
2
- require "em-synchrony"
3
- require "logger"
4
-
5
- require "rity/connection"
6
-
7
- module Rity
8
- class Server
9
- def self.start(address, port, app)
10
- EM.synchrony do
11
- trap("INT") { EM.stop }
12
- trap("TERM") { EM.stop }
13
-
14
- EM.epoll
15
-
16
- puts "Binding to #{address}:#{port}"
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
24
- end
25
- end
26
- end
27
- end