Skip to content

Introduction to File Endpoints

thofrey edited this page Apr 1, 2014 · 8 revisions

Table of Contents

  1. Introduction to Endpoints
  2. The File Endpoint
  3. File Endpoint Configuration
  4. Restricting Access
  5. Customizing the Behavior of the Endpoint
  6. Getting endpoint specific URLs
  7. Differences Between cfcontent and Apache mod_xsendfile

Introduction to Endpoints

In all simplicity, endpoints are simple and lightweight "servlet" like handlers that accept requests and respond to them as required. Since endpoints will be used for non-human interactions (non-GUI), they are purposely designed to be extremely lightweight, and singular in purpose by performing without the need of plugins, filters, subroutines or other Mach-II constructs that the full Mach-II event request lifecycle provides. However, an endpoint does have the ability to invoke the full Mach-II request lifecycle if required. Since endpoints are registered with the framework itself, the deployment of Mach-II applications that leverage other technologies would be simplified.

In the end, endpoints make it simple to expose services such as REST, file serving, etc. that leverage Mach-II's mature application structure. All endpoints have full access to Mach-II's ColdSpring integration (via the depends attribute), which provides hooks into other features like Mach-II's Caching and Logging. We've worked hard to make it as easy as possible for developers to quickly create intuitive, high-performance API "servlets."

The File Endpoint

An endpoint should respond in a manner that is valid for the type of endpoint it is. In this case, the file endpoint use cfcontent to serve the user the requested file. Alternatively, the endpoint can be configured send a HTML header so the document could be served by Apache x-sendfile so the CFML engine does not have to tie up threads serving files.

For example, the Mach-II Dashboard leverages a file endpoint so deployment of the Dashboard does not have to be complicated by any copying of image / javascript / css asset files into a webroot.

Features

  • Configurable serving engine (cfcontent or Apache mod x-sendfile)
  • Setting expires headers based on file type
  • Serve file as attachment based on file type
  • Conditionally return 304 "not modified" header if file modification time stamp and header sent by browser match
  • Set a timestamp to the URL for browser caching
  • Basic security measures such as restricting by file type, directory location, etc.

Why use the file endpoint?

  • You would like to serve protected files
  • You would like to serve files located outside of your webroot
  • You would like to serve files with timestamps on the URI and expire headers
  • You would like to serve files using Apache mod_xsendfile
  • You would like to serve files using cfcontent
  • You would like to serve a .cfm file as another file type (mycss.cfm -> mycss.css) because you need to dynamically generate output

File Endpoint Configuration

The file endpoint is setup by adding it to your Mach-II configuration file just like a listener or plugin. Beware that endpoint names must be unique across your application and therefore the name of an endpoint in a module cannot be the same name in another module.

Example Configuration

    <endpoints>
        <endpoint name="dashboard.serveAsset" type="MachII.endpoints.file.BaseEndpoint">
            <parameters>
                <parameter name="secure" value="false"/>
                <parameter name="basePath" value="/MachII/dashboard/assets"/>
                <parameter name="servingEngineType" value="cfcontent|sendfile" />
                <parameter name="expiresDefault" value="access plus 365,0,0,0" />
                <parameter name="attachmentDefault" value="false" />
                <parameter name="timestampDefault" value="true" />
                <parameter name="fileTypeSettings">
                    <struct>
                        <key name=".js,.css,.jpg,.gif,.png">
                            <struct>
                                <key name="expires" value="access plus 365,0,0,0"/>
                                <key name="attachment" value="false" />
                            </struct>
                        </key>
                        <key name=".pdf">
                            <struct>
                                <key name="expires" value="access plus 0,0,0,0"/>
                                <key name="attachment" value="true" />
                                <key name="timestamp" value="false" />
                            </struct>
                        </key>
                    </struct>
                </parameter>
                <parameter name="cfmFiles">
                    <array>
                        <element value="/css/basic.cfm" />
                        <element value="/css/dialog.cfm" />
                        <element value="/css/combined.cfm" />
                        <!-- Alternatively for all .cfm files in this
                            directory which is based off the basePath parameter -->
                        <element value="/css/*.cfm" />
                        <!--
                        Alternatively for all .cfm files in this directory which is
                        based off the basePath parameter and below
                        -->
                        <element value="/css/**/*.cfm" />
                    </array>
                </parameter>
            </parameters>
        </endpoint>
    </endpoints>

Parameters

Parameter Name Required Default Description
secure no false Sets whether to use the value from urlBase or urlBaseSecure which indicates HTTPS/SSL URL base.
basePath yes n/a The base path to the directory in which to base the relative file paths from. This value will be evaluated with expandPath() which allows for CFML mappings, etc.
servingEngineType no - cfcontent or sendfile cfcontent Which file serving engine to use. For more info, see below.
expiresDefault no access plus 365,0,0,0 The default amount to set the expires headers. Uses an Apache like syntax which supports "access" or "modified" with days,hours,minutes,seconds timespan format or the value of none if no expires header should be used.
attachmentDefault no false A boolean indicating if the file should be served as an attachment so it will be downloaded by the browser.
timestampDefault no true A boolean indicating if the file timestamp should be append to the end of the URL to aid web browsers in caching.
fileTypeSettings no n/a A struct and keys of settings for specific file types. See example above for configuration of expires, attachment and timestamp settings.
cfmFiles no n/a An array or list of specific relative paths to .cfm file you want to allow the endpoint to server. You can use the directive of * as a wildcard to indicate patterns by creating an array or list of the specific file patterns. You can read more about the patterns we support in Simple Pattern Matcher documentation.

Restricting Access

By Path

One of our "security" measures by design is to the use of the basePath parameter. The basePath parameter allows you to restrict base directory in which you can serve files from. It would be bad security practice to pass the full absolute file path on the disk in the URL as this inadvertently gives hackers knowledge about the structure of your file system. Using the base path allows you pass a relative path to the base path and therefore not exposing a complete file path to the client. It is also recommended that you separate out any assets to be served by a file endpoint into their own directory to mitigate any security risk.

By .cfm File

One of the features of the File endpoint is a feature that allows you to reference .cfm files but serve the content of the file as another MIME type. This feature is very powerful in certain circumstances. For example, we use this feature in the Mach-II Dashboard to serve our CSS files (which are .cfm) with custom paths to images. If you were to look at the /assets/css/basic.cfm file in the Dashboard, you would see entries like this that allow us to create the correct path to the background image for the Dashboard:

    body {
        font: 0.75em/1.5em Arial, Helvetica, sans-serif;
        margin:0;
        background: ##FFF url(#BuildEndpointUrl("/img/headerBk.jpg")#) top left repeat-x;
    }

When the browser needs the CSS file, it actually is calling a File endpoint for the dashboard. Mach-II processes the "mock" CSS file that is really a .cfm file and pipes the results back to the browser as the correct MIME type for CSS (text/css). This is done by using the pipe directive (:css) in the URI of the CSS file:

    <view:style endpoint="dashboard.serveAsset"
        p:file="/css/basic.cfm:css"
        media="screen,projection"
        outputType="inline" />

Because serving allowing the ability to serve .cfm files as other MIME types is a potential security hazard, you must explicitly name the relative path to all .cfm files you want to process and pipe through a File endpoint. You can indicate which .cfm you desire in the cfmFile parameter when configuring the endpoint.

Customizing the Behavior of the Endpoint

By default, the file endpoint does not offer any advanced security measures such as checking if an user is logged in or has permission to access the requested file. This means if a client such as a web browser makes a request for a resource that exists then your configured file endpoint would serve the file. For example, this is not an issue for the Mach-II Dashboard which uses a file endpoint to serve JS / CSS / Image assets. The assets are publicly available and therefore not a security risk.

Providing Security

In a majority of circumstances, most developers will want to check to see if an user is logged or possibly if they have the privilege to access the requested file. This can easily be done by extending the file endpoint and providing your own customizations while leveraging the infrastructure already provided.

In our example, we're going to check to make sure that user is logged in. This example won't actually show how we are authenticating the user as that will depend on how you authenticate your users in your application. We simple extend the MachII.endpoints.file.BaseEndpoint CFC and use the onAuthenticate method.

Example:

    <cfcomponent displayname="SecureServeAsset"
        extends="MachII.endpoints.file.BaseEndpoint"
        output="false">

        <cffunction name="onAuthenticate" access="public" returntype="void" output="false"
            hint="Runs between preProcess and handleRequest (only if implemented).">
            <cfargument name="event" type="MachII.framework.Event" required="true" />

            <!---
            If the user is not logged in, then throw an exception which will cause the
            default onException endpoint process to occur and short-circuit the request
            --->
            <cfif NOT StructKeyExists(session, "loggedIn") AND session.loggedIn EQ false>
                <!---
                You don't have to send 401 HTTP header as the onException point will
                trap this exception and handle it
                --->
                <cfthrow type="MachII.endpoints.file.notAuthorized" message="The user is not logged in." />
            </cfif>
        </cffunction>

    </cfcomponent>

We are using the onAuthenticate cut-point into the endpoint lifecycle. This point runs after preProcess and before handleRequest. If the user is not logged in, we simple set an HTTP header and set a CFML throw so the onException cut-point in the endpoint lifecycle is invoke. By default, all Mach-II endpoints have "basic" onException handling which can be customized by overriding the onException method with our custom code.

Generating Custom CSS / JS / Etc Files

It is extremely easy to use the file endpoint to generate and server custom files. In this example, let's pretend that we our website has multiple brands and each brand has a unique number assigned to it (i.e. 2697) that corresponds to a record of information in our fictitious database. We're going to accomplish this by extending the file endpoint with our own custom CFC.

Register Our Custom Endpoint

    <endpoints>
        <endpoint name="serveBrandAsset" type="myApp.endpoint.ServeBrandAssetEndpoint">
            <parameters>
                <parameter name="basePath" value="/myApp/brand/assets"/>
                <parameter name="servingEngineType" value="cfcontent" />
                <parameter name="fileTypeSettings">
                    <struct>
                        <key name=".js,.css,.jpg,.gif,.png">
                            <struct>
                                <key name="expires" value="access plus 365,0,0,0"/>
                                <key name="attachment" value="false" />
                            </struct>
                        </key>
                    </struct>
                </parameter>
                <parameter name="cfmFiles">
                    <array>
                        <!--
                        Alternatively for all .cfm files in this directory which
                        is based off the basePath parameter
                        -->
                        <element value="/css/*.cfm" />
                    </array>
                </parameter>
            </parameters>
        </endpoint>
    </endpoints>

Our Custom Endpoint CFC

Remember where going to create a custom endpoint by extending the Mach-II file endpoint. Notice that in the cfcomponent tag's extends attribute that we are using MachII.endpoints.file.BaseEndpoint.

The other important piece is that we are going to override the preProcess method that we inherited from the base endpoint. Remember there are is logic in the inherited preProcess method that still needs to run so we have to call super.preProcess(arguments.event) so that stuff occurs or our endpoint will not function properly.

Just like any other Mach-II process, all endpoints have access to the event object. So you append query string parameters to file endpoint URLs. We'll be interfacing with a fictitious BrandManager that is injected by ColdSpring using the depends attribute. Once we have bran info, we put it into our event object. Because our asset (like .css file) is a .cfm file that is "rendered" by the file endpoint -- it has access to the event object.

    <cfcomponent
        extends="MachII.endpoints.file.BaseEndpoint"
        depends="BrandManager">

        <!---
        PUBLIC FUNCTIONS
        --->
        <cffunction name="preProcess" access="public" returntype="void" output="false"
            hint="Runs when an endpoint request begins. Override to provide custom functionality and call super.preProcess().">
            <cfargument name="event" type="MachII.framework.Event" required="true" />

            <!--- Run the inherited method first --->
            <cfset super.preProcess(arguments.event) />

            <!--- Only get the brand info if the request file is a .cfm file otherwise it's could be a jpg, gif, pdf, etc. --->
            <cfif arguments.event.getArg("fileExtension") EQ "cfm">
                <!---
                Code to get brand info. This blackbox in this example, but in this case we'll use a
                fictitious BrandManager that is injected. This is a struct.
                --->
                <cfset event.setArg("brandInfoStruct", getBrandManager().getBrand( arguments.event.getArg("brandId", 0)) />
            </cfif>
        </cffunction>

    </cfcomponent>

Our ".css" File

Since we want to have a dynamic .css file, we're going to create a .cfm file so we can use variables.

Stylesheet.cfm

    // Grab the logo path from our brand info struct
    div#logo { background: url('img/#event.getArg("brandInfoStruct").logoPath#'); }

    // We could even reference BuildEndpointUrl to a png file
    div#background {
        background: url('#BuildEndpointUrl(event.getArg("brandInfoStruct").backgroundPath)#');
        }

Using Dynamic ".css" File

It is as simple as using the view tag library and the style custom tag. Notice that we are doing something special with the file path /css/Stylesheet.cfm:css. We are saying to serve us a .cfm page but using the :css tells the file endpoint to pipe the results of our Stylesheet.cfm file as a .css file (with the right MIME type etc) for browsers.

    <cfimport prefix="view" taglib="/MachII/customtags/view" />
    <view:style endpoint="serveBrandAsset"
        p:file="/css/Stylesheet.cfm:css"
        p:brandId="2697"
        media="screen,projection" outputType="inline" />

Getting endpoint specific URLs

Getting URLs that are built specifically to a file endpoint is as simple as calling the buildEndpointUrl() method in any framework aware component (view, listener, etc.).

Example:

    <img src="#BuildEndpointUrl('dashboard.serveAsset', 'file=/img/logo.gif')#" />

BuildEndpointUrl() method URL parameters (the second argument) accepts the following attributes either is a | (pipe) list or struct:

URL Parameter Name Required Description
file yes The file path which is relative to the base path.
pipe no The file type you want to pipe the requested file to. This allows you pipe a .cfm file with the MIME-type header of a .css file.
attachment no The file name you would like the browser to download as an attachment. Leaving the value as "" indicates that you want the endpoint to compute the file name based off the relative file path.

The attachment URL parameter will override any settings defined in the endpoint configuration. The pipe URL parameter is only useful if processing a .cfm page to another MIME-type.

Also, all the view tag library custom tags support endpoints such as img, script, style and link:

    <view:script endpoint="dashboard.serveAsset" p:file="/js/prototype.js" outputType="inline" />

Differences Between cfcontent and Apache mod_xsendfile

The file endpoint ships with the ability to use cfcontent which is built into your CFML engine or Apache mod_xsendfile. One of the major differences is that the use of cfcontent to serve files ties us the total number of threads your CFML engine will process at any given point. This is usually not an issue for small files, but can become a big problem as the size of the file increases and the total time it takes a client to download the file. Large files can take minutes to hours for clients that have limited bandwidth internet connections. This means you can take down your server by tying up all available threads with long downloading files with cfcontent.

To the rescue: mod_xsendfile for Apache webserver. This allows us to set an HTTP header in which the CFML request ends quickly and Apache looks for this header to serve the file natively. This allows request which would tie up a CFML engine thread to end quickly (10-20ms) and have the web server tier take care of serving the file (which it is designed to do better than any CFML engine could).

If you are going to be serving many files, large files or any combination of both, Team Mach-II highly recommends you install Apache mod_xsendfile on your server and use the the option in the Mach-II File Endpoint.

At this time, an equivalent ISAPI module for Microsoft IIS is not available. Please update this section if a replacement for IIS becomes available and file a ticket with Mach-II so this option can be supported for the File Endpoint.

Clone this wiki locally