maveric 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|