From 1a02907a2b103488c9e47cac5039a01dc6c7c27e Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Fri, 23 Nov 2007 17:32:21 -0800 Subject: [PATCH] static files --- lib/sinatra.rb | 43 ++++++- lib/sinatra/mime_types.rb | 256 ++++++++++++++++++++++++++++++++++++++ lib/sinatra/test.rb | 6 +- test/public/foo.foo | 1 + test/public/foo.xml | 1 + test/public/test.js | 1 + test/public/xyz | 1 + test/static_test.rb | 65 ++++++++++ 8 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 lib/sinatra/mime_types.rb create mode 100644 test/public/foo.foo create mode 100644 test/public/foo.xml create mode 100644 test/public/test.js create mode 100644 test/public/xyz create mode 100644 test/static_test.rb diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 796f2cf1..1b23d394 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -1,6 +1,15 @@ require "rubygems" require "rack" +require 'sinatra/mime_types' + +def silence_warnings + old_verbose, $VERBOSE = $VERBOSE, nil + yield +ensure + $VERBOSE = old_verbose +end + class String def to_param URI.escape(self) @@ -87,7 +96,7 @@ module Sinatra end def config - @config ||= @default_config + @config ||= @default_config.dup end def config=(c) @@ -98,7 +107,9 @@ module Sinatra @default_config ||= { :run => true, :raise_errors => false, - :env => :development + :env => :development, + :root => File.dirname($0), + :default_static_mime_type => 'text/plain' } end @@ -106,8 +117,23 @@ module Sinatra routes[verb].eject { |r| r.match(path) } || routes[404] end + def content_type_for(path) + ext = File.extname(path)[1..-1] + Sinatra.mime_types[ext] || config[:default_static_mime_type] + end + def call(env) request = Rack::Request.new(env) + + path = Sinatra.config[:root] + '/public' + request.path_info + if File.file?(path) + headers = { + 'Content-Type' => Array(content_type_for(path)), + 'Content-Length' => Array(File.size(path)) + } + return [200, headers, File.read(path)] + end + response = Rack::Response.new route = determine_route( request.request_method.downcase.to_sym, @@ -139,10 +165,12 @@ module Sinatra end class Route - + URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR) PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM) + Result = Struct.new(:path, :block, :params, :default_status) + attr_reader :block, :path def initialize(path, &b) @@ -153,13 +181,12 @@ module Sinatra "(#{URI_CHAR}+)" end @pattern = /^#{regex}$/ - @struct = Struct.new(:path, :block, :params, :default_status) end def match(path) return nil unless path =~ @pattern params = @param_keys.zip($~.captures.map(&:from_param)).to_hash - @struct.new(@path, @block, params, 200) + Result.new(@path, @block, params, 200) end end @@ -177,7 +204,7 @@ module Sinatra end end - + end def get(*paths, &b) @@ -189,4 +216,8 @@ def error(*codes, &b) codes.each { |code| Sinatra.define_error(code, &b) } end +def mime_type(content_type, *exts) + exts.each { |ext| Sinatra::MIME_TYPES.merge(ext.to_s, content_type) } +end + Sinatra.setup_default_events! diff --git a/lib/sinatra/mime_types.rb b/lib/sinatra/mime_types.rb new file mode 100644 index 00000000..4b133385 --- /dev/null +++ b/lib/sinatra/mime_types.rb @@ -0,0 +1,256 @@ +module Sinatra + + def mime_types + + @default_mime_types ||= { + "csh"=>"application/x-csh", + "x_t"=>"model/vnd.parasolid.transmit.text", + "ksp"=>"application/vnd.kde.kspread", + "fsc"=>"application/vnd.fsc.weblaunch", + "vcs"=>"text/x-vcalendar", + "hvs"=>"application/vnd.yamaha.hv-script", + "seml"=>"application/vnd.sealed.eml", + "movie"=>"video/x-sgi-movie", + "plt"=>"application/vnd.hp-HPGL", + "wav"=>"audio/x-wav", + "3gpp"=>"audio/3gpp", + "eol"=>"audio/vnd.digital-winds", + "rtf"=>"application/rtf", + "kfo"=>"application/vnd.kde.kformula", + "rdf"=>"application/rdf+xml", + "rgb"=>"image/x-rgb", + "wpd"=>"application/vnd.wordperfect", + "sit"=>"application/x-stuffit", + "ogg"=>"application/ogg", + "pbm"=>"image/x-portable-bitmap", + "dwf"=>"x-drawing/dwf", + "smp3"=>"audio/vnd.sealedmedia.softseal.mpeg", + "html"=>"text/html", + "dwg"=>"image/vnd.dwg", + "see"=>"application/vnd.seemail", + "igs"=>"model/iges", + "siv"=>"application/sieve", + "jad"=>"text/vnd.sun.j2me.app-descriptor", + "wml"=>"text/vnd.wap.wml", + "ustar"=>"application/x-ustar", + "vis"=>"application/vnd.visionary", + "pkipath"=>"application/pkix-pkipath", + "ecelp4800"=>"audio/vnd.nuera.ecelp4800", + "ice"=>"x-conference/x-cooltalk", + "qcp"=>"audio/QCELP", + "ai"=>"application/postscript", + "sppt"=>"application/vnd.sealed.ppt", + "ltx"=>"application/x-latex", + "nim"=>"video/vnd.nokia.interleaved-multimedia", + "igx"=>"application/vnd.micrografx.igx", + "viv"=>"video/vnd.vivo", + "wpl"=>"application/vnd.ms-wpl", + "ami"=>"application/vnd.amiga.ami", + "tcl"=>"application/x-tcl", + "l16"=>"audio/L16", + "css"=>"text/css", + "vbk"=>"audio/vnd.nortel.vbk", + "pki"=>"application/pkixcmp", + "ras"=>"image/x-cmu-raster", + "jpeg"=>"image/jpeg", + "chrt"=>"application/vnd.kde.kchart", + "cil"=>"application/vnd.ms-artgalry", + "mxu"=>"video/vnd.mpegurl", + "xwd"=>"image/x-xwindowdump", + "kon"=>"application/vnd.kde.kontour", + "dgn"=>"image/x-vnd.dgn", + "csv"=>"text/csv", + "png"=>"image/png", + "xfdf"=>"application/vnd.adobe.xfdf", + "asf"=>"application/vnd.ms-asf", + "sxls"=>"application/vnd.sealed.xls", + "dl"=>"video/dl", + "karbon"=>"application/vnd.kde.karbon", + "ico"=>"image/vnd.microsoft.icon", + "sus"=>"application/vnd.sus-calendar", + "pdb"=>"x-chemical/x-pdb", + "wif"=>"application/watcherinfo+xml", + "ser"=>"application/x-java-serialized-object", + "mdi"=>"image/vnd.ms-modi", + "qt"=>"video/quicktime", + "rtx"=>"text/richtext", + "jar"=>"application/x-java-archive", + "rcprofile"=>"application/vnd.ipunplugged.rcprofile", + "tsv"=>"text/tab-separated-values", + "pnm"=>"image/x-portable-anymap", + "me"=>"application/x-troff-me", + "amr"=>"audio/AMR", + "wp5"=>"application/wordperfect5.1", + "gl"=>"video/gl", + "man"=>"application/x-troff-man", + "pdf"=>"application/pdf", + "pgb"=>"image/vnd.globalgraphics.pgb", + "au"=>"audio/basic", + "avi"=>"video/x-msvideo", + "wmlsc"=>"application/vnd.wap.wmlscriptc", + "wbxml"=>"application/vnd.wap.wbxml", + "saf"=>"application/vnd.yamaha.smaf-audio", + "gtar"=>"application/x-gtar", + "crl"=>"application/pkix-crl", + "pti"=>"application/vnd.pvi.ptid1", + "rdz"=>"application/vnd.data-vision.rdz", + "flo"=>"application/vnd.micrografx.flo", + "aif"=>"audio/x-aiff", + "qxd"=>"application/vnd.Quark.QuarkXPress", + "kne"=>"application/vnd.Kinar", + "rpm"=>"audio/x-pn-realaudio-plugin", + "lvp"=>"audio/vnd.lucent.voice", + "stml"=>"application/vnd.sealedmedia.softseal.html", + "sc"=>"application/vnd.ibm.secure-container", + "jnlp"=>"application/x-java-jnlp-file", + "smov"=>"video/vnd.sealedmedia.softseal.mov", + "dvi"=>"application/x-dvi", + "jisp"=>"application/vnd.jisp", + "wv"=>"application/vnd.wv.csp+wbxml", + "mseq"=>"application/vnd.mseq", + "mmf"=>"application/vnd.smaf", + "mpc"=>"application/vnd.mophun.certificate", + "stk"=>"application/hyperstudio", + "txd"=>"application/vnd.genomatix.tuxedo", + "ent"=>"application/vnd.nervana", + "hdf"=>"application/x-hdf", + "xml"=>"application/xml", + "mpga"=>"audio/mpeg", + "sh"=>"application/x-sh", + "pgm"=>"image/x-portable-graymap", + "lbd"=>"application/vnd.llamagraphics.life-balance.desktop", + "flw"=>"application/vnd.kde.kivio", + "si"=>"text/vnd.wap.si", + "zip"=>"application/zip", + "ecelp7470"=>"audio/vnd.nuera.ecelp7470", + "lbe"=>"application/vnd.llamagraphics.life-balance.exchange+xml", + "p10"=>"application/pkcs10", + "ief"=>"image/ief", + "doc"=>"application/msword", + "efif"=>"application/vnd.picsel", + "jpm"=>"image/jpm", + "gz"=>"application/x-gzip", + "xhtml"=>"application/xhtml+xml", + "irm"=>"application/vnd.ibm.rights-management", + "bcpio"=>"application/x-bcpio", + "mcd"=>"application/vnd.mcd", + "ecelp9600"=>"audio/vnd.nuera.ecelp9600", + "ms"=>"application/x-troff-ms", + "gif"=>"image/gif", + "kwd"=>"application/vnd.kde.kword", + "wrl"=>"model/vrml", + "sl"=>"text/vnd.wap.sl", + "xpm"=>"image/x-xpixmap", + "cer"=>"application/pkix-cert", + "mxmf"=>"audio/vnd.nokia.mobile-xmf", + "psb"=>"application/vnd.3gpp.pic-bw-small", + "cab"=>"application/vnd.ms-cab-compressed", + "rst"=>"text/prs.fallenstein.rst", + "p7m"=>"application/pkcs7-mime", + "spng"=>"image/vnd.sealed.png", + "mj2"=>"video/MJ2", + "wbmp"=>"image/vnd.wap.wbmp", + "irp"=>"application/vnd.irepository.package+xml", + "smht"=>"application/vnd.sealed.mht", + "msh"=>"model/mesh", + "htke"=>"application/vnd.kenameaapp", + "s11"=>"video/vnd.sealed.mpeg1", + "mpm"=>"application/vnd.blueice.multipass", + "mpn"=>"application/vnd.mophun.application", + "dfac"=>"application/vnd.dreamfactory", + "pvb"=>"application/vnd.3gpp.pic-bw-var", + "lrm"=>"application/vnd.ms-lrm", + "tiff"=>"image/tiff", + "jp2"=>"image/jp2", + "rpss"=>"application/vnd.nokia.radio-presets", + "wmlc"=>"application/vnd.wap.wmlc", + "rpst"=>"application/vnd.nokia.radio-preset", + "etx"=>"text/x-setext", + "bmp"=>"image/bmp", + "mpp"=>"application/vnd.ms-project", + "spf"=>"application/vnd.yamaha.smaf-phrase", + "3gp"=>"video/3gpp", + "mid"=>"audio/x-midi", + "hqx"=>"application/mac-binhex40", + "p7s"=>"application/pkcs7-signature", + "wbs"=>"application/vnd.criticaltools.wbs+xml", + "emm"=>"application/vnd.ibm.electronic-media", + "ppm"=>"image/x-portable-pixmap", + "texinfo"=>"application/x-texinfo", + "mp2"=>"video/mpeg", + "jpx"=>"image/jpx", + "evc"=>"audio/EVRC", + "mif"=>"application/x-mif", + "ra"=>"audio/x-realaudio", + "spdf"=>"application/vnd.sealedmedia.softseal.pdf", + "x_b"=>"model/vnd.parasolid.transmit.binary", + "class"=>"application/x-java-vm", + "txt"=>"text/plain", + "mp4"=>"video/vnd.objectvideo", + "hbci"=>"application/vnd.hbci", + "wqd"=>"application/vnd.wqd", + "vsc"=>"application/vnd.vidsoft.vidconference", + "mfm"=>"application/vnd.mfmp", + "sgml"=>"text/sgml", + "tex"=>"application/x-tex", + "curl"=>"application/vnd.curl", + "djvu"=>"image/vnd.djvu", + "cw"=>"application/prs.cww", + "vsd"=>"application/vnd.visio", + "tga"=>"image/targa", + "wtb"=>"application/vnd.webturbo", + "plb"=>"application/vnd.3gpp.pic-bw-large", + "t"=>"text/troff", + "les"=>"application/vnd.hhe.lesson-player", + "sms"=>"application/vnd.3gpp.sms", + "hvd"=>"application/vnd.yamaha.hv-dic", + "yaml"=>"text/x-yaml", + "ppt"=>"application/powerpoint", + "psp"=>"image/x-paintshoppro", + "sic"=>"application/vnd.wap.sic", + "cmc"=>"application/vnd.cosmocaller", + "vcf"=>"text/x-vcard", + "sjpg"=>"image/vnd.sealedmedia.softseal.jpg", + "sid"=>"audio/prs.sid", + "sv4cpio"=>"application/x-sv4cpio", + "sswf"=>"video/vnd.sealed.swf", + "cpio"=>"application/x-cpio", + "smv"=>"audio/SMV", + "wks"=>"application/vnd.lotus-1-2-3", + "sig"=>"application/pgp-signature", + "slc"=>"application/vnd.wap.slc", + "z"=>"application/x-compressed", + "rm"=>"audio/x-pn-realaudio", + "bin"=>"application/octet-stream", + "smpg"=>"video/vnd.sealed.mpeg4", + "wmls"=>"text/vnd.wap.wmlscript", + "atc"=>"application/vnd.acucorp", + "pfr"=>"application/font-tdpfr", + "plj"=>"audio/vnd.everad.plj", + "rnd"=>"application/prs.nprend", + "xls"=>"application/excel", + "sdoc"=>"application/vnd.sealed.doc", + "tar"=>"application/x-tar", + "oda"=>"application/oda", + "kia"=>"application/vnd.kidspiration", + "prc"=>"application/vnd.palm", + "sgif"=>"image/vnd.sealedmedia.softseal.gif", + "soc"=>"application/sgml-open-catalog", + "xyz"=>"x-chemical/x-xyz", + "awb"=>"audio/AMR-WB", + "xbm"=>"image/x-xbitmap", + "ccc"=>"text/vnd.net2phone.commcenter.command", + "xul"=>"application/vnd.mozilla.xul+xml", + "cdy"=>"application/vnd.cinderella", + "kpr"=>"application/vnd.kde.kpresenter", + "shar"=>"application/x-shar", + "src"=>"application/x-wais-source", + "hvp"=>"application/vnd.yamaha.hv-voice", + "nc"=>"application/netcdf", + "sv4crc"=>"application/x-sv4crc", + "js" => "text/javascript" + } + + end + +end diff --git a/lib/sinatra/test.rb b/lib/sinatra/test.rb index 74143686..a3ed22b4 100644 --- a/lib/sinatra/test.rb +++ b/lib/sinatra/test.rb @@ -1,10 +1,6 @@ require 'test/unit' require File.dirname(__FILE__) + "/test/methods" -class Test::Unit::TestCase - include Sinatra::Test::Methods -end - -include Sinatra::Test::Methods +Test::Unit::TestCase.send :include, Sinatra::Test::Methods Sinatra.default_config[:raise_errors] = true diff --git a/test/public/foo.foo b/test/public/foo.foo new file mode 100644 index 00000000..ba0e162e --- /dev/null +++ b/test/public/foo.foo @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/test/public/foo.xml b/test/public/foo.xml new file mode 100644 index 00000000..54181ed4 --- /dev/null +++ b/test/public/foo.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/public/test.js b/test/public/test.js new file mode 100644 index 00000000..69a82a27 --- /dev/null +++ b/test/public/test.js @@ -0,0 +1 @@ +var i = 11; \ No newline at end of file diff --git a/test/public/xyz b/test/public/xyz new file mode 100644 index 00000000..f2ba8f84 --- /dev/null +++ b/test/public/xyz @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/test/static_test.rb b/test/static_test.rb new file mode 100644 index 00000000..d8462fa0 --- /dev/null +++ b/test/static_test.rb @@ -0,0 +1,65 @@ +require File.dirname(__FILE__) + '/helper' + +context "Static" do + + setup do + Sinatra.routes.clear + Sinatra.config = nil + Sinatra.setup_default_events! + + Sinatra.config[:root] = File.dirname(__FILE__) + end + + specify "sends files" do + + get_it '/foo.xml' + + should.be.ok + body.should.equal '' + headers.should.equal 'Content-Type' => 'application/xml', + 'Content-Length' => ''.size + + end + + specify "defaults to text/plain" do + + get_it '/foo.foo' + + should.be.ok + body.should.equal 'bar' + headers.should.equal 'Content-Type' => 'text/plain', + 'Content-Length' => 'bar'.size + end + + specify "default to user definied type" do + Sinatra.config[:default_static_mime_type] = 'foo/bar' + + get_it '/foo.foo' + + should.be.ok + body.should.equal 'bar' + headers.should.equal 'Content-Type' => 'foo/bar', + 'Content-Length' => 'bar'.size + end + + specify "handles files without ext" do + get_it '/xyz' + + should.be.ok + body.should.equal 'abc' + headers.should.equal 'Content-Type' => 'text/plain', + 'Content-Length' => 'bar'.size + end + + specify "should handle javascript correctly" do + + get_it '/test.js' + + should.be.ok + body.should.equal 'var i = 11;' + headers.should.equal 'Content-Type' => 'text/javascript', + 'Content-Length' => 'var i = 11;'.size + + end + +end