rubycas-server 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +38 -0
- data/Manifest.txt +4 -11
- data/README.txt +1 -1
- data/Rakefile +2 -2
- data/bin/rubycas-server +16 -47
- data/bin/rubycas-server-ctl +13 -154
- data/config.example.yml +113 -64
- data/lib/casserver.rb +78 -80
- data/lib/casserver/authenticators/active_directory_ldap.rb +3 -0
- data/lib/casserver/authenticators/ldap.rb +14 -1
- data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
- data/lib/casserver/controllers.rb +19 -8
- data/lib/casserver/environment.rb +23 -0
- data/lib/casserver/models.rb +104 -53
- data/lib/casserver/utils.rb +0 -24
- data/lib/casserver/version.rb +2 -2
- data/lib/casserver/views.rb +28 -0
- data/lib/rubycas-server.rb +1 -0
- metadata +77 -68
- data/vendor/camping-1.5.180/CHANGELOG +0 -99
- data/vendor/camping-1.5.180/COPYING +0 -18
- data/vendor/camping-1.5.180/README +0 -119
- data/vendor/camping-1.5.180/Rakefile +0 -117
- data/vendor/camping-1.5.180/lib/camping-unabridged.rb +0 -762
- data/vendor/camping-1.5.180/lib/camping.rb +0 -55
- data/vendor/camping-1.5.180/lib/camping/db.rb +0 -78
- data/vendor/camping-1.5.180/lib/camping/fastcgi.rb +0 -244
- data/vendor/camping-1.5.180/lib/camping/reloader.rb +0 -163
- data/vendor/camping-1.5.180/lib/camping/session.rb +0 -123
- data/vendor/camping-1.5.180/lib/camping/webrick.rb +0 -68
@@ -1,55 +0,0 @@
|
|
1
|
-
%w[active_support markaby tempfile uri].map{|l|require l}
|
2
|
-
module Camping;Apps=[];C=self;S=IO.read(__FILE__).sub(/S=I.+$/,'')
|
3
|
-
P="Cam\ping Problem!";module Helpers;def R(c,*g);p,h=/\(.+?\)/,g.grep(Hash)
|
4
|
-
(g-=h).inject(c.urls.find{|x|x.scan(p).size==g.size}.dup){|s,a|s.sub p,C.
|
5
|
-
escape((a[a.class.primary_key]rescue a))}+(h.any?? "?"+h[0].map{|x|x.map{|z|C.
|
6
|
-
escape z}*"="}*"&": "")end;def URL c='/',*a;c=R(c,*a)if c.
|
7
|
-
respond_to?:urls;c=self/c;c="//"+@env.HTTP_HOST+c if c[/^\//];URI(c)end;def/p
|
8
|
-
p[/^\//]?@root+p : p end;def errors_for o;ul.errors{o.errors.each_full{|x|li x}
|
9
|
-
}if o.errors.any?end end;module Base;include Helpers;attr_accessor:input,
|
10
|
-
:cookies,:env,:headers,:body,:status,:root;def method_missing*a,&b
|
11
|
-
a.shift if a[0]==:render;m=Mab.new({},self);s=m.capture{send(*a,&b)}
|
12
|
-
s=m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?:layout
|
13
|
-
s end;def r s,b,h={};@status=s;@headers.merge!h;@body=b end
|
14
|
-
def redirect*a;r 302,'','Location'=>URL(*a)end;Z="\r\n"
|
15
|
-
def to_a;[@status,@body,@headers]end
|
16
|
-
def initialize r,e,m;e=H[e.to_hash];@status,@method,@env,@headers,@root=200,m.
|
17
|
-
downcase,e,{'Content-Type'=>"text/html"},e.SCRIPT_NAME.sub(/\/$/,'')
|
18
|
-
@k=C.kp e.HTTP_COOKIE;q=C.qsp e.QUERY_STRING;@in=r
|
19
|
-
if%r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n.match e.CONTENT_TYPE
|
20
|
-
b=/(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/;until@in.eof?;fh=H[];for l in@in
|
21
|
-
case l;when Z;break;when/^Content-D.+?: form-data;/;fh.u H[*$'.
|
22
|
-
scan(/(?:\s(\w+)="([^"]+)")/).flatten];when/^Content-Type: (.+?)(\r$|\Z)/m;fh[
|
23
|
-
:type]=$1;end;end;fn=fh[:name];o=if fh[:filename];o=fh[:tempfile]=Tempfile.new(:C)
|
24
|
-
o.binmode;else;fh=""end;while l=@in.read(16384);if l=~b;o<<$`.chomp;@in.seek(-$'.
|
25
|
-
size,IO::SEEK_CUR);break;end;o<<l;end;C.qsp(fn,'&;',fh,q) if fn;fh[:tempfile].rewind if
|
26
|
-
fh.is_a?H;end;elsif@method=="post";q.u C.qsp(@in.read)end;@cookies,@input=
|
27
|
-
@k.dup,q.dup end;def service*a;@body=send(@method,*a)if respond_to?@method
|
28
|
-
@headers["Set-Cookie"]=@cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/'/'
|
29
|
-
}"if v!=@k[k]}-[nil];self end;def to_s;a=[];@headers.map{|k,v|[*v].map{|x|a<<
|
30
|
-
"#{k}: #{x}"}};"Status: #{@status}#{Z+a*Z+Z*2+@body}"end;end
|
31
|
-
X=module Controllers;@r=[];class<<self;def r;@r;end;def R*u;r=@r;Class.new{
|
32
|
-
meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end;def M;def M;end;constants.map{|c|
|
33
|
-
k=const_get(c);k.send:include,C,Base,Models;r[0,0]=k if !r.include?k;k.meta_def(
|
34
|
-
:urls){["/#{c.downcase}"]}if !k.respond_to?:urls}end;def D p;r.map{|k|k.urls.
|
35
|
-
map{|x|return k,$~[1..-1]if p=~/^#{x}\/?$/}};[NotFound,[p]]end end;class
|
36
|
-
NotFound<R();def get p;r(404,Mab.new{h1 P;h2 p+" not found"})end end;class
|
37
|
-
ServerError<R();def get k,m,e;r(500,Mab.new{h1 P;h2"#{k}.#{m}";h3"#{e.class
|
38
|
-
} #{e.message}:";ul{e.backtrace.each{|bt|li(bt)}}}.to_s)end end;self;end;class<<
|
39
|
-
self;def goes m;eval S.gsub(/Camping/,m.to_s).gsub("A\pps=[]","Cam\ping::Apps<<\
|
40
|
-
self"),TOPLEVEL_BINDING;end;def escape s;s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.
|
41
|
-
unpack('H2'*$&.size)*'%').upcase}.tr(' ','+')end;def un s;s.tr('+',' ').gsub(
|
42
|
-
/%([\da-f]{2})/in){[$1].pack('H*')}end;def qsp q,d='&;',y=nil,z=H[];m=proc{|_,o,n|o.u(
|
43
|
-
n,&m)rescue([*o]<<n)};q.to_s.split(/[#{d}] */n).inject((b,z=z,H[])[0]){|h,p|k,v=un(p).
|
44
|
-
split('=',2);h.u k.split(/[\]\[]+/).reverse.inject(y||v){|x,i|H[i,x]},&m}end;def
|
45
|
-
kp s;c=qsp(s,';,')end;def run r=$stdin,e=ENV;X.M;k,a=X.D un("/#{e[
|
46
|
-
'PATH_INFO']}".gsub(/\/+/,'/'));k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).Y.
|
47
|
-
service *a;rescue Object=>x;X::ServerError.new(r,e,'get').service(k,m,x)end
|
48
|
-
def method_missing m,c,*a;X.M;k=X.const_get(c).new(StringIO.new,H['HTTP_HOST',
|
49
|
-
'','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s);H.new(a.pop).each{|e,f|k.send(
|
50
|
-
"#{e}=",f)}if Hash===a[-1];k.service *a;end;end;module Views;include X,Helpers
|
51
|
-
end;module Models;autoload:Base,'camping/db';def Y;self;end;end;class Mab<Markaby::Builder
|
52
|
-
include Views;def tag!*g,&b;h=g[-1];[:href,:action,:src].map{|a|(h[a]=self/h[a])rescue
|
53
|
-
0};super end end;H=HashWithIndifferentAccess;class H;def method_missing m,*a
|
54
|
-
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")end
|
55
|
-
alias_method:u,:regular_update;end end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
class MissingLibrary < Exception #:nodoc: all
|
2
|
-
end
|
3
|
-
begin
|
4
|
-
require 'active_record'
|
5
|
-
rescue LoadError => e
|
6
|
-
raise MissingLibrary, "ActiveRecord could not be loaded (is it installed?): #{e.message}"
|
7
|
-
end
|
8
|
-
|
9
|
-
$AR_EXTRAS = %{
|
10
|
-
Base = ActiveRecord::Base unless const_defined? :Base
|
11
|
-
|
12
|
-
def Y; ActiveRecord::Base.verify_active_connections!; self; end
|
13
|
-
|
14
|
-
class SchemaInfo < Base
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.V(n)
|
18
|
-
@final = [n, @final.to_i].max
|
19
|
-
m = (@migrations ||= [])
|
20
|
-
Class.new(ActiveRecord::Migration) do
|
21
|
-
meta_def(:version) { n }
|
22
|
-
meta_def(:inherited) { |k| m << k }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.create_schema(opts = {})
|
27
|
-
opts[:assume] ||= 0
|
28
|
-
opts[:version] ||= @final
|
29
|
-
if @migrations
|
30
|
-
unless SchemaInfo.table_exists?
|
31
|
-
ActiveRecord::Schema.define do
|
32
|
-
create_table SchemaInfo.table_name do |t|
|
33
|
-
t.column :version, :float
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
si = SchemaInfo.find(:first) || SchemaInfo.new(:version => opts[:assume])
|
39
|
-
if si.version < opts[:version]
|
40
|
-
@migrations.each do |k|
|
41
|
-
k.migrate(:up) if si.version < k.version and k.version <= opts[:version]
|
42
|
-
k.migrate(:down) if si.version > k.version and k.version > opts[:version]
|
43
|
-
end
|
44
|
-
si.update_attributes(:version => opts[:version])
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
}
|
49
|
-
|
50
|
-
module Camping
|
51
|
-
module Models
|
52
|
-
A = ActiveRecord
|
53
|
-
# Base is an alias for ActiveRecord::Base. The big warning I'm going to give you
|
54
|
-
# about this: *Base overloads table_name_prefix.* This means that if you have a
|
55
|
-
# model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>.
|
56
|
-
#
|
57
|
-
# ActiveRecord is not loaded if you never reference this class. The minute you
|
58
|
-
# use the ActiveRecord or Camping::Models::Base class, then the ActiveRecord library
|
59
|
-
# is loaded.
|
60
|
-
Base = A::Base
|
61
|
-
|
62
|
-
# The default prefix for Camping model classes is the topmost module name lowercase
|
63
|
-
# and followed with an underscore.
|
64
|
-
#
|
65
|
-
# Tepee::Models::Page.table_name_prefix
|
66
|
-
# #=> "tepee_pages"
|
67
|
-
#
|
68
|
-
def Base.table_name_prefix
|
69
|
-
"#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'')
|
70
|
-
end
|
71
|
-
module_eval $AR_EXTRAS
|
72
|
-
end
|
73
|
-
end
|
74
|
-
Camping::S.sub! "autoload:Base,'camping/db'", ""
|
75
|
-
Camping::S.sub! "def Y;self;end", $AR_EXTRAS
|
76
|
-
Camping::Apps.each do |app|
|
77
|
-
app::Models.module_eval $AR_EXTRAS
|
78
|
-
end
|
@@ -1,244 +0,0 @@
|
|
1
|
-
# == About camping/fastcgi.rb
|
2
|
-
#
|
3
|
-
# Camping works very well with FastCGI, since your application is only loaded
|
4
|
-
# once -- when FastCGI starts. In addition, this class lets you mount several
|
5
|
-
# Camping apps under a single FastCGI process, to help save memory costs.
|
6
|
-
#
|
7
|
-
# So where do you use the Camping::FastCGI class? Use it in your application's
|
8
|
-
# postamble and then you can point your web server directly at your application.
|
9
|
-
# See Camping::FastCGI docs for more.
|
10
|
-
require 'camping'
|
11
|
-
require 'fcgi'
|
12
|
-
|
13
|
-
module Camping
|
14
|
-
# Camping::FastCGI is a small class for hooking one or more Camping apps up to
|
15
|
-
# FastCGI. Generally, you'll use this class in your application's postamble.
|
16
|
-
#
|
17
|
-
# == The Smallest Example
|
18
|
-
#
|
19
|
-
# if __FILE__ == $0
|
20
|
-
# require 'camping/fastcgi'
|
21
|
-
# Camping::FastCGI.start(YourApp)
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# This example is stripped down to the basics. The postamble has no database
|
25
|
-
# connection. It just loads this class and calls Camping::FastCGI.start.
|
26
|
-
#
|
27
|
-
# Now, in Lighttpd or Apache, you can point to your app's file, which will
|
28
|
-
# be executed, only to discover that your app now speaks the FastCGI protocol.
|
29
|
-
#
|
30
|
-
# Here's a sample lighttpd.conf (tested with Lighttpd 1.4.11) to serve as example:
|
31
|
-
#
|
32
|
-
# server.port = 3044
|
33
|
-
# server.bind = "127.0.0.1"
|
34
|
-
# server.modules = ( "mod_fastcgi" )
|
35
|
-
# server.document-root = "/var/www/camping/blog/"
|
36
|
-
# server.errorlog = "/var/www/camping/blog/error.log"
|
37
|
-
#
|
38
|
-
# #### fastcgi module
|
39
|
-
# fastcgi.server = ( "/" => (
|
40
|
-
# "localhost" => (
|
41
|
-
# "socket" => "/tmp/camping-blog.socket",
|
42
|
-
# "bin-path" => "/var/www/camping/blog/blog.rb",
|
43
|
-
# "check-local" => "disable",
|
44
|
-
# "max-procs" => 1 ) ) )
|
45
|
-
#
|
46
|
-
# The file <tt>/var/www/camping/blog/blog.rb</tt> is the Camping app with
|
47
|
-
# the postamble.
|
48
|
-
#
|
49
|
-
# == Mounting Many Apps
|
50
|
-
#
|
51
|
-
# require 'camping/fastcgi'
|
52
|
-
# fast = Camping::FastCGI.new
|
53
|
-
# fast.mount("/blog", Blog)
|
54
|
-
# fast.mount("/tepee", Tepee)
|
55
|
-
# fast.mount("/", Index)
|
56
|
-
# fast.start
|
57
|
-
#
|
58
|
-
class FastCGI
|
59
|
-
CHUNK_SIZE=(4 * 1024)
|
60
|
-
|
61
|
-
attr_reader :mounts
|
62
|
-
|
63
|
-
# Creates a Camping::FastCGI class with empty mounts.
|
64
|
-
def initialize
|
65
|
-
@mounts = {}
|
66
|
-
end
|
67
|
-
# Mounts a Camping application. The +dir+ being the name of the directory
|
68
|
-
# to serve as the application's root. The +app+ is a Camping class.
|
69
|
-
def mount(dir, app)
|
70
|
-
dir.gsub!(/\/{2,}/, '/')
|
71
|
-
dir.gsub!(/\/+$/, '')
|
72
|
-
@mounts[dir] = app
|
73
|
-
end
|
74
|
-
|
75
|
-
#
|
76
|
-
# Starts the FastCGI main loop.
|
77
|
-
def start(&blk)
|
78
|
-
FCGI.each do |req|
|
79
|
-
camp_do(req, &blk)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# A simple single-app starter mechanism
|
84
|
-
#
|
85
|
-
# Camping::FastCGI.start(Blog)
|
86
|
-
#
|
87
|
-
def self.start(app)
|
88
|
-
cf = Camping::FastCGI.new
|
89
|
-
cf.mount("/", app)
|
90
|
-
cf.start
|
91
|
-
end
|
92
|
-
|
93
|
-
# Serve an entire directory of Camping apps. (See
|
94
|
-
# http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.)
|
95
|
-
#
|
96
|
-
# Use this method inside your FastCGI dispatcher:
|
97
|
-
#
|
98
|
-
# #!/usr/local/bin/ruby
|
99
|
-
# require 'rubygems'
|
100
|
-
# require 'camping/fastcgi'
|
101
|
-
# Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => "/path/to/db"
|
102
|
-
# Camping::FastCGI.serve("/home/why/cvs/camping/examples")
|
103
|
-
#
|
104
|
-
def self.serve(path, index=nil)
|
105
|
-
require 'camping/reloader'
|
106
|
-
if File.directory? path
|
107
|
-
fast = Camping::FastCGI.new
|
108
|
-
script_load = proc do |script|
|
109
|
-
app = Camping::Reloader.new(script)
|
110
|
-
fast.mount("/#{app.mount}", app)
|
111
|
-
app
|
112
|
-
end
|
113
|
-
Dir[File.join(path, '*.rb')].each &script_load
|
114
|
-
fast.mount("/", index) if index
|
115
|
-
|
116
|
-
fast.start do |dir, app|
|
117
|
-
Dir[File.join(path, dir, '*.rb')].each do |script|
|
118
|
-
smount = "/" + File.basename(script, '.rb')
|
119
|
-
script_load[script] unless fast.mounts.has_key? smount
|
120
|
-
end
|
121
|
-
end
|
122
|
-
else
|
123
|
-
start(Camping::Reloader.new(path))
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def camp_do(req)
|
130
|
-
root, path, dir, app = "/"
|
131
|
-
if ENV['FORCE_ROOT'] and ENV['FORCE_ROOT'].to_i == 1
|
132
|
-
path = req.env['SCRIPT_NAME']
|
133
|
-
else
|
134
|
-
root = req.env['SCRIPT_NAME']
|
135
|
-
path = req.env['PATH_INFO']
|
136
|
-
end
|
137
|
-
|
138
|
-
dir, app = @mounts.max { |a,b| match(path, a[0]) <=> match(path, b[0]) }
|
139
|
-
unless dir and app
|
140
|
-
dir, app = '/', Camping
|
141
|
-
end
|
142
|
-
yield dir, app if block_given?
|
143
|
-
|
144
|
-
req.env['SERVER_SCRIPT_NAME'] = req.env['SCRIPT_NAME']
|
145
|
-
req.env['SERVER_PATH_INFO'] = req.env['PATH_INFO']
|
146
|
-
req.env['SCRIPT_NAME'] = File.join(root, dir)
|
147
|
-
req.env['PATH_INFO'] = path.gsub(/^#{dir}/, '')
|
148
|
-
|
149
|
-
controller = app.run(SeekStream.new(req.in), req.env)
|
150
|
-
sendfile = nil
|
151
|
-
headers = {}
|
152
|
-
controller.headers.each do |k, v|
|
153
|
-
if k =~ /^X-SENDFILE$/i and !ENV['SERVER_X_SENDFILE']
|
154
|
-
sendfile = v
|
155
|
-
else
|
156
|
-
headers[k] = v
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
body = controller.body
|
161
|
-
controller.body = ""
|
162
|
-
controller.headers = headers
|
163
|
-
|
164
|
-
req.out << controller.to_s
|
165
|
-
if sendfile
|
166
|
-
File.open(sendfile, "rb") do |f|
|
167
|
-
while chunk = f.read(CHUNK_SIZE) and chunk.length > 0
|
168
|
-
req.out << chunk
|
169
|
-
end
|
170
|
-
end
|
171
|
-
elsif body.respond_to? :read
|
172
|
-
while chunk = body.read(CHUNK_SIZE) and chunk.length > 0
|
173
|
-
req.out << chunk
|
174
|
-
end
|
175
|
-
body.close if body.respond_to? :close
|
176
|
-
else
|
177
|
-
req.out << body.to_s
|
178
|
-
end
|
179
|
-
rescue Exception => e
|
180
|
-
req.out << server_error(root, path, exc, req)
|
181
|
-
ensure
|
182
|
-
req.finish
|
183
|
-
end
|
184
|
-
|
185
|
-
def server_error(root, path, exc, req)
|
186
|
-
"Content-Type: text/html\r\n\r\n" +
|
187
|
-
"<h1>Camping Problem!</h1>" +
|
188
|
-
"<h2><strong>#{root}</strong>#{path}</h2>" +
|
189
|
-
"<h3>#{exc.class} #{esc exc.message}</h3>" +
|
190
|
-
"<ul>" + exc.backtrace.map { |bt| "<li>#{esc bt}</li>" }.join + "</ul>" +
|
191
|
-
"<hr /><p>#{req.env.inspect}</p>"
|
192
|
-
end
|
193
|
-
|
194
|
-
def match(path, mount)
|
195
|
-
m = path.match(/^#{Regexp::quote mount}(\/|$)/)
|
196
|
-
if m; m.end(0)
|
197
|
-
else -1
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def esc(str)
|
202
|
-
str.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
|
203
|
-
end
|
204
|
-
|
205
|
-
class SeekStream
|
206
|
-
def initialize(stream)
|
207
|
-
@last_read = ""
|
208
|
-
@stream = stream
|
209
|
-
@buffer = ""
|
210
|
-
end
|
211
|
-
def eof?
|
212
|
-
@buffer.empty? && @stream.eof?
|
213
|
-
end
|
214
|
-
def each
|
215
|
-
while true
|
216
|
-
pull(1024) until eof? or @buffer.index("\n")
|
217
|
-
return nil if eof?
|
218
|
-
yield @buffer.slice!(0..(@buffer.index("\n") || -1))
|
219
|
-
end
|
220
|
-
end
|
221
|
-
def pull(len)
|
222
|
-
@buffer += @stream.read(len).to_s
|
223
|
-
end
|
224
|
-
def read(len = 16384)
|
225
|
-
pull(len)
|
226
|
-
@last_read =
|
227
|
-
if eof?
|
228
|
-
nil
|
229
|
-
else
|
230
|
-
@buffer.slice!(0...len)
|
231
|
-
end
|
232
|
-
end
|
233
|
-
def seek(len, typ)
|
234
|
-
raise NotImplementedError, "only IO::SEEK_CUR is supported with SeekStream" if typ != IO::SEEK_CUR
|
235
|
-
raise NotImplementedError, "only rewinding is supported with SeekStream" if len > 0
|
236
|
-
raise NotImplementedError, "rewinding #{-len} past the buffer #{@last_read.size} start not supported with SeekStream" if -len > @last_read.size
|
237
|
-
@buffer = @last_read[len..-1] + @buffer
|
238
|
-
@last_read = ""
|
239
|
-
self
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
end
|
244
|
-
end
|
@@ -1,163 +0,0 @@
|
|
1
|
-
module Camping
|
2
|
-
# == The Camping Reloader
|
3
|
-
#
|
4
|
-
# Camping apps are generally small and predictable. Many Camping apps are
|
5
|
-
# contained within a single file. Larger apps are split into a handful of
|
6
|
-
# other Ruby libraries within the same directory.
|
7
|
-
#
|
8
|
-
# Since Camping apps (and their dependencies) are loaded with Ruby's require
|
9
|
-
# method, there is a record of them in $LOADED_FEATURES. Which leaves a
|
10
|
-
# perfect space for this class to manage auto-reloading an app if any of its
|
11
|
-
# immediate dependencies changes.
|
12
|
-
#
|
13
|
-
# == Wrapping Your Apps
|
14
|
-
#
|
15
|
-
# Since bin/camping and the Camping::FastCGI class already use the Reloader,
|
16
|
-
# you probably don't need to hack it on your own. But, if you're rolling your
|
17
|
-
# own situation, here's how.
|
18
|
-
#
|
19
|
-
# Rather than this:
|
20
|
-
#
|
21
|
-
# require 'yourapp'
|
22
|
-
#
|
23
|
-
# Use this:
|
24
|
-
#
|
25
|
-
# require 'camping/reloader'
|
26
|
-
# Camping::Reloader.new('/path/to/yourapp.rb')
|
27
|
-
#
|
28
|
-
# The reloader will take care of requiring the app and monitoring all files
|
29
|
-
# for alterations.
|
30
|
-
class Reloader
|
31
|
-
attr_accessor :klass, :mtime, :mount, :requires
|
32
|
-
|
33
|
-
# Creates the reloader, assigns a +script+ to it and initially loads the
|
34
|
-
# application. Pass in the full path to the script, otherwise the script
|
35
|
-
# will be loaded relative to the current working directory.
|
36
|
-
def initialize(script)
|
37
|
-
@script = File.expand_path(script)
|
38
|
-
@mount = File.basename(script, '.rb')
|
39
|
-
@requires = nil
|
40
|
-
load_app
|
41
|
-
end
|
42
|
-
|
43
|
-
# Find the application, based on the script name.
|
44
|
-
def find_app(title)
|
45
|
-
@klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil
|
46
|
-
end
|
47
|
-
|
48
|
-
# If the file isn't found, if we need to remove the app from the global
|
49
|
-
# namespace, this will be sure to do so and set @klass to nil.
|
50
|
-
def remove_app
|
51
|
-
Object.send :remove_const, @klass.name if @klass
|
52
|
-
@klass = nil
|
53
|
-
end
|
54
|
-
|
55
|
-
# Loads (or reloads) the application. The reloader will take care of calling
|
56
|
-
# this for you. You can certainly call it yourself if you feel it's warranted.
|
57
|
-
def load_app
|
58
|
-
title = File.basename(@script)[/^([\w_]+)/,1].gsub /_/,''
|
59
|
-
begin
|
60
|
-
all_requires = $LOADED_FEATURES.dup
|
61
|
-
load @script
|
62
|
-
@requires = ($LOADED_FEATURES - all_requires).select do |req|
|
63
|
-
req.index(File.basename(@script) + "/") == 0 || req.index(title + "/") == 0
|
64
|
-
end
|
65
|
-
rescue Exception => e
|
66
|
-
puts "!! trouble loading #{title}: [#{e.class}] #{e.message}"
|
67
|
-
puts e.backtrace.join("\n")
|
68
|
-
find_app title
|
69
|
-
remove_app
|
70
|
-
return
|
71
|
-
end
|
72
|
-
|
73
|
-
@mtime = mtime
|
74
|
-
find_app title
|
75
|
-
unless @klass and @klass.const_defined? :C
|
76
|
-
puts "!! trouble loading #{title}: not a Camping app, no #{title.capitalize} module found"
|
77
|
-
remove_app
|
78
|
-
return
|
79
|
-
end
|
80
|
-
|
81
|
-
Reloader.conditional_connect
|
82
|
-
@klass.create if @klass.respond_to? :create
|
83
|
-
@klass
|
84
|
-
end
|
85
|
-
|
86
|
-
# The timestamp of the most recently modified app dependency.
|
87
|
-
def mtime
|
88
|
-
((@requires || []) + [@script]).map do |fname|
|
89
|
-
fname = fname.gsub(/^#{Regexp::quote File.dirname(@script)}\//, '')
|
90
|
-
begin
|
91
|
-
File.mtime(File.join(File.dirname(@script), fname))
|
92
|
-
rescue Errno::ENOENT
|
93
|
-
remove_app
|
94
|
-
@mtime
|
95
|
-
end
|
96
|
-
end.max
|
97
|
-
end
|
98
|
-
|
99
|
-
# Conditional reloading of the app. This gets called on each request and
|
100
|
-
# only reloads if the modification times on any of the files is updated.
|
101
|
-
def reload_app
|
102
|
-
return if @klass and @mtime and mtime <= @mtime
|
103
|
-
|
104
|
-
if @requires
|
105
|
-
@requires.each { |req| $LOADED_FEATURES.delete(req) }
|
106
|
-
end
|
107
|
-
k = @klass
|
108
|
-
Object.send :remove_const, k.name if k
|
109
|
-
load_app
|
110
|
-
end
|
111
|
-
|
112
|
-
# Conditionally reloads (using reload_app.) Then passes the request through
|
113
|
-
# to the wrapped Camping app.
|
114
|
-
def run(*a)
|
115
|
-
reload_app
|
116
|
-
if @klass
|
117
|
-
@klass.run(*a)
|
118
|
-
else
|
119
|
-
Camping.run(*a)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
# Returns source code for the main script in the application.
|
124
|
-
def view_source
|
125
|
-
File.read(@script)
|
126
|
-
end
|
127
|
-
|
128
|
-
class << self
|
129
|
-
def database=(db)
|
130
|
-
@database = db
|
131
|
-
end
|
132
|
-
def log=(log)
|
133
|
-
@log = log
|
134
|
-
end
|
135
|
-
def conditional_connect
|
136
|
-
# If database models are present, `autoload?` will return nil.
|
137
|
-
unless Camping::Models.autoload? :Base
|
138
|
-
require 'logger'
|
139
|
-
require 'camping/session'
|
140
|
-
Camping::Models::Base.establish_connection @database if @database
|
141
|
-
|
142
|
-
case @log
|
143
|
-
when Logger
|
144
|
-
Camping::Models::Base.logger = @log
|
145
|
-
when String
|
146
|
-
Camping::Models::Base.logger = Logger.new(@log == "-" ? STDOUT : @log)
|
147
|
-
end
|
148
|
-
|
149
|
-
Camping::Models::Session.create_schema
|
150
|
-
|
151
|
-
if @database and @database[:adapter] == 'sqlite3'
|
152
|
-
begin
|
153
|
-
require 'sqlite3_api'
|
154
|
-
rescue LoadError
|
155
|
-
puts "!! Your SQLite3 adapter isn't a compiled extension."
|
156
|
-
abort "!! Please check out http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for tips."
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|