More/fixed documentation on the octoprint.server.util module

This commit is contained in:
Gina Häußge 2015-05-26 13:15:48 +02:00
parent 7263fb74a9
commit ad054338b5
4 changed files with 214 additions and 109 deletions

View file

@ -10,6 +10,7 @@ Internal Modules
filemanager.rst filemanager.rst
plugin.rst plugin.rst
printer.rst printer.rst
server.rst
settings.rst settings.rst
slicing.rst slicing.rst
util.rst util.rst

42
docs/modules/server.rst Normal file
View file

@ -0,0 +1,42 @@
.. _sec-modules-server:
octoprint.server
----------------
.. automodule:: octoprint.server
:members:
.. _sec-modules-server-util:
octoprint.server.util
---------------------
.. automodule:: octoprint.server.util
:members:
.. _sec-modules-server-util-flask:
octoprint.server.util.flask
---------------------------
.. automodule:: octoprint.server.util.flask
:members:
.. _sec-modules-server-util-sockjs:
octoprint.server.util.sockjs
----------------------------
.. automodule:: octoprint.server.util.sockjs
:members:
.. _sec-modules-server-util-tornado:
octoprint.server.util.tornado
-----------------------------
.. automodule:: octoprint.server.util.tornado
:members:

View file

@ -20,10 +20,11 @@ from . import watchdog
def apiKeyRequestHandler(): def apiKeyRequestHandler():
""" """
All requests in this blueprint need to be made supplying an API key. This may be the UI_API_KEY, in which case ``before_request`` handler for blueprints for which all requests need to be made supplying an API key.
the underlying request processing will directly take place, or it may be the global or a user specific case. In any
case it has to be present and must be valid, so anything other than the above three types will result in denying This may be the UI_API_KEY, in which case the underlying request processing will directly take place, or it may be
the request. the global, an app specific or a user specific one. In any case it has to be present and must be valid, so anything
other than the above types will result in the application denying the request.
""" """
import octoprint.server import octoprint.server
@ -62,6 +63,11 @@ def apiKeyRequestHandler():
def corsResponseHandler(resp): def corsResponseHandler(resp):
"""
``after_request`` handler for blueprints for which CORS is supported.
Sets ``Access-Control-Allow-Origin`` headers for ``Origin`` request header on response.
"""
# Allow crossdomain # Allow crossdomain
allowCrossOrigin = settings().getBoolean(["api", "allowCrossOrigin"]) allowCrossOrigin = settings().getBoolean(["api", "allowCrossOrigin"])
@ -72,7 +78,9 @@ def corsResponseHandler(resp):
def optionsAllowOrigin(request): def optionsAllowOrigin(request):
""" Always reply 200 on OPTIONS request """ """
Shortcut for request handling for CORS OPTIONS requests to set CORS headers.
"""
resp = _flask.current_app.make_default_options_response() resp = _flask.current_app.make_default_options_response()
@ -131,18 +139,24 @@ class ReverseProxied(object):
different than what is used locally. different than what is used locally.
In nginx: In nginx:
location /myprefix {
proxy_pass http://192.168.0.1:5001; .. code-block:: none
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location /myprefix {
proxy_set_header X-Scheme $scheme; proxy_pass http://192.168.0.1:5001;
proxy_set_header X-Script-Name /myprefix; proxy_set_header Host $host;
} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /myprefix;
}
Alternatively define prefix and scheme via config.yaml: Alternatively define prefix and scheme via config.yaml:
server:
baseUrl: /myprefix .. code-block:: yaml
scheme: http
server:
baseUrl: /myprefix
scheme: http
:param app: the WSGI application :param app: the WSGI application
:param header_script_name: the HTTP header in the wsgi environment from which to determine the prefix :param header_script_name: the HTTP header in the wsgi environment from which to determine the prefix

View file

@ -34,7 +34,7 @@ import octoprint.util
def fix_ioloop_scheduling(): def fix_ioloop_scheduling():
""" """
This monkey patches tornado's :meth:`tornado.ioloop.PeriodicCallback._schedule_next` method so it no longer This monkey patches tornado's :meth:``tornado.ioloop.PeriodicCallback._schedule_next`` method so it no longer
blocks for long times on slow machines (RPi) when the system time happens to change by a large amount (e.g. due to blocks for long times on slow machines (RPi) when the system time happens to change by a large amount (e.g. due to
the first ever contact to an NTP server). the first ever contact to an NTP server).
@ -65,63 +65,67 @@ def fix_ioloop_scheduling():
@tornado.web.stream_request_body @tornado.web.stream_request_body
class UploadStorageFallbackHandler(tornado.web.RequestHandler): class UploadStorageFallbackHandler(tornado.web.RequestHandler):
""" """
A `RequestHandler` similar to `tornado.web.FallbackHandler` which fetches any files contained in the request bodies A ``RequestHandler`` similar to ``tornado.web.FallbackHandler`` which fetches any files contained in the request bodies
of content type `multipart`, stores them in temporary files and supplies the `fallback` with the file's `name`, of content type ``multipart``, stores them in temporary files and supplies the ``fallback`` with the file's ``name``,
`content_type`, `path` and `size` instead via a rewritten body. ``content_type``, ``path`` and ``size`` instead via a rewritten body.
Basically similar to what the nginx upload module does. Basically similar to what the nginx upload module does.
Basic request body example: Basic request body example:
------WebKitFormBoundarypYiSUx63abAmhT5C .. code-block:: none
Content-Disposition: form-data; name="file"; filename="test.gcode"
Content-Type: application/octet-stream
... ------WebKitFormBoundarypYiSUx63abAmhT5C
------WebKitFormBoundarypYiSUx63abAmhT5C Content-Disposition: form-data; name="file"; filename="test.gcode"
Content-Disposition: form-data; name="apikey" Content-Type: application/octet-stream
my_funny_apikey ...
------WebKitFormBoundarypYiSUx63abAmhT5C ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select" Content-Disposition: form-data; name="apikey"
true my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C-- ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select"
true
------WebKitFormBoundarypYiSUx63abAmhT5C--
That would get turned into: That would get turned into:
------WebKitFormBoundarypYiSUx63abAmhT5C .. code-block:: none
Content-Disposition: form-data; name="apikey"
my_funny_apikey ------WebKitFormBoundarypYiSUx63abAmhT5C
------WebKitFormBoundarypYiSUx63abAmhT5C Content-Disposition: form-data; name="apikey"
Content-Disposition: form-data; name="select"
true my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.path" Content-Disposition: form-data; name="select"
/tmp/tmpzupkro true
------WebKitFormBoundarypYiSUx63abAmhT5C ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.name" Content-Disposition: form-data; name="file.path"
test.gcode /tmp/tmpzupkro
------WebKitFormBoundarypYiSUx63abAmhT5C ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.content_type" Content-Disposition: form-data; name="file.name"
application/octet-stream test.gcode
------WebKitFormBoundarypYiSUx63abAmhT5C ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.size" Content-Disposition: form-data; name="file.content_type"
349182 application/octet-stream
------WebKitFormBoundarypYiSUx63abAmhT5C-- ------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.size"
349182
------WebKitFormBoundarypYiSUx63abAmhT5C--
The underlying application can then access the contained files via their respective paths and just move them The underlying application can then access the contained files via their respective paths and just move them
where necessary. where necessary.
""" """
# the request methods that may contain a request body
BODY_METHODS = ("POST", "PATCH", "PUT") BODY_METHODS = ("POST", "PATCH", "PUT")
""" The request methods that may contain a request body. """
def initialize(self, fallback, file_prefix="tmp", file_suffix="", path=None, suffixes=None): def initialize(self, fallback, file_prefix="tmp", file_suffix="", path=None, suffixes=None):
if not suffixes: if not suffixes:
@ -162,8 +166,8 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def prepare(self): def prepare(self):
""" """
Prepares the processing of the request. If it's a request that may contain a request body (as defined in Prepares the processing of the request. If it's a request that may contain a request body (as defined in
`UploadStorageFallbackHandler.BODY_METHODS) prepares the multipart parsing if content type fits. If it's a :attr:`UploadStorageFallbackHandler.BODY_METHODS`) prepares the multipart parsing if content type fits. If it's a
body-less request, just calls the `fallback` with an empty body and finishes the request. body-less request, just calls the ``fallback`` with an empty body and finishes the request.
""" """
if self.request.method in UploadStorageFallbackHandler.BODY_METHODS: if self.request.method in UploadStorageFallbackHandler.BODY_METHODS:
self._bytes_left = self.request.headers.get("Content-Length", 0) self._bytes_left = self.request.headers.get("Content-Length", 0)
@ -194,7 +198,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def data_received(self, chunk): def data_received(self, chunk):
""" """
Called by Tornado on receiving a chunk of the request body. If request is a multipart request, takes care of Called by Tornado on receiving a chunk of the request body. If request is a multipart request, takes care of
processing the multipart data structure via `self._process_multipart_data`. If not, just adds the chunk to processing the multipart data structure via :func:`_process_multipart_data`. If not, just adds the chunk to
internal in-memory buffer. internal in-memory buffer.
:param chunk: chunk of data received from Tornado :param chunk: chunk of data received from Tornado
@ -207,7 +211,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
self._buffer = data self._buffer = data
def is_multipart(self): def is_multipart(self):
"""Checks whether this request is a `multipart` request""" """Checks whether this request is a ``multipart`` request"""
return self._content_type is not None and self._content_type.startswith("multipart") return self._content_type is not None and self._content_type.startswith("multipart")
def _process_multipart_data(self, data): def _process_multipart_data(self, data):
@ -251,7 +255,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _on_part_header(self, header): def _on_part_header(self, header):
""" """
Called for a new multipart header, takes care of parsing the header and calling `self._on_part` with the Called for a new multipart header, takes care of parsing the header and calling :func:`_on_part` with the
relevant data, setting the current part in the process. relevant data, setting the current part in the process.
:param header: header to parse :param header: header to parse
@ -283,23 +287,24 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _on_part_start(self, name, content_type, filename=None): def _on_part_start(self, name, content_type, filename=None):
""" """
Called for new parts in the multipart stream. If `filename` is given creates new `file` part (which leads Called for new parts in the multipart stream. If ``filename`` is given creates new ``file`` part (which leads
to storage of the data as temporary file on disk), if not creates a new `data` part (which stores to storage of the data as temporary file on disk), if not creates a new ``data`` part (which stores
incoming data in memory). incoming data in memory).
Structure of `file` parts: Structure of ``file`` parts:
* `name`: name of the part * ``name``: name of the part
* `filename`: filename associated with the part * ``filename``: filename associated with the part
* `path`: path to the temporary file storing the file's data * ``path``: path to the temporary file storing the file's data
* `content_type`: content type of the part * ``content_type``: content type of the part
* `file`: file handle for the temporary file (mode "wb", not deleted on close!) * ``file``: file handle for the temporary file (mode "wb", not deleted on close, will be deleted however after
handling of the request has finished in :func:`_handle_method`)
Structure of `data` parts: Structure of ``data`` parts:
* `name`: name of the part * ``name``: name of the part
* `content_type`: content type of the part * ``content_type``: content type of the part
* `data`: bytes of the part (initialized to "") * ``data``: bytes of the part (initialized to an empty string)
:param name: name of the part :param name: name of the part
:param content_type: content type of the part :param content_type: content type of the part
@ -321,7 +326,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _on_part_data(self, part, data): def _on_part_data(self, part, data):
""" """
Called when new bytes are received for the given `part`, takes care of writing them to their storage. Called when new bytes are received for the given ``part``, takes care of writing them to their storage.
:param part: part for which data was received :param part: part for which data was received
:param data: data chunk which was received :param data: data chunk which was received
@ -334,7 +339,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _on_part_finish(self, part): def _on_part_finish(self, part):
""" """
Called when a part gets closed, takes care of storing the finished part in the internal parts storage and for Called when a part gets closed, takes care of storing the finished part in the internal parts storage and for
`file` parts closing the temporary file and storing the part in the internal files storage. ``file`` parts closing the temporary file and storing the part in the internal files storage.
:param part: part which was closed :param part: part which was closed
""" """
@ -348,8 +353,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _on_request_body_finish(self): def _on_request_body_finish(self):
""" """
Called when the request body has been read completely. Takes care of creating the replacement body out of the Called when the request body has been read completely. Takes care of creating the replacement body out of the
logged parts, turning `file` parts into new logged parts, turning ``file`` parts into new ``data`` parts.
:return:
""" """
self._new_body = b"" self._new_body = b""
@ -375,8 +379,8 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _handle_method(self, *args, **kwargs): def _handle_method(self, *args, **kwargs):
""" """
Handler for any request method, takes care of defining the new request body if necessary and forwarding Takes care of defining the new request body if necessary and forwarding
the current request and changed body to the `fallback`. the current request and changed body to the ``fallback``.
""" """
# determine which body to supply # determine which body to supply
@ -417,20 +421,22 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
class WsgiInputContainer(object): class WsgiInputContainer(object):
""" """
A WSGI container for use with Tornado that allows supplying the request body to be used for `wsgi.input` in the A WSGI container for use with Tornado that allows supplying the request body to be used for ``wsgi.input`` in the
generated WSGI environment upon call. generated WSGI environment upon call.
A `RequestHandler` can thus provide the WSGI application with a stream for the request body, or a modified body. A ``RequestHandler`` can thus provide the WSGI application with a stream for the request body, or a modified body.
Example usage: Example usage:
wsgi_app = octoprint.server.util.WsgiInputContainer(octoprint_app) .. code-block:: python
application = tornado.web.Application([
(r".*", UploadStorageFallbackHandler, dict(fallback=wsgi_app),
])
The implementation logic is basically the same as `tornado.wsgi.WSGIContainer` but the `__call__` and `environ` wsgi_app = octoprint.server.util.WsgiInputContainer(octoprint_app)
methods have been adjusted to for an optionally supplied `body` argument which is then used for `wsgi.input`. application = tornado.web.Application([
(r".*", UploadStorageFallbackHandler, dict(fallback=wsgi_app),
])
The implementation logic is basically the same as ``tornado.wsgi.WSGIContainer`` but the ``__call__`` and ``environ``
methods have been adjusted to allow for an optionally supplied ``body`` argument which is then used for ``wsgi.input``.
""" """
def __init__(self, wsgi_application): def __init__(self, wsgi_application):
@ -438,10 +444,10 @@ class WsgiInputContainer(object):
def __call__(self, request, body=None): def __call__(self, request, body=None):
""" """
Wraps the call against the WSGI app, deriving the WSGI environment from the supplied Tornado `HTTPServerRequest`. Wraps the call against the WSGI app, deriving the WSGI environment from the supplied Tornado ``HTTPServerRequest``.
:param request: the `tornado.httpserver.HTTPServerRequest` to derive the WSGI environment from :param request: the ``tornado.httpserver.HTTPServerRequest`` to derive the WSGI environment from
:param body: an optional body to use as `wsgi.input` instead of `request.body`, can be a string or a stream :param body: an optional body to use as ``wsgi.input`` instead of ``request.body``, can be a string or a stream
""" """
data = {} data = {}
@ -486,13 +492,13 @@ class WsgiInputContainer(object):
@staticmethod @staticmethod
def environ(request, body=None): def environ(request, body=None):
""" """
Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment. Converts a ``tornado.httputil.HTTPServerRequest`` to a WSGI environment.
An optional `body` to be used for populating `wsgi.input` can be supplied (either a string or a stream). If not An optional ``body`` to be used for populating ``wsgi.input`` can be supplied (either a string or a stream). If not
supplied, `request.body` will be wrapped into a `io.BytesIO` stream and used instead. supplied, ``request.body`` will be wrapped into a ``io.BytesIO`` stream and used instead.
:param request: the `tornado.httpserver.HTTPServerRequest` to derive the WSGI environment from :param request: the ``tornado.httpserver.HTTPServerRequest`` to derive the WSGI environment from
:param body: an optional body to use as `wsgi.input` instead of `request.body`, can be a string or a stream :param body: an optional body to use as ``wsgi.input`` instead of ``request.body``, can be a string or a stream
""" """
from tornado.wsgi import to_wsgi_str from tornado.wsgi import to_wsgi_str
import sys import sys
@ -560,19 +566,19 @@ class WsgiInputContainer(object):
class CustomHTTPServer(tornado.httpserver.HTTPServer): class CustomHTTPServer(tornado.httpserver.HTTPServer):
""" """
Custom implementation of `tornado.httpserver.HTTPServer` that allows defining max body sizes depending on path and Custom implementation of ``tornado.httpserver.HTTPServer`` that allows defining max body sizes depending on path and
method. method.
The implementation is mostly taken from `tornado.httpserver.HTTPServer`, the only difference is the creation The implementation is mostly taken from ``tornado.httpserver.HTTPServer``, the only difference is the creation
of a `CustomHTTP1ConnectionParameters` instance instead of `tornado.http1connection.HTTP1ConnectionParameters` of a ``CustomHTTP1ConnectionParameters`` instance instead of ``tornado.http1connection.HTTP1ConnectionParameters``
which is supplied with the two new constructor arguments `max_body_sizes` and `max_default_body_size` and the which is supplied with the two new constructor arguments ``max_body_sizes`` and ``max_default_body_size`` and the
creation of a `CustomHTTP1ServerConnection` instead of a `tornado.http1connection.HTTP1ServerConnection` upon creation of a ``CustomHTTP1ServerConnection`` instead of a ``tornado.http1connection.HTTP1ServerConnection`` upon
connection by a client. connection by a client.
`max_body_sizes` is expected to be an iterable containing tuples of the form (method, path regex, maximum body size), ``max_body_sizes`` is expected to be an iterable containing tuples of the form (method, path regex, maximum body size),
with method and path regex having to match in order for maximum body size to take affect. with method and path regex having to match in order for maximum body size to take affect.
`default_max_body_size` is the default maximum body size to apply if no specific one from `max_body_sizes` matches. ``default_max_body_size`` is the default maximum body size to apply if no specific one from ``max_body_sizes`` matches.
""" """
def __init__(self, request_callback, no_keep_alive=False, io_loop=None, def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
@ -610,9 +616,9 @@ class CustomHTTPServer(tornado.httpserver.HTTPServer):
class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection): class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection):
""" """
A custom implementation of `tornado.http1connection.HTTP1ServerConnection` which utilizes a `CustomHTTP1Connection` A custom implementation of ``tornado.http1connection.HTTP1ServerConnection`` which utilizes a ``CustomHTTP1Connection``
instead of a `tornado.http1connection.HTTP1Connection` in `_server_request_loop`. The implementation logic is instead of a ``tornado.http1connection.HTTP1Connection`` in ``_server_request_loop``. The implementation logic is
otherwise the same as `tornado.http1connection.HTTP1ServerConnection`. otherwise the same as ``tornado.http1connection.HTTP1ServerConnection``.
""" """
@tornado.gen.coroutine @tornado.gen.coroutine
@ -644,8 +650,8 @@ class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection)
class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection): class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
""" """
A custom implementation of `tornado.http1connection.HTTP1Connection` which upon checking the `Content-Length` of A custom implementation of ``tornado.http1connection.HTTP1Connection`` which upon checking the ``Content-Length`` of
the request against the configured maximum utilizes `max_body_sizes` and `default_max_body_size` as a fallback. the request against the configured maximum utilizes ``max_body_sizes`` and ``default_max_body_size`` as a fallback.
""" """
def __init__(self, stream, is_client, params=None, context=None): def __init__(self, stream, is_client, params=None, context=None):
@ -657,12 +663,12 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
def _read_body(self, code, headers, delegate): def _read_body(self, code, headers, delegate):
""" """
Basically the same as `tornado.http1connection.HTTP1Connection._read_body`, but determines the maximum Basically the same as ``tornado.http1connection.HTTP1Connection._read_body``, but determines the maximum
content length individually for the request (utilizing `._get_max_content_length`). content length individually for the request (utilizing ``._get_max_content_length``).
If the individual max content length is 0 or smaller no content length is checked. If the content length of the If the individual max content length is 0 or smaller no content length is checked. If the content length of the
current request exceeds the individual max content length, the request processing is aborted and an current request exceeds the individual max content length, the request processing is aborted and an
`HTTPInputError` is raised. ``HTTPInputError`` is raised.
""" """
content_length = headers.get("Content-Length") content_length = headers.get("Content-Length")
if "Content-Length" in headers: if "Content-Length" in headers:
@ -706,8 +712,8 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
def _get_max_content_length(self, method, path): def _get_max_content_length(self, method, path):
""" """
Gets the max content length for the given method and path. Checks whether method and path match against any Gets the max content length for the given method and path. Checks whether method and path match against any
of the specific maximum content lengths supplied in `max_body_sizes` and returns that as the maximum content of the specific maximum content lengths supplied in ``max_body_sizes`` and returns that as the maximum content
length if available, otherwise returns `default_max_body_size`. length if available, otherwise returns ``default_max_body_size``.
:param method: method of the request to match against :param method: method of the request to match against
:param path: path od the request to match against :param path: path od the request to match against
@ -723,10 +729,10 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionParameters): class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionParameters):
""" """
An implementation of `tornado.http1connection.HTTP1ConnectionParameters` that adds to new parameters An implementation of ``tornado.http1connection.HTTP1ConnectionParameters`` that adds two new parameters
`max_body_sizes` and `default_max_body_size`. ``max_body_sizes`` and ``default_max_body_size``.
For a description of these please see the documentation of `CustomHTTPServer` above. For a description of these please see the documentation of ``CustomHTTPServer`` above.
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -738,6 +744,23 @@ class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionPar
class LargeResponseHandler(tornado.web.StaticFileHandler): class LargeResponseHandler(tornado.web.StaticFileHandler):
"""
Customized `tornado.web.StaticFileHandler <http://tornado.readthedocs.org/en/branch4.0/web.html#tornado.web.StaticFileHandler>`_
that allows delivery of the requested resource as attachment and access validation through an optional callback.
Arguments:
path (str): The system path from which to serve files (this will be forwarded to the ``initialize`` method of
:class:``~tornado.web.StaticFileHandler``)
default_filename (str): The default filename to serve if none is explicitely specified and the request references
a subdirectory of the served path (this will be forwarded to the ``initialize`` method of
:class:``~tornado.web.StaticFileHandler`` as the ``default_filename`` keyword parameter). Defaults to ``None``.
as_attachment (bool): Whether to serve requested files with ``Content-Disposition: attachment`` header (``True``)
or not. Defaults to ``False``.
access_validation (function): Callback to call in the ``get`` method to validate access to the resource. Will
be called with ``self.request`` as parameter which contains the full tornado request object. Should raise
a ``tornado.web.HTTPError`` if access is not allowed in which case the request will not be further processed.
Defaults to ``None`` and hence no access validation being performed.
"""
def initialize(self, path, default_filename=None, as_attachment=False, access_validation=None): def initialize(self, path, default_filename=None, as_attachment=False, access_validation=None):
tornado.web.StaticFileHandler.initialize(self, os.path.abspath(path), default_filename) tornado.web.StaticFileHandler.initialize(self, os.path.abspath(path), default_filename)
@ -764,6 +787,31 @@ class LargeResponseHandler(tornado.web.StaticFileHandler):
class UrlForwardHandler(tornado.web.RequestHandler): class UrlForwardHandler(tornado.web.RequestHandler):
"""
`tornado.web.RequestHandler <http://tornado.readthedocs.org/en/branch4.0/web.html#request-handlers>`_ that proxies
requests to a preconfigured url and returns the response. Allows delivery of the requested content as attachment
and access validation through an optional callback.
This will use `tornado.httpclient.AsyncHTTPClient <http://tornado.readthedocs.org/en/branch4.0/httpclient.html#tornado.httpclient.AsyncHTTPClient>`_
for making the request to the configured endpoint and return the body of the client response with the status code
from the client response and the following headers:
* ``Date``, ``Cache-Control``, ``Server``, ``Content-Type`` and ``Location`` will be copied over.
* If ``as_attachment`` is set to True, ``Content-Disposition`` will be set to ``attachment``. If ``basename`` is
set including the attachement's ``filename`` attribute will be set to the base name followed by the extension
guessed based on the MIME type from the ``Content-Type`` header of the response. If no extension can be guessed
no ``filename`` attribute will be set.
Arguments:
url (str): URL to forward any requests to. A 404 response will be returned if this is not set. Defaults to ``None``.
as_attachment (bool): Whether to serve files with ``Content-Disposition: attachment`` header (``True``)
or not. Defaults to ``False``.
basename (str): base name of file names to return as part of the attachment header, see above. Defaults to ``None``.
access_validation (function): Callback to call in the ``get`` method to validate access to the resource. Will
be called with ``self.request`` as parameter which contains the full tornado request object. Should raise
a ``tornado.web.HTTPError`` if access is not allowed in which case the request will not be further processed.
Defaults to ``None`` and hence no access validation being performed.
"""
def initialize(self, url=None, as_attachment=False, basename=None, access_validation=None): def initialize(self, url=None, as_attachment=False, basename=None, access_validation=None):
tornado.web.RequestHandler.initialize(self) tornado.web.RequestHandler.initialize(self)