# Built on Rackrequire'rack'require'rack/file'require'rack/lint'require'rack/head'require'middleman-core/util'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'# Sourcemap format::Rack::Mime::MIME_TYPES['.map']='application/json; charset=utf-8'app.extendClassMethodsapp.extendServerMethodsMiddleman.extendCompatibleClassMethods# Include instance methodsapp.send:include,InstanceMethodsendalias_method:included,:registeredendmoduleClassMethods# Reset Rack setup## @privatedefreset!@rack_app=nilend# 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]attr_writer:inst# Return built Rack app## @private# @return [Rack::Builder]defto_rack_app(&block)@rack_app||=beginapp=::Rack::Builder.newapp.useRack::Lintapp.useRack::HeadArray(@middleware).eachdo|klass,options,middleware_block|app.use(klass,*options,&middleware_block)endinner_app=inst(&block)app.map('/'){runinner_app}Array(@mappings).eachdo|path,map_block|app.map(path,&map_block)endappendend# 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.## rubocop:disable ClassVars# @return [Class]defserver(&block)@@servercounter||=0@@servercounter+=1const_set("MiddlemanApplication#{@@servercounter}",Class.new(Middleman::Application,&block))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.server(&block)endend# Methods to be mixed-in to Middleman::ApplicationmoduleInstanceMethods# Backwards-compatibility with old request.path signaturedefrequestThread.current[:legacy_request]end# Accessor for current path# @return [String]defcurrent_pathThread.current[:current_path]end# Set the current path## @param [String] path The new current path# @return [void]defcurrent_path=(path)Thread.current[:current_path]=pathThread.current[:legacy_request]=::Thor::CoreExt::HashWithIndifferentAccess.new(path: path,params: req?::Thor::CoreExt::HashWithIndifferentAccess.new(req.params):{})enddelegate:use,to: :"self.class"delegate:map,to: :"self.class"# Rack request# @return [Rack::Request]defreqThread.current[:req]enddefreq=(value)Thread.current[:req]=valueenddefcall(env)dup.call!(env)end# Rack Interface## @param env Rack environmentdefcall!(env)# Store environment, request and response for laterself.req=req=::Rack::Request.new(env)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.nowcurrent_path=nilrequest_path=URI.decode(env['PATH_INFO'].dup)ifrequest_path.respond_to?:force_encodingrequest_path.force_encoding('UTF-8')endrequest_path=::Middleman::Util.full_path(request_path,self)# 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?# If this path is a binary file, send it immediatelyreturnsend_file(resource,env)ifresource.binary?current_path=resource.destination_pathres['Content-Type']=resource.content_type||'text/plain'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)type=".#{type}"unlesstype.to_s[0]=='.'::Rack::Mime::MIME_TYPES[type]=valueend# Halt request and return 404defnot_found(res,path)res.status=404res.write"<html><head></head><body><h1>File Not Found</h1><p>#{path}</p></body></html>"res.finishend# Immediately send static filedefsend_file(resource,env)file=::Rack::File.newnilfile.path=resource.source_fileresponse=file.serving(env)status=response[0]response[1]['Content-Encoding']='gzip'if%w(.svgz .gz).include?(resource.ext)# Do not set Content-Type if status is 1xx, 204, 205 or 304, otherwise# Rack will throw an error (500)if!(100..199).include?(status)&&![204,205,304].include?(status)response[1]['Content-Type']=resource.content_type||'application/octet-stream'endhaltresponseendendendendend