ebb 0.0.1
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 +119 -0
- data/VERSION +1 -0
- data/benchmark/application.rb +84 -0
- data/benchmark/bench_results.rb +45 -0
- data/benchmark/server_test.rb +152 -0
- data/benchmark/test.rb +141 -0
- data/bin/ebb_rails +88 -0
- data/libev/ev.c +2440 -0
- data/libev/ev.h +551 -0
- data/libev/ev_epoll.c +174 -0
- data/libev/ev_kqueue.c +186 -0
- data/libev/ev_poll.c +127 -0
- data/libev/ev_port.c +155 -0
- data/libev/ev_select.c +236 -0
- data/libev/ev_vars.h +108 -0
- data/libev/ev_win32.c +117 -0
- data/libev/ev_wrap.h +132 -0
- data/ruby_lib/daemonizable.rb +134 -0
- data/ruby_lib/ebb.rb +179 -0
- data/ruby_lib/rack/adapter/rails.rb +152 -0
- data/src/ebb.c +709 -0
- data/src/ebb.h +102 -0
- data/src/ebb_ruby.c +256 -0
- data/src/extconf.rb +44 -0
- data/src/parser.c +1584 -0
- data/src/parser.h +50 -0
- metadata +80 -0
data/libev/ev_win32.c
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
/*
|
2
|
+
* libev win32 compatibility cruft
|
3
|
+
*
|
4
|
+
* Copyright (c) 2007 Marc Alexander Lehmann <libev@schmorp.de>
|
5
|
+
* All rights reserved.
|
6
|
+
*
|
7
|
+
* Redistribution and use in source and binary forms, with or without
|
8
|
+
* modification, are permitted provided that the following conditions are
|
9
|
+
* met:
|
10
|
+
*
|
11
|
+
* * Redistributions of source code must retain the above copyright
|
12
|
+
* notice, this list of conditions and the following disclaimer.
|
13
|
+
*
|
14
|
+
* * Redistributions in binary form must reproduce the above
|
15
|
+
* copyright notice, this list of conditions and the following
|
16
|
+
* disclaimer in the documentation and/or other materials provided
|
17
|
+
* with the distribution.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20
|
+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21
|
+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22
|
+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23
|
+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24
|
+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25
|
+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
26
|
+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
27
|
+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
*/
|
31
|
+
|
32
|
+
#ifdef _WIN32
|
33
|
+
|
34
|
+
#include <sys/timeb.h>
|
35
|
+
|
36
|
+
/* note: the comment below could not be substantiated, but what would I care */
|
37
|
+
/* MSDN says this is required to handle SIGFPE */
|
38
|
+
volatile double SIGFPE_REQ = 0.0f;
|
39
|
+
|
40
|
+
/* oh, the humanity! */
|
41
|
+
static int
|
42
|
+
ev_pipe (int filedes [2])
|
43
|
+
{
|
44
|
+
struct sockaddr_in addr = { 0 };
|
45
|
+
int addr_size = sizeof (addr);
|
46
|
+
SOCKET listener;
|
47
|
+
SOCKET sock [2] = { -1, -1 };
|
48
|
+
|
49
|
+
if ((listener = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
|
50
|
+
return -1;
|
51
|
+
|
52
|
+
addr.sin_family = AF_INET;
|
53
|
+
addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
|
54
|
+
addr.sin_port = 0;
|
55
|
+
|
56
|
+
if (bind (listener, (struct sockaddr *)&addr, addr_size))
|
57
|
+
goto fail;
|
58
|
+
|
59
|
+
if (getsockname(listener, (struct sockaddr *)&addr, &addr_size))
|
60
|
+
goto fail;
|
61
|
+
|
62
|
+
if (listen (listener, 1))
|
63
|
+
goto fail;
|
64
|
+
|
65
|
+
if ((sock [0] = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
|
66
|
+
goto fail;
|
67
|
+
|
68
|
+
if (connect (sock[0], (struct sockaddr *)&addr, addr_size))
|
69
|
+
goto fail;
|
70
|
+
|
71
|
+
if ((sock[1] = accept (listener, 0, 0)) < 0)
|
72
|
+
goto fail;
|
73
|
+
|
74
|
+
closesocket (listener);
|
75
|
+
|
76
|
+
#if EV_SELECT_IS_WINSOCKET
|
77
|
+
filedes [0] = _open_osfhandle (sock [0], 0);
|
78
|
+
filedes [1] = _open_osfhandle (sock [1], 0);
|
79
|
+
#else
|
80
|
+
/* when select isn't winsocket, we also expect socket, connect, accept etc.
|
81
|
+
* to work on fds */
|
82
|
+
filedes [0] = sock [0];
|
83
|
+
filedes [1] = sock [1];
|
84
|
+
#endif
|
85
|
+
|
86
|
+
return 0;
|
87
|
+
|
88
|
+
fail:
|
89
|
+
closesocket (listener);
|
90
|
+
|
91
|
+
if (sock [0] != INVALID_SOCKET) closesocket (sock [0]);
|
92
|
+
if (sock [1] != INVALID_SOCKET) closesocket (sock [1]);
|
93
|
+
|
94
|
+
return -1;
|
95
|
+
}
|
96
|
+
|
97
|
+
#undef pipe
|
98
|
+
#define pipe(filedes) ev_pipe (filedes)
|
99
|
+
|
100
|
+
static int
|
101
|
+
ev_gettimeofday (struct timeval *tv, struct timezone *tz)
|
102
|
+
{
|
103
|
+
struct _timeb tb;
|
104
|
+
|
105
|
+
_ftime (&tb);
|
106
|
+
|
107
|
+
tv->tv_sec = (long)tb.time;
|
108
|
+
tv->tv_usec = ((long)tb.millitm) * 1000;
|
109
|
+
|
110
|
+
return 0;
|
111
|
+
}
|
112
|
+
|
113
|
+
#undef gettimeofday
|
114
|
+
#define gettimeofday(tv,tz) ev_gettimeofday (tv, tz)
|
115
|
+
|
116
|
+
#endif
|
117
|
+
|
data/libev/ev_wrap.h
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
/* DO NOT EDIT, automatically generated by update_ev_wrap */
|
2
|
+
#ifndef EV_WRAP_H
|
3
|
+
#define EV_WRAP_H
|
4
|
+
#define now_floor ((loop)->now_floor)
|
5
|
+
#define mn_now ((loop)->mn_now)
|
6
|
+
#define rtmn_diff ((loop)->rtmn_diff)
|
7
|
+
#define io_blocktime ((loop)->io_blocktime)
|
8
|
+
#define timeout_blocktime ((loop)->timeout_blocktime)
|
9
|
+
#define backend ((loop)->backend)
|
10
|
+
#define activecnt ((loop)->activecnt)
|
11
|
+
#define loop_count ((loop)->loop_count)
|
12
|
+
#define backend_fd ((loop)->backend_fd)
|
13
|
+
#define backend_fudge ((loop)->backend_fudge)
|
14
|
+
#define backend_modify ((loop)->backend_modify)
|
15
|
+
#define backend_poll ((loop)->backend_poll)
|
16
|
+
#define curpid ((loop)->curpid)
|
17
|
+
#define postfork ((loop)->postfork)
|
18
|
+
#define vec_ri ((loop)->vec_ri)
|
19
|
+
#define vec_ro ((loop)->vec_ro)
|
20
|
+
#define vec_wi ((loop)->vec_wi)
|
21
|
+
#define vec_wo ((loop)->vec_wo)
|
22
|
+
#define vec_max ((loop)->vec_max)
|
23
|
+
#define polls ((loop)->polls)
|
24
|
+
#define pollmax ((loop)->pollmax)
|
25
|
+
#define pollcnt ((loop)->pollcnt)
|
26
|
+
#define pollidxs ((loop)->pollidxs)
|
27
|
+
#define pollidxmax ((loop)->pollidxmax)
|
28
|
+
#define epoll_events ((loop)->epoll_events)
|
29
|
+
#define epoll_eventmax ((loop)->epoll_eventmax)
|
30
|
+
#define kqueue_changes ((loop)->kqueue_changes)
|
31
|
+
#define kqueue_changemax ((loop)->kqueue_changemax)
|
32
|
+
#define kqueue_changecnt ((loop)->kqueue_changecnt)
|
33
|
+
#define kqueue_events ((loop)->kqueue_events)
|
34
|
+
#define kqueue_eventmax ((loop)->kqueue_eventmax)
|
35
|
+
#define port_events ((loop)->port_events)
|
36
|
+
#define port_eventmax ((loop)->port_eventmax)
|
37
|
+
#define anfds ((loop)->anfds)
|
38
|
+
#define anfdmax ((loop)->anfdmax)
|
39
|
+
#define pendings ((loop)->pendings)
|
40
|
+
#define pendingmax ((loop)->pendingmax)
|
41
|
+
#define pendingcnt ((loop)->pendingcnt)
|
42
|
+
#define fdchanges ((loop)->fdchanges)
|
43
|
+
#define fdchangemax ((loop)->fdchangemax)
|
44
|
+
#define fdchangecnt ((loop)->fdchangecnt)
|
45
|
+
#define timers ((loop)->timers)
|
46
|
+
#define timermax ((loop)->timermax)
|
47
|
+
#define timercnt ((loop)->timercnt)
|
48
|
+
#define periodics ((loop)->periodics)
|
49
|
+
#define periodicmax ((loop)->periodicmax)
|
50
|
+
#define periodiccnt ((loop)->periodiccnt)
|
51
|
+
#define idles ((loop)->idles)
|
52
|
+
#define idlemax ((loop)->idlemax)
|
53
|
+
#define idlecnt ((loop)->idlecnt)
|
54
|
+
#define idleall ((loop)->idleall)
|
55
|
+
#define prepares ((loop)->prepares)
|
56
|
+
#define preparemax ((loop)->preparemax)
|
57
|
+
#define preparecnt ((loop)->preparecnt)
|
58
|
+
#define checks ((loop)->checks)
|
59
|
+
#define checkmax ((loop)->checkmax)
|
60
|
+
#define checkcnt ((loop)->checkcnt)
|
61
|
+
#define forks ((loop)->forks)
|
62
|
+
#define forkmax ((loop)->forkmax)
|
63
|
+
#define forkcnt ((loop)->forkcnt)
|
64
|
+
#define fs_fd ((loop)->fs_fd)
|
65
|
+
#define fs_w ((loop)->fs_w)
|
66
|
+
#define fs_hash ((loop)->fs_hash)
|
67
|
+
#else
|
68
|
+
#undef EV_WRAP_H
|
69
|
+
#undef now_floor
|
70
|
+
#undef mn_now
|
71
|
+
#undef rtmn_diff
|
72
|
+
#undef io_blocktime
|
73
|
+
#undef timeout_blocktime
|
74
|
+
#undef backend
|
75
|
+
#undef activecnt
|
76
|
+
#undef loop_count
|
77
|
+
#undef backend_fd
|
78
|
+
#undef backend_fudge
|
79
|
+
#undef backend_modify
|
80
|
+
#undef backend_poll
|
81
|
+
#undef curpid
|
82
|
+
#undef postfork
|
83
|
+
#undef vec_ri
|
84
|
+
#undef vec_ro
|
85
|
+
#undef vec_wi
|
86
|
+
#undef vec_wo
|
87
|
+
#undef vec_max
|
88
|
+
#undef polls
|
89
|
+
#undef pollmax
|
90
|
+
#undef pollcnt
|
91
|
+
#undef pollidxs
|
92
|
+
#undef pollidxmax
|
93
|
+
#undef epoll_events
|
94
|
+
#undef epoll_eventmax
|
95
|
+
#undef kqueue_changes
|
96
|
+
#undef kqueue_changemax
|
97
|
+
#undef kqueue_changecnt
|
98
|
+
#undef kqueue_events
|
99
|
+
#undef kqueue_eventmax
|
100
|
+
#undef port_events
|
101
|
+
#undef port_eventmax
|
102
|
+
#undef anfds
|
103
|
+
#undef anfdmax
|
104
|
+
#undef pendings
|
105
|
+
#undef pendingmax
|
106
|
+
#undef pendingcnt
|
107
|
+
#undef fdchanges
|
108
|
+
#undef fdchangemax
|
109
|
+
#undef fdchangecnt
|
110
|
+
#undef timers
|
111
|
+
#undef timermax
|
112
|
+
#undef timercnt
|
113
|
+
#undef periodics
|
114
|
+
#undef periodicmax
|
115
|
+
#undef periodiccnt
|
116
|
+
#undef idles
|
117
|
+
#undef idlemax
|
118
|
+
#undef idlecnt
|
119
|
+
#undef idleall
|
120
|
+
#undef prepares
|
121
|
+
#undef preparemax
|
122
|
+
#undef preparecnt
|
123
|
+
#undef checks
|
124
|
+
#undef checkmax
|
125
|
+
#undef checkcnt
|
126
|
+
#undef forks
|
127
|
+
#undef forkmax
|
128
|
+
#undef forkcnt
|
129
|
+
#undef fs_fd
|
130
|
+
#undef fs_w
|
131
|
+
#undef fs_hash
|
132
|
+
#endif
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright (c) 2007 Marc-André Cournoyer
|
2
|
+
require 'etc'
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
unless respond_to? :daemonize # Already part of Ruby 1.9, yeah!
|
6
|
+
# Turns the current script into a daemon process that detaches from the console.
|
7
|
+
# It can be shut down with a TERM signal. Taken from ActiveSupport.
|
8
|
+
def daemonize
|
9
|
+
exit if fork # Parent exits, child continues.
|
10
|
+
Process.setsid # Become session leader.
|
11
|
+
exit if fork # Zap session leader. See [1].
|
12
|
+
Dir.chdir "/" # Release old working directory.
|
13
|
+
File.umask 0000 # Ensure sensible umask. Adjust as needed.
|
14
|
+
STDIN.reopen "/dev/null" # Free file descriptors and
|
15
|
+
STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
|
16
|
+
STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
|
17
|
+
trap("TERM") { exit }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Process
|
23
|
+
# Returns +true+ the process identied by +pid+ is running.
|
24
|
+
def running?(pid)
|
25
|
+
Process.getpgid(pid) != -1
|
26
|
+
rescue Errno::ESRCH
|
27
|
+
false
|
28
|
+
end
|
29
|
+
module_function :running?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Module included in classes that can be turned into a daemon.
|
33
|
+
# Handle stuff like:
|
34
|
+
# * storing the PID in a file
|
35
|
+
# * redirecting output to the log file
|
36
|
+
# * changing processs privileges
|
37
|
+
# * killing the process gracefully
|
38
|
+
module Daemonizable
|
39
|
+
attr_accessor :pid_file, :log_file
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.extend ClassMethods
|
43
|
+
end
|
44
|
+
|
45
|
+
def daemonizable_init(options)
|
46
|
+
pid_file = options[:pid_file]
|
47
|
+
log_file = options[:log_file]
|
48
|
+
if options[:daemonize]
|
49
|
+
change_privilege options[:user], options[:group] if options[:user] && options[:group]
|
50
|
+
daemonize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def pid
|
55
|
+
File.exist?(pid_file) ? open(pid_file).read : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Turns the current script into a daemon process that detaches from the console.
|
59
|
+
def daemonize
|
60
|
+
raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
|
61
|
+
|
62
|
+
pwd = Dir.pwd # Current directory is changed during daemonization, so store it
|
63
|
+
super # Calls Kernel#daemonize
|
64
|
+
Dir.chdir pwd
|
65
|
+
|
66
|
+
trap('HUP', 'IGNORE') # Don't die upon logout
|
67
|
+
|
68
|
+
# Redirect output to the logfile
|
69
|
+
[STDOUT, STDERR].each { |f| f.reopen @log_file, 'a' } if @log_file
|
70
|
+
|
71
|
+
write_pid_file
|
72
|
+
at_exit do
|
73
|
+
log ">> Exiting!"
|
74
|
+
remove_pid_file
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Change privileges of the process
|
79
|
+
# to the specified user and group.
|
80
|
+
def change_privilege(user, group=user)
|
81
|
+
log ">> Changing process privilege to #{user}:#{group}"
|
82
|
+
|
83
|
+
uid, gid = Process.euid, Process.egid
|
84
|
+
target_uid = Etc.getpwnam(user).uid
|
85
|
+
target_gid = Etc.getgrnam(group).gid
|
86
|
+
|
87
|
+
if uid != target_uid || gid != target_gid
|
88
|
+
# Change process ownership
|
89
|
+
Process.initgroups(user, target_gid)
|
90
|
+
Process::GID.change_privilege(target_gid)
|
91
|
+
Process::UID.change_privilege(target_uid)
|
92
|
+
end
|
93
|
+
rescue Errno::EPERM => e
|
94
|
+
log "Couldn't change user and group to #{user}:#{group}: #{e}"
|
95
|
+
end
|
96
|
+
|
97
|
+
module ClassMethods
|
98
|
+
# Kill the process which PID is stored in +pid_file+.
|
99
|
+
def kill(pid_file, timeout=60)
|
100
|
+
if pid = open(pid_file).read
|
101
|
+
pid = pid.to_i
|
102
|
+
print "Sending INT signal to process #{pid} ... "
|
103
|
+
begin
|
104
|
+
Process.kill('INT', pid)
|
105
|
+
Timeout.timeout(timeout) do
|
106
|
+
sleep 0.1 while Process.running?(pid)
|
107
|
+
end
|
108
|
+
rescue Timeout::Error
|
109
|
+
print "timeout, Sending KILL signal ... "
|
110
|
+
Process.kill('KILL', pid)
|
111
|
+
end
|
112
|
+
puts "stopped!"
|
113
|
+
else
|
114
|
+
puts "Can't stop process, no PID found in #{@pid_file}"
|
115
|
+
end
|
116
|
+
rescue Errno::ESRCH # No such process
|
117
|
+
puts "process not found!"
|
118
|
+
ensure
|
119
|
+
File.delete(pid_file) rescue nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def remove_pid_file
|
125
|
+
File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_pid_file
|
129
|
+
log ">> Writing PID to #{@pid_file}"
|
130
|
+
FileUtils.mkdir_p File.dirname(@pid_file)
|
131
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
132
|
+
File.chmod(0644, @pid_file)
|
133
|
+
end
|
134
|
+
end
|
data/ruby_lib/ebb.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# A ruby binding to the ebb web server
|
2
|
+
# Copyright (c) 2007 Ry Dahl <ry.d4hl@gmail.com>
|
3
|
+
# This software is released under the "MIT License". See README file for details.
|
4
|
+
module Ebb
|
5
|
+
LIBDIR = File.dirname(__FILE__)
|
6
|
+
VERSION = File.read(LIBDIR + "/../VERSION").gsub(/\s/,'')
|
7
|
+
end
|
8
|
+
|
9
|
+
require Ebb::LIBDIR + '/../src/ebb_ext'
|
10
|
+
require Ebb::LIBDIR + '/daemonizable'
|
11
|
+
|
12
|
+
module Ebb
|
13
|
+
class Client
|
14
|
+
BASE_ENV = {
|
15
|
+
'SCRIPT_NAME' => '',
|
16
|
+
'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}",
|
17
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
18
|
+
'GATEWAY_INTERFACE' => 'CGI/1.2',
|
19
|
+
'rack.version' => [0, 1],
|
20
|
+
'rack.errors' => STDERR,
|
21
|
+
'rack.url_scheme' => 'http',
|
22
|
+
'rack.multithread' => false,
|
23
|
+
'rack.multiprocess' => false,
|
24
|
+
'rack.run_once' => false
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
def env
|
28
|
+
@env ||= begin
|
29
|
+
env = FFI::client_env(self).update(BASE_ENV)
|
30
|
+
env['rack.input'] = RequestBody.new(self)
|
31
|
+
env
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def finished
|
36
|
+
FFI::client_finished(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def write(data)
|
40
|
+
FFI::client_write(self, data)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class RequestBody
|
45
|
+
def initialize(client)
|
46
|
+
@client = client
|
47
|
+
end
|
48
|
+
|
49
|
+
def read(len)
|
50
|
+
FFI::client_read_input(@client, len)
|
51
|
+
end
|
52
|
+
|
53
|
+
def gets
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
def each
|
58
|
+
raise NotImplementedError
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Server
|
63
|
+
include Daemonizable
|
64
|
+
def self.run(app, options={})
|
65
|
+
# port must be an integer
|
66
|
+
server = self.new(app, options)
|
67
|
+
yield server if block_given?
|
68
|
+
server.start
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(app, options={})
|
72
|
+
@socket = options[:socket]
|
73
|
+
@port = (options[:port] || 4001).to_i
|
74
|
+
@timeout = options[:timeout]
|
75
|
+
@app = app
|
76
|
+
|
77
|
+
daemonizable_init(options)
|
78
|
+
FFI::server_initialize(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
def start
|
82
|
+
trap('INT') { @running = false }
|
83
|
+
|
84
|
+
if @socket
|
85
|
+
raise NotImplemented
|
86
|
+
FFI::server_listen_on_socket(self, @socket) or raise "Problem listening on socket #{@socket}"
|
87
|
+
else
|
88
|
+
FFI::server_listen_on_port(self, @port) or raise "Problem listening on port #{@port}"
|
89
|
+
end
|
90
|
+
@waiting_clients = []
|
91
|
+
|
92
|
+
puts "Ebb listening at http://0.0.0.0:#{@port}/"
|
93
|
+
|
94
|
+
@running = true
|
95
|
+
while FFI::server_process_connections(self) and @running
|
96
|
+
unless @waiting_clients.empty?
|
97
|
+
if $DEBUG and @waiting_clients.length > 1
|
98
|
+
puts "#{@waiting_clients.length} waiting clients"
|
99
|
+
end
|
100
|
+
client = @waiting_clients.shift
|
101
|
+
process_client(client)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
puts "Ebb unlistening"
|
105
|
+
FFI::server_unlisten(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_client(client)
|
109
|
+
#puts "Request: #{client.env.inspect}\n"
|
110
|
+
begin
|
111
|
+
status, headers, body = @app.call(client.env)
|
112
|
+
rescue
|
113
|
+
raise if $DEBUG
|
114
|
+
status = 500
|
115
|
+
headers = {'Content-Type' => 'text/plain'}
|
116
|
+
body = "Internal Server Error\n"
|
117
|
+
end
|
118
|
+
|
119
|
+
client.write "HTTP/1.1 %d %s\r\n" % [status, HTTP_STATUS_CODES[status]]
|
120
|
+
|
121
|
+
if body.respond_to? :length and status != 304
|
122
|
+
client.write "Connection: close\r\n"
|
123
|
+
headers['Content-Length'] = body.length
|
124
|
+
end
|
125
|
+
|
126
|
+
headers.each { |k, v| client.write "#{k}: #{v}\r\n" }
|
127
|
+
client.write "\r\n"
|
128
|
+
|
129
|
+
# Not many apps use streaming yet so i'll hold off on that feature
|
130
|
+
# until the rest of ebb is more developed.
|
131
|
+
if body.kind_of?(String)
|
132
|
+
client.write body
|
133
|
+
else
|
134
|
+
body.each { |p| client.write p }
|
135
|
+
end
|
136
|
+
client.finished
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
HTTP_STATUS_CODES = {
|
141
|
+
100 => 'Continue',
|
142
|
+
101 => 'Switching Protocols',
|
143
|
+
200 => 'OK',
|
144
|
+
201 => 'Created',
|
145
|
+
202 => 'Accepted',
|
146
|
+
203 => 'Non-Authoritative Information',
|
147
|
+
204 => 'No Content',
|
148
|
+
205 => 'Reset Content',
|
149
|
+
206 => 'Partial Content',
|
150
|
+
300 => 'Multiple Choices',
|
151
|
+
301 => 'Moved Permanently',
|
152
|
+
302 => 'Moved Temporarily',
|
153
|
+
303 => 'See Other',
|
154
|
+
304 => 'Not Modified',
|
155
|
+
305 => 'Use Proxy',
|
156
|
+
400 => 'Bad Request',
|
157
|
+
401 => 'Unauthorized',
|
158
|
+
402 => 'Payment Required',
|
159
|
+
403 => 'Forbidden',
|
160
|
+
404 => 'Not Found',
|
161
|
+
405 => 'Method Not Allowed',
|
162
|
+
406 => 'Not Acceptable',
|
163
|
+
407 => 'Proxy Authentication Required',
|
164
|
+
408 => 'Request Time-out',
|
165
|
+
409 => 'Conflict',
|
166
|
+
410 => 'Gone',
|
167
|
+
411 => 'Length Required',
|
168
|
+
412 => 'Precondition Failed',
|
169
|
+
413 => 'Request Entity Too Large',
|
170
|
+
414 => 'Request-URI Too Large',
|
171
|
+
415 => 'Unsupported Media Type',
|
172
|
+
500 => 'Internal Server Error',
|
173
|
+
501 => 'Not Implemented',
|
174
|
+
502 => 'Bad Gateway',
|
175
|
+
503 => 'Service Unavailable',
|
176
|
+
504 => 'Gateway Time-out',
|
177
|
+
505 => 'HTTP Version not supported'
|
178
|
+
}.freeze
|
179
|
+
end
|