{ "info": { "author": "AaylaSecura1138", "author_email": "aayla.secura.1138@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# Overview\n\n`mixnmatchttp` is a modular HTTP/S server based on [Python's http\nserver](https://docs.python.org/3/library/http.server.html) that lets you \"mix\n'n' match\" various functionalities. It defines several request handlers, which\nare wrappers around `http.server.SimpleHTTPRequestHandler`, as well as\na `ThreadingHTTPServer` which can be used in place of Python's\n`http.server.HTTPServer` for multi-threading support.\n\n# Quick start\n\nRequest handlers define special endpoints and/or templates as class attributes.\n\nEndpoints are the RESTful API of the server. A request which does not map to an\nendpoint is treated as a request for a file or directory (Python's http server\nhandles it, unless your class overrides the `do_(GET|POST|...)` methods).\nA request which does map to an endpoint will call an endpoint handler: a method\nof your class by the name `do_{underscope-separated path}` with \"path\" being\nthe most-specific (longest) path for which a method is defined. E.g.\n`/foo/bar/baz` will try to call `do_foo_bar_baz`, then `do_foo_bar`, then\n`do_foo`, and finally `do_default`. `do_default` is defined in\n`BaseHTTPRequestHandler` but your class may want to override it.\n\nTemplate pages are parametrized response bodies. Templates specify a template\npage to be used with and give a dictionary of parameters and values to use with\nthe template. Each parameter value may also contain dynamic parameters, which\nare given to the `BaseHTTPRequestHandler.page_from_template` method, which\nconstructs the final page.\n\nYou can inherit from one or more of the `*HTTPRequestHandlers`. Each parent's\nendpoints/templates will be copied to, without overwriting, your child class'\nendpoints/templates.\n\n---\n\n**Important notes:**\n\n * If you need to override any of the HTTP method handlers (e.g. `do_GET`),\n you must decorate them with `mixnmatchttp.handlers.base.methodhandler`, as\n shown in the demo below. And if you need to call any of the parent's HTTP\n method handlers you must call the original wrapped method using the\n `__wrapped__` attribute, as shown in the demo.\n\n---\n\n## Defining endpoints\n\nEndpoints, templates and template pages constructors have the same signature as\nfor a dictionary. Endpoints are of type `mixnmatchttp.endpoints.Endpoint`,\nwhile templates and template pages are of type\n`mixnmatchttp.common.DictNoClobber`. However, you can define them as\na dictionary, or any type which has a dictionary-like interface, and\n`BaseHTTPRequestHandler`'s meta class will convert them to the appropriate\nclass.\n\nFor example you can define endpoints like so:\n\n```python\nclass MyHandler(BaseHTTPRequestHandler):\n _endpoints = mixnmatchttp.endpoints.Endpoint(\n some_sub={\n '$allowed_methods': {'GET', 'POST'},\n '$nargs': 1,\n 'some_sub_sub': {\n '$nargs': endpoints.ARGS_ANY,\n '$raw_args': True, # don't canonicalize rest of path\n }\n },\n some_other_sub={})\n```\n\nor like so:\n\n```python\nclass MyHandler(BaseHTTPRequestHandler):\n _endpoints = mixnmatchttp.endpoints.Endpoint({\n 'some_sub': {\n '$allowed_methods': {'GET', 'POST'},\n '$nargs': 1,\n 'some_sub_sub': {\n '$nargs': endpoints.ARGS_ANY,\n '$raw_args': True, # don't canonicalize rest of path\n }\n },\n },\n some_other_sub={})\n```\n\nor like so:\n\n```python\nclass MyHandler(BaseHTTPRequestHandler):\n _endpoints = {\n 'some_sub': {\n '$allowed_methods': {'GET', 'POST'},\n '$nargs': 1,\n 'some_sub_sub': {\n '$nargs': endpoints.ARGS_ANY,\n '$raw_args': True, # don't canonicalize rest of path\n }\n },\n 'some_other_sub': {}\n }\n```\n\n\nAny keyword arguments or dictionary keys starting with `$` correspond to an\nattribute (without the `$`) which specifies how an endpoint can be called.\nAll other keyword arguments/keys become child endpoints of the parent; their\nvalue should be another `Endpoint` (or any dictionary-like object).\n\nDefault endpoint attributes are:\n\n```python\ndisabled=False|True # specifies if the enpoint cannot be called directly;\n # False for child endpoints but True for root endpoint\nallowed_methods={'GET','HEAD'} # a set of allowed HTTP methods; HEAD is added to the\n # set if 'GET' is present\nnargs=0 # how many slash-separated arguments the endpoint can take;\n # can be a number of any of:\n # mixnmatchttp.endpoints.ARGS_OPTIONAL for 0 or 1\n # mixnmatchttp.endpoints.ARGS_ANY for any number\n # mixnmatchttp.endpoints.ARGS_REQUIRED for 1 or more\n # !!only reliable if raw_args is False!!\nraw_args=False # whether arguments should not be canonicalized,\n # e.g. /foo/..//bar/./baz will not be turned to /bar/baz\n```\n\nChild endpoints are enabled by default, the root endpoint is disabled by\ndefault; if you want it enabled, either manually change the `disabled`\nattribute, or construct it like so:\n\n```python\nclass MyHandler(BaseHTTPRequestHandler):\n _endpoints = {\n 'some_sub': { ... },\n '$disabled': False, # a request for / will now call do_ or do_default\n # instead of do_(GET|POST|...)\n }\n```\n\n## Handling parsed endpoints\n\nWhen a path resolves to an endpoint, `ep`, the corresponding endpoint handler\n(`do_???`) will be passed a single argument:\na `mixnmatchttp.endpoints.ParsedEndpoint` (inherits from\n`mixnmatchttp.endpoints.Endpoint`) initialized from the original `ep` with the\nfollowing additional attributes:\n\n * `httpreq`: the instance of `BaseHTTPRequestHandler` for this request\n * `handler`: partial of the `httpreq`'s method selected as a handler, with the\n first argument being the `ParsedEndpoint` itself\n * `root`: longest path of the endpoint (with a leading `/`) corresponding to\n a defined handler, i.e. if the path is `/foo/bar` and `do_foo` is\n selected, `root` will be `/foo`; if using `do_default`, `root` is empty (`''`).\n * `sub`: rest of the path of the endpoint without a leading `/`, e.g. `bar` or `foo/bar`\n * `args`: everything following the endpoint's path without a leading `/`\n * `argslen`: the number of arguments it was called with (length of array from `/` separated `args`)\n\n## Example: Implementing a server\n\nSome methods that you may want to override, as well as implementing a custom\nendpoint and template, are shown below for `MyHandler`:\n\n```python\n# from __future__ import unicode_literals\nfrom __future__ import print_function\nfrom __future__ import division\nfrom __future__ import absolute_import\nfrom builtins import *\nfrom future import standard_library\nstandard_library.install_aliases()\nimport re\nimport ssl\nfrom http.server import HTTPServer\nfrom mixnmatchttp.servers import ThreadingHTTPServer\nfrom mixnmatchttp import endpoints\nfrom mixnmatchttp.handlers import BaseHTTPRequestHandler,methodhandler\nfrom mixnmatchttp.common import DictNoClobber\n\nclass MyHandler(BaseHTTPRequestHandler):\n _endpoints = endpoints.Endpoint(\n foobar={ }, # will use do_default handler\n refreshme={\n '$nargs': endpoints.ARGS_OPTIONAL,\n },\n debug={\n # these are for when /debug is called\n '$allowed_methods': {'GET', 'POST'},\n '$nargs': 1,\n 'sub': { # will use do_debug handler\n # these are for when /debug/sub is called\n '$nargs': endpoints.ARGS_ANY,\n '$raw_args': True, # don't canonicalize rest of path\n }\n },\n )\n _template_pages = DictNoClobber(\n simpletxt={\n 'data':'$CONTENT',\n 'type':'text/html'\n },\n simplehtml={\n 'data':'''\n \n \n \n \n $HEAD\n $TITLE\n \n \n $BODY\n \n \n ''',\n 'type':'text/html'\n },\n )\n _templates = DictNoClobber(\n refresh={\n 'fields':{\n 'HEAD':'',\n 'TITLE':'Example',\n 'BODY':'

Example page, will refresh every ${interval}s.

',\n },\n 'page': 'simplehtml',\n },\n debug={\n 'fields':{\n 'CONTENT':'${info}You called endpoint $root ($sub) ($args)',\n },\n 'page': 'simpletxt',\n },\n )\n\n def do_refreshme(self, ep):\n interval = ep.args\n if not interval:\n interval = '30'\n\n '''Handler for the endpoint /refreshme'''\n page = self.page_from_template(self.templates['refresh'],\n {'interval': interval})\n self.render(page)\n\n def do_debug(self, ep):\n '''Handler for the endpoint /debug'''\n # set a header just for this request\n self.headers_to_send['X-Debug'] = 'Foo'\n page = self.page_from_template(self.templates['debug'],\n {'root': ep.root, 'sub': ep.sub, 'args': ep.args})\n self.render(page)\n\n def do_default(self, ep):\n '''Default endpoints handler'''\n page = self.page_from_template(self.templates['debug'],\n {'info': 'This is do_default. ',\n 'root': ep.root, 'sub': ep.sub, 'args': ep.args})\n self.render(page)\n\n # Don't forget this decorator!\n @methodhandler\n def do_GET(self):\n # Do something here, then call parent's undecorated method\n super().do_GET.__wrapped__()\n\n def denied(self):\n '''Deny access to /forbidden'''\n if re.match('^/forbidden(/|$)', self.pathname):\n # return args are passed to BaseHTTPRequestHandler.send_error\n # in that order; both messages are optional\n return (403, None, 'Access denied')\n return super().denied()\n\n def no_cache(self):\n '''Only allow caching of scripts'''\n return (not self.pathname.endswith('.js')) or super().no_cache()\n\n def send_custom_headers(self):\n '''Send our custom headers'''\n self.send_header('X-Foo', 'Foobar')\n\n\nif __name__ == \"__main__\":\n use_SSL = False\n keyfile = '' # path to PEM key, if use_SSL is True\n certfile = '' # path to PEM certificate, if use_SSL is True\n srv_cls = HTTPServer\n # srv_cls = ThreadingHTTPServer # if using multi-threading\n address = '127.0.0.1'\n port = 58080\n\n httpd = srv_cls((address, port), MyHandler)\n if use_SSL:\n httpd.socket = ssl.wrap_socket(\n httpd.socket,\n keyfile=keyfile,\n certfile=certfile,\n server_side=True)\n\n try:\n httpd.serve_forever()\n except KeyboardInterrupt:\n httpd.server_close()\n```\n\n * A request for `/debug/sub/..//foo/../bar/./baz` will call `self.do_debug` with `ep`, a copy of `MyHandler._endpoints['debug']['sub']`. `ep.root` will be \"debug\", `ep.sub` will be \"sub\", `ep.args` will be \"..//foo/../bar/./baz\". `self.command` will give the HTTP method.\n * A request for `/debug/foo/../bar` will canonicalize the path to `/debug/bar` (since `raw_args` for `/debug` is `False`) call `do_debug` with `ep`, a copy of `MyHandler._endpoints['debug']`. `ep.root` will be \"debug\", `ep.sub` will be \"\", `ep.args` will be \"bar\".\n * A request for `/debug/../bar` will raise `mixnmatchttp.endpoints.NotAnEndpointError` since the path will be canonicalized (as above) and result in `/bar`, and `bar` is not a valid endpoint.\n * A `POST` request for `/foobar` will raise `mixnmatchttp.endpoints.MethodNotAllowedError` since `/foobar` only allows HTTP `GET`.\n * A request for `/debug/../foobar` will call parent's `do_default` with `ep`, a copy of `MyHandler._endpoints['foobar']`. `ep.root` will be \"\", `ep.sub` will be \"foobar\", `ep.args` will be \"\".\n * A request for `/debug/foo/bar` will raise `mixnmatchttp.endpoints.ExtraArgsError` since `/debug` expect exactly one argument.\n * A request for `/debug` will raise `mixnmatchttp.endpoints.MissingArgsError` since `/debug` expect exactly one argument.\n * A request for `/refreshme` will render a page which refreshes every 30 seconds (default).\n * A request for `/refreshme/5` will render a page which refreshes every 5 seconds.\n\n# Handlers\n\n## AuthHTTPRequestHandler\n\nImplements username:password authentication via form or JSON `POST` request. Has configurable file paths/endpoints for which authentication is required.\n\nIf no file containing username:password is set (as a `_userfile` class attribute), it implements dummy authentication (all logins succeed).\n\n * `GET|POST /login`: Issues a `SESSION` cookie if username and password is valid\n - Supported URL parameters:\n + `goto`: Redirect to this URL\n - Required body or URL parameters (unless no userfile was given, in which case it always authenticates):\n + `username`: Username (duh)\n + `password`: Password (duh)\n - Response codes:\n + `200 OK`: Authentication successful; `SESSION` cookie is set\n + `401 Unauthorized`: Username or password invalid;\n + `302 Found`: Location is as requested via the `goto` parameter\n - Notes:\n + Sessions are forgotten on the server side upon restart\n + Cookies are issued with the `HttpOnly` flag, and if over SSL with the `Secure` flag as well\n * `GET /logout`: Clears the `SESSION` cookie from the browser and the server\n - Supported URL parameters:\n + `goto`: Redirect to this URL\n - Response codes:\n + `200 OK`: Empty body\n + `302 Found`: Location is as requested via the `goto` parameter\n * `GET|POST /changepwd`: Changes the password for a given username\n - Supported URL parameters:\n + `goto`: Redirect to this URL\n - Required body or URL parameters:\n + `username`: Username (duh)\n + `password`: Current password\n + `new_password`: New password (duh)\n - Response codes:\n + `200 OK`: Success; password is changed, current\n `SESSION` is invalidated and a new `SESSION` cookie is set\n + `401 Unauthorized`: Username or password invalid\n + `302 Found`: Location is as requested via the `goto` parameter\n\n## CachingHTTPRequestHandler\n\nAllows saving of content as a named page for later request, or displaying the encoded (URL or base64) request content as a page in response.\n\n * `POST /echo`: Render the requested content\n - Supported URL parameters:\n + `data`: The encoded content of the page to be rendered (required)\n + `type`: The content type of the rendered page (defaults to text/plain)\n - Supported formats:\n + `application/json` with `base64` encoded data\n + `application/x-www-form-urlencoded` (with URL encoded data)\n - Response codes:\n + `200 OK`: The body and `Content-Type` are as requested\n + `400 Bad Request`: Cannot decode data or find the data parameter\n * `POST /cache/{name}`: Temporarily save the requested content (in memory only)\n - Supported URL parameters and formats are the same as for `POST /echo`\n - Response codes:\n + `204 No Content`: Page cached\n + `500 Server Error`: Maximum cache memory reached, or page `{name}` already cached\n - Notes:\n + Once saved, a page cannot be overwritten (until the server is shutdown) even if it is cleared from memory (see `/cache/clear`)\n * `GET /cache/{name}`: Retrieve a previously saved page\n - Response codes:\n + `200 OK`: The body and `Content-Type` are as requested during caching\n + `500 Server Error`: No such cached page, or page cleared from memory\n * `GET /cache/clear/{name}`: Clear a previously saved page to free memory\n - Response codes:\n + `204 No Content`: Page cleared\n * `GET /cache/clear`: Clear all previously saved pages to free memory\n - Response codes:\n + `204 No Content`: All pages cleared\n * `GET /cache/new`: Get a random UUID\n - Response codes:\n + `200 OK`: Body contains a randomly generated UUID; use in `POST /cache/{uuid}`\n\n## ProxyingHTTPRequestHandler\n\nRedirects (with `307`) to any address given in the URL.\n\n * `GET /goto/{address}`: Redirect to this (URI-decoded) address\n - Response codes:\n + `302 Found`: Location is the address which follows `/goto/`; if domain is not given (i.e. address does not start with `schema://` or `//`) it is taken from the `Referer`, `Origin`, `X-Forwarded-Host`, `X-Forwarded-For` or `Forwarded`\n + `200 OK`: Empty body; this happens if address was relative (no domain) and neither of the aforementioned headers was given\n - Notes:\n + Unlike the address given as a `goto` parameter to some of the other endpoints, the address here is not URI-decoded\n + The `{address}` is not parsed at all, its path is not canonicalized unlike calls to other endpoints. I.e. `/login///foo.baz/../..//cache` will call `/cache`, but `/goto///foo.baz/../..//cache` will redirect to `//foo.baz/../..//cache` (remote host `foo.baz` with path `../..//cache`)\n\n# Known issues\n\n * Clearing of cache is not done safely in mutli-threaded context. You may experience issues under heavy load. *Solution*: Wait for fix...\n * When running as a signle thread (default), the server sometimes hangs. It seems to be an issue whereby some browsers don't close the socket. *Solution*: Run the server in multi-thread mode (`-t` option).\n * Occasionally a `BrokenPipeError` is thrown. It happens with some browsers which close the socket abruptly. *Solution*: Just ignore it.\n\n# Coming soon\n\n * MT-safe saving and clearing of cache\n\n## Possibly coming at some point\n\n * Database lookup of users\n * Password policy\n\n# Demos and source\n\nSource code and demo scripts which build on the handlers can be found at [https://github.com/aayla-secura/mixnmatchttp](https://github.com/aayla-secura/mixnmatchttp)\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/aayla-secura/mixnmatchttp", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "mixnmatchttp", "package_url": "https://pypi.org/project/mixnmatchttp/", "platform": "", "project_url": "https://pypi.org/project/mixnmatchttp/", "project_urls": { "Homepage": "https://github.com/aayla-secura/mixnmatchttp" }, "release_url": "https://pypi.org/project/mixnmatchttp/0.2a0.post1/", "requires_dist": [ "future (>=0.12)", "wrapt (>=1)" ], "requires_python": "", "summary": "Modular HTTP server: Auth, Caching, Proxy, and more", "version": "0.2a0.post1" }, "last_serial": 5126420, "releases": { "0.1a0.post1": [ { "comment_text": "", "digests": { "md5": "6b102c5f34839a6c69b29901f94431f2", "sha256": "7735d039598a393a1c5292ea2e1de3204dafc1387f41955324b03a033bb32f94" }, "downloads": -1, "filename": "mixnmatchttp-0.1a0.post1-py3-none-any.whl", "has_sig": false, "md5_digest": "6b102c5f34839a6c69b29901f94431f2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 21110, "upload_time": "2019-03-28T02:24:17", "url": "https://files.pythonhosted.org/packages/45/f7/9fd53c7807a1323a8cc7e5df329f13036610a43bf6233b085501b45b8460/mixnmatchttp-0.1a0.post1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "410a9d8b4ea3092f7771e00e584601e7", "sha256": "1c74c3de8b400cdd4a871bda1220e3bcae34cc1cdd1921c12086ccf3314ee729" }, "downloads": -1, "filename": "mixnmatchttp-0.1a0.post1.tar.gz", "has_sig": false, "md5_digest": "410a9d8b4ea3092f7771e00e584601e7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20198, "upload_time": "2019-03-28T02:24:19", "url": "https://files.pythonhosted.org/packages/62/ef/9357d6b519fd1b2e7fde95d718afb1f174f4140ac1e2a5bbe39965318c7b/mixnmatchttp-0.1a0.post1.tar.gz" } ], "0.2a0.post1": [ { "comment_text": "", "digests": { "md5": "5ce0cff05c13848049339882a1e7c5d4", "sha256": "73c136e077ebe71d5a9e3b8c755dea8aa9088292b56e4787933e2b7dd5acc38d" }, "downloads": -1, "filename": "mixnmatchttp-0.2a0.post1-py3-none-any.whl", "has_sig": false, "md5_digest": "5ce0cff05c13848049339882a1e7c5d4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 28821, "upload_time": "2019-04-10T23:09:02", "url": "https://files.pythonhosted.org/packages/95/79/f1f51020ff7760e7459f483b2e6f60d1a18ef5e9ce4637f5cbe86e5d524b/mixnmatchttp-0.2a0.post1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "408c20b0bc6a54a60c60ce539fea44ee", "sha256": "206ebcf8d18cc70228b4fb7dc9f65d2ea4ba553e1c4c3bb12c89c9c5de64682e" }, "downloads": -1, "filename": "mixnmatchttp-0.2a0.post1.tar.gz", "has_sig": false, "md5_digest": "408c20b0bc6a54a60c60ce539fea44ee", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29513, "upload_time": "2019-04-10T23:09:04", "url": "https://files.pythonhosted.org/packages/5b/7f/59d3dc77c47d8b27e509d470ffb2e3be7dec8118843fcf15b2af64656248/mixnmatchttp-0.2a0.post1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "5ce0cff05c13848049339882a1e7c5d4", "sha256": "73c136e077ebe71d5a9e3b8c755dea8aa9088292b56e4787933e2b7dd5acc38d" }, "downloads": -1, "filename": "mixnmatchttp-0.2a0.post1-py3-none-any.whl", "has_sig": false, "md5_digest": "5ce0cff05c13848049339882a1e7c5d4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 28821, "upload_time": "2019-04-10T23:09:02", "url": "https://files.pythonhosted.org/packages/95/79/f1f51020ff7760e7459f483b2e6f60d1a18ef5e9ce4637f5cbe86e5d524b/mixnmatchttp-0.2a0.post1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "408c20b0bc6a54a60c60ce539fea44ee", "sha256": "206ebcf8d18cc70228b4fb7dc9f65d2ea4ba553e1c4c3bb12c89c9c5de64682e" }, "downloads": -1, "filename": "mixnmatchttp-0.2a0.post1.tar.gz", "has_sig": false, "md5_digest": "408c20b0bc6a54a60c60ce539fea44ee", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29513, "upload_time": "2019-04-10T23:09:04", "url": "https://files.pythonhosted.org/packages/5b/7f/59d3dc77c47d8b27e509d470ffb2e3be7dec8118843fcf15b2af64656248/mixnmatchttp-0.2a0.post1.tar.gz" } ] }