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
plugin.rst
printer.rst
server.rst
settings.rst
slicing.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():
"""
All requests in this blueprint need to be made supplying an API key. This may be the UI_API_KEY, in which case
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
the request.
``before_request`` handler for blueprints for which all requests need to be made supplying an API key.
This may be the UI_API_KEY, in which case the underlying request processing will directly take place, or it may be
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
@ -62,6 +63,11 @@ def apiKeyRequestHandler():
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
allowCrossOrigin = settings().getBoolean(["api", "allowCrossOrigin"])
@ -72,7 +78,9 @@ def corsResponseHandler(resp):
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()
@ -131,18 +139,24 @@ class ReverseProxied(object):
different than what is used locally.
In nginx:
location /myprefix {
proxy_pass http://192.168.0.1:5001;
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;
}
.. code-block:: none
location /myprefix {
proxy_pass http://192.168.0.1:5001;
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:
server:
baseUrl: /myprefix
scheme: http
.. code-block:: yaml
server:
baseUrl: /myprefix
scheme: http
:param app: the WSGI application
: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():
"""
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
the first ever contact to an NTP server).
@ -65,63 +65,67 @@ def fix_ioloop_scheduling():
@tornado.web.stream_request_body
class UploadStorageFallbackHandler(tornado.web.RequestHandler):
"""
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`,
`content_type`, `path` and `size` instead via a rewritten body.
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``,
``content_type``, ``path`` and ``size`` instead via a rewritten body.
Basically similar to what the nginx upload module does.
Basic request body example:
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file"; filename="test.gcode"
Content-Type: application/octet-stream
.. code-block:: none
...
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="apikey"
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file"; filename="test.gcode"
Content-Type: application/octet-stream
my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select"
...
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="apikey"
true
------WebKitFormBoundarypYiSUx63abAmhT5C--
my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select"
true
------WebKitFormBoundarypYiSUx63abAmhT5C--
That would get turned into:
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="apikey"
.. code-block:: none
my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select"
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="apikey"
true
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.path"
my_funny_apikey
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="select"
/tmp/tmpzupkro
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.name"
true
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.path"
test.gcode
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.content_type"
/tmp/tmpzupkro
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.name"
application/octet-stream
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.size"
test.gcode
------WebKitFormBoundarypYiSUx63abAmhT5C
Content-Disposition: form-data; name="file.content_type"
349182
------WebKitFormBoundarypYiSUx63abAmhT5C--
application/octet-stream
------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
where necessary.
"""
# the request methods that may contain a request body
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):
if not suffixes:
@ -162,8 +166,8 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def prepare(self):
"""
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
body-less request, just calls the `fallback` with an empty body and finishes the request.
: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.
"""
if self.request.method in UploadStorageFallbackHandler.BODY_METHODS:
self._bytes_left = self.request.headers.get("Content-Length", 0)
@ -194,7 +198,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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
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.
:param chunk: chunk of data received from Tornado
@ -207,7 +211,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
self._buffer = data
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")
def _process_multipart_data(self, data):
@ -251,7 +255,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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.
:param header: header to parse
@ -283,23 +287,24 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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
to storage of the data as temporary file on disk), if not creates a new `data` part (which stores
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
incoming data in memory).
Structure of `file` parts:
Structure of ``file`` parts:
* `name`: name of the part
* `filename`: filename associated with the part
* `path`: path to the temporary file storing the file's data
* `content_type`: content type of the part
* `file`: file handle for the temporary file (mode "wb", not deleted on close!)
* ``name``: name of the part
* ``filename``: filename associated with the part
* ``path``: path to the temporary file storing the file's data
* ``content_type``: content type of the part
* ``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
* `content_type`: content type of the part
* `data`: bytes of the part (initialized to "")
* ``name``: name of the part
* ``content_type``: content type of the part
* ``data``: bytes of the part (initialized to an empty string)
:param name: name 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):
"""
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 data: data chunk which was received
@ -334,7 +339,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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
`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
"""
@ -348,8 +353,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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
logged parts, turning `file` parts into new
:return:
logged parts, turning ``file`` parts into new ``data`` parts.
"""
self._new_body = b""
@ -375,8 +379,8 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
def _handle_method(self, *args, **kwargs):
"""
Handler for any request method, takes care of defining the new request body if necessary and forwarding
the current request and changed body to the `fallback`.
Takes care of defining the new request body if necessary and forwarding
the current request and changed body to the ``fallback``.
"""
# determine which body to supply
@ -417,20 +421,22 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler):
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.
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:
wsgi_app = octoprint.server.util.WsgiInputContainer(octoprint_app)
application = tornado.web.Application([
(r".*", UploadStorageFallbackHandler, dict(fallback=wsgi_app),
])
.. code-block:: python
The implementation logic is basically the same as `tornado.wsgi.WSGIContainer` but the `__call__` and `environ`
methods have been adjusted to for an optionally supplied `body` argument which is then used for `wsgi.input`.
wsgi_app = octoprint.server.util.WsgiInputContainer(octoprint_app)
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):
@ -438,10 +444,10 @@ class WsgiInputContainer(object):
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 body: an optional body to use as `wsgi.input` instead of `request.body`, can be a string or a stream
: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
"""
data = {}
@ -486,13 +492,13 @@ class WsgiInputContainer(object):
@staticmethod
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
supplied, `request.body` will be wrapped into a `io.BytesIO` stream and used instead.
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.
: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 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
"""
from tornado.wsgi import to_wsgi_str
import sys
@ -560,19 +566,19 @@ class WsgiInputContainer(object):
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.
The implementation is mostly taken from `tornado.httpserver.HTTPServer`, the only difference is the creation
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
creation of a `CustomHTTP1ServerConnection` instead of a `tornado.http1connection.HTTP1ServerConnection` upon
The implementation is mostly taken from ``tornado.httpserver.HTTPServer``, the only difference is the creation
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
creation of a ``CustomHTTP1ServerConnection`` instead of a ``tornado.http1connection.HTTP1ServerConnection`` upon
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.
`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,
@ -610,9 +616,9 @@ class CustomHTTPServer(tornado.httpserver.HTTPServer):
class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection):
"""
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
otherwise the same as `tornado.http1connection.HTTP1ServerConnection`.
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
otherwise the same as ``tornado.http1connection.HTTP1ServerConnection``.
"""
@tornado.gen.coroutine
@ -644,8 +650,8 @@ class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection)
class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
"""
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.
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.
"""
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):
"""
Basically the same as `tornado.http1connection.HTTP1Connection._read_body`, but determines the maximum
content length individually for the request (utilizing `._get_max_content_length`).
Basically the same as ``tornado.http1connection.HTTP1Connection._read_body``, but determines the maximum
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
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")
if "Content-Length" in headers:
@ -706,8 +712,8 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection):
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
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`.
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``.
:param method: method of 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):
"""
An implementation of `tornado.http1connection.HTTP1ConnectionParameters` that adds to new parameters
`max_body_sizes` and `default_max_body_size`.
An implementation of ``tornado.http1connection.HTTP1ConnectionParameters`` that adds two new parameters
``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):
@ -738,6 +744,23 @@ class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionPar
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):
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):
"""
`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):
tornado.web.RequestHandler.initialize(self)