maveric 0.1.0
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/maveric.rb +769 -0
- data/maveric/fastcgi.rb +14 -0
- data/maveric/mongrel.rb +44 -0
- data/maveric/stadik-impl.rb +113 -0
- data/maveric/webrick.rb +33 -0
- metadata +58 -0
data/maveric.rb
ADDED
@@ -0,0 +1,769 @@
|
|
1
|
+
##
|
2
|
+
# = Maveric: A simple, non-magical, framework
|
3
|
+
#
|
4
|
+
# == Version
|
5
|
+
#
|
6
|
+
# This is 0.1.0 th first real release of Maveric. It's rough around the edges
|
7
|
+
# and several squishy bits in the middle. At the moment it's still operation
|
8
|
+
# conceptualization over algorithm implementation. It's usable, but kinda.
|
9
|
+
#
|
10
|
+
# == Authors
|
11
|
+
#
|
12
|
+
# Maveric is designed and coded by {blink}[mailto:blinketje@gmail.com]
|
13
|
+
# of #ruby-lang on irc.freenode.net
|
14
|
+
#
|
15
|
+
# == Licence
|
16
|
+
#
|
17
|
+
# This software is licensed under the
|
18
|
+
# {CC-GNU LGPL}[http://creativecommons.org/licenses/LGPL/2.1/]
|
19
|
+
#
|
20
|
+
# == Disclaimer
|
21
|
+
#
|
22
|
+
# This software is provided "as is" and without any express or
|
23
|
+
# implied warranties, including, without limitation, the implied
|
24
|
+
# warranties of merchantability and fitness for a particular purpose.
|
25
|
+
# Authors are not responsible for any damages, direct or indirect.
|
26
|
+
#
|
27
|
+
# = Maveric: History
|
28
|
+
#
|
29
|
+
# Maveric was initially designed as a replacement for Camping in the style of a
|
30
|
+
# Model-View-Controller framework. Early implementations aimed to reduce the
|
31
|
+
# amount of "magic" to 0. Outstanding magic of Camping is that nothing is
|
32
|
+
# related due to the reading, gsub-ing, and eval-ing of the Camping source file
|
33
|
+
# Also, even the unobfuscated/unabridged version of Camping is a bit hard to
|
34
|
+
# follow at times.
|
35
|
+
#
|
36
|
+
# However, the result of this initial attempt was a spaghetti of winding code,
|
37
|
+
# empty template modules, and rediculous inheritance paths between the template
|
38
|
+
# modules and a plethora of dynamically generated classes and modules. I got
|
39
|
+
# more complaints with that particular jumble of code than any other, evar.
|
40
|
+
#
|
41
|
+
# The next iteration of Maveric was a seeking of simplification and departure
|
42
|
+
# from the concept of cloning Camping and its functionality and settling for
|
43
|
+
# Camping-esqe. At this point, I started talking to ezmobius on #ruby-lang and
|
44
|
+
# their Merb project. We talked a bit about merging and brainstorming but my
|
45
|
+
# lone wolf tendencies stirred me away from such an endeavour, but I came away
|
46
|
+
# a compatriot and inspiration for a new routing methodology inspired by Merb's.
|
47
|
+
# The result is the Route that's been stable since, only varying in method
|
48
|
+
# placement and data management.
|
49
|
+
#
|
50
|
+
# After building to a version that stood on it's owned and was able to run a
|
51
|
+
# variance of my website as functional as the Camping version. I noticed a few
|
52
|
+
# tendncies of my coding and refactored. I also redesigned the concept from a
|
53
|
+
# modules that included the Maveric module to that of subclassing Maveric, which
|
54
|
+
# is a Mongrel::HttpHandlerPlugin. This allowed the running of Maveric apps
|
55
|
+
# without using Mongrel::CampingHandler as well as not worrying about namespace
|
56
|
+
# clobbering.
|
57
|
+
#
|
58
|
+
# Because ehird from #ruby-lang was whining about it so much I removed its
|
59
|
+
# dependancy on Mongrel sooner than later. Maveric should now be very maveric.
|
60
|
+
#
|
61
|
+
# = Features
|
62
|
+
# * Sessions inherent (kept to each instance of Maveric)
|
63
|
+
# * Flexible and strong route setup
|
64
|
+
# * Sharing of a Controller between Maveric instances is facilitated
|
65
|
+
# * Inheritance is highly respected
|
66
|
+
#
|
67
|
+
# == Not so Maveric
|
68
|
+
#
|
69
|
+
# For these examples we will utilize the simplest functional Maveric possible
|
70
|
+
# require 'maveric'
|
71
|
+
# class Maveric::HelloWorld < Maveric::Controller
|
72
|
+
# def get
|
73
|
+
# 'Hello'
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# To use Mongrel:
|
78
|
+
# require 'maveric/mongrel'
|
79
|
+
# h = ::Mongrel::HttpHandler ip, port
|
80
|
+
# h.register mountpoint, ::Maveric::MongrelHandler.new(MyMaveric)
|
81
|
+
# h.join.run
|
82
|
+
#
|
83
|
+
# To use FastCGI:
|
84
|
+
# require 'maveric/fastcgi'
|
85
|
+
# ::Maveric::FCGI.new(MyMaveric)
|
86
|
+
#
|
87
|
+
# To use WEBrick, which I have come to loathe:
|
88
|
+
# server = ::WEBrick::HTTPServer.new(:Port => 9090)
|
89
|
+
# trap('INT'){ puts "Shutting down server."; server.stop }
|
90
|
+
# server.mount '/', ::Maveric::WEBrickServlet, Maveric
|
91
|
+
# server.start
|
92
|
+
#
|
93
|
+
# However, if your script is mounted at a point other than /, use the
|
94
|
+
# :path_prefix option to adjust your routes. This affects all routes generated
|
95
|
+
# from MyMaveric.
|
96
|
+
# ::Maveric::FCGI.new(MyMaveric, :path_prefix => '/mount/point/here'
|
97
|
+
#
|
98
|
+
# --------------------------------
|
99
|
+
#
|
100
|
+
# NOTE: Due to rampant debugging and benchmarking there is a plethora of
|
101
|
+
# pointless time checks in place. Anything greater than the WARN level on the
|
102
|
+
# logging output tends to output a good deal of information. DEBUG is not for
|
103
|
+
# the meek.
|
104
|
+
|
105
|
+
require 'log4r'
|
106
|
+
require 'benchmark'
|
107
|
+
require 'stringio'
|
108
|
+
|
109
|
+
class Module
|
110
|
+
protected
|
111
|
+
##
|
112
|
+
# Decends through the heirarchy of classes and modules looking fot
|
113
|
+
# objects in constants that &test[const] == true
|
114
|
+
def nested_search d=0, &test
|
115
|
+
constants.map do |c| c, r = const_get(c), []
|
116
|
+
next if c == self
|
117
|
+
r << c if test[c]
|
118
|
+
r.concat c.nested_search(d+1, &test) if c.is_a? Class or c.is_a? Module
|
119
|
+
r
|
120
|
+
end.flatten.compact
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# = The Maveric: Yeargh.
|
126
|
+
# The Maveric may be used alone or may be used in a cadre of loosely aligned
|
127
|
+
# Maveric instances. The Maveric stands tall and proud, relying on it's fine
|
128
|
+
# family and it's inventory of goods to get things done with little magic or
|
129
|
+
# trickery.
|
130
|
+
#
|
131
|
+
# = Usage
|
132
|
+
# We could technically have a running Maveric with:
|
133
|
+
# require 'maveric'
|
134
|
+
# class Maveric::Index < Maveric::Controller
|
135
|
+
# def get
|
136
|
+
# 'Hello, world!'
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
# maveric = Maveric.new # I'm a real boy now!
|
140
|
+
# But that's not very friendly or useful.
|
141
|
+
class Maveric
|
142
|
+
EOL="\r\n"
|
143
|
+
MP_BOUND_REGEX = /\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n
|
144
|
+
|
145
|
+
@log = Log4r::Logger.new 'mvc'
|
146
|
+
@log.outputters = Log4r::Outputter['stderr']
|
147
|
+
@log.level = Log4r::INFO
|
148
|
+
#@sessions = Hash.new {|h,k| s = Maveric::Session.new; h[s.id]=s; }
|
149
|
+
@@sessions = Hash.new {|h,k| s = Maveric::Session.new; h[s.id]=s; }
|
150
|
+
|
151
|
+
@log.info "#{self.class} integrated at #{Time.now}"
|
152
|
+
|
153
|
+
class << self
|
154
|
+
##
|
155
|
+
# Family is important.
|
156
|
+
#
|
157
|
+
# Sets up a new Maveric with everything it needs for normal
|
158
|
+
# operation. Many things are either copied or included from it's
|
159
|
+
# parent The logger is copied and Models and Views are included by
|
160
|
+
# their respective modules.
|
161
|
+
def inherited klass
|
162
|
+
Maveric.log.info "Maveric: #{klass} inherits from #{self}."
|
163
|
+
# By default a Maveric's logger is its parent's
|
164
|
+
klass.instance_variable_set :@log, @log
|
165
|
+
parent = self
|
166
|
+
klass.class_eval do
|
167
|
+
const_set(:Models, Module.new).
|
168
|
+
module_eval { include parent::Models }
|
169
|
+
const_set(:Views, Module.new).
|
170
|
+
module_eval { include parent::Views }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
attr_reader :log
|
175
|
+
|
176
|
+
def sessions; @@sessions; end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Performs URI escaping.
|
180
|
+
def escape(s)
|
181
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
182
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
183
|
+
}.tr(' ', '+')
|
184
|
+
end
|
185
|
+
|
186
|
+
## Unescapes a URI escaped string.
|
187
|
+
def unescape(s)
|
188
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
189
|
+
[$1.delete('%')].pack('H*')
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Parses a query string by breaking it up around the
|
195
|
+
# delimiting characters. You can also use this to parse
|
196
|
+
# cookies by changing the characters used in the second
|
197
|
+
# parameter (which defaults to ';,'.
|
198
|
+
def query_parse(qs, delim = '&;')
|
199
|
+
(qs||'').split(/[#{delim}] */n).inject({}) { |h,p|
|
200
|
+
k, v = unescape(p).split('=',2)
|
201
|
+
if h.key? k
|
202
|
+
if h[k].is_a? Array then h[k] << v
|
203
|
+
else h[k] = [h[k], v] end
|
204
|
+
else h[k] = v end
|
205
|
+
h
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Parse a multipart/form-data entity. Adapated from cgi.rb. The body
|
211
|
+
# argument may either be a StringIO or a IO of sublcass thereof.
|
212
|
+
def parse_multipart boundary, body
|
213
|
+
::Maveric.type_check :body, body, IO
|
214
|
+
values = {}
|
215
|
+
bound = /(?:\r?\n|\A)#{Regexp::quote('--'+boundary)}(?:--)?\r$/
|
216
|
+
until body.eof?
|
217
|
+
fv = {}
|
218
|
+
until body.eof? or /^#{EOL}$/=~l
|
219
|
+
case l = body.readline
|
220
|
+
when /^Content-Type: (.+?)(\r$|\Z)/m
|
221
|
+
fv[:type] = $1
|
222
|
+
when /^Content-Disposition: form-data;/
|
223
|
+
$'.scan(/(?:\s(\w+)="([^"]+)")/) {|w| fv[w[0].intern] = w[1] }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
o = unless fv[:filename] then ''
|
228
|
+
else fv[:tempfile] = Tempfile.new('MVC').binmode end
|
229
|
+
body.inject do |buf,line|
|
230
|
+
o << buf.chomp and break if bound =~ line
|
231
|
+
o << buf
|
232
|
+
line
|
233
|
+
end
|
234
|
+
|
235
|
+
fv[:tempfile].rewind if fv.key? :tempfile
|
236
|
+
values[fv[:name]] = fv.key?(:filename) ? fv : o
|
237
|
+
end
|
238
|
+
body.rewind
|
239
|
+
values
|
240
|
+
end
|
241
|
+
|
242
|
+
## Help us find our contained implicit Controller classes.
|
243
|
+
def contained_controllers
|
244
|
+
nested_search {|v| v.is_a? Class and v < ::Maveric::Controller }
|
245
|
+
end
|
246
|
+
|
247
|
+
## So we don't have ginormous test+raise statements everywhere.
|
248
|
+
def type_check name, value, *klasses, &test
|
249
|
+
if klasses.any? {|klass| value.is_a? klass } then return true
|
250
|
+
elsif test and test[value] then return true
|
251
|
+
else
|
252
|
+
raise TypeError, "Expected #{klasses*' or '} for #{name},"+
|
253
|
+
" got #{value.class}:#{value.inspect}."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# When instantiated, the Maveric look search through its constants for
|
260
|
+
# nested Controllers. If It finds them, they will be asked if they have
|
261
|
+
# already chosen their routes. If they haven't, a default route will
|
262
|
+
# be derived from their name. AClassName would become '/a_class_name'
|
263
|
+
# and Maveric::BongleBing::ThisPage would become '/bongle_bing/this_page'.
|
264
|
+
#
|
265
|
+
# FIXME: Does not do nested paths for a nested Controller
|
266
|
+
def initialize *opts
|
267
|
+
klass = self.class
|
268
|
+
klass.log.info "#{self.class} instantiated at #{Time.now}"
|
269
|
+
# We have no where to go. populate me plz!
|
270
|
+
@routings = []
|
271
|
+
# By default we use a global session repository
|
272
|
+
@sessions = @@sessions
|
273
|
+
|
274
|
+
# defualt settings
|
275
|
+
@path_prefix = ''
|
276
|
+
# let's hande some options!
|
277
|
+
if (hopts = opts[-1]).is_a? Hash
|
278
|
+
# should not end with / !!!!!
|
279
|
+
@path_prefix = hopts[:path_prefix] if hopts.key? :path_prefix
|
280
|
+
end
|
281
|
+
|
282
|
+
# auto population
|
283
|
+
unless (controllers = klass.contained_controllers).empty?
|
284
|
+
controllers.each do |c|
|
285
|
+
klass.log.info "Setting routes for #{c}."
|
286
|
+
r = if not c.routes.empty? then c.routes
|
287
|
+
elsif (s = c.to_s[/::(.*)/,1]) =~ /^index$/i then '/'
|
288
|
+
else # I see we want to be difficult
|
289
|
+
'/'+s.gsub(/([^A-Z_])([A-Z])/){$1+'_'+$2}.downcase
|
290
|
+
end
|
291
|
+
set_routes c, *r
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
attr_reader :routings, :sessions
|
297
|
+
def log; self.class.log; end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Creates a Route to the object c. The c argument should be a Proc or a
|
301
|
+
# Maveric::Controller subclass.
|
302
|
+
def add_route c, r
|
303
|
+
log.info "#{self}#add_route #{c} #{r.inspect}"
|
304
|
+
route = Route.new @path_prefix+r, c
|
305
|
+
@routings.delete_if{|e| e === route } << route
|
306
|
+
end
|
307
|
+
|
308
|
+
## Creates routes to object c, via Maveric#add_route
|
309
|
+
def add_routes c, *r
|
310
|
+
r.flatten.map {|route| add_route c, route }
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# Removes all routes directed at c, and adds routes to the given object,
|
315
|
+
# viaa Maveric#add_routes
|
316
|
+
def set_routes c, *r
|
317
|
+
log.info "#{self}#set_route #{c} #{r.inspect}"
|
318
|
+
@routings.delete_if{|e| e.run == c }
|
319
|
+
add_routes c, *r
|
320
|
+
end
|
321
|
+
|
322
|
+
##
|
323
|
+
# The env argument should be a normal environment hash derived from HTTP.
|
324
|
+
#
|
325
|
+
# Determines session info then sets the Maveric and the session info into
|
326
|
+
# the environment hash at the keys of :maveric and :session respectively.
|
327
|
+
# Parses the REQUEST_URI to determine the appropriate routing and compiles
|
328
|
+
# the route info. Checks the REQUEST_METHOD for the post method and tests
|
329
|
+
# for a multipart/form-data payload.
|
330
|
+
def prep_env env
|
331
|
+
::Maveric.type_check :env, env, Hash
|
332
|
+
class << env; include HashGenOnKey; end
|
333
|
+
env[:maveric] = self
|
334
|
+
|
335
|
+
# determines the appropriate route and build the route info.
|
336
|
+
url = Maveric.unescape env['REQUEST_URI']
|
337
|
+
route, rinfo = routings.
|
338
|
+
find {|r| o = r.match_url(url) and break [r, o] }
|
339
|
+
env.update :route => route, :route_info => rinfo
|
340
|
+
#::Maveric.log.warn env.map{|k,v|"#{k}: #{v.inspect}"}*"\n"
|
341
|
+
|
342
|
+
# determine if there's a multipart boundary
|
343
|
+
if env['REQUEST_METHOD'] =~ /^post$/i \
|
344
|
+
and env['CONTENT_TYPE'] =~ MP_BOUND_REGEX
|
345
|
+
env[:multipart_boundary] = $1
|
346
|
+
end
|
347
|
+
|
348
|
+
env[:params]
|
349
|
+
# session data!
|
350
|
+
session_id = env[:cookies][Maveric::Session::COOKIE_NAME]
|
351
|
+
session_id = session_id[0] if session_id.is_a? Array
|
352
|
+
env[:session] = sessions[ session_id ]
|
353
|
+
env
|
354
|
+
end
|
355
|
+
|
356
|
+
##
|
357
|
+
# A Maveric's cue to start doing the heavy lifting. The env should be
|
358
|
+
# a hash with the typical assignments from an HTTP environment or crying
|
359
|
+
# and whining will ensue. The req_body argument should be a String, StringIO,
|
360
|
+
# or an IO or subclass thereof instance.
|
361
|
+
def dispatch req_body=$stdin, env=ENV, *opts
|
362
|
+
log.info "#{self.class}#dispatch #{id=Integer(rand*10000)}\n "+
|
363
|
+
"[#{Time.now}] #{env['REMOTE_ADDR']} => #{env['REQUEST_URI']}"
|
364
|
+
::Maveric.type_check :req_body, req_body, IO, StringIO
|
365
|
+
::Maveric.type_check :env, env, Hash
|
366
|
+
response = ''
|
367
|
+
n = Benchmark.measure do
|
368
|
+
response = begin
|
369
|
+
prep_env env unless [:maveric, :route, :route_info].all?{|k|env.key? k}
|
370
|
+
raise ServerError, [404, "Page not found. Sorry!"] unless env[:route]
|
371
|
+
|
372
|
+
if env['REQUEST_METHOD'] =~ /^post$/i
|
373
|
+
qparams = env.key?(:multipart_boundary) ?
|
374
|
+
parse_multipart(env[:multipart_boundary], req_body) :
|
375
|
+
Maveric.query_parse(req_body.read)
|
376
|
+
env[:params].update qparams
|
377
|
+
end
|
378
|
+
|
379
|
+
env[:route].run[req_body, env, *opts]
|
380
|
+
rescue ServerError then $!
|
381
|
+
rescue
|
382
|
+
log.fatal "#{Time.now}: #{$!.to_s}\n#{$!.backtrace*"\n"}"
|
383
|
+
# have a nice fail, then have a hard fail if something fails there.
|
384
|
+
begin
|
385
|
+
ServerError.new($!)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
log.info "#{self.class}#dispatch #{id}\n#{n}".chomp
|
390
|
+
response
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
##
|
395
|
+
# Conveniance class to build simple error responses. Raise it from within a
|
396
|
+
# dispatch to attain a quick end to a possibly messy situation.
|
397
|
+
#
|
398
|
+
# TODO: Adapt it to be a subclass of Maveric::Controller
|
399
|
+
class Maveric::ServerError < RuntimeError
|
400
|
+
|
401
|
+
class << self
|
402
|
+
##
|
403
|
+
# If given an instance of the same class it shall return that class as
|
404
|
+
# doubling up is wasteful.
|
405
|
+
def new a
|
406
|
+
a.instance_of?(self) ? a : super(a)
|
407
|
+
end
|
408
|
+
alias_method :exception, :new
|
409
|
+
end
|
410
|
+
|
411
|
+
##
|
412
|
+
# After a bit of experimenting with Exception and raise I determinee a fun
|
413
|
+
# and simple way to generate fast HTTP response error thingies. As long as
|
414
|
+
# you're within a call to dispatch (with no further rescue clauses) the
|
415
|
+
# ServerError will propogate up and be returned. As a ServerError has similar
|
416
|
+
# accessors as a Controller, they should be compatible with any outputting
|
417
|
+
# implimentation.
|
418
|
+
#
|
419
|
+
# If the status code is 5xx (500 by default) then a backtrace is appended to
|
420
|
+
# the response body.
|
421
|
+
#
|
422
|
+
# raise ServerErrpr; # 500 error
|
423
|
+
# raise ServerError, 'Crap!'; # 500 error with message set to 'Crap!'
|
424
|
+
# raise ServerError, $!; # 500 error with same message and backtrace as
|
425
|
+
# # $! with a note in the message as to the original exception class
|
426
|
+
# raise ServerError, [status, headers, body, *other_data] # Magic!
|
427
|
+
#
|
428
|
+
# In the final example line an Array is passed to raise as a second argument
|
429
|
+
# rather than a String. This only works as intended if the first element is
|
430
|
+
# an Integer. This is taken as the HTTP status code. The next element is
|
431
|
+
# tested to be a Hash, if so then it's values are merged into the HTTP
|
432
|
+
# headers. Then next item is tested to be a String, if so it is appended to
|
433
|
+
# the response body. All remaining elements are appended to the response
|
434
|
+
# body in inspect format.
|
435
|
+
def initialize data=nil
|
436
|
+
@status, @headers, @body = 500, {'Content-Type'=>'text/plain'}, ''
|
437
|
+
case msg = data
|
438
|
+
when Exception
|
439
|
+
msg = @body << data.class.to_s+" "+data.message
|
440
|
+
set_backtrace data.backtrace
|
441
|
+
@body << "\n\n"+data.backtrace*"\n"
|
442
|
+
when String
|
443
|
+
@body << msg
|
444
|
+
when Array
|
445
|
+
if data[0].is_a? Integer
|
446
|
+
# [status, headers, body, request]
|
447
|
+
@status = data.shift
|
448
|
+
@headers.update data.shift if data.first.is_a? Hash
|
449
|
+
@body.concat data.shift if data.first.is_a? String
|
450
|
+
msg = @body.dup
|
451
|
+
@body << "\n\n#{data.map{|e|e.inspect}*"\n"}" unless data.empty?
|
452
|
+
end
|
453
|
+
end
|
454
|
+
if @status/100%10 == 5
|
455
|
+
@body << "\n\n#{$!}\n#{$@[0..10]*"\n"}"
|
456
|
+
end
|
457
|
+
super msg
|
458
|
+
end
|
459
|
+
attr_reader :status, :headers, :body
|
460
|
+
|
461
|
+
## Allows fun with splat (*).
|
462
|
+
def to_a
|
463
|
+
[
|
464
|
+
(@status.dup.freeze rescue @status),
|
465
|
+
@headers.dup.freeze,
|
466
|
+
@body.dup.freeze,
|
467
|
+
(@env.dup.freeze rescue @env)
|
468
|
+
].freeze
|
469
|
+
end
|
470
|
+
|
471
|
+
## Fun with raw output!
|
472
|
+
def to_s
|
473
|
+
status, headers, body, e, i = to_a
|
474
|
+
response = "Status: #{status}"+Maveric::EOL
|
475
|
+
response << headers.map{|(k,v)| "#{k}: #{v}" }*Maveric::EOL
|
476
|
+
response << Maveric::EOL*2
|
477
|
+
response << body
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
##
|
482
|
+
# Contains session data and provides conveniance in generating an appropriate
|
483
|
+
# cookie.
|
484
|
+
class Maveric::Session
|
485
|
+
COOKIE_NAME = 'SESSIONID'
|
486
|
+
DURATION = 15*60
|
487
|
+
KEY_GRADE = 16
|
488
|
+
KEY_LENGTH = 16
|
489
|
+
|
490
|
+
## Do you want to alter the duration of sessions? M'kay.
|
491
|
+
def initialize duration=DURATION
|
492
|
+
@id = Array.new(KEY_LENGTH){rand(KEY_GRADE).to_s(KEY_GRADE)}*''
|
493
|
+
@duration, @data = duration, {}
|
494
|
+
Maveric.log.debug self
|
495
|
+
end
|
496
|
+
attr_reader :id, :expires, :duration, :data
|
497
|
+
attr_writer :duration
|
498
|
+
|
499
|
+
##
|
500
|
+
# Update the Session's lifetime by @duration seconds from the time this
|
501
|
+
# method is called. Returns self for chaining uses.
|
502
|
+
#
|
503
|
+
# NOTE: No bookeeping algorithms are implemented, so a Session will last
|
504
|
+
# forever (within the app, the client's cookie will expire) at this point.
|
505
|
+
def touch
|
506
|
+
@expires = Time.now.gmtime + Integer(@duration)
|
507
|
+
Maveric.log.info "#{self.class}#touch: #{self.inspect}"
|
508
|
+
self
|
509
|
+
end
|
510
|
+
|
511
|
+
##
|
512
|
+
# Representation of the Session in cookie form. Session specific data
|
513
|
+
# is not used in any way.
|
514
|
+
def to_s env=Hash.new{|h,k|"#{k} not defined"}
|
515
|
+
touch unless @expires
|
516
|
+
c = "#{COOKIE_NAME}=#{id};" #required
|
517
|
+
c << " expires=#{@expires.httpdate}" if @expires
|
518
|
+
c << " path=#{env['SCRIPT_NAME']};"
|
519
|
+
c << " domain=#{env['SERVER_NAME']};"
|
520
|
+
c << " secure" if false
|
521
|
+
c
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
##
|
526
|
+
# Instances of Route are used for determining how to act upon particular urls
|
527
|
+
# in each Maveric instance. Routes should be defined in terms of / as the
|
528
|
+
# mount point of the Maveric. The following code would print the request
|
529
|
+
# instance on the request of '/foo/'.
|
530
|
+
# Maveric.add_route '/', proc{p @request}
|
531
|
+
# mongrelserver.register('/foo', Maveric::MongrelHandler(Maveric))
|
532
|
+
class Maveric::Route
|
533
|
+
## Seperator characters.
|
534
|
+
URI_CHAR = '[^/?:,&#]'
|
535
|
+
|
536
|
+
##
|
537
|
+
# Create a Route from the String route where varying parts of the url are
|
538
|
+
# represented as Symbol like sigils. The run argument should be a Proc or
|
539
|
+
# a Maveric::Controller subclass.
|
540
|
+
# Route.new '/:page', proc{|request| p request.params[:params][:page] }
|
541
|
+
# Route.new '/:page', MyController # MyController < Maveric::Controller
|
542
|
+
def initialize route, run
|
543
|
+
::Maveric.type_check :route, route, String
|
544
|
+
::Maveric.type_check :run, run, Proc, ::Maveric::Controller do |k|
|
545
|
+
k < ::Maveric::Controller
|
546
|
+
end
|
547
|
+
@run = run
|
548
|
+
@route = route.gsub /\/+/, '/'
|
549
|
+
@params = []
|
550
|
+
@regex = Regexp.new '^'+@route.gsub(/\/:(#{URI_CHAR}+)/){
|
551
|
+
@params << $1.to_sym
|
552
|
+
raise 'Duplicated parameters.' unless @params.uniq.size == @params.size
|
553
|
+
"/(#{URI_CHAR}+)"
|
554
|
+
}+'$'
|
555
|
+
Maveric.log.debug self
|
556
|
+
end
|
557
|
+
attr_reader :route, :run, :regex, :params
|
558
|
+
|
559
|
+
##
|
560
|
+
# Create a url from a Hash by replacing the Symbol like sigils of the route
|
561
|
+
# with the corresponding hash values. An incomplete url may be returned if
|
562
|
+
# not all values of @params do not have corresponding value in the given
|
563
|
+
# Hash.
|
564
|
+
def build_url hsh
|
565
|
+
Maveric.log.debug "#{self.class}#build_url"+
|
566
|
+
" #{hsh.inspect} : #{self.inspect}"
|
567
|
+
#@params.inject(@route){|r,k| r.gsub(/#{k.inspect}/, hsh[k]) }
|
568
|
+
hsh.inject(@route){|r,(k,v)| r.gsub(/:#{k}/, v.to_s) }
|
569
|
+
end
|
570
|
+
|
571
|
+
##
|
572
|
+
# Build a Hash by picking out portions of the provided url that correspond
|
573
|
+
# to each sigil of the route. Returns nil if the url does not match.
|
574
|
+
def match_url url
|
575
|
+
Maveric.log.debug "#{self.class}#match_url"+
|
576
|
+
" #{url.inspect} : #{self.inspect}"
|
577
|
+
return nil unless md = @regex.match(url)
|
578
|
+
Hash[ *@params.zip(md.captures).flatten ]
|
579
|
+
end
|
580
|
+
|
581
|
+
##
|
582
|
+
# Provides comparisons with instances of Array, Hash, Strings or Proc, as
|
583
|
+
# well as subclasses of Maveric::Controller.
|
584
|
+
def === arg
|
585
|
+
Maveric.log.debug "#{self.class}#==="+
|
586
|
+
" #{arg.inspect} : #{self.inspect}"
|
587
|
+
case arg
|
588
|
+
when Array then arg.size == @params.size and \
|
589
|
+
not arg.any?{|v|!@params.include?(v)}
|
590
|
+
when Hash then self === arg.keys
|
591
|
+
when String then @regex.match(arg).nil?
|
592
|
+
when Proc then @run == arg
|
593
|
+
when Class then @run == arg if arg < Maveric::Controller
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
##
|
599
|
+
# Controller instances are the work horses of Maveric instances, pulling them
|
600
|
+
# around the wide prarie of the net, lashed by means of a Route.
|
601
|
+
#
|
602
|
+
# They may be defined anywhere you want, being contained in a Maveric is not
|
603
|
+
# needed, all that is needed is for that class to be a subclass of
|
604
|
+
# Maveric::Controller. While you may do everything to the all-father of
|
605
|
+
# Maveric::Controller that you can do with a child, it is not recommended for
|
606
|
+
# the great one are prone to pass down their weirding ways to their children
|
607
|
+
# which will probably instigate a small war or other conflicts later on.
|
608
|
+
#
|
609
|
+
# If the methods get, post, put, or delete are called (typically called in
|
610
|
+
# Controller.new when setting the body of the response) and they haven't been
|
611
|
+
# defined then a ServerError is thrown with a status of 503.
|
612
|
+
#
|
613
|
+
# * Access to the raw request is provided through @request.
|
614
|
+
# * Cookies are reflected in @cookies, alterations will be passed to the
|
615
|
+
# client response.
|
616
|
+
# * Alteration of the session id cookie is not recommended.
|
617
|
+
# * HTTP headers should be set in @headers. The HTTP status should be set
|
618
|
+
# in @status. Cookies from @cookies are included at the end of processing.
|
619
|
+
class Maveric::Controller
|
620
|
+
REQUEST_METHODS = [:post, :get, :put, :delete, :head] # CRUD
|
621
|
+
|
622
|
+
@routes = []
|
623
|
+
class << self
|
624
|
+
## Added to allow utilitilizing of Route#run[stuff]
|
625
|
+
alias_method :[], :new
|
626
|
+
def add_route r
|
627
|
+
@routes << r
|
628
|
+
end
|
629
|
+
def set_routes *r
|
630
|
+
@routes.clear
|
631
|
+
r.flatten.map{|e| add_route e }
|
632
|
+
end
|
633
|
+
def inherited klass
|
634
|
+
klass.instance_variable_set :@routes, @routes.dup
|
635
|
+
end
|
636
|
+
attr_reader :routes
|
637
|
+
end
|
638
|
+
|
639
|
+
##
|
640
|
+
# Main processing method of Controller. After initial setup it will call
|
641
|
+
# its method with name equivalent to REQUEST_METHOD or 'get' in downcased
|
642
|
+
# form. The result of this method is used as the response body.
|
643
|
+
def initialize req_body, env, *opts
|
644
|
+
Maveric.log.warn "Provided env has not been properly processed, results "+
|
645
|
+
"may vary!" unless ([:maveric, :route, :route_info]-env.keys).empty?
|
646
|
+
::Maveric.type_check :req_body, req_body, IO, StringIO
|
647
|
+
Maveric.log.debug env
|
648
|
+
@status, @headers = 200, {'Content-Type'=>'text/html'}
|
649
|
+
@env = env
|
650
|
+
@in = req_body
|
651
|
+
@cookies = @env[:cookies].dup
|
652
|
+
method = (@env['REQUEST_METHOD'] || 'get').downcase
|
653
|
+
|
654
|
+
n = Benchmark.measure{@body = __send__(method)}
|
655
|
+
Maveric.log.info "#{self.class}@body\n#{n}".chomp
|
656
|
+
|
657
|
+
@headers['Set-Cookie'] = [@env[:session].touch.to_s(@env)]
|
658
|
+
@cookies.each do |k,v|
|
659
|
+
next unless v != @env[:cookies][k]
|
660
|
+
@headers['Set-Cookie'] << \
|
661
|
+
"#{k}=#{Maveric.escape(v)}; path=#{@env['SCRIPT_NAME']}"
|
662
|
+
end
|
663
|
+
Maveric.log.debug self
|
664
|
+
end
|
665
|
+
|
666
|
+
attr_reader :status, :headers, :body
|
667
|
+
|
668
|
+
##
|
669
|
+
# This is the new renderer. It uses nothing. Yay, configurability!
|
670
|
+
def render view, s=''
|
671
|
+
Maveric.log.debug "#{self.class}#render"+
|
672
|
+
" #{view.inspect}, #{s}"
|
673
|
+
::Maveric.type_check :view, view, Symbol, String
|
674
|
+
extend @env[:maveric].class::Views
|
675
|
+
n = Benchmark.measure do
|
676
|
+
s = __send__ view, s if respond_to? view
|
677
|
+
s = __send__ :layout, s unless view.to_s[/^(_|layout$)/]
|
678
|
+
end
|
679
|
+
Maveric.log.info "#{self.class}#render #{view.inspect}\n#{n}".chomp
|
680
|
+
return s
|
681
|
+
end
|
682
|
+
|
683
|
+
## Allows fun with splat (8).
|
684
|
+
def to_a
|
685
|
+
[
|
686
|
+
(@status.dup.freeze rescue @status),
|
687
|
+
@headers.dup.freeze,
|
688
|
+
@body.dup.freeze,
|
689
|
+
(@env.dup.freeze rescue @env)
|
690
|
+
].freeze
|
691
|
+
end
|
692
|
+
|
693
|
+
## Because simple output is sometimes needed
|
694
|
+
def to_s
|
695
|
+
response = "Status: #{@status}" + Maveric::EOL
|
696
|
+
response << @headers.map{|k,v| "#{k}: #{v}" }*Maveric::EOL
|
697
|
+
response << Maveric::EOL*2 + @body
|
698
|
+
end
|
699
|
+
|
700
|
+
##
|
701
|
+
# If the method requested is a standard HTTP REQUEST_METHOD name then
|
702
|
+
# we've probably just requested something not implemented. So, we throw
|
703
|
+
# a 503 error!
|
704
|
+
def method_missing m, *a, &b
|
705
|
+
Maveric.log.debug "#{self.class}#missing_method"+
|
706
|
+
"( #{m.inspect}, #{a.inspect}, #{b.inspect}) "
|
707
|
+
raise ServerError, [503, "Method #{m} not implemented.", @env] if \
|
708
|
+
REQUEST_METHODS.include? m
|
709
|
+
super m, *a, &b
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
##
|
714
|
+
# Repository for global Views helpers and methods. For Maveric specific
|
715
|
+
# views they should be defined in the Views module within the Maveric
|
716
|
+
# subclass itself.
|
717
|
+
#
|
718
|
+
# NOTE: They really shouldn't be going here, but I cannot recall a better
|
719
|
+
# location to place helper methods. Truthfully, Views related helpers should
|
720
|
+
# go here, while Controller related ones should be in their Controller.
|
721
|
+
module Maveric::Views
|
722
|
+
def path_to c, a={}
|
723
|
+
path = ''
|
724
|
+
n = Benchmark.measure do
|
725
|
+
path = (@env['SCRIPT_NAME']+@env[:maveric].routings.find do |r|
|
726
|
+
r === a and u = r.build_url(a) and break u if r === c
|
727
|
+
end).gsub /\/+/, '/'
|
728
|
+
end
|
729
|
+
Maveric.log.info "#{self.class}#path_to #{c} #{a.inspect}\n#{n}".chomp
|
730
|
+
path
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
## Container module for Models. Akin to Maveric::Views
|
735
|
+
module Maveric::Models
|
736
|
+
end
|
737
|
+
|
738
|
+
##
|
739
|
+
# Utilizes a hash in @@gen_on_key, with Symbol keys and Proc values, to
|
740
|
+
# generate default values for keys that don't exist.
|
741
|
+
#
|
742
|
+
# Used to extend the environment hash to automatically build the
|
743
|
+
# data hashes for cookies and query parameters (limited to those gathered from
|
744
|
+
# QUERY_STRING). Each data hash is triggered and accessable from the keys of
|
745
|
+
# :cookies and :params, respectively.
|
746
|
+
module HashGenOnKey
|
747
|
+
# Store procs for generating data sets on the look up of a particular key
|
748
|
+
@@gen_on_key = Hash[
|
749
|
+
:cookies,
|
750
|
+
proc{|h| Maveric.query_parse(h['HTTP_COOKIE'], ';,') },
|
751
|
+
:params,
|
752
|
+
proc{|h| Maveric.query_parse(h['QUERY_STRING']) }
|
753
|
+
]
|
754
|
+
|
755
|
+
## If a key has no assigned value then test to see if there's a generator.
|
756
|
+
def default k
|
757
|
+
unless @@gen_on_key.key? k then super k
|
758
|
+
else store k, @@gen_on_key[k][self]
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
## If there's a symbol key or corresponding generator, then return the value.
|
763
|
+
def method_missing m, *a, &b
|
764
|
+
if key? m then self[m]
|
765
|
+
elsif @@gen_on_key.key? m then default m
|
766
|
+
else super m, *a, &b
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
data/maveric/fastcgi.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
%w~fcgi~.each{|m|require m}
|
2
|
+
|
3
|
+
class Maveric::FCGI
|
4
|
+
def initialize maveric, *opts
|
5
|
+
@maveric = maveric.new *opts
|
6
|
+
::FCGI.each do |req|
|
7
|
+
req_body = ::StringIO.new( req.in.read || '' )
|
8
|
+
result = @maveric.dispatch req_body, req.env
|
9
|
+
req.out << "Status: 200 OK\r\n\r\n"
|
10
|
+
req.out << result.to_s
|
11
|
+
req.finish
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/maveric/mongrel.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
%w~mongrel~.each{|m|require m}
|
2
|
+
|
3
|
+
class Maveric::MongrelHandler < Mongrel::HttpHandler
|
4
|
+
def initialize maveric, opts={}
|
5
|
+
::Maveric.type_check :maveric, maveric do |k| k <= ::Maveric end
|
6
|
+
::Maveric.type_check :opts, opts, Hash
|
7
|
+
super()
|
8
|
+
@request_notify = true
|
9
|
+
@maveric = maveric.new opts
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# NOTE: Update doc.
|
14
|
+
def process request, response
|
15
|
+
Maveric.log.info "Mongrel+#{self.class}#process"
|
16
|
+
reply = @maveric.dispatch request.body, request.params
|
17
|
+
# output the result
|
18
|
+
response.start reply.status do |head,out|
|
19
|
+
reply.headers.each {|k,v| head[k] = v }
|
20
|
+
out.write reply.body
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
## Prepares by loading session and reflection data into the request params.
|
25
|
+
def request_begins params
|
26
|
+
begin
|
27
|
+
Maveric.log.info "Mongrel+#{self.class}#request_begins"
|
28
|
+
@maveric.prep_env params
|
29
|
+
rescue
|
30
|
+
Maveric.log.fatal "#{$!.inspect}\n#{$@[0..5]*"\n"}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
## Does nothing yet. Yet.
|
35
|
+
def request_progess params, clen, total
|
36
|
+
begin
|
37
|
+
Maveric.log.info "Mongrel+#{self.class}#request_progess"+
|
38
|
+
": #{clen}/#{total}"
|
39
|
+
Maveric.log.debug params
|
40
|
+
rescue
|
41
|
+
Maveric.log.fatal "#{$!.inspect}\n#{$@[0..5]*"\n"}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
%w~maveric maveric/mongrel erubis~.each{|m|require m}
|
3
|
+
Dir.chdir File.dirname __FILE__
|
4
|
+
|
5
|
+
class SDKNet < Maveric
|
6
|
+
def self.load_mime_map(file,mime={})
|
7
|
+
mime = mime.merge(YAML.load_file(file))
|
8
|
+
mime.each {|k,v| $log.warn{"WARNING: MIME type #{k} must start with '.'"} if k.index(".") != 0 }
|
9
|
+
mime
|
10
|
+
end
|
11
|
+
module Views
|
12
|
+
def standard content
|
13
|
+
menu = [
|
14
|
+
[path_to(Index, :page => 'about'), 'About', 'Myth/Truth'],
|
15
|
+
['http://blog.stadik.net', 'Blog', 'Finding('+%w(Thought Home Popularity Greatness Definition Meaning Happiness Reason Love Life Intelligence Worth).at(rand(12))+')'],
|
16
|
+
[path_to(Index, :page => 'resume'), 'Resumé', 'I work'],
|
17
|
+
[path_to(Index, :page => 'link'), 'Linkage', 'here we go']
|
18
|
+
]
|
19
|
+
std = <<-STD
|
20
|
+
<div class="tablist" id="pmenu">
|
21
|
+
<ul>
|
22
|
+
<li><a title="Home" href="<%= path_to(Index) %>"><img border="0" src="http://media.stadik.net/stadik.png" title="SDK | StaDiK" id="homelogo" alt="" /></a></li>
|
23
|
+
<% menu.each do |entry| %>
|
24
|
+
<li class='<%= 'here' if @page == entry[0] %>'><a href='<%= entry[0] %>' title='<%= entry[2] %>'><%= entry[1] %></a></li>
|
25
|
+
<% end %>
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div id='pcontent'>
|
30
|
+
<!-- START CONTENT -->
|
31
|
+
|
32
|
+
<%= content %>
|
33
|
+
|
34
|
+
<!-- END CONTENT -->
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<div class="tablist" id="pfoot">
|
38
|
+
<ul>
|
39
|
+
<li><a href="http://www.gvisit.com/map.php?sid=9e05735b2b78466e2fbc8538bf9b0c1c">where</a></li>
|
40
|
+
<li><a href="http://validator.w3.org/check?uri=referer">valid?</a></li>
|
41
|
+
<li><a>1995-2006©SDKm</a><a rel="licence" title="All content on this website (including text, photographs, audio files, and any other original works), unless otherwise noted, is licensed under a Creative Commons License." href="http://creativecommons.org/licenses/by-nc-nd/2.5/">CCL:2.5</a></li>
|
42
|
+
</ul>
|
43
|
+
</div>
|
44
|
+
STD
|
45
|
+
::Erubis::Eruby.new(std).result(binding)
|
46
|
+
end
|
47
|
+
def layout content
|
48
|
+
lyt = <<-LAYOUT
|
49
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
50
|
+
<!DOCTYPE html PUBLIC>
|
51
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
52
|
+
<head>
|
53
|
+
<title>StaDiK.net</title>
|
54
|
+
<link rel="stylesheet" href="/z/style.css" type="text/css" media="screen"/>
|
55
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
56
|
+
<meta name="DC.title" content="StaDiK Motions"/>
|
57
|
+
<meta name="DC.description" content="Scytrin&apos;s mucking about on the net."/>
|
58
|
+
<meta name="DC.creator.name" content="Scytrin dai Kinthra"/>
|
59
|
+
<meta name="geo.position" content="37.3069;-121.9271"/>
|
60
|
+
<meta name="geo.placename" content="San Jose"/>
|
61
|
+
<meta name="geo.region" content="US-CA"/>
|
62
|
+
<meta name="microid" content="5abf847090c9afe2172e0efe6636ba2c86e3d60d"/>
|
63
|
+
<% if @page and File.exists?(csf=@page+'.css') %>
|
64
|
+
<link rel='stylesheet' href='/z/<%= csf %>' type='text/css' media='screen' />
|
65
|
+
<% end %>
|
66
|
+
</head>
|
67
|
+
<body id="<%= @page || 'default' %>">
|
68
|
+
<%= content %>
|
69
|
+
</body>
|
70
|
+
</html>
|
71
|
+
LAYOUT
|
72
|
+
::Erubis::Eruby.new(lyt).result(binding)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
class Index < Controller
|
76
|
+
MIME_MAP = SDKNet.
|
77
|
+
load_mime_map('mime.yaml')
|
78
|
+
set_routes %w~/ /:page~
|
79
|
+
def get
|
80
|
+
@page = @env[:route_info][:page] || 'home'
|
81
|
+
|
82
|
+
fns = Dir[File.join(Dir.pwd,@page)+'.*'].
|
83
|
+
delete_if{|e| File.basename(e)[/^\./] }.
|
84
|
+
select{|e| MIME_MAP.include?(File.extname(e)) }
|
85
|
+
|
86
|
+
if fn = fns.inject do |f,e| # going to hell for this code.
|
87
|
+
%w{.wiki .erb .html .htm .txt}.include?(File.extname(e)) ? e : f
|
88
|
+
end
|
89
|
+
content = File.read(fn)
|
90
|
+
content = ::Erubis::Eruby.new(content).
|
91
|
+
result(binding) if File.extname(fn)=='.erb'
|
92
|
+
render :standard, content
|
93
|
+
else
|
94
|
+
raise ::Maveric::ServerError, [404, 'Fucking drop bears.']
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
h = Mongrel::HttpServer.new("0.0.0.0", "9081")
|
101
|
+
h.register("/", Maveric::MongrelHandler.new(SDKNet))
|
102
|
+
Signal.trap('INT') do
|
103
|
+
puts "Shutting down server."
|
104
|
+
h.acceptor.raise Mongrel::StopServer
|
105
|
+
end
|
106
|
+
h.run.join
|
107
|
+
|
108
|
+
__END__
|
109
|
+
|
110
|
+
require 'ruby-prof'
|
111
|
+
result = RubyProf.profile { h.run.join }
|
112
|
+
RubyProf::GraphHtmlPrinter.new(result).
|
113
|
+
print(File.open('prof.html','w'), 0)
|
data/maveric/webrick.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
%w~webrick webrick/httpservlet/abstract~.each{|m|require m}
|
2
|
+
|
3
|
+
## Adaptation of CampingHandler, quick works.
|
4
|
+
class Maveric::WEBrickServlet < WEBrick::HTTPServlet::AbstractServlet
|
5
|
+
|
6
|
+
##
|
7
|
+
# As a WEBrick servlet, but pass all but the first option to the Maveric on
|
8
|
+
# initialization. The Maveric (not an instance) is expected as the first
|
9
|
+
# option.
|
10
|
+
def initialize server, maveric, *opts
|
11
|
+
::Maveric.type_check :maveric, maveric do |k| k <= ::Maveric end
|
12
|
+
@maveric = maveric.new *@options
|
13
|
+
super server, *opts
|
14
|
+
end
|
15
|
+
|
16
|
+
## We don't need no stinkin' do_* methods.
|
17
|
+
def service req, res
|
18
|
+
begin
|
19
|
+
::Maveric.log.warn req.inspect
|
20
|
+
req_body = StringIO.new(req.body || '')
|
21
|
+
env = req.meta_vars
|
22
|
+
env['REQUEST_URI'] = req.unparsed_uri
|
23
|
+
result = @maveric.dispatch req_body, env, req
|
24
|
+
result.headers.each do |k, v|
|
25
|
+
if k =~ /^X-SENDFILE$/i then @local_path = v
|
26
|
+
else [*v].each {|x| res[k] = x } end
|
27
|
+
end
|
28
|
+
res.status, h, res.body = *result
|
29
|
+
rescue ::Maveric::ServerError => error
|
30
|
+
Maveric.log.fatal "WEBrick done got f'd up: #{error.inspect}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: maveric
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-01-19 00:00:00 -07:00
|
8
|
+
summary: A simple, non-magical, framework
|
9
|
+
require_paths:
|
10
|
+
- .
|
11
|
+
email: blinketje@gmail.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: maveric
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- blink
|
31
|
+
files:
|
32
|
+
- maveric.rb
|
33
|
+
- maveric/mongrel.rb
|
34
|
+
- maveric/fastcgi.rb
|
35
|
+
- maveric/webrick.rb
|
36
|
+
- maveric/stadik-impl.rb
|
37
|
+
test_files: []
|
38
|
+
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
executables: []
|
44
|
+
|
45
|
+
extensions: []
|
46
|
+
|
47
|
+
requirements: []
|
48
|
+
|
49
|
+
dependencies:
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: log4r
|
52
|
+
version_requirement:
|
53
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 0.0.0
|
58
|
+
version:
|