rity 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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