ebb 0.2.1 → 0.3.0

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.
@@ -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