egalite 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/.gitignore +17 -0
- data/README.md +91 -0
- data/auth/basic.rb +32 -0
- data/blank.rb +53 -0
- data/egalite.rb +742 -0
- data/errorconsole.rb +77 -0
- data/examples/simple/example.rb +39 -0
- data/examples/simple/pages/test.html +15 -0
- data/examples/simple_db/example_db.rb +103 -0
- data/examples/simple_db/pages/edit.html +6 -0
- data/helper.rb +251 -0
- data/keitai/keitai.rb +107 -0
- data/keitai/ketai.rb +11 -0
- data/keitai/rack/ketai/carrier/abstract.rb +131 -0
- data/keitai/rack/ketai/carrier/au.rb +78 -0
- data/keitai/rack/ketai/carrier/docomo.rb +80 -0
- data/keitai/rack/ketai/carrier/emoji/ausjisstrtoemojiid.rb +1391 -0
- data/keitai/rack/ketai/carrier/emoji/docomosjisstrtoemojiid.rb +759 -0
- data/keitai/rack/ketai/carrier/emoji/emojidata.rb +836 -0
- data/keitai/rack/ketai/carrier/emoji/softbankutf8strtoemojiid.rb +1119 -0
- data/keitai/rack/ketai/carrier/emoji/softbankwebcodetoutf8str.rb +499 -0
- data/keitai/rack/ketai/carrier/iphone.rb +8 -0
- data/keitai/rack/ketai/carrier/softbank.rb +82 -0
- data/keitai/rack/ketai/carrier.rb +17 -0
- data/keitai/rack/ketai/middleware.rb +24 -0
- data/m17n.rb +193 -0
- data/rack/auth/abstract/handler.rb +37 -0
- data/rack/auth/abstract/request.rb +37 -0
- data/rack/auth/basic.rb +58 -0
- data/rack/auth/digest/md5.rb +124 -0
- data/rack/auth/digest/nonce.rb +51 -0
- data/rack/auth/digest/params.rb +55 -0
- data/rack/auth/digest/request.rb +40 -0
- data/rack/builder.rb +80 -0
- data/rack/cascade.rb +41 -0
- data/rack/chunked.rb +49 -0
- data/rack/commonlogger.rb +49 -0
- data/rack/conditionalget.rb +47 -0
- data/rack/config.rb +15 -0
- data/rack/content_length.rb +29 -0
- data/rack/content_type.rb +23 -0
- data/rack/deflater.rb +96 -0
- data/rack/directory.rb +157 -0
- data/rack/etag.rb +32 -0
- data/rack/file.rb +92 -0
- data/rack/handler/cgi.rb +62 -0
- data/rack/handler/evented_mongrel.rb +8 -0
- data/rack/handler/fastcgi.rb +89 -0
- data/rack/handler/lsws.rb +63 -0
- data/rack/handler/mongrel.rb +90 -0
- data/rack/handler/scgi.rb +59 -0
- data/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/rack/handler/thin.rb +18 -0
- data/rack/handler/webrick.rb +73 -0
- data/rack/handler.rb +88 -0
- data/rack/head.rb +19 -0
- data/rack/lint.rb +567 -0
- data/rack/lobster.rb +65 -0
- data/rack/lock.rb +16 -0
- data/rack/logger.rb +20 -0
- data/rack/methodoverride.rb +27 -0
- data/rack/mime.rb +208 -0
- data/rack/mock.rb +190 -0
- data/rack/nulllogger.rb +18 -0
- data/rack/recursive.rb +61 -0
- data/rack/reloader.rb +109 -0
- data/rack/request.rb +273 -0
- data/rack/response.rb +150 -0
- data/rack/rewindable_input.rb +103 -0
- data/rack/runtime.rb +27 -0
- data/rack/sendfile.rb +144 -0
- data/rack/server.rb +271 -0
- data/rack/session/abstract/id.rb +140 -0
- data/rack/session/cookie.rb +90 -0
- data/rack/session/memcache.rb +119 -0
- data/rack/session/pool.rb +100 -0
- data/rack/showexceptions.rb +349 -0
- data/rack/showstatus.rb +106 -0
- data/rack/static.rb +38 -0
- data/rack/urlmap.rb +55 -0
- data/rack/utils.rb +662 -0
- data/rack.rb +81 -0
- data/route.rb +231 -0
- data/sendmail.rb +222 -0
- data/sequel_helper.rb +20 -0
- data/session.rb +132 -0
- data/stringify_hash.rb +63 -0
- data/support.rb +35 -0
- data/template.rb +287 -0
- data/test/french.html +13 -0
- data/test/french_msg.html +3 -0
- data/test/m17n.txt +30 -0
- data/test/mobile.html +15 -0
- data/test/setup.rb +8 -0
- data/test/static/test.txt +1 -0
- data/test/template.html +58 -0
- data/test/template_inner.html +1 -0
- data/test/template_innerparam.html +1 -0
- data/test/test_auth.rb +43 -0
- data/test/test_blank.rb +44 -0
- data/test/test_csrf.rb +87 -0
- data/test/test_errorconsole.rb +91 -0
- data/test/test_handler.rb +155 -0
- data/test/test_helper.rb +296 -0
- data/test/test_keitai.rb +107 -0
- data/test/test_m17n.rb +129 -0
- data/test/test_route.rb +192 -0
- data/test/test_sendmail.rb +146 -0
- data/test/test_session.rb +83 -0
- data/test/test_stringify_hash.rb +67 -0
- data/test/test_template.rb +114 -0
- data/test.bat +2 -0
- metadata +240 -0
data/rack/server.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Server
|
5
|
+
class Options
|
6
|
+
def parse!(args)
|
7
|
+
options = {}
|
8
|
+
opt_parser = OptionParser.new("", 24, ' ') do |opts|
|
9
|
+
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
|
10
|
+
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Ruby options:"
|
13
|
+
|
14
|
+
lineno = 1
|
15
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
16
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
17
|
+
lineno += 1
|
18
|
+
}
|
19
|
+
|
20
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
21
|
+
options[:debug] = true
|
22
|
+
}
|
23
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
24
|
+
options[:warn] = true
|
25
|
+
}
|
26
|
+
|
27
|
+
opts.on("-I", "--include PATH",
|
28
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
29
|
+
options[:include] = path.split(":")
|
30
|
+
}
|
31
|
+
|
32
|
+
opts.on("-r", "--require LIBRARY",
|
33
|
+
"require the library, before executing your script") { |library|
|
34
|
+
options[:require] = library
|
35
|
+
}
|
36
|
+
|
37
|
+
opts.separator ""
|
38
|
+
opts.separator "Rack options:"
|
39
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
|
40
|
+
options[:server] = s
|
41
|
+
}
|
42
|
+
|
43
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
|
44
|
+
options[:Host] = host
|
45
|
+
}
|
46
|
+
|
47
|
+
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
48
|
+
options[:Port] = port
|
49
|
+
}
|
50
|
+
|
51
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
52
|
+
options[:environment] = e
|
53
|
+
}
|
54
|
+
|
55
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
56
|
+
options[:daemonize] = d ? true : false
|
57
|
+
}
|
58
|
+
|
59
|
+
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
|
60
|
+
options[:pid] = f
|
61
|
+
}
|
62
|
+
|
63
|
+
opts.separator ""
|
64
|
+
opts.separator "Common options:"
|
65
|
+
|
66
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
67
|
+
puts opts
|
68
|
+
exit
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on_tail("--version", "Show version") do
|
72
|
+
puts "Rack #{Rack.version}"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
end
|
76
|
+
opt_parser.parse! args
|
77
|
+
options[:config] = args.last if args.last
|
78
|
+
options
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Start a new rack server (like running rackup). This will parse ARGV and
|
83
|
+
# provide standard ARGV rackup options, defaulting to load 'config.ru'.
|
84
|
+
#
|
85
|
+
# Providing an options hash will prevent ARGV parsing and will not include
|
86
|
+
# any default options.
|
87
|
+
#
|
88
|
+
# This method can be used to very easily launch a CGI application, for
|
89
|
+
# example:
|
90
|
+
#
|
91
|
+
# Rack::Server.start(
|
92
|
+
# :app => lambda do |e|
|
93
|
+
# [200, {'Content-Type' => 'text/html'}, ['hello world']]
|
94
|
+
# end,
|
95
|
+
# :server => 'cgi'
|
96
|
+
# )
|
97
|
+
#
|
98
|
+
# Further options available here are documented on Rack::Server#initialize
|
99
|
+
def self.start(options = nil)
|
100
|
+
new(options).start
|
101
|
+
end
|
102
|
+
|
103
|
+
attr_writer :options
|
104
|
+
|
105
|
+
# Options may include:
|
106
|
+
# * :app
|
107
|
+
# a rack application to run (overrides :config)
|
108
|
+
# * :config
|
109
|
+
# a rackup configuration file path to load (.ru)
|
110
|
+
# * :environment
|
111
|
+
# this selects the middleware that will be wrapped around
|
112
|
+
# your application. Default options available are:
|
113
|
+
# - development: CommonLogger, ShowExceptions, and Lint
|
114
|
+
# - deployment: CommonLogger
|
115
|
+
# - none: no extra middleware
|
116
|
+
# note: when the server is a cgi server, CommonLogger is not included.
|
117
|
+
# * :server
|
118
|
+
# choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
|
119
|
+
# * :daemonize
|
120
|
+
# if true, the server will daemonize itself (fork, detach, etc)
|
121
|
+
# * :pid
|
122
|
+
# path to write a pid file after daemonize
|
123
|
+
# * :Host
|
124
|
+
# the host address to bind to (used by supporting Rack::Handler)
|
125
|
+
# * :Port
|
126
|
+
# the port to bind to (used by supporting Rack::Handler)
|
127
|
+
# * :AccessLog
|
128
|
+
# webrick acess log options (or supporting Rack::Handler)
|
129
|
+
# * :debug
|
130
|
+
# turn on debug output ($DEBUG = true)
|
131
|
+
# * :warn
|
132
|
+
# turn on warnings ($-w = true)
|
133
|
+
# * :include
|
134
|
+
# add given paths to $LOAD_PATH
|
135
|
+
# * :require
|
136
|
+
# require the given libraries
|
137
|
+
def initialize(options = nil)
|
138
|
+
@options = options
|
139
|
+
end
|
140
|
+
|
141
|
+
def options
|
142
|
+
@options ||= parse_options(ARGV)
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_options
|
146
|
+
{
|
147
|
+
:environment => "development",
|
148
|
+
:pid => nil,
|
149
|
+
:Port => 9292,
|
150
|
+
:Host => "0.0.0.0",
|
151
|
+
:AccessLog => [],
|
152
|
+
:config => "config.ru"
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def app
|
157
|
+
@app ||= begin
|
158
|
+
if !::File.exist? options[:config]
|
159
|
+
abort "configuration #{options[:config]} not found"
|
160
|
+
end
|
161
|
+
|
162
|
+
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
|
163
|
+
self.options.merge! options
|
164
|
+
app
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.middleware
|
169
|
+
@middleware ||= begin
|
170
|
+
m = Hash.new {|h,k| h[k] = []}
|
171
|
+
m["deployment"].concat [lambda {|server| server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
|
172
|
+
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
|
173
|
+
m
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def middleware
|
178
|
+
self.class.middleware
|
179
|
+
end
|
180
|
+
|
181
|
+
def start
|
182
|
+
if options[:debug]
|
183
|
+
$DEBUG = true
|
184
|
+
require 'pp'
|
185
|
+
p options[:server]
|
186
|
+
pp wrapped_app
|
187
|
+
pp app
|
188
|
+
end
|
189
|
+
|
190
|
+
if options[:warn]
|
191
|
+
$-w = true
|
192
|
+
end
|
193
|
+
|
194
|
+
if includes = options[:include]
|
195
|
+
$LOAD_PATH.unshift(*includes)
|
196
|
+
end
|
197
|
+
|
198
|
+
if library = options[:require]
|
199
|
+
require library
|
200
|
+
end
|
201
|
+
|
202
|
+
daemonize_app if options[:daemonize]
|
203
|
+
write_pid if options[:pid]
|
204
|
+
|
205
|
+
trap(:INT) do
|
206
|
+
if server.respond_to?(:shutdown)
|
207
|
+
server.shutdown
|
208
|
+
else
|
209
|
+
exit
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
server.run wrapped_app, options
|
214
|
+
end
|
215
|
+
|
216
|
+
def server
|
217
|
+
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
def parse_options(args)
|
222
|
+
options = default_options
|
223
|
+
|
224
|
+
# Don't evaluate CGI ISINDEX parameters.
|
225
|
+
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
|
226
|
+
args.clear if ENV.include?("REQUEST_METHOD")
|
227
|
+
|
228
|
+
options.merge! opt_parser.parse! args
|
229
|
+
ENV["RACK_ENV"] = options[:environment]
|
230
|
+
options
|
231
|
+
end
|
232
|
+
|
233
|
+
def opt_parser
|
234
|
+
Options.new
|
235
|
+
end
|
236
|
+
|
237
|
+
def build_app(app)
|
238
|
+
middleware[options[:environment]].reverse_each do |middleware|
|
239
|
+
middleware = middleware.call(self) if middleware.respond_to?(:call)
|
240
|
+
next unless middleware
|
241
|
+
klass = middleware.shift
|
242
|
+
app = klass.new(app, *middleware)
|
243
|
+
end
|
244
|
+
app
|
245
|
+
end
|
246
|
+
|
247
|
+
def wrapped_app
|
248
|
+
@wrapped_app ||= build_app app
|
249
|
+
end
|
250
|
+
|
251
|
+
def daemonize_app
|
252
|
+
if RUBY_VERSION < "1.9"
|
253
|
+
exit if fork
|
254
|
+
Process.setsid
|
255
|
+
exit if fork
|
256
|
+
Dir.chdir "/"
|
257
|
+
::File.umask 0000
|
258
|
+
STDIN.reopen "/dev/null"
|
259
|
+
STDOUT.reopen "/dev/null", "a"
|
260
|
+
STDERR.reopen "/dev/null", "a"
|
261
|
+
else
|
262
|
+
Process.daemon
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def write_pid
|
267
|
+
::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
|
268
|
+
at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
2
|
+
# bugrep: Andreas Zehnder
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
require 'rack/request'
|
6
|
+
require 'rack/response'
|
7
|
+
|
8
|
+
module Rack
|
9
|
+
|
10
|
+
module Session
|
11
|
+
|
12
|
+
module Abstract
|
13
|
+
|
14
|
+
# ID sets up a basic framework for implementing an id based sessioning
|
15
|
+
# service. Cookies sent to the client for maintaining sessions will only
|
16
|
+
# contain an id reference. Only #get_session and #set_session are
|
17
|
+
# required to be overwritten.
|
18
|
+
#
|
19
|
+
# All parameters are optional.
|
20
|
+
# * :key determines the name of the cookie, by default it is
|
21
|
+
# 'rack.session'
|
22
|
+
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
23
|
+
# cookie options as by Rack::Response#add_cookie
|
24
|
+
# * :defer will not set a cookie in the response.
|
25
|
+
# * :renew (implementation dependent) will prompt the generation of a new
|
26
|
+
# session id, and migration of data to be referenced at the new id. If
|
27
|
+
# :defer is set, it will be overridden and the cookie will be set.
|
28
|
+
# * :sidbits sets the number of bits in length that a generated session
|
29
|
+
# id will be.
|
30
|
+
#
|
31
|
+
# These options can be set on a per request basis, at the location of
|
32
|
+
# env['rack.session.options']. Additionally the id of the session can be
|
33
|
+
# found within the options hash at the key :id. It is highly not
|
34
|
+
# recommended to change its value.
|
35
|
+
#
|
36
|
+
# Is Rack::Utils::Context compatible.
|
37
|
+
|
38
|
+
class ID
|
39
|
+
DEFAULT_OPTIONS = {
|
40
|
+
:path => '/',
|
41
|
+
:domain => nil,
|
42
|
+
:expire_after => nil,
|
43
|
+
:secure => false,
|
44
|
+
:httponly => true,
|
45
|
+
:defer => false,
|
46
|
+
:renew => false,
|
47
|
+
:sidbits => 128
|
48
|
+
}
|
49
|
+
|
50
|
+
attr_reader :key, :default_options
|
51
|
+
def initialize(app, options={})
|
52
|
+
@app = app
|
53
|
+
@key = options[:key] || "rack.session"
|
54
|
+
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
context(env)
|
59
|
+
end
|
60
|
+
|
61
|
+
def context(env, app=@app)
|
62
|
+
load_session(env)
|
63
|
+
status, headers, body = app.call(env)
|
64
|
+
commit_session(env, status, headers, body)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Generate a new session id using Ruby #rand. The size of the
|
70
|
+
# session id is controlled by the :sidbits option.
|
71
|
+
# Monkey patch this to use custom methods for session id generation.
|
72
|
+
|
73
|
+
def generate_sid
|
74
|
+
"%0#{@default_options[:sidbits] / 4}x" %
|
75
|
+
rand(2**@default_options[:sidbits] - 1)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extracts the session id from provided cookies and passes it and the
|
79
|
+
# environment to #get_session. It then sets the resulting session into
|
80
|
+
# 'rack.session', and places options and session metadata into
|
81
|
+
# 'rack.session.options'.
|
82
|
+
|
83
|
+
def load_session(env)
|
84
|
+
request = Rack::Request.new(env)
|
85
|
+
session_id = request.cookies[@key]
|
86
|
+
|
87
|
+
begin
|
88
|
+
session_id, session = get_session(env, session_id)
|
89
|
+
env['rack.session'] = session
|
90
|
+
rescue
|
91
|
+
env['rack.session'] = Hash.new
|
92
|
+
end
|
93
|
+
|
94
|
+
env['rack.session.options'] = @default_options.
|
95
|
+
merge(:id => session_id)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Acquires the session from the environment and the session id from
|
99
|
+
# the session options and passes them to #set_session. If successful
|
100
|
+
# and the :defer option is not true, a cookie will be added to the
|
101
|
+
# response with the session's id.
|
102
|
+
|
103
|
+
def commit_session(env, status, headers, body)
|
104
|
+
session = env['rack.session']
|
105
|
+
options = env['rack.session.options']
|
106
|
+
session_id = options[:id]
|
107
|
+
|
108
|
+
if not session_id = set_session(env, session_id, session, options)
|
109
|
+
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
110
|
+
elsif options[:defer] and not options[:renew]
|
111
|
+
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
|
112
|
+
else
|
113
|
+
cookie = Hash.new
|
114
|
+
cookie[:value] = session_id
|
115
|
+
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
|
116
|
+
Utils.set_cookie_header!(headers, @key, cookie.merge(options))
|
117
|
+
end
|
118
|
+
|
119
|
+
[status, headers, body]
|
120
|
+
end
|
121
|
+
|
122
|
+
# All thread safety and session retrival proceedures should occur here.
|
123
|
+
# Should return [session_id, session].
|
124
|
+
# If nil is provided as the session id, generation of a new valid id
|
125
|
+
# should occur within.
|
126
|
+
|
127
|
+
def get_session(env, sid)
|
128
|
+
raise '#get_session not implemented.'
|
129
|
+
end
|
130
|
+
|
131
|
+
# All thread safety and session storage proceedures should occur here.
|
132
|
+
# Should return true or false dependant on whether or not the session
|
133
|
+
# was saved or not.
|
134
|
+
def set_session(env, sid, session, options)
|
135
|
+
raise '#set_session not implemented.'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/response'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
|
7
|
+
module Session
|
8
|
+
|
9
|
+
# Rack::Session::Cookie provides simple cookie based session management.
|
10
|
+
# The session is a Ruby Hash stored as base64 encoded marshalled data
|
11
|
+
# set to :key (default: rack.session).
|
12
|
+
# When the secret key is set, cookie data is checked for data integrity.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# use Rack::Session::Cookie, :key => 'rack.session',
|
17
|
+
# :domain => 'foo.com',
|
18
|
+
# :path => '/',
|
19
|
+
# :expire_after => 2592000,
|
20
|
+
# :secret => 'change_me'
|
21
|
+
#
|
22
|
+
# All parameters are optional.
|
23
|
+
|
24
|
+
class Cookie
|
25
|
+
|
26
|
+
def initialize(app, options={})
|
27
|
+
@app = app
|
28
|
+
@key = options[:key] || "rack.session"
|
29
|
+
@secret = options[:secret]
|
30
|
+
@default_options = {:domain => nil,
|
31
|
+
:path => "/",
|
32
|
+
:expire_after => nil}.merge(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
load_session(env)
|
37
|
+
status, headers, body = @app.call(env)
|
38
|
+
commit_session(env, status, headers, body)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def load_session(env)
|
44
|
+
request = Rack::Request.new(env)
|
45
|
+
session_data = request.cookies[@key]
|
46
|
+
|
47
|
+
if @secret && session_data
|
48
|
+
session_data, digest = session_data.split("--")
|
49
|
+
session_data = nil unless digest == generate_hmac(session_data)
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
session_data = session_data.unpack("m*").first
|
54
|
+
session_data = Marshal.load(session_data)
|
55
|
+
env["rack.session"] = session_data
|
56
|
+
rescue
|
57
|
+
env["rack.session"] = Hash.new
|
58
|
+
end
|
59
|
+
|
60
|
+
env["rack.session.options"] = @default_options.dup
|
61
|
+
end
|
62
|
+
|
63
|
+
def commit_session(env, status, headers, body)
|
64
|
+
session_data = Marshal.dump(env["rack.session"])
|
65
|
+
session_data = [session_data].pack("m*")
|
66
|
+
|
67
|
+
if @secret
|
68
|
+
session_data = "#{session_data}--#{generate_hmac(session_data)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
if session_data.size > (4096 - @key.size)
|
72
|
+
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
|
73
|
+
else
|
74
|
+
options = env["rack.session.options"]
|
75
|
+
cookie = Hash.new
|
76
|
+
cookie[:value] = session_data
|
77
|
+
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
|
78
|
+
Utils.set_cookie_header!(headers, @key, cookie.merge(options))
|
79
|
+
end
|
80
|
+
|
81
|
+
[status, headers, body]
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_hmac(data)
|
85
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
2
|
+
|
3
|
+
require 'rack/session/abstract/id'
|
4
|
+
require 'memcache'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Session
|
8
|
+
# Rack::Session::Memcache provides simple cookie based session management.
|
9
|
+
# Session data is stored in memcached. The corresponding session key is
|
10
|
+
# maintained in the cookie.
|
11
|
+
# You may treat Session::Memcache as you would Session::Pool with the
|
12
|
+
# following caveats.
|
13
|
+
#
|
14
|
+
# * Setting :expire_after to 0 would note to the Memcache server to hang
|
15
|
+
# onto the session data until it would drop it according to it's own
|
16
|
+
# specifications. However, the cookie sent to the client would expire
|
17
|
+
# immediately.
|
18
|
+
#
|
19
|
+
# Note that memcache does drop data before it may be listed to expire. For
|
20
|
+
# a full description of behaviour, please see memcache's documentation.
|
21
|
+
|
22
|
+
class Memcache < Abstract::ID
|
23
|
+
attr_reader :mutex, :pool
|
24
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
25
|
+
:namespace => 'rack:session',
|
26
|
+
:memcache_server => 'localhost:11211'
|
27
|
+
|
28
|
+
def initialize(app, options={})
|
29
|
+
super
|
30
|
+
|
31
|
+
@mutex = Mutex.new
|
32
|
+
mserv = @default_options[:memcache_server]
|
33
|
+
mopts = @default_options.
|
34
|
+
reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
|
35
|
+
@pool = MemCache.new mserv, mopts
|
36
|
+
unless @pool.active? and @pool.servers.any?{|c| c.alive? }
|
37
|
+
raise 'No memcache servers'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_sid
|
42
|
+
loop do
|
43
|
+
sid = super
|
44
|
+
break sid unless @pool.get(sid, true)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_session(env, session_id)
|
49
|
+
@mutex.lock if env['rack.multithread']
|
50
|
+
unless session_id and session = @pool.get(session_id)
|
51
|
+
session_id, session = generate_sid, {}
|
52
|
+
unless /^STORED/ =~ @pool.add(session_id, session)
|
53
|
+
raise "Session collision on '#{session_id.inspect}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
session.instance_variable_set '@old', @pool.get(session_id, true)
|
57
|
+
return [session_id, session]
|
58
|
+
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
59
|
+
# MemCache server cannot be contacted
|
60
|
+
warn "#{self} is unable to find memcached server."
|
61
|
+
warn $!.inspect
|
62
|
+
return [ nil, {} ]
|
63
|
+
ensure
|
64
|
+
@mutex.unlock if @mutex.locked?
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_session(env, session_id, new_session, options)
|
68
|
+
expiry = options[:expire_after]
|
69
|
+
expiry = expiry.nil? ? 0 : expiry + 1
|
70
|
+
|
71
|
+
@mutex.lock if env['rack.multithread']
|
72
|
+
if options[:renew] or options[:drop]
|
73
|
+
@pool.delete session_id
|
74
|
+
return false if options[:drop]
|
75
|
+
session_id = generate_sid
|
76
|
+
@pool.add session_id, {} # so we don't worry about cache miss on #set
|
77
|
+
end
|
78
|
+
|
79
|
+
session = @pool.get(session_id) || {}
|
80
|
+
old_session = new_session.instance_variable_get '@old'
|
81
|
+
old_session = old_session ? Marshal.load(old_session) : {}
|
82
|
+
|
83
|
+
unless Hash === old_session and Hash === new_session
|
84
|
+
env['rack.errors'].
|
85
|
+
puts 'Bad old_session or new_session sessions provided.'
|
86
|
+
else # merge sessions
|
87
|
+
# alterations are either update or delete, making as few changes as
|
88
|
+
# possible to prevent possible issues.
|
89
|
+
|
90
|
+
# removed keys
|
91
|
+
delete = old_session.keys - new_session.keys
|
92
|
+
if $VERBOSE and not delete.empty?
|
93
|
+
env['rack.errors'].
|
94
|
+
puts "//@#{session_id}: delete #{delete*','}"
|
95
|
+
end
|
96
|
+
delete.each{|k| session.delete k }
|
97
|
+
|
98
|
+
# added or altered keys
|
99
|
+
update = new_session.keys.
|
100
|
+
select{|k| new_session[k] != old_session[k] }
|
101
|
+
if $VERBOSE and not update.empty?
|
102
|
+
env['rack.errors'].puts "//@#{session_id}: update #{update*','}"
|
103
|
+
end
|
104
|
+
update.each{|k| session[k] = new_session[k] }
|
105
|
+
end
|
106
|
+
|
107
|
+
@pool.set session_id, session, expiry
|
108
|
+
return session_id
|
109
|
+
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
110
|
+
# MemCache server cannot be contacted
|
111
|
+
warn "#{self} is unable to find memcached server."
|
112
|
+
warn $!.inspect
|
113
|
+
return false
|
114
|
+
ensure
|
115
|
+
@mutex.unlock if @mutex.locked?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|