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: ...
Even with an empty gramex.yaml
, Gramex uses a default FileHandler. It does the following:
default.template.html
or default.tmpl.html
, it renders it as a HTML templateindex.html
, it renders as a HTML file (not a template).sass
and .scss
files to CSS and renders them.ts
files to JavaScript and renders them.vue
single-file components to JavaScript and renders them.template.html
and .tmpl.html
as templates and renders themassets/
and favicon.ico
are cached for 1 daynode_modules/
are cached for 10 yearsSee 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
When a FileHandler points to a directory:
default_filename:
is specified and exists, it renders that fileindex: true
, it lists files in the directoryYou 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.
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
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:
{0}
matches the first capture group (in brackets, like (.*)
),
{1}
matches the second capture group, etc.{file}
matches the named capture group (?P<file>.*)
, etc?file=
, the first value that replaces
{file}
– but only if there is no such named capture groupWhen 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
See how to cache static files
See File patterns
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.
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
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
Templates can use all variables in the template syntax. This includes:
handler
: the current request handler objecthandler.request.uri
, handler.current_user
, etc.handler.args
- a dict of lists containing the URL query parametersrequest
: alias for handler.request
current_user
: alias for handler.current_user
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 %}
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.
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:
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'' %}
handler.redirect(new_url)
redirects to new_url
handler.include_body = False
prevents FileHandler from rendering any returned textreturn b''
returns immediately with an empty bytestring – avoiding rendering the rest of the templateOr 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) %}
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%);
}
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.
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;
}
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:
?format=
: iife
(default) or esm
<script type="module" src="file.ts?format=esm">
imports the script as ESM<script src="file.ts?global-name=myname">
imports the script with exported functions under myname
?global-name=
: name of the global variable to export (for ?format=iife
)?bundle=
: true
(default) inlines all dependencies. false
does not?minify=
: true
(default) minifies the output. false
does not?target=
: esnext
(default). Can also be es2020
, chrome58
, edge15
, etc.?charset=
: utf8
(default)?keep-names=
: false
(default) does not preserve function names when minified. true
does?drop:debugger=
: false
(default) does not drop debugger statements. true
does?drop:console=
: false
(default) does not drop console statements. true
doesv1.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>
(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
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.
X-Requested-With: XMLHttpRequest
header for AJAX.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:
_xsrf
cookie when
tornado.web.RequestHandler.xsrf_token()
is called. It is httponly
by
default in Gramex because of the default xsrf_cookie_kwargs
setting, but not
set to secure
to allow HTTP sites to access it_xsrf
GET/POST argument (or the X-Xsrftoken
or C-Xsrftoken
headers) is a valid XSRF token, and checks that it matches the cookie valueBy 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]
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.
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:
function: mymodule.transform(content, handler)
.content
has the file contentshandler
has the FileHandler objectutf-8
.
If None
(the default), the file is read as bytes, and the transform function
MUST accept the content as bytesGramex adds the following kwargs to all FileHandlers for security reasons, ignoring these files:
gramex.yaml
files (gramex*.yaml
).
(.*
).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