ruby-fcgi 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.config +20 -0
- data/.document +5 -0
- data/.gitignore +26 -0
- data/ChangeLog +33 -0
- data/InstalledFiles +1 -0
- data/LICENSE +20 -0
- data/README +110 -0
- data/README.rdoc +110 -0
- data/README.signals +76 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/ext/fcgi/MANIFEST +3 -0
- data/ext/fcgi/Makefile +181 -0
- data/ext/fcgi/extconf.rb +7 -0
- data/ext/fcgi/fcgi.c +558 -0
- data/install.rb +1551 -0
- data/lib/fcgi.rb +618 -0
- data/test/helper.rb +9 -0
- data/test/test_fcgi.rb +7 -0
- metadata +78 -0
data/.config
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
prefix=/usr
|
2
|
+
bindir=$prefix/bin
|
3
|
+
libdir=$prefix/lib
|
4
|
+
datadir=$prefix/share
|
5
|
+
mandir=$prefix/share/man
|
6
|
+
sysconfdir=/etc
|
7
|
+
localstatedir=/var
|
8
|
+
libruby=/usr/lib/ruby
|
9
|
+
librubyver=/usr/lib/ruby/1.9.1
|
10
|
+
librubyverarch=/usr/lib/ruby/1.9.1/i486-linux
|
11
|
+
siteruby=/usr/local/lib/site_ruby
|
12
|
+
siterubyver=/usr/local/lib/site_ruby/1.9.1
|
13
|
+
siterubyverarch=/usr/local/lib/site_ruby/1.9.1/i486-linux
|
14
|
+
rbdir=$siterubyver
|
15
|
+
sodir=$siterubyverarch
|
16
|
+
rubypath=/usr/bin/ruby1.9.1
|
17
|
+
rubyprog=/usr/bin/ruby1.9.1
|
18
|
+
makeprog=make
|
19
|
+
shebang=ruby
|
20
|
+
without-ext=no
|
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
|
21
|
+
## PROJECT::SPECIFIC
|
22
|
+
*.so
|
23
|
+
*.o
|
24
|
+
*.log
|
25
|
+
*.gemspec
|
26
|
+
tmp/*
|
data/ChangeLog
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Thu Nov 21 10:14:15 JST 2009 saksmlz@gmail.com
|
2
|
+
* patch from http://blog.rubyists.com/2009/06/02/ruby-fcgi-on-1-9-x
|
3
|
+
|
4
|
+
Sun Jun 25 12:46:14 JST 2006 moonwolf@moonwolf.com
|
5
|
+
* patch from http://www.kbmj.com/tech/index.php?itemid=26
|
6
|
+
* patch from http://sean.treadway.info/articles/2005/12/24/open-season-for-eagain
|
7
|
+
|
8
|
+
Fri Apr 1 10:20:14 JST 2005 sugi@nemui.org
|
9
|
+
* Include errno.h
|
10
|
+
|
11
|
+
Fri Apr 1 08:09:13 JST 2005 aredridel@nbtsc.org
|
12
|
+
* Report actual errors
|
13
|
+
|
14
|
+
Adds reporting of errors fcgi experiences. Credit to David Heinemier Hansson
|
15
|
+
for discovery.
|
16
|
+
|
17
|
+
Fri Apr 1 08:08:07 JST 2005 aredridel@nbtsc.org
|
18
|
+
* FHS Include Paths
|
19
|
+
|
20
|
+
Wed Mar 30 21:45:11 JST 2005 sugi@nemui.org
|
21
|
+
* 16k+request-memleak
|
22
|
+
fix memory leak when 16k+/reqest.
|
23
|
+
from http://enigo.com/projects/iowa/fcgipatch.html
|
24
|
+
|
25
|
+
Wed Mar 30 21:43:02 JST 2005 sugi@nemui.org
|
26
|
+
* fix-check_stream_error
|
27
|
+
Simple fix in CHECK_STREAM_ERROR for Potential DoS
|
28
|
+
see http://groups-beta.google.com/group/comp.lang.ruby/browse_thread/thread/f51e79974a454b70/54fe207411e9eb05
|
29
|
+
for details.
|
30
|
+
|
31
|
+
Wed Mar 30 21:40:22 JST 2005 sugi@nemui.org
|
32
|
+
* init-from-0.8.5
|
33
|
+
Initalize DARCS repository from MoonWolf's 0.8.5 release.
|
data/InstalledFiles
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/usr/local/lib/site_ruby/1.9.1/i486-linux/fcgi.so
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 saks
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
= fcgi - FastCGI library for Ruby
|
2
|
+
|
3
|
+
Version 0.8.8
|
4
|
+
|
5
|
+
== Depends
|
6
|
+
|
7
|
+
=== C version
|
8
|
+
* ((<libfcgi|URL:http://www.fastcgi.com/#TheDevKit>))(FastCGI Developer's Kit)
|
9
|
+
|
10
|
+
=== Pure Ruby Version
|
11
|
+
* StringIO
|
12
|
+
|
13
|
+
== Install
|
14
|
+
|
15
|
+
$ ruby install.rb config
|
16
|
+
(Pure Ruby Version: ruby install.rb config --without-ext)
|
17
|
+
(Some systems need: ruby install.rb config -- --with-fcgi-include=/usr/local/include --with-fcgi-lib=/usr/local/lib)
|
18
|
+
$ ruby install.rb setup
|
19
|
+
# ruby install.rb install
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
=== Class Method
|
23
|
+
--- FCGI.accept
|
24
|
+
Returns FCGI instance
|
25
|
+
--- FCGI.each
|
26
|
+
|
27
|
+
--- FCGI.each_request
|
28
|
+
|
29
|
+
--- FCGI.is_cgi?
|
30
|
+
|
31
|
+
--- FCGI.each_cgi
|
32
|
+
Automatically detects whether this program is running under the FastCGI
|
33
|
+
environment, and generates a 'CGI' type object for each request. Also
|
34
|
+
installs signal handlers for graceful handling of SIGPIPE (which may
|
35
|
+
occur if a client gives up on a request before it is complete) and
|
36
|
+
SIGUSR1 (generated by Apache for a 'graceful' exit)
|
37
|
+
|
38
|
+
If you are using the HTML output methods you can also pass the HTML type
|
39
|
+
e.g. FCGI.each_cgi('html3') do ... end
|
40
|
+
|
41
|
+
However, you should beware that the CGI library is quite slow when
|
42
|
+
used in this way, as it dynamically adds a large number of methods
|
43
|
+
to itself each time a new instance is created.
|
44
|
+
|
45
|
+
|
46
|
+
=== Instance Method
|
47
|
+
--- FCGI#finish
|
48
|
+
Finish
|
49
|
+
|
50
|
+
--- FCGI#in
|
51
|
+
Returns Stream or StringIO
|
52
|
+
|
53
|
+
--- FCGI#out
|
54
|
+
Returns Stream or StringIO
|
55
|
+
|
56
|
+
--- FCGI#err
|
57
|
+
Returns Stream or StringIO
|
58
|
+
|
59
|
+
--- FCGI#env
|
60
|
+
Returns Environment(Hash)
|
61
|
+
|
62
|
+
== Sample
|
63
|
+
Using the FastCGI native interface:
|
64
|
+
|
65
|
+
#!/usr/bin/ruby
|
66
|
+
require "fcgi"
|
67
|
+
|
68
|
+
FCGI.each {|request|
|
69
|
+
out = request.out
|
70
|
+
out.print "Content-Type: text/plain\r\n"
|
71
|
+
out.print "\r\n"
|
72
|
+
out.print Time.now.to_s
|
73
|
+
request.finish
|
74
|
+
}
|
75
|
+
|
76
|
+
Using the CGI-compatible interface, which works both as a standalone CGI
|
77
|
+
and under FastCGI with no modifications:
|
78
|
+
|
79
|
+
#!/usr/bin/ruby
|
80
|
+
require "fcgi"
|
81
|
+
|
82
|
+
FCGI.each_cgi {|cgi|
|
83
|
+
name = cgi['name'][0]
|
84
|
+
puts cgi.header
|
85
|
+
puts "You are #{name} " if name
|
86
|
+
puts "Connecting from #{cgi.remote_addr}"
|
87
|
+
}
|
88
|
+
|
89
|
+
Note: you can't reference CGI environment variables using ENV when under
|
90
|
+
FastCGI. It is recommended that you use the CGI-generated methods, e.g.
|
91
|
+
cgi.remote_addr as above.
|
92
|
+
|
93
|
+
If you need to access environment variables directly, perhaps extra ones set
|
94
|
+
in your Apache config, then use cgi.env_table['REMOTE_ADDR'] instead. This
|
95
|
+
isn't quite as portable because env_table is a private method in the
|
96
|
+
standard CGI library.
|
97
|
+
|
98
|
+
== License
|
99
|
+
|
100
|
+
* ((<URL:http://www.ruby-lang.org/ja/LICENSE.txt>)) (Japanese)
|
101
|
+
* ((<URL:http://www.ruby-lang.org/en/LICENSE.txt>)) (English)
|
102
|
+
|
103
|
+
== Copyright
|
104
|
+
|
105
|
+
fcgi.c 0.1 Copyright (C) 1998-1999 Network Applied Communication Laboratory, Inc.
|
106
|
+
0.8 Copyright (C) 2002 MoonWolf <moonwolf@moonwolf.com>
|
107
|
+
|
108
|
+
fastcgi.rb 0.7 Copyright (C) 2001 Eli Green
|
109
|
+
fcgi.rb 0.8 Copyright (C) 2002 MoonWolf <moonwolf@moonwolf.com>
|
110
|
+
fcgi.rb 0.8.5 Copyright (C) 2004 Minero Aoki
|
data/README.rdoc
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
= fcgi - FastCGI library for Ruby
|
2
|
+
|
3
|
+
Version 0.8.8
|
4
|
+
|
5
|
+
== Depends
|
6
|
+
|
7
|
+
=== C version
|
8
|
+
* ((<libfcgi|URL:http://www.fastcgi.com/#TheDevKit>))(FastCGI Developer's Kit)
|
9
|
+
|
10
|
+
=== Pure Ruby Version
|
11
|
+
* StringIO
|
12
|
+
|
13
|
+
== Install
|
14
|
+
|
15
|
+
$ ruby install.rb config
|
16
|
+
(Pure Ruby Version: ruby install.rb config --without-ext)
|
17
|
+
(Some systems need: ruby install.rb config -- --with-fcgi-include=/usr/local/include --with-fcgi-lib=/usr/local/lib)
|
18
|
+
$ ruby install.rb setup
|
19
|
+
# ruby install.rb install
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
=== Class Method
|
23
|
+
--- FCGI.accept
|
24
|
+
Returns FCGI instance
|
25
|
+
--- FCGI.each
|
26
|
+
|
27
|
+
--- FCGI.each_request
|
28
|
+
|
29
|
+
--- FCGI.is_cgi?
|
30
|
+
|
31
|
+
--- FCGI.each_cgi
|
32
|
+
Automatically detects whether this program is running under the FastCGI
|
33
|
+
environment, and generates a 'CGI' type object for each request. Also
|
34
|
+
installs signal handlers for graceful handling of SIGPIPE (which may
|
35
|
+
occur if a client gives up on a request before it is complete) and
|
36
|
+
SIGUSR1 (generated by Apache for a 'graceful' exit)
|
37
|
+
|
38
|
+
If you are using the HTML output methods you can also pass the HTML type
|
39
|
+
e.g. FCGI.each_cgi('html3') do ... end
|
40
|
+
|
41
|
+
However, you should beware that the CGI library is quite slow when
|
42
|
+
used in this way, as it dynamically adds a large number of methods
|
43
|
+
to itself each time a new instance is created.
|
44
|
+
|
45
|
+
|
46
|
+
=== Instance Method
|
47
|
+
--- FCGI#finish
|
48
|
+
Finish
|
49
|
+
|
50
|
+
--- FCGI#in
|
51
|
+
Returns Stream or StringIO
|
52
|
+
|
53
|
+
--- FCGI#out
|
54
|
+
Returns Stream or StringIO
|
55
|
+
|
56
|
+
--- FCGI#err
|
57
|
+
Returns Stream or StringIO
|
58
|
+
|
59
|
+
--- FCGI#env
|
60
|
+
Returns Environment(Hash)
|
61
|
+
|
62
|
+
== Sample
|
63
|
+
Using the FastCGI native interface:
|
64
|
+
|
65
|
+
#!/usr/bin/ruby
|
66
|
+
require "fcgi"
|
67
|
+
|
68
|
+
FCGI.each {|request|
|
69
|
+
out = request.out
|
70
|
+
out.print "Content-Type: text/plain\r\n"
|
71
|
+
out.print "\r\n"
|
72
|
+
out.print Time.now.to_s
|
73
|
+
request.finish
|
74
|
+
}
|
75
|
+
|
76
|
+
Using the CGI-compatible interface, which works both as a standalone CGI
|
77
|
+
and under FastCGI with no modifications:
|
78
|
+
|
79
|
+
#!/usr/bin/ruby
|
80
|
+
require "fcgi"
|
81
|
+
|
82
|
+
FCGI.each_cgi {|cgi|
|
83
|
+
name = cgi['name'][0]
|
84
|
+
puts cgi.header
|
85
|
+
puts "You are #{name} " if name
|
86
|
+
puts "Connecting from #{cgi.remote_addr}"
|
87
|
+
}
|
88
|
+
|
89
|
+
Note: you can't reference CGI environment variables using ENV when under
|
90
|
+
FastCGI. It is recommended that you use the CGI-generated methods, e.g.
|
91
|
+
cgi.remote_addr as above.
|
92
|
+
|
93
|
+
If you need to access environment variables directly, perhaps extra ones set
|
94
|
+
in your Apache config, then use cgi.env_table['REMOTE_ADDR'] instead. This
|
95
|
+
isn't quite as portable because env_table is a private method in the
|
96
|
+
standard CGI library.
|
97
|
+
|
98
|
+
== License
|
99
|
+
|
100
|
+
* ((<URL:http://www.ruby-lang.org/ja/LICENSE.txt>)) (Japanese)
|
101
|
+
* ((<URL:http://www.ruby-lang.org/en/LICENSE.txt>)) (English)
|
102
|
+
|
103
|
+
== Copyright
|
104
|
+
|
105
|
+
fcgi.c 0.1 Copyright (C) 1998-1999 Network Applied Communication Laboratory, Inc.
|
106
|
+
0.8 Copyright (C) 2002 MoonWolf <moonwolf@moonwolf.com>
|
107
|
+
|
108
|
+
fastcgi.rb 0.7 Copyright (C) 2001 Eli Green
|
109
|
+
fcgi.rb 0.8 Copyright (C) 2002 MoonWolf <moonwolf@moonwolf.com>
|
110
|
+
fcgi.rb 0.8.5 Copyright (C) 2004 Minero Aoki
|
data/README.signals
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= Handling of SIGUSR1
|
2
|
+
|
3
|
+
When you request a 'graceful' restart of Apache, mod_fastcgi sends a SIGUSR1
|
4
|
+
to each of the fastcgi worker processes. The intention is that each one
|
5
|
+
should finish the current request, and then exit, at which point Apache will
|
6
|
+
restart it. Of course, if the worker isn't doing anything, it should die
|
7
|
+
immediately.
|
8
|
+
|
9
|
+
This is implemented in the fcgi C library as follows:
|
10
|
+
|
11
|
+
- a signal handler is installed for SIGUSR1, which just sets a flag
|
12
|
+
(shutdownPending) and returns
|
13
|
+
- fcgi sits inside an accept() call waiting for a new request
|
14
|
+
- if this accept() call terminates with EINTR, and this flag is set, then
|
15
|
+
it returns with no request
|
16
|
+
|
17
|
+
Unfortunately, Ruby defeats this mechanism in at least two ways:
|
18
|
+
|
19
|
+
1. Ruby installs its own signal handlers for a host of common signals,
|
20
|
+
including USR1. The fcgi library will not install its own handler if it
|
21
|
+
detects that a handler has already been set (i.e. the current handler is not
|
22
|
+
SIG_DFL)
|
23
|
+
|
24
|
+
2. When Ruby installs its own signal handlers, it does so with SA_RESTART
|
25
|
+
set. This means that the accept() call does not terminate with EINTR; it is
|
26
|
+
restarted automatically by the OS.
|
27
|
+
|
28
|
+
When a signal comes in during the accept(), Ruby's own handler does nothing
|
29
|
+
except store it in a queue to be processed later. It is only when the
|
30
|
+
accept() call completes, i.e. when a genuine new request comes in, that Ruby
|
31
|
+
takes action. Unfortunately it's too late by then, and if that
|
32
|
+
already-accepted request is not honoured, a 500 Internal Error will be
|
33
|
+
returned to the client.
|
34
|
+
|
35
|
+
The simplest solution to this would be to remove Ruby's SIGUSR1 handler
|
36
|
+
before initialising the FastCGI library.
|
37
|
+
|
38
|
+
However, a cleaner solution is to call rb_thread_select before going into
|
39
|
+
FastCGI's accept loop. If a signal happens during the select, it can be
|
40
|
+
handled using Ruby's normal mechanisms. This also gives a very useful
|
41
|
+
side-benefit, which is that FCGI::accept no longer blocks out other Ruby
|
42
|
+
threads. The program below demonstrates this problem; its background logging
|
43
|
+
thread is supposed to write a message every 10 seconds, but under older
|
44
|
+
versions of ruby-fcgi it does not do so if it is waiting for a new request.
|
45
|
+
|
46
|
+
#!/usr/local/bin/ruby
|
47
|
+
require "fcgi"
|
48
|
+
|
49
|
+
Thread.new do
|
50
|
+
f = File.new("/tmp/fcgi.log","a")
|
51
|
+
f.sync=true
|
52
|
+
while true
|
53
|
+
f.puts "#{Time.now.to_s} pid #{$$}"
|
54
|
+
sleep 10
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
FCGI.each_cgi {|cgi|
|
59
|
+
name = cgi['name'][0]
|
60
|
+
puts cgi.header
|
61
|
+
puts "Hey! You are #{name} " if name
|
62
|
+
puts "Connecting from #{cgi.remote_addr}"
|
63
|
+
}
|
64
|
+
|
65
|
+
Having protected the accept() with a ruby select(), you can then handle
|
66
|
+
signals as follows:
|
67
|
+
|
68
|
+
- call FCGI::accept (it will raise an exception if USR1 is called)
|
69
|
+
- install a USR1 handler
|
70
|
+
- process the request
|
71
|
+
- remove the USR1 handler
|
72
|
+
|
73
|
+
The USR1 handler should set a flag to note if a USR1 signal came in while
|
74
|
+
the request was being processed; you terminate the loop if it was set. The
|
75
|
+
overall effect is that USR1 will cause the process to terminate, but without
|
76
|
+
causing a half-completed request.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ruby-fcgi"
|
8
|
+
gem.summary = %Q{FastCGI library for Ruby.}
|
9
|
+
gem.description = %Q{FastCGI is a language independent, scalable, open extension to CGI that provides high performance without the limitations of server specific APIs. For more information, see http://www.fastcgi.com/.}
|
10
|
+
gem.email = "saksmlz@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/saks/fcgi"
|
12
|
+
gem.authors = ["saks"]
|
13
|
+
gem.files << "ext/fcgi/extconf.rb" << "ext/fcgi/fcgi.c" << "ext/fcgi/Makefile" << "ext/fcgi/MANIFEST"
|
14
|
+
gem.extensions = ['ext/fcgi/extconf.rb']
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "fcgi #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
55
|
+
|