diff --git a/docs/api/http-server.rst b/docs/api/http-server.rst index db10d979..eb568718 100644 --- a/docs/api/http-server.rst +++ b/docs/api/http-server.rst @@ -4,9 +4,173 @@ HTTP server side API ******************** -TODO: Describe how this is used. -TODO: Static web client example. -TODO: WSGI app example. +The :ref:`ext-http` extension comes with an HTTP server to host Mopidy's +:ref:`http-api`. This web server can also be used by other extensions that need +to expose something over HTTP. + +The HTTP server side API can be used to: + +- host static files for e.g. a Mopidy client written in pure JavaScript, +- host a `Tornado `__ application, or +- host a WSGI application. + +To extend the web server, an extension needs to create a subclass of +:class:`mopidy.http.Router` and register the subclass with the extension +registry under the ``http:router`` key. + +For details on how to make a Mopidy extension, see the :ref:`extensiondev` +guide. + + +Static web client example +========================= + +To serve static files, you just need to declare a +:attr:`~mopidy.http.Router.name` for your router and tell where the static +files are located by setting the :attr:`~mopidy.http.Router.static_file_path` +attribute on your router class. + +The :attr:`~mopidy.http.Router.name` attribute is used to build the URL to +make the files available on. By convention, it should be identical with the +extension's :attr:`~mopidy.ext.Extension.ext_name`, like in the examples here. + +Assuming that the code below is located in the file +:file:`mywebclient/__init__.py`, the files in the directory +:file:`mywebclient/static/` will be made available at ``/mywebclient/`` on +Mopidy's web server. For example, :file:`mywebclient/static/foo.html` will be +available at http://localhost:6680/mywebclient/foo.html. + +:: + + from __future__ import unicode_literals + + import os + + from mopidy import ext, http + + + class MyStaticFilesRouter(http.Router): + name = 'mywebclient' + static_file_path = os.path.join(os.path.dirname(__file__), 'static') + + + class MyWebClientExtension(ext.Extension): + ext_name = 'mywebclient' + + def setup(self, registry): + registry.add('http:router', MyStaticFilesRouter) + + # See the Extension API for the full details on this class + + +Tornado application example +=========================== + +The :ref:`ext-http` extension's web server is based on the `Tornado +`__ web framework. Thus, it has first class support +for Tornado request handlers. + +In the following example, we create a :class:`tornado.web.RequestHandler` +called :class:`MyRequestHandler` that responds to HTTP GET requests with the +string ``Hello, world!``. The router registers this Tornado request handler on +the root URL, ``/``. The URLs returned from +:meth:`~mopidy.http.Router.get_request_handlers` are combined with the +:attr:`~mopidy.http.Router.name`` attribute of the router, so the full absolute +URL for the request handler becomes ``/mywebclient/``. + +The router is added to the extension registry by +:meth:`MyWebClientExtension.setup`. When the extension is installed, Mopidy +will respond to requests to http://localhost:6680/mywebclient/ with the string +``Hello, world!``. + +:: + + from __future__ import unicode_literals + + import os + + import tornado.web + + from mopidy import ext, http + + + class MyRequestHandler(tornado.web.RequestHandler): + def get(self): + self.write('Hello, world!') + + + class MyTornadoRouter(http.Router): + name = 'mywebclient' + + def get_request_handlers(self): + return [ + ('/', MyRequestHandler) + ] + + + class MyWebClientExtension(ext.Extension): + ext_name = 'mywebclient' + + def setup(self, registry): + registry.add('http:router', MyTornadoRouter) + + # See the Extension API for the full details on this class + + + +WSGI application example +======================== + +WSGI applications are second-class citizens on Mopidy's HTTP server. The WSGI +applications are run inside Tornado, which is based on non-blocking I/O and a +single event loop. In other words, your WSGI applications will only have a +single thread to run on, and if your application is doing blocking I/O, it will +block all other requests from being handled by the web server as well. + +The example below shows how a WSGI application that returns the string +``Hello, world!`` on all requests. The WSGI application is wrapped as a Tornado +application and mounted at http://localhost:6680/mywebclient/. + +:: + + from __future__ import unicode_literals + + import os + + import tornado.web + import tornado.wsgi + + from mopidy import ext, http + + + class MyWSGIRouter(http.Router): + name = 'mywebclient' + + def get_request_handlers(self): + def wsgi_app(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['Hello, world!\n'] + + return [ + ('(.*)', tornado.web.FallbackHandler, { + 'fallback': tornado.wsgi.WSGIContainer(wsgi_app), + }), + ] + + + class MyWebClientExtension(ext.Extension): + ext_name = 'mywebclient' + + def setup(self, registry): + registry.add('http:router', MyTornadoRouter) + + # See the Extension API for the full details on this class + + +HTTP Router API +=============== .. autoclass:: mopidy.http.Router :members: