rubycas-server 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
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
@@ -0,0 +1,123 @@
1
+ # == About camping/session.rb
2
+ #
3
+ # This file contains two modules which supply basic sessioning to your Camping app.
4
+ # Again, we're dealing with a pretty little bit of code: approx. 60 lines.
5
+ #
6
+ # * Camping::Models::Session is a module which adds a single <tt>sessions</tt> table
7
+ # to your database.
8
+ # * Camping::Session is a module which you will mix into your application (or into
9
+ # specific controllers which require sessions) to supply a <tt>@state</tt> variable
10
+ # you can use in controllers and views.
11
+ #
12
+ # For a basic tutorial, see the *Getting Started* section of the Camping::Session module.
13
+ require 'camping'
14
+
15
+ module Camping::Models
16
+ # A database table for storing Camping sessions. Contains a unique 32-character hashid, a
17
+ # creation timestamp, and a column of serialized data called <tt>ivars</tt>.
18
+ class Session < Base
19
+ serialize :ivars
20
+ def []=(k, v) # :nodoc:
21
+ self.ivars[k] = v
22
+ end
23
+ def [](k) # :nodoc:
24
+ self.ivars[k] rescue nil
25
+ end
26
+
27
+ RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
28
+
29
+ # Generates a new session ID and creates a row for the new session in the database.
30
+ def self.generate cookies
31
+ rand_max = RAND_CHARS.size
32
+ sid = (0...32).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }
33
+ sess = Session.create :hashid => sid, :ivars => Camping::H[]
34
+ cookies.camping_sid = sess.hashid
35
+ sess
36
+ end
37
+
38
+ # Gets the existing session based on the <tt>camping_sid</tt> available in cookies.
39
+ # If none is found, generates a new session.
40
+ def self.persist cookies
41
+ if cookies.camping_sid
42
+ session = Camping::Models::Session.find_by_hashid cookies.camping_sid
43
+ end
44
+ unless session
45
+ session = Camping::Models::Session.generate cookies
46
+ end
47
+ session
48
+ end
49
+
50
+ # Builds the session table in the database. To be used in your application's
51
+ # <tt>create</tt> method.
52
+ #
53
+ # Like so:
54
+ #
55
+ # def Blog.create
56
+ # Camping::Models::Session.create_schema
57
+ # unless Blog::Models::Post.table_exists?
58
+ # ActiveRecord::Schema.define(&Blog::Models.schema)
59
+ # end
60
+ # end
61
+ #
62
+ def self.create_schema
63
+ unless table_exists?
64
+ ActiveRecord::Schema.define do
65
+ create_table :sessions, :force => true do |t|
66
+ t.column :id, :integer, :null => false
67
+ t.column :hashid, :string, :limit => 32
68
+ t.column :created_at, :datetime
69
+ t.column :ivars, :text
70
+ end
71
+ end
72
+ reset_column_information
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ module Camping
79
+ # The Camping::Session module is designed to be mixed into your application or into specific
80
+ # controllers which require sessions. This module defines a <tt>service</tt> method which
81
+ # intercepts all requests handed to those controllers.
82
+ #
83
+ # == Getting Started
84
+ #
85
+ # To get sessions working for your application:
86
+ #
87
+ # 1. <tt>require 'camping/session'</tt>
88
+ # 2. Mixin the module: <tt>module YourApp; include Camping::Session end</tt>
89
+ # 3. In your application's <tt>create</tt> method, add a call to <tt>Camping::Models::Session.create_schema</tt>
90
+ # 4. Throughout your application, use the <tt>@state</tt> var like a hash to store your application's data.
91
+ #
92
+ # If you are unfamiliar with the <tt>create</tt> method, see
93
+ # http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
94
+ #
95
+ # == A Few Notes
96
+ #
97
+ # * The session ID is stored in a cookie. Look in <tt>@cookies.camping_sid</tt>.
98
+ # * The session data is stored in the <tt>sessions</tt> table in your database.
99
+ # * All mounted Camping apps using this class will use the same database table.
100
+ # * However, your application's data is stored in its own hash.
101
+ # * Session data is only saved if it has changed.
102
+ module Session
103
+ # This <tt>service</tt> method, when mixed into controllers, intercepts requests
104
+ # and wraps them with code to start and close the session. If a session isn't found
105
+ # in the database it is created. The <tt>@state</tt> variable is set and if it changes,
106
+ # it is saved back into the database.
107
+ def service(*a)
108
+ session = Camping::Models::Session.persist @cookies
109
+ app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
110
+ @state = (session[app] ||= Camping::H[])
111
+ hash_before = Marshal.dump(@state).hash
112
+ s = super(*a)
113
+ if session
114
+ hash_after = Marshal.dump(@state).hash
115
+ unless hash_before == hash_after
116
+ session[app] = @state
117
+ session.save
118
+ end
119
+ end
120
+ s
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,65 @@
1
+ # == About camping/webrick.rb
2
+ #
3
+ # For many who have Ruby installed, Camping and WEBrick is a great option.
4
+ # It's definitely the easiest configuration, however some performance is sacrificed.
5
+ # For better speed, check out Mongrel at http://mongrel.rubyforge.org/, which comes
6
+ # with Camping hooks and is supported by the Camping Tool.
7
+ require 'camping'
8
+ require 'webrick/httpservlet/abstract.rb'
9
+
10
+ module WEBrick
11
+ # WEBrick::CampingHandler is a very simple handle for hosting Camping apps in
12
+ # a WEBrick server. It's used much like any other WEBrick handler.
13
+ #
14
+ # == Mounting a Camping App
15
+ #
16
+ # Assuming Camping.goes(:Blog), the Blog application can be mounted alongside
17
+ # other WEBrick mounts.
18
+ #
19
+ # s = WEBrick::HTTPServer.new(:BindAddress => host, :Port => port)
20
+ # s.mount "/blog", WEBrick::CampingHandler, Blog
21
+ # s.mount_proc("/") { ... }
22
+ #
23
+ # == How Does it Compare?
24
+ #
25
+ # Compared to other handlers, WEBrick is well-equipped in terms of features.
26
+ #
27
+ # * The <tt>X-Sendfile</tt> header is supported, along with etags and
28
+ # modification time headers for the file served. Since this handler
29
+ # is a subclass of WEBrick::HTTPServlet::DefaultFileHandler, all of its
30
+ # logic is used.
31
+ # * IO is streaming up and down. When you upload a file, it is streamed to
32
+ # the server's filesystem. When you download a file, it is streamed to
33
+ # your browser.
34
+ #
35
+ # While WEBrick is a bit slower than Mongrel and FastCGI options, it's
36
+ # a decent choice, for sure!
37
+ class CampingHandler < WEBrick::HTTPServlet::DefaultFileHandler
38
+ # Creates a CampingHandler, which answers for the application within +klass+.
39
+ def initialize(server, klass)
40
+ super(server, klass)
41
+ @klass = klass
42
+ end
43
+ # Handler for WEBrick requests (also aliased as do_POST).
44
+ def service(req, resp)
45
+ controller = @klass.run((req.body and StringIO.new(req.body)), req.meta_vars)
46
+ resp.status = controller.status
47
+ @local_path = nil
48
+ controller.headers.each do |k, v|
49
+ if k =~ /^X-SENDFILE$/i
50
+ @local_path = v
51
+ else
52
+ [*v].each do |vi|
53
+ resp[k] = vi
54
+ end
55
+ end
56
+ end
57
+
58
+ if @local_path
59
+ do_GET(req, resp)
60
+ else
61
+ resp.body = controller.body.to_s
62
+ end
63
+ end
64
+ end
65
+ end