Skip to content

Commit

Permalink
* Introduced error 501 handling and changed NotFound and ServerError …
Browse files Browse the repository at this point in the history
…controllers to #r404 and #r500 methods. camping.rb is exactly at 4000 octets !

* Some doc fixes in man page, README, CHANGELOG. Unfortunately the rdoc are still badly parsed.
  • Loading branch information
Jonas Pfenniger committed Oct 2, 2007
1 parent 4b6a2c8 commit d485565
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 134 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
= 1.6
=== ???, 2007

* Camping::Apps removed, it wasn't reliable.
* bin/camping server kinds splitted in various files.
* NotFound and ServerError controllers changed to methods :

r404 : called when a controller was not found
r500 : called on uncaught exception
r501 : called on undefined method

All of those can be overridden at your taste.

* Markaby no longer required. Like AR, is it autoloaded on (Mab) usage.
* Camping::H is now inheriting from Hash instead of HashWithIndifferentAccess.
* Which made possible to remove the last strict dependency : active_support
* #errors_for removed, it wasn't really used
* Bug fixes !

= 1.5
=== 3rd Oct, 2006

Expand Down
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ If you run them from the commandline, you'll probably just see a pile of HTML.
Camping comes with an tool for launching apps from the commandline:

* Run: <tt>camping blog.rb</tt>
* Visit http://localhost:3301/blog/ to use the app.
* Visit http://localhost:3301/ to use the app.

== How the Camping Tool Works

Expand Down
3 changes: 1 addition & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ namespace :check do
end
if File.size("lib/camping.rb") > SIZE_LIMIT
STDERR.puts "lib/camping.rb: file is too big (> #{SIZE_LIMIT})"
exit 1
end
end

Expand All @@ -155,7 +154,7 @@ namespace :check do
i = 1
File.open("lib/camping.rb").each_line do |line|
if line.size > 81 # 1 added for \n
puts "lib/camping.rb:#{i}: line too long (#{line[-10..-1].inspect})"
STDERR.puts "lib/camping.rb:#{i}: line too long (#{line[-10..-1].inspect})"
end
i += 1
end
Expand Down
1 change: 0 additions & 1 deletion bin/camping
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ require 'ostruct'
require 'stringio'
require 'yaml'

begin require 'rubygems' rescue LoadError end
require 'camping'
require 'camping/server'

Expand Down
Binary file modified doc/camping.1.gz
Binary file not shown.
129 changes: 60 additions & 69 deletions lib/camping-unabridged.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@
#
%w[tempfile uri].map { |l| require l }

class Object
# Define a method m with the passed block on the metaclass.
def meta_def(m,&b)
class Object #:nodoc:
def meta_def(m,&b) #:nodoc:
(class<<self;self end).send(:define_method,m,&b)
end
end
Expand Down Expand Up @@ -91,7 +90,7 @@ def meta_def(m,&b)
module Camping
C = self
S = IO.read(__FILE__) rescue nil
P = "Cam\ping Problem!"
P = '<h1>Cam\ping Problem!</h1><h2>%s</h2>'
# An object-like Hash.
# All Camping query string and cookie variables are loaded as this.
#
Expand Down Expand Up @@ -322,6 +321,18 @@ def method_missing(*a,&b)
s
end

# A quick means of setting this controller's status, body and headers.
# Used internally by Camping, but... by all means...
#
# r(302, '', 'Location' => self / "/view/12")
#
# Is equivalent to:
#
# redirect "/view/12"
#
# See also: #r404, #r500 and #r501
def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end

# Formulate a redirect response: a 302 status with <tt>Location</tt> header
# and a blank body. Uses Helpers#URL to build the location from a controller
# route or path.
Expand All @@ -338,16 +349,41 @@ def redirect(*a)
r(302,'','Location'=>URL(*a))
end

# A quick means of setting this controller's status, body and headers.
# Used internally by Camping, but... by all means...
# Called when a controller was not found. It is mainly used internally, but it can
# also be useful for you, if you want to filter some parameters.
#
# module Camping
# def r404(p=env.PATH)
# @status = 404
# div do
# h1 'Camping Problem!'
# h2 "#{p} not found"
# end
# end
# end
#
# r(302, '', 'Location' => self / "/view/12")
# See: I
def r404(p=env.PATH)
r(404, P % "#{p} not found")
end

# If there is a parse error in Camping or in your application's source code, it will not be caught
# by Camping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
#
# Is equivalent to:
# You can overide it, but if you have an error in here, it will be uncaught !
#
# redirect "/view/12"
# See: I
def r500(k,m,x)
r(500, P % "#{k}.#{m}" + "<h3>#{x.class} #{x.message}: <ul>#{x.backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>")
end

# Called if an undefined method is called on a Controller, along with the request method +m+ (GET, POST, etc.)
#
def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end
# See: I
def r501(m=@method)
r(501, P % "#{m.upcase} not implemented")
end

# Turn a controller into an array. This is designed to be used to pipe
# controllers into the <tt>r</tt> method. A great way to forward your
Expand All @@ -364,7 +400,7 @@ def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end
def to_a;[status, body, headers] end

def initialize(r, e, m) #:nodoc:
@status, @method, @env, @headers, @root = 200, m.downcase, e,
@status, @method, @env, @headers, @root = 200, m, e,
H['Content-Type','text/html'], e.SCRIPT_NAME.sub(/\/$/,'')
@k = C.kp(e.HTTP_COOKIE)
q = C.qsp(e.QUERY_STRING)
Expand Down Expand Up @@ -417,7 +453,7 @@ def initialize(r, e, m) #:nodoc:
# See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
# on before and after overrides with Camping.
def service(*a)
@body = send(@method, *a) if respond_to? @method
@body = send(@method, *a)
headers['Set-Cookie'] = cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
self
end
Expand Down Expand Up @@ -493,13 +529,14 @@ def R *u
# # Classes with routes are searched in order of their creation.
#
# So, define your catch-all controllers last.
def D(p)
def D(p, m)
r.map { |k|
k.urls.map { |x|
return k, $~[1..-1] if p =~ /^#{x}\/?$/
return (k.instance_method(m) rescue nil) ?
[k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
}
}
[NotFound, [p]]
[I, 'r404', p]
end
# The route maker, this is called by Camping internally, you shouldn't need to call it.
Expand All @@ -517,61 +554,15 @@ def M #:nodoc:
constants.map { |c|
k=const_get(c)
k.send :include,C,Base,Helpers,Models
r[0,0]=k if !r.include?k
@r=[k]+r if r-[k]==r
k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls
}
end
end
# The NotFound class is a special controller class for handling 404 errors, in case you'd
# like to alter the appearance of the 404. The path is passed in as +p+.
#
# module Camping::Controllers
# class NotFound
# def get(p)
# @status = 404
# div do
# h1 'Camping Problem!'
# h2 "#{p} not found"
# end
# end
# end
# end
#
class NotFound < R()
def get(p)
r(404, "<h1>#{P}</h1><h2>#{p} not found</h2>")
end
end
# The ServerError class is a special controller class for handling many (but not all) 500 errors.
# If there is a parse error in Camping or in your application's source code, it will not be caught
# by Camping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
#
# module Camping::Controllers
# class ServerError
# def get(k,m,e)
# @status = 500
# div do
# h1 'Camping Problem!'
# h2 "in #{k}.#{m}"
# h3 "#{e.class} #{e.message}:"
# ul do
# e.backtrace.each do |bt|
# li bt
# end
# end
# end
# end
# end
# end
#
class ServerError < R()
def get(k,m,e)
r(500, "<h1>#{P}</h1><h2>#{k}.#{m}</h2><h3>#{e.class} #{e.message
}:<ul>#{e.backtrace.map{ |b| "<li>#{b}</li>" } }")
end
# Internal controller with no route. Used by #D and C.run to show internal messages.
class I < R()
end
self
Expand Down Expand Up @@ -660,10 +651,10 @@ def kp(s); c = qsp(s, ';,'); end
def run(r=$stdin,e=ENV)
X.M
e = H[e.to_hash]
k,a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/'))
k.new(r,e,(m=e.REQUEST_METHOD||"GET")).Y.service(*a)
rescue=>x
X::ServerError.new(r,e,'get').service(k,m,x)
k,m,*a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')),(e.REQUEST_METHOD||'get').downcase
k.new(r,e,m).Y.service(*a)
rescue => x
X::I.new(r,e,'r500').service(k,m,x)
end
# The Camping scriptable dispatcher. Any unhandled method call to the app module will
Expand Down
117 changes: 60 additions & 57 deletions lib/camping.rb
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
%w[tempfile uri].map{|l|require l};class Object;def meta_def m,&b
(class<<self;self end).send(:define_method,m,&b)end end
module Camping;C=self;S=IO.read(__FILE__)rescue nil;P="Cam\ping Problem!"
class H<Hash;def method_missing m,*a
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end;alias u merge!
undef id, type;end
module Helpers def R c,*g;p,h=/\(.+?\)/,g.grep(Hash);g-=h;
raise"bad route"unless u=c.urls.find{|x|break x if x.scan(p).size==g.size&&
/^#{x}\/?$/=~(x=g.inject(x){|x,a|x.sub p,C.escape((a[a.class.primary_key
]rescue a))})}
h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&":u end
def /(p);p[/^\//]?@root+p:p;end;def URL c='/',*a
c=R(c,*a)if c.respond_to?:urls;c=self/c;c="//"+@env.HTTP_HOST+c if c[/^\//]
URI(c) end end;module Base;attr_accessor:input,:cookies,:env,:headers,:body,
:status,:root;Z="\r\n";def method_missing*a,&b;a.shift if a[0]==:render
m=Mab.new({},self);s=m.capture{send(*a,&b)};s=m.capture{send(:layout){s}}if
/^_/!~a[0].to_s and m.respond_to?:layout;s end;def redirect*a
r 302,'','Location'=>URL(*a)end;def r s,b,h={};@status=s;headers.u h
@body=b end;def to_a;[status,body,headers]end;def initialize r,e,m
@status,@method,@env,@headers,@root=200,m.downcase,e,H[
'Content-Type',"text/html"],e.SCRIPT_NAME.sub(/\/$/,'');@k=C.kp e.HTTP_COOKIE
q=C.qsp e.QUERY_STRING;@in=r;case e.CONTENT_TYPE
when %r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n
b=/(?:\r?\n|\A)#{Regexp::quote"--#$1"}(?:--)?\r$/;until
@in.eof?;fh=H[];for l in@in;case l;when Z;break;when/^Content-D.+?: form-data;/
fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten];when
/^Content-Type: (.+?)(\r$|\Z)/m;fh.type=$1;end;end;fn=fh.name;o=if fh.
filename;o=fh.tempfile=Tempfile.new(:C);o.binmode;else;fh=""end;s=8192;k=''
l=@in.read(s*2);while l;if(k<<l)=~b;o<<$`.chomp;@in.seek(-$'.size,IO::SEEK_CUR)
break;end;o<<k.slice!(0...s);[email protected](s);end;C.qsp(fn,'&;',fh,q)if fn;fh.
tempfile.rewind if fh.is_a?H;end;when "application/x-www-form-urlencoded"
q.u C.qsp(@in.read)end;@cookies,@[email protected],q.dup end;def service*a
@body=send(@method,*a)if respond_to?@method
headers["Set-Cookie"]=cookies.map{|k,v|"#{k}=#{C.escape v}; path=#{self/'/'}"if
v!=@k[k]}-[nil];self end;def to_s;"Status: #@status#{Z+headers.map{|k,v|[*v].
map{|x|[k,v]*": "}}*Z+Z}#@body"end;end;X=module Controllers;@r=[];class<<
self;def r;@r;end;def R*u;r=@r;Class.new{meta_def(:urls){u};meta_def(:inherited
){|x|r<<x}}end;def D p;r.map{|k|k.urls.map{|x|return k,$~[1..-1]if p=~/^#{x
}\/?$/}};[NotFound,[p]]end;def M;def M;end;constants.map{|c|k=const_get(c);k.
send:include,C,Base,Helpers,Models;r[0,0]=k if !r.include?k;k.meta_def(:urls){[
"/#{c.downcase}"]}if !k.respond_to?:urls}end;end;class NotFound<R();def get p
r(404,"<h1>#{P}</h1><h2>#{p} not found</h2>")end end;class ServerError<R()
def get k,m,e;r(500,"<h1>#{P}</h1><h2>#{k}.#{m}</h2><h3>#{e.class} #{e.message
}:<ul>#{e.backtrace.map{ |b| "<li>#{b}</li>" } }")end end;self;end;class<<self
def goes m;eval S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING;end;def escape s
s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ','+')
end;def un s;s.tr('+',' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}end
%w[tempfile uri].map{|l|require l};class Object;def meta_def m,&b;(class<<self
self end).send:define_method,m,&b end end;module Camping;C=self
S=IO.read(__FILE__)rescue nil;P="<h1>Cam\\ping Problem!</h1><h2>%s</h2>"
class H<Hash
def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end
alias u merge!;undef id,type;end;module Helpers;def R c,*g
p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|
break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|
x.sub p,C.escape((a[a.class.primary_key]rescue a))})}
h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&":u end;def / p
p[/^\//]?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
c=self/c;c="//"+@env.HTTP_HOST+c if c[/^\//];URI c end end;module Base
attr_accessor:input,:cookies,:env,:headers,:body,:status,:root;Z="\r\n"
def method_missing *a,&b;a.shift if a[0]==:render;m=Mab.new({},self)
s=m.capture{send(*a,&b)};s=m.capture{send(:layout){s}}if/^_/!~a[0].to_s and
m.respond_to?:layout;s end;def r s,b,h={};@status=s;headers.u(h);@body=b
end;def redirect *a;r 302,'','Location'=>URL(*a)end;def r404 p=env.PATH
r 404,P%"#{p} not found"end;def r500 k,m,x
r 500,P%"#{k}.#{m}"+"<h3>#{x.class} #{x.message}: <ul>#{x.
backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>"end;def r501 m=@method
r 501,P%"#{m.upcase} not implemented"end;def to_a
[status,body,headers]end;def initialize r,e,m;@status,@method,@env,@headers,
@root=200,m,e,H['Content-Type','text/html'],e.SCRIPT_NAME.sub(/\/$/,'')
@k=C.kp e.HTTP_COOKIE;q=C.qsp e.QUERY_STRING;@in=r;case e.CONTENT_TYPE
when%r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n
b=/(?:\r?\n|\A)#{Regexp.quote"--#$1"}(?:--)?\r$/;until@in.eof?;fh=H[]
for l in@in;case l;when Z;break;when/^Content-D.+?: form-data;/
fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
when/^Content-Type: (.+?)(\r$|\Z)/m: fh.type = $1 end end;fn=fh.name
o=if fh.filename;o=fh.tempfile=Tempfile.new(:C);o.binmode;else;fh="";end;s=8192
k='';l=@in.read(s*2);while l;if(k<<l)=~b;o<<$`.chomp
@in.seek(-$'.size,IO::SEEK_CUR);break end;o<<k.slice!(0...s);[email protected](s) end
C.qsp(fn,'&;',fh,q)if fn;fh.tempfile.rewind if fh.is_a?H end;when
"application/x-www-form-urlencoded": q.u(C.qsp(@in.read))end
@cookies,@[email protected],q.dup end;def service *a;@body=send @method,*a
headers['Set-Cookie']=cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/
"/"}"if v!=@k[k]}-[nil];self end;def to_s
"Status: #@status#{Z+headers.map{|k,v|[*v].map{|x|[k,v]*": "}}*Z+Z}#@body"end
end;X=module Controllers;@r=[];class<<self;def r;@r end;def R *u;r=@r
Class.new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end
def D p,m;r.map{|k|k.urls.map{|x|return(k.instance_method(m)rescue nil)?
[k,m,*$~[1..-1]]:[I,'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p]
end;def M;def M;end;constants.map{|c|k=const_get(c)
k.send:include,C,Base,Helpers,Models;@r=[k]+r if r-[k]==r
k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls}end end;class I<R()
end;self end;class<<self;def goes m
eval S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING end;def escape s
s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr' ','+'
end;def un s;s.tr('+',' ').gsub(/%([\da-f]{2})/in){[$1].pack'H*'}end
def qsp q,d='&;',y=nil,z=H[];m=proc{|_,o,n|o.u(n,&m)rescue([*o]<<n)}
(q.to_s.split(/[#{d}]+ */n)-['']).inject((b,z=z,H[])[0]){|h,p|k,v=un(p).
split('=',2);h.u k.split(/[\]\[]+/).reverse.inject(y||v){|x,i|H[i,x]},&m}end
def kp s;c=qsp(s,';,')end;def run r=$stdin,e=ENV;X.M;e=H[e.to_hash]
k,a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/'));k.new(
r,e,(m=e.REQUEST_METHOD||"GET")).Y.service(*a);rescue=>x;X::ServerError.new(
r,e,'get').service(k,m,x)end;def method_missing m,c,*a;X.M;k=X.const_get(c).
new(StringIO.new,H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s)
H[a.pop].each{|e,f|k.send("#{e}=",f)}if Hash===a[-1];k.service(*a);end;end
module Views;include X,Helpers;end;module Models;autoload:Base,'camping/db';def
Y;self;end;end;autoload:Mab,'camping/mab'end
(q.to_s.split(/[#{d}]+ */n)-[""]).inject((b,z=z,H[])[0]){|h,p|k,v=un(p).
split'=',2;h.u k.split(/[\]\[]+/).reverse.inject(y||v){|x,i|H[i,x]},&m}end
def kp s;c=qsp s,';,'end;def run r=$stdin,e=ENV;X.M;e=H[e.to_hash];k,m,*a=X.D e.
PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')),
(e.REQUEST_METHOD||'get').downcase
k.new(r,e,m).Y.service(*a);rescue=>x;X::I.new(r,e,'r500').service k,m,x
end;def method_missing m,c,*a;X.M;k=X.const_get(c).new StringIO.new,
H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s
H[a.pop].each{|e,f|k.send"#{e}=",f}if Hash===a[-1];k.service(*a)end end
module Views;include X,Helpers end;module Models;autoload:Base,'camping/db'
def Y;self;end end;autoload:Mab,'camping/mab'end
5 changes: 1 addition & 4 deletions lib/camping/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ def apps
private

def insert_app(script)
self[script] = Application.new(script)
self[script] = Reloader.new(script)
end
end

class Application < Camping::Reloader
end
end

Loading

0 comments on commit d485565

Please sign in to comment.