# Built on Rackrequire"rack"require"rack/file"moduleMiddlemanmoduleCoreExtensions# Base helper to manipulate asset pathsmoduleRequest# Extension registeredclass<<self# @privatedefregistered(app)# CSSPIE HTC File::Rack::Mime::MIME_TYPES['.htc']='text/x-component'# Let's serve all HTML as UTF-8::Rack::Mime::MIME_TYPES['.html']='text/html; charset=utf-8'::Rack::Mime::MIME_TYPES['.htm']='text/html; charset=utf-8'app.extendClassMethodsapp.extendServerMethodsMiddleman.extendCompatibleClassMethods# Include instance methodsapp.send:include,InstanceMethodsendalias:included:registeredendmoduleClassMethods# Reset Rack setup## @privatedefreset!@app=nilend# The shared Rack instance being build## @private# @return [Rack::Builder]defapp@app||=::Rack::Builder.newend# Get the static instance## @private# @return [Middleman::Application]definst(&block)@inst||=beginmm=new(&block)mm.run_hook:readymmendend# Set the shared instance## @private# @param [Middleman::Application] inst# @return [void]definst=(inst)@inst=instend# Return built Rack app## @private# @return [Rack::Builder]defto_rack_app(&block)inner_app=inst(&block)(@middleware||[]).eachdo|m|app.use(m[0],*m[1],&m[2])endapp.map("/"){runinner_app}(@mappings||[]).eachdo|m|app.map(m[0],&m[1])endappend# Prototype app. Used in config.ru## @private# @return [Rack::Builder]defprototypereset!to_rack_append# Call prototype, use in config.ru## @privatedefcall(env)prototype.call(env)end# Use Rack middleware## @param [Class] middleware Middleware module# @return [void]defuse(middleware,*args,&block)@middleware||=[]@middleware<<[middleware,args,block]end# Add Rack App mapped to specific path## @param [String] map Path to map# @return [void]defmap(map,&block)@mappings||=[]@mappings<<[map,block]endendmoduleServerMethods# Create a new Class which is based on Middleman::Application# Used to create a safe sandbox into which extensions and# configuration can be included later without impacting# other classes and instances.## @return [Class]defserver(&block)@@servercounter||=0@@servercounter+=1const_set("MiddlemanApplication#{@@servercounter}",Class.new(Middleman::Application))endendmoduleCompatibleClassMethods# Create a new Class which is based on Middleman::Application# Used to create a safe sandbox into which extensions and# configuration can be included later without impacting# other classes and instances.## @return [Class]defserver(&block)::Middleman::Application.serverendend# Methods to be mixed-in to Middleman::ApplicationmoduleInstanceMethods# Backwards-compatibility with old request.path signatureattr_reader:request# Accessor for current path# @return [String]attr_reader:current_path# Set the current path## @param [String] path The new current path# @return [void]defcurrent_path=(path)@current_path=path@request=::Thor::CoreExt::HashWithIndifferentAccess.new({:path=>path,:params=>req?::Thor::CoreExt::HashWithIndifferentAccess.new(req.params):{}})enddefuse(*args,&block);self.class.use(*args,&block);enddefmap(*args,&block);self.class.map(*args,&block);end# Rack envattr_accessor:env# Rack request# @return [Rack::Request]attr_accessor:req# Rack response# @return [Rack::Response]attr_accessor:resdefcall(env)dup.call!(env)end# Rack Interface## @param env Rack environmentdefcall!(env)self.env=env# Store environment, request and response for laterself.req=req=::Rack::Request.new(env)self.res=res=::Rack::Response.newlogger.debug"== Request: #{env["PATH_INFO"]}"# Catch :halt exceptions and use that response if givencatch(:halt)doprocess_request(env,req,res)res.status=404res.finishendend# Halt the current request and return a response## @param [String] response Response valuedefhalt(response)throw:halt,responseend# Core response method. We process the request, check with# the sitemap, and return the correct file, response or status# message.## @param env# @param [Rack::Request] req# @param [Rack::Response] resdefprocess_request(env,req,res)start_time=Time.now@current_path=nilrequest_path=URI.decode(env["PATH_INFO"].dup)ifrequest_path.respond_to?:force_encodingrequest_path.force_encoding('UTF-8')endrequest_path=full_path(request_path)# Run before callbacksrun_hook:before# Get the resource object for this pathresource=sitemap.find_resource_by_destination_path(request_path)# Return 404 if not in sitemapreturnnot_found(res,request_path)unlessresource&&!resource.ignored?current_path=resource.destination_path# If this path is a static file, send it immediatelyreturnsend_file(resource.source_file,env,res)unlessresource.template?# Set a HTTP content type based on the request's extensionscontent_type(res,resource.mime_type)begin# Write out the contents of the pageoutput=resource.renderdoself.req=reqself.current_path=current_pathendres.writeoutput# Valid content is a 200 statusres.status=200rescueMiddleman::CoreExtensions::Rendering::TemplateNotFound=>eres.write"Error: #{e.message}"res.status=500end# End the requestlogger.debug"== Finishing Request: #{current_path} (#{(Time.now-start_time).round(2)}s)"haltres.finishend# Add a new mime-type for a specific extension## @param [Symbol] type File extension# @param [String] value Mime type# @return [void]defmime_type(type,value=nil)returntypeiftype.nil?||type.to_s.include?('/')type=".#{type}"unlesstype.to_s[0]==?.return::Rack::Mime.mime_type(type,nil)unlessvalue::Rack::Mime::MIME_TYPES[type]=valueend# Halt request and return 404defnot_found(res,path)res.status==404res.write"<html><body><h1>File Not Found</h1><p>#{path}</p></body>"res.finishend# Immediately send static file## @param [String] path File to senddefsend_file(path,env,res)extension=File.extname(path)matched_mime=mime_type(extension)matched_mime="application/octet-stream"ifmatched_mime.nil?content_typeres,matched_mimefile=::Rack::File.newnilfile.path=pathresponse=file.serving(env)response[1]['Content-Encoding']='gzip'if%w(.svgz).include?(extension)haltresponseend# Set the content type for the current request## @param [String] type Content type# @param [Hash] params# @return [void]defcontent_type(res,type,params={})returnres['Content-Type']unlesstypedefault=params.delete:defaultmime_type=mime_type(type)||defaultthrow"Unknown media type: %p"%typeifmime_type.nil?mime_type=mime_type.dupunlessparams.include?:charsetparams[:charset]=params.delete('charset')||"utf-8"endparams.delete:charsetifmime_type.include?'charset'unlessparams.empty?mime_type<<(mime_type.include?(';')?', ':';')mime_type<<params.map{|kv|kv.join('=')}.join(', ')endres['Content-Type']=mime_typeendendendendend