ebb 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require File.dirname(__FILE__) + '/../ruby_lib/ebb'
3
-
4
- Ebb::Runner::Rails.new.run(ARGV)
@@ -1,257 +0,0 @@
1
- # Ruby Binding to the Ebb Web Server
2
- # Copyright (c) 2008 Ry Dahl. This software is released under the MIT License.
3
- # See README file for details.
4
- require 'stringio'
5
- module Ebb
6
- VERSION = "0.2.1"
7
- LIBDIR = File.dirname(__FILE__)
8
- autoload :Runner, LIBDIR + '/ebb/runner'
9
- autoload :FFI, LIBDIR + '/../src/ebb_ext'
10
-
11
- def self.start_server(app, options={})
12
- if options.has_key?(:fileno)
13
- fd = options[:fileno].to_i
14
- FFI::server_listen_on_fd(fd)
15
- log.puts "Ebb is listening on file descriptor #{fd}"
16
- elsif options.has_key?(:unix_socket)
17
- socketfile = options[:unix_socket]
18
- FFI::server_listen_on_unix_socket(socketfile)
19
- log.puts "Ebb is listening on unix socket #{socketfile}"
20
- else
21
- port = (options[:Port] || options[:port] || 4001).to_i
22
- FFI::server_listen_on_port(port)
23
- log.puts "Ebb is listening at http://0.0.0.0:#{port}/"
24
- end
25
- log.puts "Ebb PID #{Process.pid}"
26
-
27
- @running = true
28
- trap('INT') { stop_server }
29
-
30
- while @running
31
- FFI::server_process_connections()
32
- while client = FFI::server_waiting_clients.shift
33
- if app.respond_to?(:deferred?) and app.deferred?(client.env)
34
- Thread.new(client) { |c| process(app, c) }
35
- else
36
- process(app, client)
37
- end
38
- end
39
- end
40
- FFI::server_unlisten()
41
- end
42
-
43
- def self.running?
44
- FFI::server_open?
45
- end
46
-
47
- def self.stop_server()
48
- @running = false
49
- end
50
-
51
- def self.process(app, client)
52
- #p client.env
53
- status, headers, body = app.call(client.env)
54
- status = status.to_i
55
-
56
- # Write the status
57
- client.write_status(status)
58
-
59
- # Add Content-Length to the headers.
60
- if !headers.has_key?('Content-Length') and
61
- headers.respond_to?(:[]=) and
62
- status != 304
63
- then
64
- # for String just use "length" method
65
- if body.kind_of?(String)
66
- headers['Content-Length'] = body.length.to_s
67
- else
68
- # for non-Array object call "each" and transform to Array
69
- unless body.kind_of?(Array)
70
- parts = []
71
- body.each {|p| parts << p}
72
- body = parts
73
- end
74
- # body is Array so calculate Content-Length as sum of length each part
75
- headers['Content-Length'] = body.inject(0) {|s, p| s + p.length }.to_s
76
- end
77
- end
78
-
79
- # Decide if we should keep the connection alive or not
80
- unless headers.has_key?('Connection')
81
- if headers.has_key?('Content-Length') and client.should_keep_alive?
82
- headers['Connection'] = 'Keep-Alive'
83
- else
84
- headers['Connection'] = 'close'
85
- end
86
- end
87
-
88
- # Write the headers
89
- headers.each { |field, value| client.write_header(field, value) }
90
-
91
- # Write the body
92
- if body.kind_of?(String)
93
- client.write_body(body)
94
- else
95
- body.each { |p| client.write_body(p) }
96
- end
97
-
98
- rescue => e
99
- log.puts "Ebb Error! #{e.class} #{e.message}"
100
- log.puts e.backtrace.join("\n")
101
- ensure
102
- client.release
103
- end
104
-
105
- @@log = STDOUT
106
- def self.log=(output)
107
- @@log = output
108
- end
109
- def self.log
110
- @@log
111
- end
112
-
113
- class Client
114
- attr_reader :fd, :body_head, :content_length
115
- BASE_ENV = {
116
- 'SERVER_NAME' => '0.0.0.0',
117
- 'SCRIPT_NAME' => '',
118
- 'QUERY_STRING' => '',
119
- 'SERVER_SOFTWARE' => "Ebb-Ruby #{Ebb::VERSION}",
120
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
121
- 'rack.version' => [0, 1],
122
- 'rack.errors' => STDERR,
123
- 'rack.url_scheme' => 'http',
124
- 'rack.multiprocess' => false,
125
- 'rack.run_once' => false
126
- }
127
-
128
- def env
129
- @env ||= begin
130
- env = FFI::client_env(self).update(BASE_ENV)
131
- env['rack.input'] = RequestBody.new(self)
132
- env
133
- end
134
- end
135
-
136
- def write_status(status)
137
- FFI::client_write_status(self, status, HTTP_STATUS_CODES[status])
138
- end
139
-
140
- def write_body(data)
141
- FFI::client_write_body(self, data)
142
- end
143
-
144
- def write_header(field, value)
145
- FFI::client_write_header(self, field, value)
146
- end
147
-
148
- def release
149
- FFI::client_release(self)
150
- end
151
-
152
- def set_keep_alive
153
- FFI::client_set_keep_alive(self)
154
- end
155
-
156
- def should_keep_alive?
157
- if env['HTTP_VERSION'] == 'HTTP/1.0'
158
- return env['HTTP_CONNECTION'] =~ /Keep-Alive/i
159
- else
160
- return env['HTTP_CONNECTION'] !~ /close/i
161
- end
162
- end
163
- end
164
-
165
- class RequestBody
166
- def initialize(client)
167
- @content_length = client.content_length
168
- if client.body_head
169
- @body_head = StringIO.new(client.body_head)
170
- if @body_head.length < @content_length
171
- @socket = IO.new(client.fd)
172
- end
173
- end
174
- @total_read = 0
175
- end
176
-
177
- def read(len = nil)
178
- to_read = len.nil? ? @content_length - @total_read : min(len, @content_length - @total_read)
179
- return nil if to_read == 0 or @body_head.nil?
180
- unless out = @body_head.read(to_read)
181
- return nil if @socket.nil?
182
- out = @socket.read(to_read)
183
- end
184
- @total_read += out.length
185
- out
186
- end
187
-
188
- def gets
189
- raise NotImplemented
190
- end
191
-
192
- def each(&block)
193
- raise NotImplemented
194
- end
195
-
196
- private
197
-
198
- # cause i don't want to create an array
199
- def min(a,b)
200
- a > b ? b : a
201
- end
202
-
203
- end
204
-
205
-
206
- HTTP_STATUS_CODES = {
207
- 100 => 'Continue',
208
- 101 => 'Switching Protocols',
209
- 200 => 'OK',
210
- 201 => 'Created',
211
- 202 => 'Accepted',
212
- 203 => 'Non-Authoritative Information',
213
- 204 => 'No Content',
214
- 205 => 'Reset Content',
215
- 206 => 'Partial Content',
216
- 300 => 'Multiple Choices',
217
- 301 => 'Moved Permanently',
218
- 302 => 'Moved Temporarily',
219
- 303 => 'See Other',
220
- 304 => 'Not Modified',
221
- 305 => 'Use Proxy',
222
- 400 => 'Bad Request',
223
- 401 => 'Unauthorized',
224
- 402 => 'Payment Required',
225
- 403 => 'Forbidden',
226
- 404 => 'Not Found',
227
- 405 => 'Method Not Allowed',
228
- 406 => 'Not Acceptable',
229
- 407 => 'Proxy Authentication Required',
230
- 408 => 'Request Time-out',
231
- 409 => 'Conflict',
232
- 410 => 'Gone',
233
- 411 => 'Length Required',
234
- 412 => 'Precondition Failed',
235
- 413 => 'Request Entity Too Large',
236
- 414 => 'Request-URI Too Large',
237
- 415 => 'Unsupported Media Type',
238
- 500 => 'Internal Server Error',
239
- 501 => 'Not Implemented',
240
- 502 => 'Bad Gateway',
241
- 503 => 'Service Unavailable',
242
- 504 => 'Gateway Time-out',
243
- 505 => 'HTTP Version not supported'
244
- }.freeze
245
- end
246
-
247
-
248
- module Rack
249
- module Handler
250
- module Ebb
251
- def self.run(app, options={})
252
- ::Ebb.start_server(app, options)
253
- end
254
- end
255
- end
256
- end
257
-
@@ -1,134 +0,0 @@
1
- require 'optparse'
2
-
3
- module Kernel
4
- unless respond_to? :daemonize # Already part of Ruby 1.9, yeah!
5
- # Turns the current script into a daemon process that detaches from the console.
6
- # It can be shut down with a TERM signal. Taken from ActiveSupport.
7
- def daemonize
8
- exit if fork # Parent exits, child continues.
9
- Process.setsid # Become session leader.
10
- exit if fork # Zap session leader. See [1].
11
- Dir.chdir "/" # Release old working directory.
12
- File.umask 0000 # Ensure sensible umask. Adjust as needed.
13
- STDIN.reopen "/dev/null" # Free file descriptors and
14
- STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
15
- STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
16
- trap("TERM") { exit }
17
- end
18
- end
19
- end
20
-
21
- module Ebb
22
- class Runner
23
- # Classes are modules and I hate this 'Base' class pattern. I'm putting
24
- # other classes inside this one.
25
- autoload :Rails, LIBDIR + '/ebb/runner/rails'
26
-
27
- # Kill the process which PID is stored in +pid_file+.
28
- def self.kill(pid_file, timeout=60)
29
- raise ArgumentError, 'You must specify a pid_file to stop deamonized server' unless pid_file
30
-
31
- if pid = File.read(pid_file)
32
- pid = pid.to_i
33
-
34
- Process.kill('KILL', pid)
35
- Ebb.log.puts "stopped!"
36
- else
37
- Ebb.log.puts "Can't stop process, no PID found in #{@pid_file}"
38
- end
39
- rescue Errno::ESRCH # No such process
40
- Ebb.log.puts "process not found!"
41
- ensure
42
- File.delete(pid_file) rescue nil
43
- end
44
-
45
- def self.remove_pid_file(file)
46
- File.delete(file) if file && File.exists?(file) && Process.pid == File.read(file)
47
- end
48
-
49
- def self.write_pid_file(file)
50
- Ebb.log.puts ">> Writing PID to #{file}"
51
- open(file,"w+") { |f| f.write(Process.pid) }
52
- File.chmod(0644, file)
53
- end
54
-
55
- attr_reader :options
56
-
57
- def initialize
58
- @parser = OptionParser.new
59
- @options = {
60
- :port => 4001,
61
- :timeout => 60
62
- }
63
- end
64
-
65
- def parse_options(argv)
66
- @parser.banner = "Usage: #{self.class} [options] start | stop"
67
- @parser.separator ""
68
- extra_options if respond_to?(:extra_options)
69
-
70
- @parser.separator ""
71
- @parser.on("-p", "--port PORT", "(default: #{@options[:port]})") { |p| @options[:port] = p }
72
- @parser.on("-s", "--socket SOCKET", "listen on unix domain socket") { |socket| options[:unix_socket] = socket }
73
- @parser.on("-d", "--daemonize", "Daemonize") { @options[:daemonize] = true }
74
- @parser.on("-l", "--log-file FILE", "File to redirect output") { |f| @options[:log_file]=f }
75
- @parser.on("-P", "--pid-file FILE", "File to store PID") { |f| @options[:pid_file]=f }
76
- # @parser.on("-t", "--timeout SECONDS", "(default: #{@options[:timeout]})") { |s| @options[:timeout]=s }
77
-
78
- @parser.separator ""
79
- @parser.on_tail("-h", "--help", "Show this message") do
80
- Ebb.log.puts @parser
81
- exit
82
- end
83
- @parser.on_tail('-v', '--version', "Show version") do
84
- Ebb.log.puts Ebb::Client::BASE_ENV['SERVER_SOFTWARE']
85
- exit
86
- end
87
-
88
- @parser.parse!(argv)
89
- end
90
-
91
- def run(argv)
92
- parse_options(argv)
93
-
94
- case argv[0]
95
- when 'start'
96
- Ebb.log.print("Ebb is loading the application...")
97
- Ebb.log.flush()
98
- @app = app(@options)
99
- Ebb.log.puts("done")
100
-
101
- if @options[:daemonize]
102
- pwd = Dir.pwd # Current directory is changed during daemonization, so store it
103
- Kernel.daemonize
104
- Dir.chdir pwd
105
- trap('HUP', 'IGNORE') # Don't die upon logout
106
- end
107
-
108
- if @options[:log_file]
109
- [STDOUT, STDERR].each { |f| f.reopen @options[:log_file], 'a' }
110
- end
111
-
112
- if @options[:pid_file]
113
- Runner.write_pid_file(@options[:pid_file])
114
- at_exit do
115
- Ebb.log.puts ">> Exiting!"
116
- Runner.remove_pid_file(@options[:pid_file])
117
- end
118
- end
119
-
120
- Ebb::start_server(@app, @options)
121
- when 'stop'
122
- Ebb::Runner.kill @options[:pid_file], @options[:timeout]
123
- when nil
124
- Ebb.log.puts "Command required"
125
- Ebb.log.puts @parser
126
- exit 1
127
- else
128
- abort "Invalid command : #{argv[0]}"
129
- end
130
-
131
- end
132
- end
133
- end
134
-
@@ -1,31 +0,0 @@
1
- module Rack
2
- module Adapter
3
- autoload :Rails, Ebb::LIBDIR + '/rack/adapter/rails'
4
- end
5
- end
6
-
7
- module Ebb
8
- class Runner
9
- class Rails < Runner
10
- def extra_options
11
- # defaults for ebb_rails
12
- @options.update(
13
- :environment => 'development',
14
- :port => 3000
15
- )
16
-
17
- @parser.on("-e", "--env ENV",
18
- "Rails environment (default: development)") do |env|
19
- @options[:environment] = env
20
- end
21
- @parser.on("-c", "--chdir DIR", "RAILS_ROOT directory") do |c|
22
- @options[:root] = c
23
- end
24
- end
25
-
26
- def app(options)
27
- Rack::Adapter::Rails.new(options)
28
- end
29
- end
30
- end
31
- end
@@ -1,159 +0,0 @@
1
- require 'cgi'
2
- require 'rubygems'
3
- require 'rack'
4
-
5
- # Adapter to run a Rails app with any supported Rack handler.
6
- # By default it will try to load the Rails application in the
7
- # current directory in the development environment.
8
- # Options:
9
- # root: Root directory of the Rails app
10
- # env: Rails environment to run in (development, production or test)
11
- # Based on http://fuzed.rubyforge.org/ Rails adapter
12
- module Rack
13
- module Adapter
14
- class Rails
15
- def initialize(options={})
16
- @root = options[:root] || Dir.pwd
17
- @env = options[:environment] || 'development'
18
- @prefix = options[:prefix]
19
-
20
- load_application
21
-
22
- @file_server = Rack::File.new(::File.join(RAILS_ROOT, "public"))
23
- end
24
-
25
- def load_application
26
- ENV['RAILS_ENV'] = @env
27
-
28
- require "#{@root}/config/environment"
29
- require 'dispatcher'
30
-
31
- ActionController::AbstractRequest.relative_url_root = @prefix if @prefix
32
- end
33
-
34
- # TODO refactor this in File#can_serve?(path) ??
35
- def file_exist?(path)
36
- full_path = ::File.join(@file_server.root, Utils.unescape(path))
37
- ::File.file?(full_path) && ::File.readable?(full_path)
38
- end
39
-
40
- def serve_file(env)
41
- @file_server.call(env)
42
- end
43
-
44
- def serve_rails(env)
45
- request = Request.new(env)
46
- response = Response.new
47
-
48
- session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
49
- cgi = CGIWrapper.new(request, response)
50
-
51
- Dispatcher.dispatch(cgi, session_options, response)
52
-
53
- response.finish
54
- end
55
-
56
- def call(env)
57
- path = env['PATH_INFO'].chomp('/')
58
- cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
59
-
60
- if file_exist?(path) # Serve the file if it's there
61
- serve_file(env)
62
- elsif file_exist?(cached_path) # Serve the page cache if it's there
63
- env['PATH_INFO'] = cached_path
64
- serve_file(env)
65
- else # No static file, let Rails handle it
66
- serve_rails(env)
67
- end
68
- end
69
-
70
- # Never spawn threads for a request
71
- def deferred?(env)
72
- false
73
- end
74
-
75
- protected
76
-
77
- class CGIWrapper < ::CGI
78
- def initialize(request, response, *args)
79
- @request = request
80
- @response = response
81
- @args = *args
82
- @input = request.body
83
-
84
- super *args
85
- end
86
-
87
- def header(options = "text/html")
88
- if options.is_a?(String)
89
- @response['Content-Type'] = options unless @response['Content-Type']
90
- else
91
- @response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
92
-
93
- @response['Content-Type'] = options.delete('type') || "text/html"
94
- @response['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
95
-
96
- @response['Content-Language'] = options.delete('language') if options['language']
97
- @response['Expires'] = options.delete('expires') if options['expires']
98
-
99
- @response.status = options.delete('Status') if options['Status']
100
-
101
- # Convert 'cookie' header to 'Set-Cookie' headers.
102
- # Because Set-Cookie header can appear more the once in the response body,
103
- # we store it in a line break seperated string that will be translated to
104
- # multiple Set-Cookie header by the handler.
105
- if cookie = options.delete('cookie')
106
- cookies = []
107
-
108
- case cookie
109
- when Array then cookie.each { |c| cookies << c.to_s }
110
- when Hash then cookie.each { |_, c| cookies << c.to_s }
111
- else cookies << cookie.to_s
112
- end
113
-
114
- @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
115
-
116
- @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact.join("\n")
117
- end
118
-
119
- options.each { |k,v| @response[k] = v }
120
- end
121
-
122
- ""
123
- end
124
-
125
- def params
126
- @params ||= @request.params
127
- end
128
-
129
- def cookies
130
- @request.cookies
131
- end
132
-
133
- def query_string
134
- @request.query_string
135
- end
136
-
137
- # Used to wrap the normal args variable used inside CGI.
138
- def args
139
- @args
140
- end
141
-
142
- # Used to wrap the normal env_table variable used inside CGI.
143
- def env_table
144
- @request.env
145
- end
146
-
147
- # Used to wrap the normal stdinput variable used inside CGI.
148
- def stdinput
149
- @input
150
- end
151
-
152
- def stdoutput
153
- STDERR.puts "stdoutput should not be used."
154
- @response.body
155
- end
156
- end
157
- end
158
- end
159
- end