ebb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +4 -7
- data/benchmark/application.rb +12 -4
- data/benchmark/server_test.rb +10 -11
- data/bin/ebb_rails +0 -0
- data/libev/ev.c +235 -128
- data/libev/ev.h +84 -27
- data/libev/ev_epoll.c +30 -22
- data/libev/ev_kqueue.c +30 -22
- data/libev/ev_poll.c +30 -22
- data/libev/ev_port.c +30 -22
- data/libev/ev_select.c +34 -26
- data/libev/ev_vars.h +49 -0
- data/libev/ev_win32.c +31 -23
- data/libev/ev_wrap.h +12 -0
- data/ruby_lib/ebb.rb +94 -67
- data/ruby_lib/ebb/runner.rb +4 -5
- data/ruby_lib/ebb/runner/rails.rb +1 -4
- data/ruby_lib/rack/adapter/rails.rb +5 -0
- data/src/ebb.c +261 -387
- data/src/ebb.h +19 -29
- data/src/ebb_ruby.c +113 -108
- data/src/extconf.rb +0 -1
- data/src/parser.c +1755 -724
- data/src/parser.h +13 -10
- data/test/basic_test.rb +18 -1
- data/test/env_test.rb +6 -5
- data/test/helper.rb +53 -1
- metadata +3 -4
- data/benchmark/bench_results.rb +0 -58
data/libev/ev_vars.h
CHANGED
@@ -1,3 +1,42 @@
|
|
1
|
+
/*
|
2
|
+
* loop member variable declarations
|
3
|
+
*
|
4
|
+
* Copyright (c) 2007,2008 Marc Alexander Lehmann <libev@schmorp.de>
|
5
|
+
* All rights reserved.
|
6
|
+
*
|
7
|
+
* Redistribution and use in source and binary forms, with or without modifica-
|
8
|
+
* tion, are permitted provided that the following conditions are met:
|
9
|
+
*
|
10
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
11
|
+
* this list of conditions and the following disclaimer.
|
12
|
+
*
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
14
|
+
* notice, this list of conditions and the following disclaimer in the
|
15
|
+
* documentation and/or other materials provided with the distribution.
|
16
|
+
*
|
17
|
+
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
18
|
+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
19
|
+
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
20
|
+
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
|
21
|
+
* CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
23
|
+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
24
|
+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
|
25
|
+
* ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
26
|
+
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
*
|
28
|
+
* Alternatively, the contents of this file may be used under the terms of
|
29
|
+
* the GNU General Public License ("GPL") version 2 or any later version,
|
30
|
+
* in which case the provisions of the GPL are applicable instead of
|
31
|
+
* the above. If you wish to allow the use of your version of this file
|
32
|
+
* only under the terms of the GPL and not to allow others to use your
|
33
|
+
* version of this file under the BSD license, indicate your decision
|
34
|
+
* by deleting the provisions above and replace them with the notice
|
35
|
+
* and other provisions required by the GPL. If you do not delete the
|
36
|
+
* provisions above, a recipient may use your version of this file under
|
37
|
+
* either the BSD or the GPL.
|
38
|
+
*/
|
39
|
+
|
1
40
|
#define VARx(type,name) VAR(name, type name)
|
2
41
|
|
3
42
|
VARx(ev_tstamp, now_floor) /* last time we refreshed rt_time */
|
@@ -16,6 +55,9 @@ VARx(ev_tstamp, backend_fudge) /* assumed typical timer resolution */
|
|
16
55
|
VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev))
|
17
56
|
VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))
|
18
57
|
|
58
|
+
VAR (evpipe, int evpipe [2])
|
59
|
+
VARx(ev_io, pipeev)
|
60
|
+
|
19
61
|
#if !defined(_WIN32) || EV_GENWRAP
|
20
62
|
VARx(pid_t, curpid)
|
21
63
|
#endif
|
@@ -98,6 +140,13 @@ VARx(int, forkmax)
|
|
98
140
|
VARx(int, forkcnt)
|
99
141
|
#endif
|
100
142
|
|
143
|
+
VARx(EV_ATOMIC_T, gotasync)
|
144
|
+
#if EV_ASYNC_ENABLE || EV_GENWRAP
|
145
|
+
VARx(struct ev_async **, asyncs)
|
146
|
+
VARx(int, asyncmax)
|
147
|
+
VARx(int, asynccnt)
|
148
|
+
#endif
|
149
|
+
|
101
150
|
#if EV_USE_INOTIFY || EV_GENWRAP
|
102
151
|
VARx(int, fs_fd)
|
103
152
|
VARx(ev_io, fs_w)
|
data/libev/ev_win32.c
CHANGED
@@ -1,32 +1,40 @@
|
|
1
1
|
/*
|
2
|
-
* libev win32 compatibility cruft
|
2
|
+
* libev win32 compatibility cruft (_not_ a backend)
|
3
3
|
*
|
4
4
|
* Copyright (c) 2007 Marc Alexander Lehmann <libev@schmorp.de>
|
5
5
|
* All rights reserved.
|
6
6
|
*
|
7
|
-
* Redistribution and use in source and binary forms, with or without
|
8
|
-
*
|
9
|
-
*
|
7
|
+
* Redistribution and use in source and binary forms, with or without modifica-
|
8
|
+
* tion, are permitted provided that the following conditions are met:
|
9
|
+
*
|
10
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
11
|
+
* this list of conditions and the following disclaimer.
|
12
|
+
*
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
14
|
+
* notice, this list of conditions and the following disclaimer in the
|
15
|
+
* documentation and/or other materials provided with the distribution.
|
16
|
+
*
|
17
|
+
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
18
|
+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
19
|
+
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
20
|
+
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
|
21
|
+
* CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
23
|
+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
24
|
+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
|
25
|
+
* ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
26
|
+
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
10
27
|
*
|
11
|
-
*
|
12
|
-
*
|
13
|
-
*
|
14
|
-
*
|
15
|
-
*
|
16
|
-
*
|
17
|
-
*
|
18
|
-
*
|
19
|
-
*
|
20
|
-
*
|
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.
|
28
|
+
* Alternatively, the contents of this file may be used under the terms of
|
29
|
+
* the GNU General Public License ("GPL") version 2 or any later version,
|
30
|
+
* in which case the provisions of the GPL are applicable instead of
|
31
|
+
* the above. If you wish to allow the use of your version of this file
|
32
|
+
* only under the terms of the GPL and not to allow others to use your
|
33
|
+
* version of this file under the BSD license, indicate your decision
|
34
|
+
* by deleting the provisions above and replace them with the notice
|
35
|
+
* and other provisions required by the GPL. If you do not delete the
|
36
|
+
* provisions above, a recipient may use your version of this file under
|
37
|
+
* either the BSD or the GPL.
|
30
38
|
*/
|
31
39
|
|
32
40
|
#ifdef _WIN32
|
data/libev/ev_wrap.h
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
#define backend_fudge ((loop)->backend_fudge)
|
14
14
|
#define backend_modify ((loop)->backend_modify)
|
15
15
|
#define backend_poll ((loop)->backend_poll)
|
16
|
+
#define evpipe ((loop)->evpipe)
|
17
|
+
#define pipeev ((loop)->pipeev)
|
16
18
|
#define curpid ((loop)->curpid)
|
17
19
|
#define postfork ((loop)->postfork)
|
18
20
|
#define vec_ri ((loop)->vec_ri)
|
@@ -61,6 +63,10 @@
|
|
61
63
|
#define forks ((loop)->forks)
|
62
64
|
#define forkmax ((loop)->forkmax)
|
63
65
|
#define forkcnt ((loop)->forkcnt)
|
66
|
+
#define gotasync ((loop)->gotasync)
|
67
|
+
#define asyncs ((loop)->asyncs)
|
68
|
+
#define asyncmax ((loop)->asyncmax)
|
69
|
+
#define asynccnt ((loop)->asynccnt)
|
64
70
|
#define fs_fd ((loop)->fs_fd)
|
65
71
|
#define fs_w ((loop)->fs_w)
|
66
72
|
#define fs_hash ((loop)->fs_hash)
|
@@ -78,6 +84,8 @@
|
|
78
84
|
#undef backend_fudge
|
79
85
|
#undef backend_modify
|
80
86
|
#undef backend_poll
|
87
|
+
#undef evpipe
|
88
|
+
#undef pipeev
|
81
89
|
#undef curpid
|
82
90
|
#undef postfork
|
83
91
|
#undef vec_ri
|
@@ -126,6 +134,10 @@
|
|
126
134
|
#undef forks
|
127
135
|
#undef forkmax
|
128
136
|
#undef forkcnt
|
137
|
+
#undef gotasync
|
138
|
+
#undef asyncs
|
139
|
+
#undef asyncmax
|
140
|
+
#undef asynccnt
|
129
141
|
#undef fs_fd
|
130
142
|
#undef fs_w
|
131
143
|
#undef fs_hash
|
data/ruby_lib/ebb.rb
CHANGED
@@ -3,33 +3,37 @@
|
|
3
3
|
# See README file for details.
|
4
4
|
require 'stringio'
|
5
5
|
module Ebb
|
6
|
+
VERSION = "0.2.0"
|
6
7
|
LIBDIR = File.dirname(__FILE__)
|
7
|
-
require Ebb::LIBDIR + '/../src/ebb_ext'
|
8
8
|
autoload :Runner, LIBDIR + '/ebb/runner'
|
9
|
+
autoload :FFI, LIBDIR + '/../src/ebb_ext'
|
9
10
|
|
10
11
|
def self.start_server(app, options={})
|
11
|
-
|
12
|
-
|
13
|
-
|
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}"
|
14
20
|
else
|
15
|
-
|
21
|
+
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}/"
|
16
24
|
end
|
25
|
+
log.puts "Ebb PID #{Process.pid}"
|
17
26
|
|
18
|
-
Client::BASE_ENV['rack.multithread'] = threaded_processing
|
19
|
-
|
20
|
-
FFI::server_listen_on_port(port)
|
21
27
|
@running = true
|
22
28
|
trap('INT') { stop_server }
|
23
29
|
|
24
|
-
log.puts "Ebb listening at http://0.0.0.0:#{port}/ (#{threaded_processing ? 'threaded' : 'sequential'} processing, PID #{Process.pid})"
|
25
|
-
|
26
30
|
while @running
|
27
31
|
FFI::server_process_connections()
|
28
|
-
while client = FFI::
|
29
|
-
if
|
30
|
-
Thread.new(client) { |c| process(app, c) }
|
31
|
-
else
|
32
|
+
while client = FFI::server_waiting_clients.shift
|
33
|
+
if app.respond_to?(:deferred?) and !app.deferred?(client.env)
|
32
34
|
process(app, client)
|
35
|
+
else
|
36
|
+
Thread.new(client) { |c| process(app, c) }
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -45,34 +49,40 @@ module Ebb
|
|
45
49
|
end
|
46
50
|
|
47
51
|
def self.process(app, client)
|
48
|
-
|
49
|
-
|
50
|
-
rescue
|
51
|
-
raise if $DEBUG
|
52
|
-
status = 500
|
53
|
-
headers = {'Content-Type' => 'text/plain'}
|
54
|
-
body = "Internal Server Error\n"
|
55
|
-
end
|
52
|
+
#p client.env
|
53
|
+
status, headers, body = app.call(client.env)
|
56
54
|
|
55
|
+
# Write the status
|
57
56
|
client.write_status(status)
|
58
57
|
|
59
|
-
|
60
|
-
|
58
|
+
# Add Content-Length to the headers.
|
59
|
+
if headers['Content-Length'].nil? and
|
60
|
+
headers.respond_to?(:[]=) and
|
61
|
+
body.respond_to?(:length) and
|
62
|
+
status != 304
|
63
|
+
then
|
61
64
|
headers['Content-Length'] = body.length.to_s
|
62
65
|
end
|
63
66
|
|
67
|
+
# Decide if we should keep the connection alive or not
|
68
|
+
if headers['Connection'].nil?
|
69
|
+
if headers['Content-Length'] and client.should_keep_alive?
|
70
|
+
headers['Connection'] = 'Keep-Alive'
|
71
|
+
else
|
72
|
+
headers['Connection'] = 'close'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Write the headers
|
64
77
|
headers.each { |field, value| client.write_header(field, value) }
|
65
|
-
client.write("\r\n")
|
66
78
|
|
79
|
+
# Write the body
|
67
80
|
if body.kind_of?(String)
|
68
|
-
client.
|
69
|
-
client.body_written()
|
70
|
-
client.begin_transmission()
|
81
|
+
client.write_body(body)
|
71
82
|
else
|
72
|
-
client.
|
73
|
-
body.each { |p| client.write(p) }
|
74
|
-
client.body_written()
|
83
|
+
body.each { |p| client.write_body(p) }
|
75
84
|
end
|
85
|
+
|
76
86
|
rescue => e
|
77
87
|
log.puts "Ebb Error! #{e.class} #{e.message}"
|
78
88
|
log.puts e.backtrace.join("\n")
|
@@ -88,16 +98,12 @@ module Ebb
|
|
88
98
|
@@log
|
89
99
|
end
|
90
100
|
|
91
|
-
# This array is created and manipulated in the C extension.
|
92
|
-
def FFI.waiting_clients
|
93
|
-
@waiting_clients
|
94
|
-
end
|
95
|
-
|
96
101
|
class Client
|
102
|
+
attr_reader :fd, :body_head, :content_length
|
97
103
|
BASE_ENV = {
|
98
104
|
'SERVER_NAME' => '0.0.0.0',
|
99
105
|
'SCRIPT_NAME' => '',
|
100
|
-
'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}",
|
106
|
+
'SERVER_SOFTWARE' => "Ebb-Ruby #{Ebb::VERSION}",
|
101
107
|
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
102
108
|
'rack.version' => [0, 1],
|
103
109
|
'rack.errors' => STDERR,
|
@@ -107,9 +113,11 @@ module Ebb
|
|
107
113
|
}
|
108
114
|
|
109
115
|
def env
|
110
|
-
env
|
111
|
-
|
112
|
-
|
116
|
+
@env ||= begin
|
117
|
+
env = FFI::client_env(self).update(BASE_ENV)
|
118
|
+
env['rack.input'] = RequestBody.new(self)
|
119
|
+
env
|
120
|
+
end
|
113
121
|
end
|
114
122
|
|
115
123
|
def write_status(status)
|
@@ -117,8 +125,8 @@ module Ebb
|
|
117
125
|
FFI::client_write_status(self, s, HTTP_STATUS_CODES[s])
|
118
126
|
end
|
119
127
|
|
120
|
-
def
|
121
|
-
FFI::
|
128
|
+
def write_body(data)
|
129
|
+
FFI::client_write_body(self, data)
|
122
130
|
end
|
123
131
|
|
124
132
|
def write_header(field, value)
|
@@ -127,50 +135,53 @@ module Ebb
|
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
def
|
131
|
-
FFI::
|
138
|
+
def release
|
139
|
+
FFI::client_release(self)
|
132
140
|
end
|
133
141
|
|
134
|
-
def
|
135
|
-
FFI::
|
142
|
+
def set_keep_alive
|
143
|
+
FFI::client_set_keep_alive(self)
|
136
144
|
end
|
137
145
|
|
138
|
-
def
|
139
|
-
|
146
|
+
def should_keep_alive?
|
147
|
+
if env['HTTP_VERSION'] == 'HTTP/1.0'
|
148
|
+
return true if env['HTTP_CONNECTION'] =~ /Keep-Alive/i
|
149
|
+
else
|
150
|
+
return true unless env['HTTP_CONNECTION'] =~ /close/i
|
151
|
+
end
|
152
|
+
false
|
140
153
|
end
|
141
154
|
end
|
142
155
|
|
143
156
|
class RequestBody
|
144
157
|
def initialize(client)
|
145
|
-
@
|
158
|
+
@content_length = client.content_length
|
159
|
+
if client.body_head
|
160
|
+
@body_head = StringIO.new(client.body_head)
|
161
|
+
if @body_head.length < @content_length
|
162
|
+
@socket = IO.new(client.fd)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
@total_read = 0
|
146
166
|
end
|
147
167
|
|
148
168
|
def read(len = nil)
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
if
|
153
|
-
|
154
|
-
while(chunk = read(10*1024)) do
|
155
|
-
s << chunk
|
156
|
-
end
|
157
|
-
s
|
158
|
-
else
|
159
|
-
FFI::client_read_input(@client, len)
|
160
|
-
end
|
169
|
+
to_read = len.nil? ? @content_length - @total_read : min(len, @content_length - @total_read)
|
170
|
+
return nil if to_read == 0 or @body_head.nil?
|
171
|
+
unless out = @body_head.read(to_read)
|
172
|
+
return nil if @socket.nil?
|
173
|
+
out = @socket.read(to_read)
|
161
174
|
end
|
175
|
+
@total_read += out.length
|
176
|
+
out
|
162
177
|
end
|
163
178
|
|
164
179
|
def gets
|
165
|
-
|
180
|
+
raise NotImplemented
|
166
181
|
end
|
167
182
|
|
168
183
|
def each(&block)
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
def io
|
173
|
-
@io ||= StringIO.new(read)
|
184
|
+
raise NotImplemented
|
174
185
|
end
|
175
186
|
end
|
176
187
|
|
@@ -215,3 +226,19 @@ module Ebb
|
|
215
226
|
505 => 'HTTP Version not supported'
|
216
227
|
}.freeze
|
217
228
|
end
|
229
|
+
|
230
|
+
|
231
|
+
module Rack
|
232
|
+
module Handler
|
233
|
+
module Ebb
|
234
|
+
def self.run(app, options={})
|
235
|
+
::Ebb.start_server(app, options)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# cause i don't want to create an array
|
242
|
+
def min(a,b)
|
243
|
+
a > b ? b : a
|
244
|
+
end
|
data/ruby_lib/ebb/runner.rb
CHANGED
@@ -58,8 +58,7 @@ module Ebb
|
|
58
58
|
@parser = OptionParser.new
|
59
59
|
@options = {
|
60
60
|
:port => 4001,
|
61
|
-
:timeout => 60
|
62
|
-
:threaded_processing => true
|
61
|
+
:timeout => 60
|
63
62
|
}
|
64
63
|
end
|
65
64
|
|
@@ -69,8 +68,8 @@ module Ebb
|
|
69
68
|
extra_options if respond_to?(:extra_options)
|
70
69
|
|
71
70
|
@parser.separator ""
|
72
|
-
|
73
|
-
@parser.on("-
|
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 }
|
74
73
|
@parser.on("-d", "--daemonize", "Daemonize") { @options[:daemonize] = true }
|
75
74
|
@parser.on("-l", "--log-file FILE", "File to redirect output") { |f| @options[:log_file]=f }
|
76
75
|
@parser.on("-P", "--pid-file FILE", "File to store PID") { |f| @options[:pid_file]=f }
|
@@ -82,7 +81,7 @@ module Ebb
|
|
82
81
|
exit
|
83
82
|
end
|
84
83
|
@parser.on_tail('-v', '--version', "Show version") do
|
85
|
-
Ebb.log.puts
|
84
|
+
Ebb.log.puts Ebb::Client::BASE_ENV['SERVER_SOFTWARE']
|
86
85
|
exit
|
87
86
|
end
|
88
87
|
|