FileHandler renders files

Video

gramex.yaml uses the FileHandler to display files.

For example, this page is rendered by a Markdown file using the following configuration:

url:
  markdown:
    pattern: /$YAMLURL/(.*) # Any URL under the current gramex.yaml folder
    handler: FileHandler # uses this handler
    kwargs:
      path: $YAMLPATH # Serve files from this YAML file's directory
      default_filename: README.md # using README.md as default
      index: true # List files if README.md is missing

Any file under the current folder is shown as is. If a directory has a README.md, that is shown by default.

$YAMLURL is replaced by the current URL’s path (in this case, /filehandler/) and $YAMLPATH is replaced by the directory of gramex.yaml.

Note: Gramex comes with a default URL handler that automatically serves files from the home directory of your folder. To prevent that, override the default pattern:

url:
  default: # This overrides the default URL handler
    pattern: ...

Default FileHandler

Even with an empty gramex.yaml, Gramex uses a default FileHandler. It does the following:

  1. If there’s a default.template.html or default.tmpl.html, it renders it as a HTML template
  2. Else if there’s an index.html, it renders as a HTML file (not a template)
  3. Else it shows a directory listing of all files/folders
  4. It compiles .sass and .scss files to CSS and renders them
  5. It compiles .ts files to JavaScript and renders them
  6. It compiles .vue single-file components to JavaScript and renders them
  7. It compiles .template.html and .tmpl.html as templates and renders them
  8. It caches on the browser for 60 seconds, but files under
    • assets/ and favicon.ico are cached for 1 day
    • node_modules/ are cached for 10 years

See the default FileHandler configuration.

To override this, add a FileHandler called default: in your gramex.yaml. For example:

url:
  default:
    kwargs:
      index: false # Disable indices
      headers:
        Cache-Control: max-age=0 # Disable browser caching

Default filename

When a FileHandler points to a directory:

You can override default_filename: to specify one or more filenames. For example:

url:
  default-filehandler:
    pattern: /$YAMLURL/(.*)
    handler: FileHandler
    kwargs:
      path: $YAMLPATH/directory/
      default_filename:
        - default.template.html # Serve this as a template, if it exists
        - index.html # Else serve this as HTML
        - README.md # Else serve this as Markdown to HTML

FileHandler checks the files in the order specified in the default_filename: The first file that exists is rendered.

Directory listing

Video

index: true lists all files in the directory if the default_filename is missing. To customize the directory listing, specify index_template: filename. This file will be shown as HTML, with $path replaced by the directory’s absolute path, and $body replaced by a list of all files in that directory.

For example,

url:
  static:
    pattern: /$YAMLURL/static/(.*) # Any URL starting with /static/
    handler: FileHandler # uses this handler
    kwargs:
      path: $YAMLPATH/static/ # Serve files from static/
      default_filename: index.html # using index.html as default
      index: true # List files if index.html is missing
      index_template: $YAMLPATH/template.html # Use template.html to list directory

Here is a trivial template.html:

<h1>$path</h1>
<p>$body</p>

index: false disables directory listing. To disable it for the default FileHandler, use:

url:
  default: # This is a special name for the default Gramex FileHandler
    handler: FileHandler
    kwargs:
      index: false # Disable directory listing here

File Patterns

Video

You can map any URL for any file. For example, to map the file filehandler/data.csv to the URL /filehandler/data, use this configuration:

pattern: /$YAMLURL/filehandler/data # The URL /filehandler/data
handler: FileHandler # uses this handler
kwargs:
  path: $YAMLPATH/filehandler/data.csv # and maps to this file

You can also map regular expressions to file patterns. For example, to add a .yaml extension automatically to a path, use:

url:
  yaml-extensions:
    pattern: /$YAMLURL/yaml/(.*) # yaml/anything
    handler: FileHandler
    kwargs:
      path: $YAMLPATH/*.yaml # becomes anything.yaml, replacing the * here

For example, yaml/gramex actually renders gramex.yaml.

To replace .html extension with .yaml, use:

url:
  replace-html-with-yaml:
    pattern: /$YAMLURL/(.*)\\.html # Note the double backslash instead of single backslash
    handler: FileHandler
    kwargs:
      path: $YAMLPATH/*.yaml # The part in brackets replaces the * here

For more complex mappings, use a dictionary of regular expression mappings:

url:
  mapping:
    pattern: /$YAMLURL/node/((foo|bar)/.*) # match /node/foo/... and node//bar/...
    handler: FileHandler
    kwargs:
      path: # If path: is a dict, it's treated as a mapping
        "foo/": $YAMLPATH/foo.html # /node/foo/ -> foo.html
        "bar/": $YAMLPATH/bar.html # /node/bar/  -> bar.html
        "foo/(.*)": $YAMLPATH/foo/{0}.html # /node/foo/x -> foo/x.html
        "bar/(?P<file>.*)": $YAMLPATH/bar/{file}.html # /node/bar/x  -> bar/x.html
        ".*": $YAMLPATH/default.html # anything else -> default.html

The mapping has keys that are regular expressions. They must match the part of the URL in brackets. (If there are multiple brackets, it matches the first one.) Values are file paths. They are formatted as string templates using the regular expression match groups and URL query parameters. So:

When using URL query parameters, you should provide default values in case the request does not pass the parameter. You can do this using default:

url:
  mapping:
    pattern: /$YAMLURL/ # Home page
    handler: FileHandler
    kwargs:
      path: # If path: is a dict, it's treated as a mapping
        "": $YAMLPATH/{dir}/{file}.{ext} #  /?dir=foo&file=bar&ext=txt -> foo/bar.txt
      default:
        dir: "" # ?dir= is the default
        file: index # ?file=index is the default
        ext: html # ?ext=html is the default

If you want to map a subset of files to a folder, you can mark them in the pattern. For example, this configuration maps /style.css and /script.js to the home directory. To ensure that this takes priority over others, you can add a higher value to the priority (which defaults to 0.)

url:
  assets:
    pattern: /(style.css|script.js) # Any of these to URLs
    priority: 2 # Give it a higher priority
    handler: FileHandler # uses this handler
    kwargs:
      path: . # Serve files from /

This can work across directories as well. For example, this maps the static and bower_components and specifies a 1-day expiry for any files under them.

url:
  static-files:
  # Any file under the current directory, starting with bower_components/
  # or with static/ is mapped to a FileHandler
  pattern: /$YAMLURL/(bower_components/.*|static/.*)
  handler: FileHandler
  kwargs:
    path: $YAMLPATH/ # Base is the current directory
    headers:
      Cache-Control: public, max-age=86400 # Cache publicly for 1 day

Caching

See how to cache static files

Redirecting files

See File patterns

Ignore files

To prevent certain files from ever being served, specify the handlers.FileHandler.ignore setting. By default, this is:

handlers:
  FileHandler:
    ignore:
      - gramex.yaml # Always ignore gramex.yaml in Filehandlers
      - ".*" # Hide dotfiles

The gramex.yaml file and all files beginning with . will be hidden by default. You can change the above setting in your gramex.yaml file. For example:

handlers:
  FileHandler:
    ignore:
      - ".*" # Protect dot-files - they are usually meant to be hidden
      - "*.git*" # Protect .gitignore, .gitattributes, etc - they list filenames
      - "*.git/*" # Protect all files under the .git/ repo - they have code history
      - "*.yaml" # Protect YAML files - they list all URLs

You can customize this further for each handler via the allow: and ignore: configurations. For example:

url:
  my-app-files:
    pattern: /$YAMLURL/(.*)
    handler: FileHandler
    kwargs:
      path: .
      ignore:
        - "*.xls*" # Ignore all Excel files
      allow:
        - public.xlsx # But allow public.xlsx

Now public.xlsx is accessible. But something-else.xlsx will raise a HTTP 403 error. The log reports Disallow: "something-else.xlsx". It matches "*.xls*".

If you import deploy.yaml, FileHandler blocks all files except specific white-listed exceptions.

MIME types

Video

The URL will be served with the MIME type of the file. CSV files have a MIME type text/csv and a Content-Disposition set to download the file. You can override these headers:

pattern: /filehandler/data
handler: FileHandler
kwargs:
  path: filehandler/data.csv
  headers:
    Content-Type: text/plain # Display as plain text
    Content-Disposition: none # Do not download the file

To convert a file type into an attachment, use:

pattern: /filehandler/data
handler: FileHandler
kwargs:
  path: filehandler/data.txt
  headers:
    Content-Type: text/plain
    Content-Disposition: attachment; filename=data.txt # Save as data.txt

From v1.23.1, to serve different files with different MIME types, use file patterns:

    pattern: /$YAMLURL/(.*)
    handler: FileHandler
    kwargs:
      path: $YAMLPATH
      headers:
        Content-Type: text/plain            # Default header
        "*.json":                           # Override headers on .json files
          Content-Type: application/json
        "json/**"                           # Override headers on json/ directory
          Content-Type: application/json

Templates

Video

FileHandler uses Tornado templates to generate content from data. For example, this page.tmpl.html renders 10 images:

{% for id in range(1, 10) %}
  <img src="https://picsum.photos/id/{{ id }}/40">
{% end %}

The default, any file that ends with .tmpl.html or .template.html is rendered as a template. You can specify amy file patterns using template: patterns. For example:

url:
  template:
    pattern: ...
    handler: FileHandler
    kwargs:
      path: ...
      template: ["template*.html", "*.tmpl.html", "*.svg"]

Template example

Template syntax

Templates can use all variables in the template syntax. This includes:

Sub-templates

Templates import sub-templates using {% include path/to/template.html %}.

For example:

{% set title, menu = 'App name', ['Home', 'Dashboard'] %}
{% include path/relative/to/template/navbar.html %}

Dynamic sub-templates

Use {{ gramex.cache.open(file) }} if file is a variable. For example:

Insert {user}.txt:
{% set user = handler.current_user.id %}
{% raw gramex.cache.open(f'{user}.txt', rel=True) %}

Insert {user}.md after converting to HTML:
{% raw gramex.cache.open(f'{user}.md', rel=True) %}

Render {user}.html as a Tornado template, passing a `user` variable:
{% raw gramex.cache.open(f'{user}.html', rel=True).generate(user=handler.current_user) %}

See gramex.cache.open() for more formats options.

rel=True loads the file relative to the template path.

UI Modules

Templates import modules using {% module Template('path/to/template.html', **kwargs) %}.

For example:

Import navbar.html in-place as a template.
{% module Template('path/relative/to/filehandler/navbar.html',
      title='App name',
      menu=['Home', 'Dashboard'])
%}

Note:

Redirecting templates

To redirect to a page (e.g. new_url) from a template, add this code:

{% set handler.redirect(new_url) %} {% set handler.include_body = False %} {%
set return b'' %}
  1. handler.redirect(new_url) redirects to new_url
  2. handler.include_body = False prevents FileHandler from rendering any returned text
  3. return b'' returns immediately with an empty bytestring – avoiding rendering the rest of the template

Or you can create a utils.py that has a `redirect_template() like this:

import tornado.gen

def redirect_template(handler, new_url):
    handler.redirect(new_url)
    handler.include_body = False
    raise tornado.gen.Return(b'')

… and call it in your template:

{% import utils %} {% set utils.redirect_template(handler, new_url) %}

SASS

Video

FileHandler can compile SCSS files. The default FileHandler compiles any .scss or .sass file is compiled into CSS. For example, this color.scss file:

$color: red !default;
body {
  background-color: lighten($color, 40%);
}

is rendered as:

body {
  background-color: #fcc;
}

See color.scss

To enable this in your FileHandler, add:

kwargs:
    ...
    sass: '*.scss, *.sass'      # Compile SCSS and SASS files into CSS

URL query parameters are automatically passed as variables to the SASS file. For example, color.scss?color=green sets $color: green.

See color.scss?color=blue

You can use this to allow users to customize your theme.

You can also pass a ?@import=path/to/filename.sass to include a file in the SASS file. This SASS file path must be relative to the requested SASS file or the directory Gramex is running in.

TypeScript

FileHandler can compile TypeScript. The default FileHandler compiles any .ts file into JS. For example, this typescript.ts file:

type WindowStates = "open" | "closed" | "minimized";
function getLength(obj: WindowStates | WindowStates[]) {
  return obj.length;
}

is rendered as:

function getLength(obj) {
  return obj.length;
}
//# sourceMappingURL=typescript.ts?map

See typescript.ts

To enable this in your FileHandler, add:

kwargs:
    ...
    ts: '*.ts'      # Compile .ts files into JS

Gramex uses esbuild to compile TypeScript. The following options are supported:

Vue

v1.92 Vue compilation is deprecated and removed. Import Vue 3 SFCs directly.

FileHandler can compile Vue single-file components. The default FileHandler compiles any .vue file into CSS. For example, this hello-world.vue file:

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
  module.exports = {
    data: function () {
      return {
        greeting: "Hello",
      };
    },
  };
</script>

<style scoped>
  p {
    font-size: 2em;
    text-align: center;
  }
</style>

is rendered as:

(function(t){var e={};function n(r){if(e[r]) /* JS contents trimmed */

See hello-world.vue

To enable this in your FileHandler, add:

kwargs:
    ...
    vue: '*.vue'    # Compile .vue files into JS

XSRF

Video

If you’re submitting forms using the POST method, you need to submit an _xsrf field that has the value of the _xsrf cookie.

When using HTML forms, you can include it in the template using handlers’ built-in xsrf_token property:

<form method="POST">
  <input type="hidden" name="_xsrf" value="{{ handler.xsrf_token }}" />
</form>

To render a file as a template, use:

url:
  template:
    pattern: /page # The URL /page
    handler: FileHandler # displays a file
    kwargs:
      path: page.html # named page.html
      template: true # Render as a template

When using AJAX, v1.85 onwards, no XSRF token is required, because Gramex checks for this automatically.

  1. XMLHttpRequest (e.g. via jQuery.post, jQuery.ajax, etc) automatically sends an X-Requested-With: XMLHttpRequest header for AJAX.
  2. Fetch automatically adds Sec-Fetch-Mode: cors headers

When submitting from a server, add either of these headers to bypass the check, like this:

import requests

requests.post('http://example.org/page', headers={'X-Requested-With': 'XMLHttpRequest'})
requests.post('http://example.org/page', headers={'Sec-Fetch-Mode': 'cors'})

You can disable XSRF for a specific handler like this:

url:
  name:
    pattern: ... # When this page is visited,
    handler: ... # no matter what the handler is,
    kwargs:
      xsrf_cookies: false # Disable XSRF cookies

You can disable XSRF for all handlers like this (but this is not recommended):

app:
  settings:
    xsrf_cookies: false

For debugging without XSRF, start Gramex with a --settings.xsrf_cookies=false from the command line.

The XSRF cookie is automatically set when a FileHandler template accesses handler.xsrf_token. You can also set it explicitly, by adding a set_xsrf: true configuration to kwargs like this:

url:
  name:
    pattern: ...              # When this page is visited,
    handler: ...              # no matter what the handler is,
    kwargs:
      ...
      set_xsrf: true        # set the xsrf cookie

How XSRF works:

FileHandler HTTP methods

Video

By default FileHandler supports GET, HEAD and POST methods. You can map any of the following methods to the file using the methods: configuration as follows:

url:
  name:
    pattern: ...
    handler: FileHandler
    kwargs:
      ...
      methods: [GET, HEAD, POST, DELETE, PATCH, PUT, OPTIONS]

File concatenation

Video

You can concatenate multiple files and serve them as a single file. For example:

    ...
    pattern: /libraries.js
    handler: FileHandler
    kwargs:
      path:
        - bower_components/jquery/dist/jquery.min.js
        - bower_components/bootstrap/dist/bootstrap.min.js
        - bower_components/d3/d3.v3.min.js
      headers:
        Cache-Control: public, max-age=86400    # Cache publicly for 1 day

This concatenates all files in path in sequence. If transforms are specified, the transforms are applied before concatenation.

This is useful to pack multiple static files into one, as the example shows.

Transforming content

Use transform: to apply functions before rendering the content. Templates, SASS, Vue and TypeScript use this feature behind the scenes.

Any function can be used to transform. For example, this renders .md or .markdown files as HTML using python-markdown:

# ... contd ...
transform:
  "*.md, *.markdown": # Any file matching .md or .markdown
    function: markdown.markdown(content, output_format='html5', extensions=['extra'])
    encoding: utf-8 # Read input file as UTF-8
    headers: #   Use these HTTP headers:
      Content-Type: text/html #     MIME type: text/html

Transform keys matches one or more glob patterns separated by space/comma (e.g. '*.md, 'data/**'.)

Transform values are dicts that accepts these keys:

Configure all FileHandlers

Gramex adds the following kwargs to all FileHandlers for security reasons, ignoring these files:

  1. Any gramex.yaml files (gramex*.yaml)
  2. Any files beginning with . (.*)
  3. Any Python files (.py*)

To modify this, update handlers.FileHandler in your gramex.yaml. For example:

handlers:
  FileHandler:
    ignore:
      - gramex*.yaml # Always ignore gramex config files
      - ".*" # Hide dotfiles
      - "*.py*" # Hide Python scripts
      - "*secret*" # Ignore all secret files
    allow:
      - "README.*" # Always allow README files, even README.secret

See the default FileHandler configuration.