[
  {
    "path": ".gitignore",
    "content": ".idea\n__pycache__\ndist\nalbatross3.egg-info/\nbuild/\n.DS_Store\n.coverage\ncover\nbench/output\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\npython:\n    - \"3.5\"\ninstall: \n    - pip install aiohttp ujson\n    - python3 setup.py install\nscript: nosetests\n"
  },
  {
    "path": "Makefile",
    "content": "COVERAGE=98\nTEST=nosetests -s --with-coverage --cover-package=albatross --cover-branches $(TARGET)\n\nupload:\n\tgit push\n\tpython3 setup.py sdist bdist_wheel upload\n\ntest:\n\t$(TEST) --cover-min-percentage $(COVERAGE)\n\ncover:\n\t$(TEST) --cover-html\n\twhich open && open cover/index.html\n\n.PHONY: cover\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/kespindler/albatross.svg?branch=master)](https://travis-ci.org/kespindler/albatross)\n\n# Albatross\n\nA modern, fast, simple, natively-async web framework. (Python3.5 only)\n\n```python\nfrom albatross import Server\nimport asyncio\n\n\nclass Handler:\n    async def on_get(self, req, res):\n        await asyncio.sleep(0.1)\n        res.write('Hello, %s' % req.args['name'])\n\n\napp = Server()\napp.add_route('/{name})', Handler())\napp.serve()\n```\n\n### Notes for Usage\n\nFor now (pre 1.0.0), I'm making no claims about API stability (but will try to avoid changes). That said,\nI'm using this framework for some small projects, and it is a joy to work in!\nReach out if you want to use this, as I'm happy to incorporate your feedback!\n\n## Install\n\n    pip3 install albatross3\n\n## Features\n\n- You can read the entire codebase in about 30 minutes.\n\n- It's natively async. Doing `await` database calls or controller calls in your views just works!\n\n- This works with the `uvloop` project, to make your server fast!\n\n## Benchmarks\n\n- My benchmarks indicate that albatross is as fast as aiohttp, both of which are twice as fast as\n  tornado. You can run the benchmarks by poking around in the `bench/` folder.\n\n"
  },
  {
    "path": "albatross/__init__.py",
    "content": "from albatross.request import Request\nfrom albatross.response import Response\nfrom albatross.server import Server\n\nfrom albatross.status_codes import *\nfrom albatross.http_error import HTTPError\n"
  },
  {
    "path": "albatross/compat.py",
    "content": "try:\n    import ujson as json\nexcept ImportError:\n    import json\n"
  },
  {
    "path": "albatross/data_types.py",
    "content": "from albatross.http_error import HTTPError\nfrom albatross.status_codes import HTTP_400\n\n\ndef caseless_pairs(seq):\n    for k, v in seq:\n        yield k.lower(), v\n\n\nclass Immutable:\n    def __setitem__(self, k, v):\n        raise TypeError('Cannot set item on %s' % self.__class__)\n\n    def update(self, E=None, **F):\n        raise TypeError('Cannot update on %s' % self.__class__)\n\n\nclass ImmutableMultiDict(Immutable, dict):\n    def __getitem__(self, k):\n        if k in self:\n            return super(ImmutableMultiDict, self).__getitem__(k)[0]\n        raise HTTPError(HTTP_400, 'Must provide parameter \\'%s\\'.' % k)\n\n    def get(self, k, d=None):\n        if k in self:\n            return super(ImmutableMultiDict, self).__getitem__(k)[0]\n        return d\n\n    def get_all(self, k, d=None):\n        if k in self:\n            return super(ImmutableMultiDict, self).__getitem__(k)\n        return d\n\n\nclass CaselessDict(dict):\n    def __init__(self, it=None, **kwargs):\n        it = caseless_pairs(it) if it else []\n        if kwargs:\n            kwargs = {k.lower(): v for k, v in kwargs.items()}\n        super(CaselessDict, self).__init__(it, **kwargs)\n\n    def __contains__(self, k):\n        return super(CaselessDict, self).__contains__(k.lower())\n\n    def __getitem__(self, k):\n        return super(CaselessDict, self).__getitem__(k.lower())\n\n    def __iter__(self):\n        for k in super(CaselessDict, self).__iter__():\n            yield k.lower()\n\n    def __setitem__(self, k, v):\n        super(CaselessDict, self).__setitem__(k.lower(), v)\n\n    def get(self, k, d=None):\n        if k in self:\n            return super(CaselessDict, self).__getitem__(k.lower())\n        return d\n\n    def update(self, other=None, **kwargs):\n        updates = {k.lower(): v for k, v in kwargs.items()}\n        if other:\n            if hasattr(other, 'items'):\n                other = other.items()\n            updates.update(caseless_pairs(other))\n        return super(CaselessDict, self).update(updates)\n\n\nclass ImmutableCaselessDict(Immutable, CaselessDict):\n    pass\n\n\nclass ImmutableCaselessMultiDict(ImmutableMultiDict, CaselessDict):\n    def __init__(self, it=None, **kwargs):\n        it = caseless_pairs(it) if it else []\n        if kwargs:\n            kwargs = {k.lower(): [v] for k, v in kwargs.items()}\n        for k, v in it:\n            if k in kwargs:\n                kwargs[k].append(v)\n            else:\n                kwargs[k] = [v]\n        super(ImmutableCaselessMultiDict, self).__init__(**kwargs)\n"
  },
  {
    "path": "albatross/http_error.py",
    "content": "\n\nclass HTTPError(Exception):\n    def __init__(self, code, message=None):\n        self.status_code = code\n        self.message = message or code\n"
  },
  {
    "path": "albatross/request.py",
    "content": "from albatross.data_types import (\n    ImmutableMultiDict,\n    ImmutableCaselessMultiDict\n)\nfrom albatross.compat import json\nimport urllib.parse as parse\nfrom httptools import parse_url\nimport cgi\nimport io\n\n\nREQUEST_STATE_PROCESSING = 0\nREQUEST_STATE_CONTINUE = 1\nREQUEST_STATE_COMPLETE = 2\n\n\ndef trim_keys(d):\n    return {k.strip(): v for k, v in d.items()}\n\n\nclass FileStorage:\n\n    def __init__(self, field_storage):\n        self.filename = field_storage.filename\n        self.value = field_storage.value\n\n\nclass Request:\n    \"\"\"\n    Attributes:\n        method (str): One of GET, POST, PUT, DELETE\n        path (str): Full request path\n        query_string (str): Full query string\n        query (dict): dict of the request query\n        body (str): Request body\n        args (dict): Dictionary of named parameters in route regex\n        form (dict): Dictionary of body parameters\n    \"\"\"\n\n    def __init__(self, method=None, path=None, query_string='',\n                 args=None, headers=None, form=None, cookies=None):\n        self._header_list = []\n        self._state = REQUEST_STATE_PROCESSING\n        self.method = method\n        self.path = path\n        self.query_string = query_string\n        self.query = None\n        self.args = args\n        self.headers = ImmutableCaselessMultiDict()\n        self.cookies = ImmutableMultiDict()\n        self.raw_body = io.BytesIO()\n        self.form = form\n\n        if query_string:\n            self.query = ImmutableMultiDict(parse.parse_qs(self.query_string))\n\n        if headers:\n            self.headers = ImmutableCaselessMultiDict(**headers)\n\n        if cookies:\n            self.cookies = ImmutableMultiDict(**cookies)\n\n    def _parse_cookie(self, value):\n        cookies = trim_keys(parse.parse_qs(value))\n        return ImmutableMultiDict(cookies)\n\n    def _parse_form(self, body_stream):\n        # TODO theres probably a way to not read whole body first)\n        env = {'REQUEST_METHOD': 'POST'}\n        form = cgi.FieldStorage(body_stream, headers=self.headers, environ=env)\n        d = {}\n        for k in form.keys():\n            if form[k].filename:\n                d[k] = [FileStorage(form[k])]\n            else:\n                d[k] = [form[k].value]\n        return ImmutableMultiDict(d)\n\n    def _parse_body(self, body_stream):\n        content_type = self.headers.get('Content-Type', '')\n        if content_type == 'application/json':\n            data = body_stream.getvalue().decode()\n            self.form = json.loads(data)\n        elif content_type.startswith('multipart/form-data'):\n            self.form = self._parse_form(body_stream)\n        elif content_type == 'application/x-www-form-urlencoded':\n            data = body_stream.getvalue().decode()\n            self.form = ImmutableMultiDict(parse.parse_qs(data))\n        body_stream.seek(0)\n\n    # HTTPRequestParser protocol methods\n    def on_url(self, url: bytes):\n        parsed = parse_url(url)\n        self.path = parsed.path.decode()\n        self.query_string = (parsed.query or b'').decode()\n        self.query = ImmutableMultiDict(parse.parse_qs(self.query_string))\n\n    def on_header(self, name: bytes, value: bytes):\n        self._header_list.append((name.decode(), value.decode()))\n        if name.lower() == b'expect' and value == b'100-continue':\n            self._state = REQUEST_STATE_CONTINUE\n\n    def on_headers_complete(self):\n        self.headers = ImmutableCaselessMultiDict(self._header_list)\n        cookie_value = self.headers.get('Cookie')\n        if cookie_value:\n            self.cookies = self._parse_cookie(cookie_value)\n\n    def on_body(self, body: bytes):\n        self.raw_body.write(body)\n\n    def on_message_complete(self):\n        self._state = REQUEST_STATE_COMPLETE\n        self.raw_body.seek(0)\n        self._parse_body(self.raw_body)\n\n    @property\n    def finished(self):\n        return self._state == REQUEST_STATE_COMPLETE\n\n    @property\n    def needs_write_continue(self):\n        return self._state == REQUEST_STATE_CONTINUE\n\n    def reset_state(self):\n        self._state = REQUEST_STATE_PROCESSING\n"
  },
  {
    "path": "albatross/response.py",
    "content": "from albatross import status_codes\nfrom albatross.compat import json\nfrom albatross.data_types import CaselessDict\nfrom albatross.http_error import HTTPError\n\n\nclass Response:\n    \"\"\"\n    Attributes:\n        status_code (str): HTTP status code\n        headers (dict): Be careful about case-sensitivity here.\n        body (str):\n\n    \"\"\"\n\n    def __init__(self):\n        self.status_code = status_codes.HTTP_200\n        self._chunks = []\n        self.headers = CaselessDict([\n            ('Content-Type', 'text/html')\n        ])\n        self.cookies = {}\n\n    def clear(self):\n        self._chunks = []\n\n    def write(self, string):\n        self._chunks.append(string.encode())\n\n    def write_bytes(self, bytes):\n        self._chunks.append(bytes)\n\n    def write_json(self, data):\n        self.headers['Content-Type'] = 'application/json'\n        self._chunks.append(json.dumps(data).encode())\n\n    def redirect(self, location, permanent=False):\n        self.headers['Location'] = location\n        if permanent:\n            self.status_code = status_codes.HTTP_301\n        else:\n            self.status_code = status_codes.HTTP_302\n        raise HTTPError(self.status_code)\n"
  },
  {
    "path": "albatross/server.py",
    "content": "import re\nimport asyncio\nfrom datetime import datetime\nfrom albatross import Request, Response\nfrom albatross.status_codes import HTTP_404, HTTP_500, HTTP_405\nfrom albatross.http_error import HTTPError\nfrom httptools import HttpRequestParser\nimport traceback\n\n\ndef write_cookie(writer, key, value):\n    if isinstance(value, tuple):\n        value, duration = value\n        if isinstance(duration, datetime):\n            format = duration.strftime('%a %d %b %Y %H:%M:%S GMT').encode()\n            writer.write(b'Set-Cookie: %s=%s;expires=%s\\r\\n' % (\n                key.encode(), str(value).encode(), format)\n            )\n        elif isinstance(duration, int):\n            writer.write(b'Set-Cookie: %s=%s;max-age=%d\\r\\n' % (\n                key.encode(), str(value).encode(), duration)\n            )\n    else:\n        writer.write(b'Set-Cookie: %s=%s\\r\\n' % (\n            key.encode(), str(value).encode())\n        )\n\n\nclass Server:\n    \"\"\"The core albatross server\n\n    Attributes:\n        _handlers (list): a list of route-handler tuples\n        _middleware (list): a list of middlewares to process requests\n    \"\"\"\n    def __init__(self):\n        self._handlers = []\n        self._middleware = []\n        self.spoof_options = True\n\n    def get_handler(self, path):\n        for route, handler in self._handlers:\n            match = route.match(path)\n            if match:\n                return handler, match.groupdict()\n        return None, None\n\n    def add_regex_route(self, route, handler):\n        route += '$'\n        compiled = re.compile(route)\n        self._handlers.append((compiled, handler))\n\n    def add_route(self, route, handler):\n        route = re.sub('{([-_a-zA-Z]+)}', '(?P<\\g<1>>[^/?]+)', route)\n        self.add_regex_route(route, handler)\n\n    def add_middleware(self, middleware):\n        self._middleware.append(middleware)\n\n    async def _parse_request(self, request_reader, response_writer):\n        limit = 2 ** 16\n        req = Request()\n        parser = HttpRequestParser(req)\n\n        while True:\n            data = await request_reader.read(limit)\n            parser.feed_data(data)\n            if req.finished or not data:\n                break\n            elif req.needs_write_continue:\n                response_writer.write(b'HTTP/1.1 100 (Continue)\\r\\n\\r\\n')\n                req.reset_state()\n\n        if req.path is None:\n            # connected without a formed HTTP request\n            return\n\n        handler, args = self.get_handler(req.path)\n\n        req.method = parser.get_method().decode().upper()\n        req.args = args\n        return req, handler\n\n    async def _route_request(self, handler, req, res):\n        method = req.method\n        if handler is None:\n            raise HTTPError(HTTP_404)\n        elif method == 'GET' and hasattr(handler, 'on_get'):\n            await handler.on_get(req, res)\n        elif method == 'POST' and hasattr(handler, 'on_post'):\n            await handler.on_post(req, res)\n        elif method == 'PUT' and hasattr(handler, 'on_put'):\n            await handler.on_put(req, res)\n        elif method == 'DELETE' and hasattr(handler, 'on_delete'):\n            await handler.on_delete(req, res)\n        elif method == 'OPTIONS':\n            if hasattr(handler, 'on_options'):\n                await handler.on_options(req, res)\n            elif self.spoof_options:\n                res.headers['Allow'] = 'GET,POST,DELETE,PUT'\n            else:\n                raise HTTPError(HTTP_405)\n        else:\n            raise HTTPError(HTTP_405)\n\n    async def _handle(self, request_reader, response_writer):\n        \"\"\"Takes reader and writer from asyncio loop server and\n        writes the response to the request.\n\n        :param request_reader:\n        :param response_writer:\n        :return:\n        \"\"\"\n        res = Response()\n\n        try:\n            req, handler = await self._parse_request(\n                request_reader, response_writer)\n\n            for middleware in self._middleware:\n                await middleware.process_request(req, res, handler)\n\n            try:\n                await self._route_request(handler, req, res)\n            except HTTPError as e:\n                self.handle_error(res, e)\n\n            for middleware in self._middleware:\n                await middleware.process_response(req, res, handler)\n        except Exception as e:\n            self.handle_error(res, e)\n\n        self._write_response(res, response_writer)\n        await response_writer.drain()\n        response_writer.close()\n\n    def handle_error(self, res, e):\n        res.clear()\n        if isinstance(e, HTTPError):\n            res.status_code = e.status_code\n            res.write(e.message)\n        else:\n            res.status_code = HTTP_500\n            res.write(res.status_code)\n        traceback.print_exc()\n\n    def _write_response(self, res, writer):\n        writer.write(b'HTTP/1.1 %s\\r\\n' % res.status_code.encode())\n        if 'Content-Length' not in res.headers:\n            length = sum(len(x) for x in res._chunks)\n            res.headers['Content-Length'] = str(length)\n        for key, value in res.headers.items():\n            writer.write(key.encode() + b': ' + str(value).encode() + b'\\r\\n')\n        for key, value in res.cookies.items():\n            write_cookie(writer, key, value)\n        writer.write(b'\\r\\n')\n        for chunk in res._chunks:\n            writer.write(chunk)\n        writer.write_eof()\n\n    async def initialize(self):\n        pass\n\n    def serve(self, port=8000, host='0.0.0.0'):\n        loop = asyncio.get_event_loop()\n        loop.run_until_complete(self.initialize())\n        print('Serving on %s:%d' % (host, port))\n        loop.create_task(asyncio.start_server(self._handle, host, port))\n        loop.run_forever()\n        loop.close()\n"
  },
  {
    "path": "albatross/status_codes.py",
    "content": "# Copied from https://github.com/falconry/falcon/blob/master/falcon/status_codes.py\nHTTP_100 = '100 Continue'\nHTTP_CONTINUE = HTTP_100\nHTTP_101 = '101 Switching Protocols'\nHTTP_SWITCHING_PROTOCOLS = HTTP_101\n\nHTTP_200 = '200 OK'\nHTTP_OK = HTTP_200\nHTTP_201 = '201 Created'\nHTTP_CREATED = HTTP_201\nHTTP_202 = '202 Accepted'\nHTTP_ACCEPTED = HTTP_202\nHTTP_203 = '203 Non-Authoritative Information'\nHTTP_NON_AUTHORITATIVE_INFORMATION = HTTP_203\nHTTP_204 = '204 No Content'\nHTTP_NO_CONTENT = HTTP_204\nHTTP_205 = '205 Reset Content'\nHTTP_RESET_CONTENT = HTTP_205\nHTTP_206 = '206 Partial Content'\nHTTP_PARTIAL_CONTENT = HTTP_206\nHTTP_226 = '226 IM Used'\nHTTP_IM_USED = HTTP_226\n\nHTTP_300 = '300 Multiple Choices'\nHTTP_MULTIPLE_CHOICES = HTTP_300\nHTTP_301 = '301 Moved Permanently'\nHTTP_MOVED_PERMANENTLY = HTTP_301\nHTTP_302 = '302 Found'\nHTTP_FOUND = HTTP_302\nHTTP_303 = '303 See Other'\nHTTP_SEE_OTHER = HTTP_303\nHTTP_304 = '304 Not Modified'\nHTTP_NOT_MODIFIED = HTTP_304\nHTTP_305 = '305 Use Proxy'\nHTTP_USE_PROXY = HTTP_305\nHTTP_307 = '307 Temporary Redirect'\nHTTP_TEMPORARY_REDIRECT = HTTP_307\nHTTP_308 = '308 Permanent Redirect'\nHTTP_PERMANENT_REDIRECT = HTTP_308\n\nHTTP_400 = '400 Bad Request'\nHTTP_BAD_REQUEST = HTTP_400\nHTTP_401 = '401 Unauthorized'  # <-- Really means \"unauthenticated\"\nHTTP_UNAUTHORIZED = HTTP_401\nHTTP_402 = '402 Payment Required'\nHTTP_PAYMENT_REQUIRED = HTTP_402\nHTTP_403 = '403 Forbidden'  # <-- Really means \"unauthorized\"\nHTTP_FORBIDDEN = HTTP_403\nHTTP_404 = '404 Not Found'\nHTTP_NOT_FOUND = HTTP_404\nHTTP_405 = '405 Method Not Allowed'\nHTTP_METHOD_NOT_ALLOWED = HTTP_405\nHTTP_406 = '406 Not Acceptable'\nHTTP_NOT_ACCEPTABLE = HTTP_406\nHTTP_407 = '407 Proxy Authentication Required'\nHTTP_PROXY_AUTHENTICATION_REQUIRED = HTTP_407\nHTTP_408 = '408 Request Time-out'\nHTTP_REQUEST_TIMEOUT = HTTP_408\nHTTP_409 = '409 Conflict'\nHTTP_CONFLICT = HTTP_409\nHTTP_410 = '410 Gone'\nHTTP_GONE = HTTP_410\nHTTP_411 = '411 Length Required'\nHTTP_LENGTH_REQUIRED = HTTP_411\nHTTP_412 = '412 Precondition Failed'\nHTTP_PRECONDITION_FAILED = HTTP_412\nHTTP_413 = '413 Payload Too Large'\nHTTP_REQUEST_ENTITY_TOO_LARGE = HTTP_413\nHTTP_414 = '414 URI Too Long'\nHTTP_REQUEST_URI_TOO_LONG = HTTP_414\nHTTP_415 = '415 Unsupported Media Type'\nHTTP_UNSUPPORTED_MEDIA_TYPE = HTTP_415\nHTTP_416 = '416 Range Not Satisfiable'\nHTTP_REQUESTED_RANGE_NOT_SATISFIABLE = HTTP_416\nHTTP_417 = '417 Expectation Failed'\nHTTP_EXPECTATION_FAILED = HTTP_417\nHTTP_418 = \"418 I'm a teapot\"\nHTTP_IM_A_TEAPOT = HTTP_418\nHTTP_422 = '422 Unprocessable Entity'\nHTTP_UNPROCESSABLE_ENTITY = HTTP_422\nHTTP_426 = '426 Upgrade Required'\nHTTP_UPGRADE_REQUIRED = HTTP_426\nHTTP_428 = '428 Precondition Required'\nHTTP_PRECONDITION_REQUIRED = HTTP_428\nHTTP_429 = '429 Too Many Requests'\nHTTP_TOO_MANY_REQUESTS = HTTP_429\nHTTP_431 = '431 Request Header Fields Too Large'\nHTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = HTTP_431\nHTTP_451 = '451 Unavailable For Legal Reasons'\nHTTP_UNAVAILABLE_FOR_LEGAL_REASONS = HTTP_451\n\nHTTP_500 = '500 Internal Server Error'\nHTTP_INTERNAL_SERVER_ERROR = HTTP_500\nHTTP_501 = '501 Not Implemented'\nHTTP_NOT_IMPLEMENTED = HTTP_501\nHTTP_502 = '502 Bad Gateway'\nHTTP_BAD_GATEWAY = HTTP_502\nHTTP_503 = '503 Service Unavailable'\nHTTP_SERVICE_UNAVAILABLE = HTTP_503\nHTTP_504 = '504 Gateway Time-out'\nHTTP_GATEWAY_TIMEOUT = HTTP_504\nHTTP_505 = '505 HTTP Version not supported'\nHTTP_HTTP_VERSION_NOT_SUPPORTED = HTTP_505\nHTTP_511 = '511 Network Authentication Required'\nHTTP_NETWORK_AUTHENTICATION_REQUIRED = HTTP_511\n\n# 70X - Inexcusable\nHTTP_701 = '701 Meh'\nHTTP_702 = '702 Emacs'\nHTTP_703 = '703 Explosion'\n\n# 71X - Novelty Implementations\nHTTP_710 = '710 PHP'\nHTTP_711 = '711 Convenience Store'\nHTTP_712 = '712 NoSQL'\nHTTP_719 = '719 I am not a teapot'\n\n# 72X - Edge Cases\nHTTP_720 = '720 Unpossible'\nHTTP_721 = '721 Known Unknowns'\nHTTP_722 = '722 Unknown Unknowns'\nHTTP_723 = '723 Tricky'\nHTTP_724 = '724 This line should be unreachable'\nHTTP_725 = '725 It works on my machine'\nHTTP_726 = \"726 It's a feature, not a bug\"\nHTTP_727 = '727 32 bits is plenty'\n\n# 74X - Meme Driven\nHTTP_740 = '740 Computer says no'\nHTTP_741 = '741 Compiling'\nHTTP_742 = '742 A kitten dies'\nHTTP_743 = '743 I thought I knew regular expressions'\nHTTP_744 = '744 Y U NO write integration tests?'\nHTTP_745 = (\"745 I don't always test my code, but when I do\"\n            'I do it in production')\nHTTP_748 = '748 Confounded by Ponies'\nHTTP_749 = '749 Reserved for Chuck Norris'\n\n# 75X - Syntax Errors\nHTTP_750 = \"750 Didn't bother to compile it\"\nHTTP_753 = '753 Syntax Error'\nHTTP_754 = '754 Too many semi-colons'\nHTTP_755 = '755 Not enough semi-colons'\nHTTP_759 = '759 Unexpected T_PAAMAYIM_NEKUDOTAYIM'\n\n# 77X - Predictable Problems\nHTTP_771 = '771 Cached for too long'\nHTTP_772 = '772 Not cached long enough'\nHTTP_773 = '773 Not cached at all'\nHTTP_774 = '774 Why was this cached?'\nHTTP_776 = '776 Error on the Exception'\nHTTP_777 = '777 Coincidence'\nHTTP_778 = '778 Off By One Error'\nHTTP_779 = '779 Off By Too Many To Count Error'\n\n# 78X - Somebody Else's Problem\nHTTP_780 = '780 Project owner not responding'\nHTTP_781 = '781 Operations'\nHTTP_782 = '782 QA'\nHTTP_783 = '783 It was a customer request, honestly'\nHTTP_784 = '784 Management, obviously'\nHTTP_785 = '785 TPS Cover Sheet not attached'\nHTTP_786 = '786 Try it now'\n\n# 79X - Internet crashed\nHTTP_791 = '791 The Internet shut down due to copyright restrictions'\nHTTP_792 = '792 Climate change driven catastrophic weather event'\nHTTP_797 = '797 This is the last page of the Internet. Go back'\nHTTP_799 = '799 End of the world'"
  },
  {
    "path": "bench/benchmark.sh",
    "content": "function run_ab() {\n  python3 run_$1.py &\n  sleep 1\n  PID=$!\n  mkdir -p output/$2\n  time ab -c 100 -n 5000 http://127.0.0.1:8000/$2 > output/$2/$1.$2.log\n  kill $PID\n  wait $PID\n}\n\nfunction run_ab_post() {\n  python3 run_$1.py &\n  sleep 1\n  PID=$!\n  mkdir -p output/$2\n  time ab -c 100 -n 5000 -p data.txt -T application/x-www-form-urlencoded http://127.0.0.1:8000/$2 > output/$2/$1.$2.log\n  kill $PID\n  wait $PID\n}\n\nrun_ab albatross hello\nrun_ab tornado hello\nrun_ab aiohttp hello\n\nrun_ab_post albatross form\nrun_ab_post tornado form\nrun_ab_post aiohttp form\n"
  },
  {
    "path": "bench/client.py",
    "content": "#!/usr/local/bin/python3.5\nimport asyncio\nfrom aiohttp import ClientSession\nfrom time import time\n\nurl = 'http://localhost:8000/name'\n\nasync def hello():\n    async with ClientSession() as session:\n        print('getting')\n        start = time()\n        async with session.get(url) as response:\n            response = await response.read()\n            dur = time() - start\n            print('read response, took %.5f' % dur)\n\n\n\nloop = asyncio.get_event_loop()\n\ntasks = []\nfor i in range(10):\n    task = asyncio.ensure_future(hello())\n    tasks.append(task)\nloop.run_until_complete(asyncio.wait(tasks))\n"
  },
  {
    "path": "bench/data.txt",
    "content": "B48eI7bn1r8m2SK3=vA3mIU9kvWG7CdvT&ott5c0%2B4sAn3vGb6=kvpe1naRw%2FzuXdJn&gkYceLsi5VG6h12l=9RAM1%2BxDzUi3PnN3&dBWFou2sY2fPt%2Fsk=ZIVa5sqwHd239nck&xBFiwiINo4huYi3l=hAHJUzcYkd9kO6IM&USFbLQ6yUf9u6Rlo=608ME1Hrq%2B39RF7X&vuG3NkvM8XtIDvm3=VcJmLqZ0FZyN8E8h&UnGywLaXxMv3mI4j=JAohRjKOxZwJOVHG&cv6Q7EFs1D1CY4yp=9QZ%2B%2FfkcwgnzSzpg&sk8Jp%2B1UWJxxgjrk=ojvsN2OL7i2kX7Ih&4iMDOPr8hvM84rnR=6%2Fy6Pd0441nkzM9o&lcap61jqTQCE8Eam=vjpf0aTd70iuTljJ&%2Boy%2BOQ5I4dSxt1VO=JOJ99iNEt%2F%2BHlVMV&gYrIOTNgvUwH%2FAVb=Q8UsKEbYRj6yHSj6&1uR7QG2M47b31XHW=%2BN2zX1bIWGgT8Ls2&zSbHofgtiRcMLO04=06drpHKlDtTnRUw3&vRaNWSKp8eu3tf6S=uf5ppsuFad7DU6uq&wugqQIByFkAQtBhE=q52Rw%2B4akofMYiJ%2F&TSvLsLlxY93BppAq=FMFSmp6hVnkAyNz3&PFefXBuddYRtlJ2k=8ExKoPsm40psks90&oncK9OmEH5NJTOL1=eV6T9e3Cak6Lrr4V&ra79pVvrMZkdpRAW=JGjG07SWkg5aNVV%2F&g7wAD4xtjnsFCYPO=KJWsjeNtE%2FwIq2a4&wH%2B6fvxyxDGUCZfG=3KEjjKXP3CkCQ7do&g15DC%2FAurLlIiH8l=%2FH7oSssJWG06x8VW&700qdkSFXq68eNTT=Xx2UA%2FU6bi7DSbSF&ZYVb%2FHHJNw5Rm6zr=Z%2FYFCItKta2d6VCc&u%2BrPoL6B0N%2Fj84N2=SwUGQUAeTBCwjThn&nHB4A1QWhf0MMh2Y=r9ExV3mYyum2El7V&l%2BoaA3GHrDiIM%2BqI=yQCAgtpuq4rxfyxp&QYLfs4ZjvxsIgD%2Bo=7bi9tdjH90lwUz3R&Tg4FzH5fMSfwfXos=6yyod%2BV45PmcZ86A&HD9gYGaTbYCoEwtL=95Xk9eyYiumrtG50&mJDFTX8Vc22gAEEO=ZBDBiPqHDoXvTO5v&8XPbrYCls%2FqIGs2N=7Nwi215RsMYF%2Be6u&VRhr7ciVEqL9Dzph=DyYdIaFCrS6v%2Bfd9&JcaR0vbfMDLCBx9M=QxcvSgjnmunbJc1F&MwAB7xQACyHX2UVs=BaiBmvsSAqJPVGZ2&9et21F0nrB4olxg2=L%2B6mDMTjfT7tNYk0&lfrBgHNYzo4xkdvM=gPISFjem%2F33vNZD5&2cXVlKbxRu%2FTJLO%2B=EoEoXreFjfTURzEG&OGDyAdIdhKF2LiW2=bKRuTpDJsSYnRG0V&iFcv5vdM1lsJi4yg=W2DKtDhZTtZWJnSF&8EV25k32IDQUhHEA=e0SqBZYMyPjS70Nu&NZRrBnNxv4xPVuyf=lZmzJ142oV42TAlO&Coi3cwnHNfxBSlUj=V%2Bph2sHKcp75Nj5g&0SbofK4QM2lA%2BZtV=N2KXmYpYghOcJq%2Bw&nQGmQ9GWm2aJPvzX=wHH0CAacHZVr%2B8pg&gXWvHHl0NU0Xg7%2BP=DHccNWDiO9QWsxU7&2%2F%2FxHLbUAP8AdxIU=Z7XSPXPE%2Fz3E5gBm&oqATBfiuCgCPI%2BqP=xKlLvQPavgYOxWp%2F&3iI%2Brzg6Ztsf9pC1=ewXJmnytT4wIjef1&C4hr4p37IjiRDL1L=4FGYYcSuDLXPjSsg&LUZxxiZAGyDgcuyX=hlsojs1WxSb%2BQEE2&VBIf5luVCH0Js08s=IPBlvNgFcUtIBgM2&xwmqKqjH7%2Be61laH=we3OB3pUR1J5qV%2FY&sy%2FH6Kc5%2B%2FxXhUsP=taF2Nl3NIJ6ofiTe&oZLLb7PR3XikiJI6=rVMCzOFtOxBPjwd6&q60Mr1nYtUOxIoGS=Wh8dtgLHC3SKqWFG&URWfglNjEyP3d60N=euWAwuKu5p%2FL4qTw&SaJz0F3xECXB2cR3=FBk6%2FXii4puHqCXi&1RzFeqBv3W%2BmXtsR=0zmXbZOZ3ntjHmFA&%2FgOXe%2FKpEm2kmOPo=4ngp%2Bh%2BN8pJeQF4H&Xae0DsLuF0QnsF6v=7ZWTDH1FnV2RWD2A&tux8myCiGZciU6MR=GRLhYRDzDm1oHQX2&x3xdb%2BmNPFNg7qPy=xdWke70J3ntxDX%2B%2B&mYS2caKTyt8sunqC=K2q4To%2BwafGoQXwL&zbRwLFQc0MXuAqyS=y6S9UGKqoWu43lYC&zJ9BjtjlU0emuSiu=SLV5x6ohpC0xHa2Q&gbgzS9Jhyh2vrzsy=d1EqARZhvEL9T9B2&%2B0acK69I1Y8%2ByV5W=B6xrwETccO1cJQvr&fc5Fz%2BpYPhvDZIkY=CNSZzp2p7XJl5FFm&MgsODsEleooK0jG9=ibYBo00UjuaOj0rO&QA0%2FM79%2FhXeCwq%2BX=v8aaZSrWyvynUlgT&pciQENnGLEHvjBOT=l2195XYilBi4wHbC&izLuy2eEvad2pqoR=uIoZvDVYhsmIYgm%2F&KvMJDW85LmMSLmrd=y4AiUxkHGxlHG2%2F%2B&yMOqnwRj02uLysB7=7fCHhad2TlQiPvZt&9IY1rw19BzwP8jD%2B=pE522xz%2FvwTvWqW6&up5hruxLgUtJEui2=kHbZegbPsrDAAXwy&kw%2FE9ESN%2FAyjE0SJ=iIdHNi2uBc6XNrs9&Wrucx0vzeSHDClAi=6SZOJ6qx0CsVLuOF&XNRS8t9MCCgI5gWl=Dp7jphSpGvtiq0DB&g34EJKojKGFK8gKU=%2B7c%2BZs%2BqbIauNMnD&h3NYwBTdiABRKDUa=KmSNnuB5th2Jkmp4&3QtYHJxCSOkT2fdL=pBiIMOTojZIK80LS&JvcrNxavyheLp8Pd=MzDCjNVGlyRR66DR&l9bnAR5tQLAIwKac=oP02kTKcwRAP5LL4&Av0Z37JjxhVvnONQ=vFA%2BN%2BJlQGHo7IWJ&BcWeCKzFaRPSWliw=tIuyJuUdXLsVx43l&kQn0U1WfPXgPiUjG=zNLCKo7VnG2FGgYA&QbMGOR3vv%2FtuIwqN=hBF3t2wGYhcfDRsk&bxjLuH%2FDCmiBboeN=vTCnBUE7x0colGYV&mpaMxrNwKlBc8iEs=hP7e7tPPL%2FHmt%2Fgw&6ShOzeXHkk%2B58MBt=bHpdZ%2B0EaQzElFwR&ZVoX70UiZ4I8yGk%2B=0rKSBH1Q0xNJCrk9&C%2FvxQUM0LJnOxi%2B%2F=FMod7Ij2VpikhEEb&F3qN%2FitwrA7EsIqD=5dG12I5hJT%2BGM5Dn&ipB4r6jC9pNa473d=QyN4eer7tyCy%2Baez&H17lYS3qBROF7pkY=4SkF%2FlvzJZhZ%2FG%2BF&zrdKzxVSwS26pNIi=hxETUhR8Z97fLHA8&UXXTXG9NIFM1Nb9J=iaV9Qm%2FUMjf6VSL2&qQOLwbK6eDsSLeLJ=RSc1iWYfsEVFdhMG&V0Ca3bcN5ui8fzIb=W%2BtirDKVmT5orWrU&dIEaT%2F%2BbBWz9vfSm=32ItVPfIQIFwo%2FqJ&l9B3A803AqDEOaJA=mqxWNBvoOvSBWTgZ&d6qZAEgy7J5phRsR=dCxeZStuOBpRgTsW&8hpl4agPZZ2xHPzb=TMdqKZrOt2gQ0ce5&yL%2F6dk4I8bDw6EMy=AbnkDdInH%2Bzh%2FIrm&WHgSyIWfnJrkM10O=S6Y4yIdDFM1pB7ts&eN6p3SQGbzlx%2BU3s=o%2BwvXel1lEFWnCet&C%2Fw%2Ftl%2FHCiGixlST=mQmTOBWFFamt2Mfi&LLaCwu4pQzzmX1yH=OdVH10wNVpISdt8C&pet97y6Z4JfejkTV=CkxvPnrgNbHiHfir&iO1Q4Dkkm3RkKYWH=u%2Bvryhn7Qp5uiwmm&Tn4BOVa440jPpEP3=g3lbnfgEismOCJCi&8SHiGCEGcziQmPv7=DOIkvmHmOR6HsCXV&Hli1gzZ9yw6mTRHb=75cQuTqbh0ZHgdoZ&%2BJUtOTph%2Fy0iUuoV=ZF9ulFWInRqyU8MN&RKPO7C3h2hGnMrZA=QN9pALjCqLZb6dfN&4mrfczWt87ZhQlBi=oUbnhgjcpSoZDq8j&ToK2JUxHeZ8eH8Ke=sqIFBZdAsJnDQDWv&xrPj%2BohEkCCcwFg9=cSLJRPpSTfSdwZJU&zZxXkeUXjE7f4BDQ=pRIMUdOKqV04ZA8K&vz5pCj1fLpGss8M4=Xz6Vb5rNH5cHVuM3&cpzXvJanzbtx5%2BaT=4Ry9iVnrd5D3JU%2Fj&2ojlVyCUOZKj%2Bjxf=%2FUYNIfVkHB3lR4si&CbLKAAV%2B2MAr730N=iXAskPZhuOFfhwEp&A9PyJlAzWI%2F4spPI=ltT99l1LQHvWPDKq&Wdpj8dAU9fE%2BzWmh=FYV7u%2BXAO0KnEfKe&%2FQX3ToVP2%2FcvBIha=jfTu7lt1UKmk3GKS&BPhQRs%2B%2Fw0yQDvgw=KWOZLgDjilAVYlzR&SEbWzLFsIFO9BS2T=7AaNWjs5rAUESQGX&iGzeRqr1VO6fuZSZ=j5Xf%2Fy9kXmS3h1TR&b7OoO%2BfaNdHnE0v4=b%2BLZWXVj8xvkplol&PbUdljzBACJfjzkV=7AcFBPbxh9NnIXBn&vyfHZ%2BGa3i%2FUllXi=gQ5UglL2Y%2FPSFcdg&BcmaeEwIYuoSBZla=R8GBYqolW3hB%2B1TZ&1ZUCPjXvOyg%2BnYrl=Q6063KAcocc1h71J&AfdOKGOwMHaADFCW=jBBAex9y73VpvgDt&08G80L0g%2BtkMmPiG=YY148Ynz%2FQ6F4OsO&2AoVFdx8nZghSE2j=J2kTL1mT8FzkCbKz&uhuuPbYrtRQWCy%2F9=NisKZSWipamjqeNj&I8EZSJiBzg1EGRsl=vRvVpvJsa%2BKUTZQJ&8hFIb7l83bWN1S%2Fg=%2BAKD%2FosCx2jjtDYH&jN7c3eVlBsTZpbey=AboVl7JIxBD%2BFysZ&Yksk6XRecFSjJIYN=zOEyqCypfvVkQtF%2F&u3AgU0ksNya0ACSE=qE6W0F%2FCzELKneKZ&8ZMjrh0CSpRrDcUO=uDq2nkN6zEj17oof&Ul4Enn8BMYfTpI7m=eA3di3pK7vMidtXr&5x7ztG3Ex4IRtqGc=PMYkbwK4LdD1sI7N&VFZ%2BvwCDLyzjl0Ay=8E%2BB7JHnxuvfYwU2&370EwctECba9ko0i=gFImZVH9auZcCkl6&CbhWg5KLhEGZz0x9=HUIbmwuh4kVp%2B9OT&24hX2abTMmi3F%2BTe=HG6aTylw2oXZxWva&J8VYp8hg9UVuce%2B4=wljocdBEXVIjp1TV&5pXRbTOfK4AgrrHZ=DTL8OAMxRyEHiFcm&cUNFk9kjtApCggGu=DVyjdKwBn93O6HVb&9XIwwj22xe8Hzazd=mkWC8lsVnHBkEfPx&DyrhgzYsgawuoElK=dk82UZll8UkEqua4&vGyZhUWs%2BzljYTI8=uwNE295rwUBrR08x&vI8k5lgPWcA%2BmbwE=whg0GVvNyiDGOQ6v&Kj9AKnHQMMbKX6k8=YeW867a8TNa9vEml&501%2Bjr1WcH7%2ByMz%2F=dZMXtAcr5DBG2M22&6Gd9M%2FL4LEe7ozen=XKzbm0ncrVrdi567&LxZ5khnxkvcFMLD0=fRMHaMuqmpS5Bhli&TPOsYbHgep9eykag=dAkunpi%2Bc2%2BuT41K&sJtEvpeV04uo4LRX=pxaXKFsa%2BgMbcNAp&j844udtMzy0nRytA=MfgSEKQ%2FXPsGumNw&hUmg7HtKtKgn6v1A=YA5geX0amoIKWfEu&JeUK1nqPz%2F5bjdr8=i87KlsdRZiD4mV0T&AZgsPaMlSiwIBm9n=QhzunJ02HVxwR%2FT6&tdrv3htz6sxjrUrK=us4txrSiCKOtbW3V&XdnDLoxTj47ZowdN=SRvYUhHKH5IgFYv2&QlSIiGFU6ur6hLZC=vVUwGhbjV9l2Po8n&v06BXP9UjMlnptHp=PaNBrZkybUvZxZzG&14wQ0vF1PSjASoXp=C6D5ToaZ43BPp32W&h5m4Q7dHEdo%2FfviE=0XE4hbhd4MbCju28&NxYe62JCUMKmE%2B4b=n0FLIXovalYjwkvf&lhTtNFPmPAI02IXF=BkofpXU%2F06mX9DDn&KRANst04hd83Kw0S=CWIonSR2LFSoiVqM&ZuAA3aO6UhCetGMU=dF4ndF4kY6Sb160%2B&ym%2Boy4mTd%2BkPkwsB=bVjrVrcyRvb2K8bd&m11pnWJCf8s%2BvHBC=RzS87GObTkLXMelg&2Pec0B2Jhw7BQdmd=pyM3ILgTe8NZfNmr&JvklmA9KDgCv2Uka=kltfO8G76tveKQjd&tuJVIY66QNZxE2ec=l6b83VcRxddUHauH&DQI6MyohAAHXo0sU=5XTIyzWApMoD%2FFLX&7pTydPzbdWJmqDkY=DlXVic%2FbShjfOmNW&YP3yGaTQUzBlBDhC=BKM4nXCgGeLw05Fg&%2F0vLN9OCK4RrLEx%2F=dHGCSWFZq%2BhzyB%2FR&l2KqH7CfotwowL6V=HQDNN4JQgiHS5bTr&aS8WL%2BG8JhySW7xs=ePSu%2BSbpOkXjgpz0&r4tRZ0mCFJlsXLFz=PNggWZ5UZSuPfofD&FSdILG5indCTTBkO=GuY27TghB%2Bm88wTu&n%2Bsih5bmgfZrDDqZ=5DHQWs%2BViVkFb%2BtM&9rci%2BN1nawfVjOWM=gVLGbo%2F5ZMvCFhxL&LS7QSQLs4j6bLdGY=7wC9aYf0yFjLGgRT&8JCVwzUmJDJwe7Lo=1SBdmUNQzB27TIJs&huBgKj2dT%2BGiZzFs=cTqPmnj1W%2FKicUh5&nOIgNZTjhozn8p5y=lalCsCepOvWr3BxD&9u87Vweue1ue%2FXlp=qIfxL0HCX3W1obRg&FilUpB9b%2FejJu3a7=ABowE5INf4YyKQuk&IyA0s25Vt8j6dy3P=s0s89SPT0BtJMBfH&fW1T8%2BHkzVSSUSVZ=4uJjasgj9phC%2FOu9&yhRhIWk4yyVdlZb5=MLyo5iSqnbxvuTJp&gItivjRRUDcE34Um=TAdcD9vGcaVUYt0A&I2K2a9kjqmh8A1gZ=ZB7%2Fm3At3RVf66DW&Buwd%2BLJLu4KkO3JT=60A1Kkg%2FHvtsQ2WF&hle4aYn2OTTtttfS=Nd4PdKQ8woWAbQtj&Y7MVIba4fVujU5a9=4u9KjgMTP94D6n06&aNo6iAbafT6niB2b=PiJ3uupZjUvb5fMd&Lu0AV%2B3i3YPM9pPD=bGSA9CXJzGJYTer6&2u78Qk03ZW6ozbby=uSGnMlCLg29V%2Fa2V&Vd5rIIwZH2uuJHf%2B=yf2W3E5FPXpMLhxp&P0IySqCZ3IsH%2FWj3=zXyU7nG0Nwr4Zw19&S57Zbb6f1RRuAsJh=ehPAMon8dawllLod&UDvkSmG35lZrZc3V=K56SGUm7A%2BMT2Ndg&VgNbt9PXNt8b1kBm=jnsrBKMeghNvhapJ&CoUG4KOHm72UjjhF=FggHZTCR%2FtMZxRnO&mgMzWKeH3jD6CWSq=wbmQElfVPt%2BFO2ct&RLz6jkgUZ72vLXRm=Wj9PCVGjUeYi3%2FeO&brKx7pwTzLF36C%2Fx=h7vzMLx2FEhZe0VW&WuA%2BIJP4VOjZFVU0=1cnX7Kd7Dc%2BKaD9l&nKfUNaZkaHnJcl6d=Ko9TPRy%2BHtiu%2B8%2BS&3aT6uyy3xF%2BynIH3=XsJasnrjpPXKMKRC&h6NOaK7HeGBNQYde=HajdxfF5ZljgaIJr&dIZmGyyRTSxXIzSQ=Son9jek0KlECUI%2Ba&v0%2BnuRqQWSoHE6sv=mV7MDi%2BOiyxsIhyL&e9wKktz8SQvaFQnF=y%2Bx8CZeM%2Fy5zgLWt&MyNEI8LLNWa%2BKGM6=tGsOX9Unlg9uMo46&XjVZGuKbXlytpkqY=Nm%2BtDGkzP%2BXrjZTx&84ioX8DU0Z7qcAzR=qAd4aqjKqucxHj%2FP&yiK8Tmnp7GediZTn=1RzRIzIG%2BRv25JD8&QW1Pb6WRXBF43Ax1=ledj5s2eJfbSf8Zs&Z%2Fm6XifHvyLOh2k2=gAjaumlIkq7Z3K4x&WsqUUs7o2ZOvf4rX=fcMxKsbBMFjOqu60&x%2Fm8KirPzTABIOzG=hQsYvDncZqGWn4UK&cGQn3FOGfQAzGx41=A8V9nK1f32fAkO2P&tLS0C%2BIGn%2FFy2KWU=%2BZ8vA7o%2FXQKo2Fl6&czbVuJxv5avg1nwf=7mD6OZ9Vgb9azCsX&Xgpwr%2FCfNacCLCC7=Jo88s2bM%2FHZFWTsM&PjtBirifHgUjGjI1=guxwpAgrWlziTgjb&hpfhLB6JevL18BSb=vcS6fsaDUV3RgWCm&WGixUXWfJch22hBH=T92kfy%2FOoZ6LO%2FBD&64NR0yD2xWiJmZW4=cV%2B4eXpBGGnxuM4R&WsOYvQbUVzquVCbI=NWtRqrREVBar9V1N&tewli%2BX1y%2F6ZSGIe=A0XvSnrNr5dMAc0G&9C5Kar7a2IU4nxRp=91Fb4Tjgx1R6QAmk&Cx55TKuTzPudOnE%2B=JFKqbZPX5hdClcPQ&yN9JuN%2FSJK6Wttqi=Q2AfRY7c3oDQD1Nq&kqczCquMuHMbbh1K=%2FiLD%2BigGyX52hEQi&tWuoEsnYJ5xHx5ac=3ToAczpui7MIrK%2Fn&sAqHRrqs7ncNLTmF=HtkBdtnTrqCQIhBL&TMOWLZuU0AvsVGyE=zDrcqLjd2f%2F%2B25%2F7&fW3TveQcVPd1GKm5=xngoUkgzdBPTAAx%2B&nWlbuCO%2F5rKYtUKF=8d5TnqLs2ng2iNPn&puBbjD3zSE0M0Smv=NXsQw%2B84ij19mb4O&alOOc4N2YhIMbUjQ=lrQ1KupgAvFlQ0fV&SHJNZotwulYyxGeo=5ZQaWBUJ43ob%2BNbY&kTIql%2BcKXvKbQjIg=XTCplovjv5bUDuH5&GLSWPz0VK0u3u4iw=1R7K%2B%2BYrD2dlIt5z&5uVZJi1LVlz8yM6e=tzgJu3xLI9P3gHNM&UATQY9HQDZUonqI%2F=myz%2FQgY2szNxyr%2B7&sakNosU%2Blmk60BqQ=zuv4UkpM5SyzAJiT&cNjo2KkP8OPYrYpt=ZmjHCoNnyCJnNQn3&NLfcA4tCid7H0Nvz=owX8n385LzmiTMX5&JaiUVtjcli6QX5uL=pwH9j0zkrFs7GqYz&2Lu4DumDhmM9gjgJ=VboF%2FO0ZR0XKhZKK&Jv2KUZ%2B6t%2F1XuCKp=aJ%2BX0B96HB1Wg2ik&YbsSKcHgJtcFxtBi=L0AWepLq3%2Bj0I20Z&Sc%2BcEfD9Uf1Ax530=JCoNJVNzZJJ0Qd37&xhvQeUwAmMPiMCeB=ua0Jp5GISHqU95kO&naFncy61mEuaguXT=LoD3dXI%2BKHeuQAjE&jiLok%2FrT5PvNMLFK=yaaje%2BKWCKzA%2FsNG&pYCMXxeX6VYn7TIS=7zQsRB0WfQKLrEx1&I0ObH9tp9G34CKWI=OQsTSp6%2BILMtnS8i&MumbgpMx0CW5g5Ou=8y72chwUmAV0y0UZ&ibUYXkLG5ELfsCw4=tg1ymIpRcVdlazIC&yalWtKhhNgTA2kvU=dvu33w7ddPfhTUiz&2NtDPIYlcVA2fBQi=4oj9HW4QAX4Cw04R&nKi5hhk7lXN9umcF=kaDd0iNE%2F9Y0BlCd&Lw9e44NwTObmO2BY=hl53JOX%2BojoMqJUb&lYXn%2FqNOv497ePW%2B=cICOOffay7InAI%2Fy&1G3PLym%2F1UUM2w27=%2FJ8Nms5mRkKZTXtS&UWj%2BHfejrydHavG4=wGKBgP0KZXiJgtbT&arlsoW%2BSLI1FLjZs=MFh9cH%2BqRS0EzCHy&OAZtQTBv%2F7b2lCfU=W%2FkNSUZ8o%2FbKIMHr&0z7EvYDXBzSdr%2BXc=fa9WgLdMdyfivRXt&Rlxr7xGepZ0eDBiB=tmASMq1tzvdW3hbQ&TvxJVjSqEQMViwDG=WhJOoDzYsDKRww5x&EQwztHJrsO58yiEb=4LPRTK4XtkfYR%2BMz&5sExGJoTLEJHwE%2B7=XHDbQKGKqzeDK4H%2B&sXZQ5SiFGat%2Bsq2j=CFl0rVwM6uOPZjVx&QrPgzBClw5LYJsZr=f%2FhNniHmngo6lBr8&qf%2FC2%2Fl6Vr01Fla3=56kD4tjFx3GhilFe&sDgKATF2HLUtooN8=udLTqcv4Z%2BWvpJyb&bcZKITE%2BS2w9uVFb=t4ZZOJltN3%2BUxbKb&t59FrZ%2BHFK8FgFzC=hNjYRCc23TwQuDPR&9o2nGFgFVYA2jGth=NIK2OLiHCE23APyQ&VZONazichvW2B5nD=a9zMcpYfKiFoWi1S&fRdbYEtvDgI%2BwUGU=GFPPPUoUwFuznsO7&lnqskG%2BdK2eSl%2B2r=ykvN9vYcaazV1qW8&WjyDt3DOGQN3vbl5=eOCBxNcnQHCk1ERD&uHoE5zmfEpIMlSvX=dTa35GmGORAEoGx8&RvsDdUIjyjmEd64X=TKZe7n%2BenM8VE3sA&Q19GYINFaiwj9yTA=Fcz%2FL1rD1N%2FsSGpn&c3kDC3Zfd2Jk3h%2BY=Bv%2FxrEeCoCinREBZ&dCjdYH8ENRHcEKj%2F=om7vYOZ%2BMI%2Be5x2%2F&7sIhj8bofnq887vu=uSXYTMLk1%2BFVCPNu&I8vbj%2BK1QsGIdIJD=sNoB1VzDN%2B8mTfW2&rDDdMDGn2FBAK4Yy=8PqElh0LOKWtuJwl&q1aNc4x5io2tVjDY=U%2FPIs6cMvHEr%2BeO8&50RmdN6C0R%2BFJwnN=vvid5tjaRam%2BxNho&oED1vvAD7OaGx3MX=fT2wpecOTiu%2F%2FryG&V0pDF2eTuzaYU2tP=G0sfV1JlOkhO%2FIst&NLNsl9d2V5wjq8P%2F=g8JwAAepMlAeIHqA&%2FAqq8yNYeBH3n4Pn=4uvyJQGDQVW8UvUn&S8Rded2sLMDPgNZJ=5pPSgp%2FggmveQW5p&5P7%2B2iEfcjLrCRKo=TGQ0%2FyCl3nHCsN%2Fp&iiWscdVzpMTFcKyM=%2FceYyGsaOeWsj2%2FF&D%2FpL3HHsv6BXQ%2BcF=ahIW6t%2FnfwYcGSB2&SlPAYs010nyWWMZO=GQ0%2BttXwNlmCX0PY&dXC4z2Fd9K%2FnKv6C=4p8sz1zvWXBWKGa7&LC8bF%2FzVDt2TRVQu=eUM4Wg0kAZjZ2r6E&d4sNfA0Uq%2Fn3BzZ6=GwvR1%2FEYnTcdbQQd&OlClZ7Wk%2BEtmk2eJ=s8W7riMgE16y3teD&ZhrYLvSln5TNYTFE=JI8s52aMAbzPrss7&eqJXM3Hu6yNAbpLW=fRU98GmZl40kf0sw&3Dj%2BpLcgi7gryyPc=1kedj8h%2BR958Q%2BGi&PkR2hlhnZi6vpF10=d7fV87vgue1Y9dRv&y126tkfhDDxIdg5u=Dwm%2FfB2tNiDrCLD9&faSt5Wu8L6fPnZ78=lZdHUaFRBuReujcp&hfvK3LeS0mYeNHf4=ige9fEO%2F6CNBv3DU&yCq0yk7TfnoS%2BaQX=JreiXHfzdtj7WxW5&rfA9FQA2tIx1gh6j=hCelGoSzSBeU1i71&usPCWgwSaoECAePJ=PhEBunhFGi3wj1rC&JMu0lG79eS09nhoS=V5JvaG5vzYXd%2Bzvm&JrLt7tgqnZRGvaa1=FfbRfCaNpQcvKhIF&%2Bt4Mje88Wbo6Zhjq=wMxIlV78MMYhP9%2F4&2%2FmvWmkBNyATXt6B=xN4mbGktNM52oZDN&Vn9PgP%2BKwAyubI9A=oB5iwwo0k%2Bim8Y1F&RmCQq0WeB%2FoX%2Bpuv=J3YSvsb3Z87VeJMf&WqjC52lPPu5UqoEP=SrSpxyB5RkajKQxu&8xpvzPtW23ZAVzQu=CqysV8nuNUK6sKSS&T%2BwMl08jUpIU5I%2BQ=5%2BUqICbyZPIMOF6a&vKMpVuHQ8rKR%2BVmU=Fnz8gHAA9b5AohUV&W%2FYXmvFDrTf6wu76=po7G%2FtWYW2bfqh67&NhxCIgargUkHLg2w=yA9r2mvLbeeD0FIq&8aqt8TYh5Q04P1k7=su2xDs82eNmFe0gQ&ZOYOKvUWBuXKSKSJ=8H168w%2F6udDZJQoN&uIJ471LqdXvx71vB=HU1SxiBPjz3qCByJ&hv9C11ZR1QLC%2BYfm=%2FUgRBbNtvNvbBwr5&%2FtR%2Foyti%2FYybH4uV=9qMZY0pYqvmfxCAo&4kLcfH95vF%2Be%2Bj4W=ZJFU32aUtcadpmnS&xiiVS5bs0%2FrzykcN=A8zZ1YbjVdaOlAzK&bP2jYpvwZKAJndBk=u6ky6HBW78mm0sU2&lrVs8qOeQlNX2kTz=eRAIMmXIU%2Bc%2BOpA6&loFsh8vTdDFVgZat=fxAwP%2BslCKSz%2Fh2%2B&VKQf8std27uaSzzb=KDTVBF7cSaprhV0y&HEbIIXF8vdghaj%2Fd=1Cb01LJ24tgCvkJ7&JOzFR7J5XOlhXTRY=xR%2BmlifpFKSL30t9&kehB73nDqOXaw8GR=FYqst7wfA2hxbCPH&bwujCnUabiBGix%2Bw=dKwsV%2F2yaIj6xZ3c&gaNrtardVmf%2BhsYc=9mJ2x4yR9INelD%2F8&VW51p0RqxJnpw%2BKe=IUXLZGqindCZf4mS&dqUk8hGSoKcvLFmQ=YmGkqGGzdL2U3e7S&frUiGma3ItMgkRNv=%2Fg%2FcreYtbJ5XkpyT&UICAC%2BP%2B9GCTQmJt=Mjyusz%2B0lIZbH0rW&zY7JaJQZiZ%2FrKCCo=V3gw5shgXL2eiFJi&PJsp7BKDTxpwUDxA=P2qVvzoQHtIHN%2B42&eA6c4Y4X9bEf88aK=C12u%2FfnYpBcqt84b&jownRo2jGrjLH2%2F4=tvqwoGunEdnYyV08&%2FaSkCPUK2RGHqm7%2B=1SNurLDgtrxqR5m9&%2BLG4fJqeXtR7cMWF=TiX%2BUUvmOSTzq4Kr&T7JbJ2phbupC2Jmz=90%2FpeUHq9KLrZqyj&%2BsH2Y9FA%2F19gx2Ue=%2BzwzfaAolmSWRphu&5gKXl%2BbtGuRhANsh=Z%2BUJVasmBr3tHw9m&U4cH9reL0tRKJoot=iXE4wRurMrHfczeX&%2FgaBW8qaqYIF8vqq=kljr4lAlhO9AmAHy&UTLS5rUuQmwA0qXv=Tx0A4jtoxzA7fE%2Bu&RQs586v71MwRb8BC=EoAsyEZ5hxH8jq2q&TTOreaLtrasqdUop=iWSa%2BLiliRd0htZ8&83znrkWefokuWaOY=E5CqwDr%2BTRa4Cp99&M5soABGOITDGmmbJ=ranG8A%2BPHX7LIgJB&sUTpLjG8qifMv04i=f%2BtDrgc93OBu6KC8&E4QzQo5AlGhVsnon=mnYngPq3SBdVcVE3&q6CSs2bOWcINTAih=S11zAiqyxrd5W1Xb&JKKe2LdVy%2FO2v%2B4B=OJr9tcCZGsg0Q6tq&EtrS3dWdOAxpPn%2Bt=26s%2B6jqpv1BAZbJZ&%2FPOASwcuk0myB99v=9pRb%2FO%2BrCrKahetA&%2BT2P8MoqPsyNoIWb=djEB6n08LIyPP5nh&ccdtjMYTesETC6T8=mI%2FuGgbdAO%2FBARx6&Xif%2BQjrE4W7HzU2r=7qFo4YjCh26ONfYD&c7dJFgUL3NBhcio9=gz6I7gkdNNcd%2FDr3&XTkBUeyajVF%2B7Z7G=XVW8%2FeaeTBomLiOp&rewTdxfsI%2BMN%2FzBL=UJBo%2FPU83BQcTktz&2Ejj7Ej%2FlHM3RVHz=AIJiFWAlrQlNky48&DXCSJPiedJey03s2=kAHVlkbuznq6aAPQ&K65BJUl%2FM4styMIx=YpAoIUOVU0q7QE7Q&8hDP2t0fMEICKKNc=hRrzoOrFm8BztKau&J5QlYoyiSdwcRX4H=28CUhBfLrACpRP8w&uC%2BZdKSmNsrKjhzh=nIF6pl3I5Dt3y2g%2F&cwIkd63JZAjhg0mY=SdOxuqEtRnmx8oP6&kdio1IxKD00UndsN=KTLKrPzqT0001zXe&o7wQtAguvt9j0osC=JBz2HX1%2F2D6dGbbo&3U2at7vjB%2FCs%2BlLh=ozOAHBYu54LqAvsZ&B6FAFb%2BhHdLVBb1d=bwaui%2B1OCEZgEOI4&ZzMOGapguuiq5MLE=C0v3mw8bVQ%2BO9Uf0&IAJ6P%2F2k6Bl9LnEy=dRBODo3ApKBh599Q&c2G0DkqYM8JyJewI=Z4v9O%2BK1W6z52X6E&zDD8vPqJI50g4Yrl=fUbMDFSk6XQ0%2FmQO&01jhZT2ZOBfNkwFP=8zyBj%2F3COCcaDIBQ&%2FgIiDvtYMvI7teGL=dokMdYKeQ%2BbliBqR&GOuZpALqff2%2FLCe%2B=sE1nhE2wnH5nmJE6&zhGYHxx2VZ3AzbQS=%2FiZIjsOK%2Bo5OdeMf&eIBQY%2BKpsKg4Yg8f=DbcxZBQ0APv2UMdy&nzyMYzS%2Bufj%2Bl8nM=fs1PQb%2BhVd0vpNer&wN7afEyBWd%2BKrHgW=pEHDgiPtLC7cclcZ&2DhFmO%2B2ydRckizi=i7L5cjeTFJElFQg9&WH%2FKTnEe2Ox%2BwsHR=T5mGutYn3yGSARTK&UKwz7jSj2qMOQ6Rg=nF5x5lZLOMpXE%2Bvj&qlj6aoj6w5rWabwr=7vxegbx3VAieHIgx&dh%2FsrxLUmr9uzhEy=HGGKcTkOgu6uF2jr&ksZ%2B0BtiHWq3UN5A=pmkNcB80awB2Gdwn&%2BJl8uH%2BVTvPzlNwx=tQXux2jEv0LvR1g3&3fk7WfijK%2ByI4b3V=p75mg6ME6FU6DnzT&tmUpG048kSnF6CkT=MvvkirQgo9j4ezfV&jLTrVCW1Ke4Ykd%2Fh=KSRLtMqEar9mrVVt&1Au%2BT%2BswcW9Y2a5v=MA2%2Fty8ouHnVvoQp&A%2FFs7E3HCqeHw2yk=Lx4hAZ7Z7FBKWmod&ErNOniprzyRNE9SV=FY4kA2taTU2HP%2BuN&WUHGXSU76ClGWRmy=45Tw9TX1K%2BvyDc24&Y%2B%2BsmaNDF7g5EC%2BG=UPbLjNNSsD1TMpkf&jX11QVDtVsLwpIne=bFwsO9UMR2Du0z5F&2gJTIx%2BVyKiA1Jb%2F=R%2BvIqdpedC9xViDP&X6hV70vXDX8t%2FDrA=HslO6HGVODR5oQb%2B&15A5riMk6ra8BEXD=OQiWqOndieC0fLuS&6VW%2FvMZUhbMZIkkJ=D0nVXlPQd7Bq05ge&hnJcaseALrCtm%2FEW=k73I81XNxvPU0e47&UOLzzgyc9GBQ5Ln5=JSVH8GcysbPlh7I2&uE9sezYlo4YvuZGH=fkMxKmXpUlw52dzy&7g6XM5wqLciyAwrA=vA4kqD%2FxQ0FxLEkC&F92AealEhPE6cE%2Fw=DzYFqNXWjdh8lirW&btW7BHV8VHoM9Zy5=8OBo1aOM%2FSzHY0bB&sWyIWX6FhN0KflIb=yzErU%2BWKZ%2FkjtVmz&0rCUqDNfRNuyYOiT=gCp5SLH63xqclP9E&VCZrOJ387Za9edqI=ooBsZFCzH7VetqTj&bHQkkj4Bg%2BZftRSP=8NGtC%2BLOB2CJURJ9&FLb9pXHDa%2B6sluiQ=zAnFp61J2UhL6Dsc&6J9Et3ZsVaNQYDUT=fWYQcVi9RHtliFkv&ajo7NipJSrMheNS4=CJXLFFQDgQiigq40&x8Jlg%2FVWRMxkKzcB=NLDPrz1HZ1tBhmw%2B&A59oTf2MBn9J8pLo=gx4lqzyuBl47gDM4&Wt4H%2Bp1XSxM91zHk=%2FXfcZcRkik0V9W%2B9&F%2F%2FBI9ukUPkNjKf5=FdhAY%2BTwSUjwO7dC&TF%2F%2BXq5HClOUtujd=kG%2BBzP4J%2FaB6vVIz&Sr4NJt7cloaCAbs4=dPEg4B5jML0w7%2BvG&RPxxEhjNsLQO%2BCz0=9vcfjmMX5v8vFY7X&f%2BCFBU%2BxH%2FkHfwbX=hSMih%2BKKGuvFnlHy&TXrthXT3TU2I0Ebm=dV6x0HaJTtwoWc3n&TlVjwnrpNldqqo8G=SE%2FAOtU5nobm2GKy&NMaZwLLwLacf9I6q=MYlYqksE1JGYFWJA&bauQS5Rvm02c2AZw=XfWriFSmYwi%2FLBes&mJ1e3k7xTxxdcm9k=9H6yozE5xHi1QgKX&V%2FyDudA3PfiucZxa=i5r2CfI7iKiEUkK0&LVFFqlsnJNAiaBQv=cA8iaPFRyQrZuS5c&31T8x9Wxu1oyqNM1=%2BX35i5gGcG3XWOLy&gfy6DfEk1mYlb0en=WcTKcN2qFvmBTUyP&qVwu%2FvuE%2F8PSEnq4=2%2BbyhDM1kB1mVqtD&BOPaVsoCJZSWvBJS=p%2FjK6VcAjdSvkx1V&WSDffEL12vDyhITe=TK%2FdmAvGvulFar1h&qklQbqN1Tv1R%2BE8M=nUhwLMdZqz4ihO4D&IrgKTv9sI5e91cUC=ORcubE95r2097aet&OYAvyGfOnuCjVuac=j8YsZWblbLV8yyPv&IS1o9p1SMGUgWTqJ=%2FxL1z5WWDLN%2F09me&P9IdHReZqgQ%2BX23p=f30Rw2vmdpBPOZHL&TDxqDLbG5uyDgak8=hd07ol1%2BKxKAqy4r&OocFYudNPfkK2IKi=wxxN4EZFFqxEgGtS&oRDCcbG1TsO9ATJH=XIPq78iPC0XmlOYj&m%2BubFJFbiuTNVa24=rT%2F69gYBlrsnPVWK&Ivfxr5rMfAlkcMJR=tUpfJzN%2BdU2sO1bi&Sx9%2Bz57VhbwRLZxr=yg%2FStUBADbr1dbhc&EBa3w%2BwKY4CcREHs=FjYzFHO1v9paVLdw&XaNM1DBo%2B3E%2B1KLF=dU29Mhm%2B3eCbUVyw&6tqoFf2rMscN6oEf=0TjfjqpYWW%2FmjJop&83UiWB8ceXezVXUP=lukK2XU4758GYPL2&blt7TwbCHB1qRyb6=do%2BTJpW3pxWFGzp3&XlZRvSUmA4ycp%2B%2Bf=%2FWgJebFafblSDN1b&43xL2qbt%2BQjNQ3%2Fl=hVKpxKCVmTf%2Bxw7L&omT3HkzricNuehxh=khceRjvt%2FMLNhM5T&%2BgtYa6LGAnteD%2BPu=QrFSGUTKLf%2BFzDiR&%2F1EGQW25W6N0I4kA=TgB%2B0951VVRgV0Gn&0k0Bun%2F2pSgbtoL%2F=OV8ASzkCKp23jbJM&m1wc6KbzymrXf63%2B=Hm4yfqzAyMOvExaU&kJdw%2FfAUPv%2Bye2iZ=wX5MbQQRsyrQQgvI&KPy5rE5nP6TsdqFC=v5FySNy9PnLFP%2Fak&vFqzXK01kZxhRS2%2F=sEOfhIQZTIt2wpC6&ElnwuDa8rhSeyCb%2F=4Na8Lsa3pm8XXHKC&ts%2BRZ3f1to0kDKTe=9rtgCHwfws56%2BkLo&sH%2FdUBFRtS%2F7HAtS=8kQjLUgGd8xiG6Dx&5usUHesurVIKS%2Fqj=auQASJf3B7vHy9QS&TbtQxf%2B3fwxdJ1Nw=L2p3mW4BkLNOwD86&eyf5XhJa%2BROultM3=NRpd%2F%2F0JzWVfjOfL&paJqz8ls1e1t1kPQ=Kys5D0LOPksACWs8&FXLLw%2FdaZO7hfpLk=TpI%2BxhHHQ86Y46hf&yTW%2FQcb1bCZj1GnX=qDgKLAKuRSBBbklh&z6i5ewmcbwUBKQVF=RZIvPMUfDLONNGs2&bAIJEdWZXYjqS4B1=eef4AphM%2BUlKP7z%2F&pjnkuTZsoYqgY0aG=MGOmFSsyzRhx7Q%2F8&TsYyyFvEwh6kQnF%2F=mQII7tLbG0iDJxFs&w5Xx%2FFzWgyCQ39FA=5inNlYi3mQKlbWnJ&sUUkwHLcLg%2BzryGR=JqqrGbDTN16Yd1vJ&gQMeQP5mFoDC%2BgwI=XuP1n%2FLEDqk0eDN7&ilIbi3%2Bo7qsb0iyR=a6KyXljSIDZRsr8R&9cripQltL%2FFUEvZi=RJhvh42QZ994O4RO&dGbVDPfNb65fCrCz=JXcAdAtH4g8KS0EZ&qORxuRt32k8ljXDl=iDy2p2Bd%2F0fj3f4t&vnwWFfg16BET%2Fbev=TbnezqPKIgKKTSsL&SumpcQV%2ByvMmZznL=Q91cOCz6yLQg2JNS&BwuyrlGOh7LJc8Dd=5hRHz3E4KJGan3hD&IaVVD%2F24Zvfw4nd2=BU14tgUKyUfUKGXG&cD25gDNzYIkq7xOM=myeZRkmE1tdBCwF%2B&SvZ4A1MeYYXOOgoI=64uwFZNL5xzaSILy&yka%2BnQME40BmuvnK=WlM0d1O6V2dkdyzZ&OMnO0rkNGLvURWqB=MZiynatckr%2FCJbbE&VphTixNyqEGSHhgl=SfzcNkB0iZSrwSzP&gb7udUlcFWuZxGKQ=tIK%2BTxgyeKzyraJt&3fpPWuOm%2FHpoCQai=8CPpKHzIhBaXuYV1&2fb%2F5kVEQdGtprx8=llQugZTnoxEzRr5X&cnT3scP3qw0egLEj=fr6LlBcGTP0bJ1Ng&IqTzrPDnj4cLHpja=ey2v0ZcL30AkPDtf&aZKUGmLVUgzyAEQ3=q3MDvL6CaEcz1JVn&y2SpDUPO4J%2BCFLJY=gdw4mtUyW67Rhynm&WXEft3ijbcyQTw08=EQkU2dEBX5PY2hC4&Yp19EP%2FVR4NpSELS=F2BSTE4dCa%2Bv9p1e&oVmDfrpy84DjckjL=P3UY7uJ56BKxlz8d&yQnXL6uMnGTFAiOo=Dw8XqTRYfK%2BvYd3Z&gA92VjvMiZE2DaRG=ylc6odA3ZfgGQvj4&VmYEMHuUa5DBI0%2Bj=iYKE8EDUw%2FQiBrbH&ilAxTLs12zxQJ5sb=liH%2Ft06Hmr4PazaO&PfXzPpupNwWFrR4t=vdzDlrpNeLZhavVn&OY2IWBZEnRpweZZ7=9Sysk4U2Rk6IIaRH&ryLaY1P4soAOJAd%2F=zORMhY2nQAL3DOwu&t29WFv1BXVnrEoBJ=WfQs44Af%2BE8GRDaj&Tqvyv7JIWCalZuM%2F=%2B1RVSlJ3nDln2HKC&YJzhvkiKgeJUCRqY=LvM6%2FDBUpUc6buEz&2DILsov5R0evIhHD=feIkJS8Q3VBGVBs1&MbTlMWUVFPBF2iGI=8RZPqqeBqS1iFQLM&gmt%2FasetG6ERsqWj=13tXsVkk3iWjvfRv&uUPTWGSDc0nYZ%2Fzo=DKVjyRG%2BPfHJRAUF&W%2BiSNvGeB1Ql6LPT=k3RAXNqD4MgyF%2BD%2B&BfBkNAZnPVN5EydY=rtMniiHr7CpthQqp&L8fEhJHafEHPodEM=OGMWmbYpYcvLKrzO&4ZEleyRp%2FwLBHiAG=aT%2BVaMxFLAHpmYz9&bFwHZKmao9DhqqUf=FQPh6tfbARUbqZtf&HeQ6QOdQk0ayTbcO=Y71hVe6Xt8BHVLcI&KPhe3fsIFn1JlGGp=tygSMtS22Ab4gNsQ&RlHKspGFIpyfl71J=uGFkVoudEngeyVRi&76m0oxfbb0Dc6QMf=6JixEHJw2uyfpaqC&UgY7%2FYq4PPeXZAcG=GDZ8onu4%2FMDPj5U9&OG2UVkwy13TfeiAE=DD6YVz0AaBdJco1v&NfRzciRkP3qGtzg1=SgMjhoBHLa%2Blrndh&%2BKNOBa5VbOrjDmJ9=jR1d2nPGWhzw8Ukb&OsMXHyTGa%2B1jnn0p=vmAGUt47CVQd7tyl&2CbBEabbzKrFbcdg=bDeAuu33wWv5ifNT&vxVBwabRUz%2FGoVJF=rpGFwarCaMaCswsD&Sim3Q6UtUwpP%2BJTf=NvWEsL3ncUSx8X4s&3bpWESNdkg4k3r3R=GhHNgJPyoDfGHbPc&YkNA24nJjK4IV9Cv=95lIk7tzzuzQh%2BLL&6zRYxCJdE4M9XIwg=ht19Dj%2BtQJe88JVr&4%2Blt2AMdd7RFREoN=AyijtjzBRQA1iuKQ&d3URA%2BfZBDM50skc=I%2BDu0EFkq0zhDBSk&PO9Kaqn7NkAmY2dC=3zlQ07eAS2V9sQO9&svDqXa6v6rACg%2Bl%2B=%2F5Z6M9Z1fWG7c%2BGH&d3EnXS4Fs%2BJ%2BdcQH=FEG0eUnf2AEla2r5&RdtCmCkLddzbU7y%2B=HOFh%2BhTWHy3q52Z9&jVlLcpIdRKmbBNzy=lE%2By9GU%2Bn5JwcJF0&COGPIiFbEPfe%2Fpqa=I%2F15DpV2J3o4dRA3&hwqYlMzNtPQd1cx9=4H5R%2BssO08v21wR8&Ro%2BfEzKVP99GOGYB=JHrcuFi6m3ZSEBRJ&MIY1xGVYFn3F%2B%2F0L=so6H4PxWQ8R3aSZp&EC%2FllNd8x0260UVU=aAtnxtoAgjXT3BjG&s6Jp1ZyxmjeqkHTC=sk4Aa3UAA6OC%2Fq90&4CEnDaORUpK%2Fcmot=X8XDg6KxUWH%2Fi6FL&NP9dfun2Ls7DarR%2F=CBIrITqNsHg9nlHm&vGfH%2FTYLO%2Bp0qhcf=4SCRDIJoXfwLbYD9&FpEh28whBZ6DkK3y=9HhBkdYyatxX1daf&RyG734LoTGyCGSzA=m4m9sqvGbp2%2BUB4m&ASXQMLpptY%2FSvgHO=z%2B%2FzMzmEzazUo%2BI7&LPFhQY0kOWLVm41m=96MDkY7TCU0a9998&f982r3J1SoBJh46B=rrHhFFS5ZNzHJgId&LlxrYvfS97bJJ6Mw=8nqJ0XFH9zembUSa&OJ3fh1QYmAFsUL1u=n8rbFz7GHZ2TuV3T&u%2FsRm9c%2FzF6OZhrx=Lr%2F7eAUJhgFDqYkQ&WaurQzJL9m%2BbXHIg=MZMK78TtEe1sac%2FH&8IeqdSYP3YHOXRkk=pEvbZ3z0BPZPT7I0&GmoSM7%2B8LtC8DS86=pVF0RoXiOMKXb5Lg&UGIPC3ZDHFI9T5p%2F=yyQFXCyxnfgGRoeu&47gTEEhAP7Zt2QM4=XwYOYn%2FyQCRpv5G3&k3hTslVXbg3UyG7w=g6qYUMSI2xQMbAS3&9S5Vby09hK8S%2BdSt=ZYuNZ9PbW8QfbuX8&4DTfvf1NnCN7FeWM=G%2FGwkGiKtFFZ%2BcVa&Rhw2Uox6goXScBk7=BZVCbv3cKPTCz3Oi&CeVK5oHOhkqx1sQh=IUo2yBJCOhK%2F%2FZcr&dAFC7G5CTQVj1oWo=vzHpGJ3sl3Q7R6wF&o7feaxFGdusID%2FcT=JeiRAiSNRWdv3euv&DdbYMiIjo%2FjziIv9=5iHreYZCTFJzJiow&%2BaaWMOlvO8%2F99kAl=9BBePwphebtfz7vV&fa%2Bmw36iN%2Be8w%2FsT=F3nfNFvP79wL2wX7&Yt55V20VczF7CZ4N=Qw6kWdo7G8VsLfCi&15kVMUTN6C%2BMlWJj=E2CX3q%2B9iO%2BtNc8O&%2B%2BsH7wur00GiwxRI=9eGfsbUgltkw3Eq8&zsKPZJU7i9wTIm%2FU=o9EAPa9Zv6UdRiZO&Jov%2BhKOkf0oAnMbv=lUmOsjzPa2WD7jsD&7lCJvJ2XTH%2FrWeGC=QwsbBe57%2FUUcZr83&l2a1VfleH9LNMFlg=UGp6%2BW4Ruqf%2BhDgz&8NrQ%2BVRkPesmkohc=p5JR3cNr9TG7Qpld&%2FZ%2FpdNgM%2BmX9KDv2=Vq0VB2ZQlGSX%2Fud7&BFfI9LWuQiI7gzhS=AwJexNIbBxGXRAiR&xd8xLhgRf1aWEi9W=rw%2FXDNMOKsNbhoiD&p4jYYe8Pz7nRM4ye=ozyuhRV9xN0p6IaN&8U9lRWTtE8UeJ7dy=keJ2HBm8mv6tgkDD&Nu01Z3FeiJD23PU2=JDp%2F8SI8s9%2F8Q8Y6&%2FMthZ6TBO4muygI%2B=7vNGDp6L%2FK%2BIKbKc&mTicsMRkRCQFzw7K=8%2BfKg%2BgWm5Vggk2K&%2F1X3kUj8QXFHzAEF=vr5KrgRPq%2B67FAMe&vd0egO1YZxNZ0OuP=EMtIqG3ujMwq3%2FIz&qEd9JzxwmvAIrE3a=HUgpuENMsICb6cTT&qu4bfaN4yv%2FbEHL%2F=k48rC2TcCMO%2Bp1xg&XlSc%2BO6F9VV63glx=y%2FuRFb5jof2Fzlvw&AJqIG9UyD91ZBhXQ=hxhaaPzReW%2BoZLLC&Lv9%2BLDj7dhP7Sfua=wzxD3h50SmdXbU3K&Hl%2FCxKFtZWr5We2R=PFw9UjywQqbAaCq1&mpE3Vi%2BAOHyFnxo1=baRmf7PGA%2F7P0dWT&l0qh3h56%2F14H0gU3=dW5v68UY5GjrHnnu&sfM9eKn9dWvoWZAc=J37pQGEGUHDff1uO&9dfoYUlhQbzZ4fAH=Ccawsmsf6BKOlRzs&YyM0Re7HOyjHBS1T=%2BbGNPECskcCW3BcR&%2B8GFcaz7f6lN7UN1=hE7qykqPK7yaF6Lo&V739PD7qwVVOZqXt=iddn7FxQetHuF%2F8r&RMiECjK3FJd3yKSt=19YtBkuAAk9%2F%2B2GY&haOZqzpwnRbqZz9h=wBGUghdiEYMl0GkF&8QwvA94DQYFDXrL1=7YhRIJg%2BQdudB9bQ&gtQqqCbtjP0MxdgJ=XYd0RnaIIbkXIpLG&B71xvvD4ZwOIw%2Fm6=X7BG7YHquElYi8XI&IiCFKRHR8eiEbMFT=V0Edwxqi%2BEwUGOEt&VbXwshvrLAwD1TiX=aOBQf14Fk4oBrxiG&uX%2Bk%2Bwm26AofQH9%2B=xnB1CuIzavHEsUNb&hyZy68sqJ%2FR%2FtCPm=42qWNjF224vuGgBj&eVKhp%2F20akiW%2F%2B4F=yT5Sx5u7iA313Urn&eT03%2B6A9lHNSEcwp=jW88iCsS9NSsbs3%2F&2Za5Hs5gkpWMtQzf=a2lTTgyDK3fDEYCt&DN4bIPb5WE7A91km=kE%2FB31IMwvxApJ39&TIUOd%2Fe816vex%2F1%2B=lR8OrOGJXIuFCxh4&YOhhaYLl%2FnXn7weZ=CbiZKwc%2FBK1xkpxv&DDpcuhNt0Ald8NFV=lsNjKxU5%2B4EGmy7R&ESWFgS9boujqGyrP=qIfiUz2lVBvKJT7V&IlzrZnq2J3VmKzR7=nLbXkND45crzbK7x&qUiBJVK8R4Xjv3rK=YIYeCRClIoe%2FLHw%2B&GZX91Tcg%2BWwYrPNy=2m1mCmoy%2BDjPzgSh&wiX03yWnyggKZzag=rxen6HxzZZ2V%2FU1y&EUM%2FkgOYZ%2FE7EN5g=w9776Ug16t1bKy3N&DpxOlOLD9a2c5f0D=eKgzxwyBgP%2FI75SL&bq7PubeC8b518gCr=d4asFrDQjm%2Fdi28K&NyEZj0%2FwumzDzy81=R%2Bnm1l2XYC%2BAVY19&7nLpuz1%2FnyjzQaFN=oSvdFGGpaAbKGlQI&Cp%2BbN7eWHnwbda2g=AxtlL0SYunBz4q4g&No%2B%2FdPVKGJSGCNZ0=3WyQdctHULTFnrKP&5e7JTUr0oRJsa1ll=Xp%2BO4AMpHS2CszUk&hXGmb1iGUggHw7cO=Z0VDLsdAwpR6HrSQ&dNAOZVY4VIukK%2Bar=SjCxN%2BezEfGp9QZA&gtHi4izbGHikGkoM=OwiV%2FaFjS9G%2FZeB0&a7nekql6kbz5uMT4=SFT4bDEZyUdXTJvA&RGAWPNoMzxCJZYJ9=g9x32aiAUJtbI137&4%2BDx9rOG8TGjxvoS=kcCPns57gthSQ3sj&VxR7YN4WhMnXwK8g=Bav6%2Bc7lmLUCJGsL&l6s3sMN812wrurpR=kiCywyFEJVhim%2FHl&lTn70UWh1PfQWOsI=nXGcLPa8Q7ZI8bVW&4hki9dvDMBSGFYPY=cJGQbHIg29nJQwWL&RP1WWxyfHCt9ObpF=WEEne2ssq5HeGk0z&36VIjqhYehBto%2FY%2B=GH91qH07vFOb%2BJKZ&hr1c0bt2560LTBhK=vmnGBV3Vgr4yVFHL&%2BwRywXzOcgVYSBbq=0MJ8%2Fg%2FbC%2FDjTsZD&%2FOeX%2Bg%2BgsN%2BXByGZ=GwcDqEqeQMUBt3c1&HoynynmOVACU82V%2B=%2F%2BPz0DuuKGEPU7vD&4u9jHGg%2FxpQN0T4x=0gmK%2F17HaXkqFcA2&SQeRuZyufuY4TvTI=0tDUEkf%2B0nJiaFwz&qjjiwjfpqEbEA6KG=Z1Y5er4DQ6ejCT3I&MOD8KL01fwhcDdsi=czXLOZAKp8eubZE%2F&DCiB3Izxx1YTt%2Bdb=1JrPMG0C5Zn6JcMP&vEKDo4Kex6YE6w6%2B=qsF8QuwWIRZo052v&mRJ8oerOMxbhEWP3=OO9wzXuGTE%2FJ1Ewv&U8Yj5TAZY8VqZzb8=AzlBw0835DoYX0vL&5RswSNk%2FIXQCdjpZ=SgEfoMM1ph%2FnxkJY&gBMXBOHEshVihlfp=OF%2B5z4ha5FGNSV08&v%2BqfVoZ%2B8Q2Ftis0=nEe7krRDt1t96E5y&t2UscPcxMmocmCag=IDN%2F3tc%2BlQqL3p1g&aOPraZHcvC0J8n4p=ZEsQxdB%2FPPfferWz&A2ImW0LDVsA80oJc=0FC1rTMOgjvxyEuj&ryhhbqvJIYaOYm%2B%2F=m1DULR0cp8rKUM8t&HE%2Be2ZlG3ef0tvGu=O5qY3Hxa%2FsjBg2z8&0CGCZTDlhiBbops2=mBsqjxAN%2FBBAcSZc&Wrt9ePRNBk100Kn%2F=IdA%2Fl9mXa%2BvOcI2F&IVnFttNYK7d%2BGOku=rQh1el1HEq65etOE&788SUGVneQz%2BN0zB=QArR8vFWOprGky%2Bh&JlNthw0t7qy7dCKG=SZEuqImohDSHnblo&YU6%2F0VNZSwe6A10G=SzWaICZ9wBRr4xW3&zusdIcfYflrt4e6X=dHsDbNOQlFJUzBEA&CbCk6eeEEzoU4TcZ=RTaISymkeMoc6xk%2B&%2BZnf14oXZRPMnG4s=Vp6RiDrldbDU8Oxx&m9vAAMIc3CevSUMM=%2BaJV%2BjBtK%2FJCwcjO&97ZzC1CGRQwftSFv=4baeLHNUGoOBmSf4&cmrBUipzA7kWalz3=Jkg2KD4pK3d%2BLYuo&9rLFjVCHk0cR%2FB%2BT=vezMWxMoEJk0oVhO&BenRceAREREkYcQ7=FxYg4xqeEO09JobJ&NxvaTjj79CoFlOC4=eF8JO9aRiBSYIVIk&EexUsTdySt%2FoBDbX=slJNj6NUNtHaG7Dc&10tRPBPqLw8p6Vff=XutVgBVYD3kEQm59&2g3Hm4krhCc1PyXw=6224P58hJxcZRGsX&BopJ1dVkBqrcIBqm=ZlHVCELoX8NcD63u&VgL%2FuCT427PcymN%2F=4xQFmwMdu1ylYbwA&01d8C84RTSbdZNjZ=DyZDzh9lqrA6j1su&oPlf8iPpZod%2Bmsmf=CPtlYp%2F2HirXbE0b&zhqjhm0emeTrtQNw=bxfsGbkZSS2wBwz%2B&GufBBZ%2FJolnXpxKc=9D5OKhujZ6dxbIUh&HaM5sMzz4j5Xsp3W=AP1nrFkeImqOqodZ&umMxO4XrZqC7fHrZ=7toXN2B%2FFtIZl%2BWC&4hcBq52yJnbsn5C2=fWxPJbRAICIXTQqr&lLFsrFKmpYlMyV%2BH=Ur82%2FgTpzBYEaz0O&GwtRI34086Y03TE3=BT2hCHhJUhktkDgC&iHVRMTgAkRvSj9%2FD=WqduxU9IPq4zhrU4&BFfUfLYZsRFmPp13=zdbp4mtY8q%2BmkKHh&yEE6MDtCbfKJ7RJk=9q%2FIZnRow4GyPLjy&hVJ0sTGcpNVPpa9v=H%2FY7Rzm9Ux0M8V51&76C7b4SNa68n84Xn=jiTCi%2BbgkucyDr3j&JUYaJHPnNuXCwXY6=c%2BX66AA6hdChgBUi&QW4w9na0elf4d7ML=K7tngI88oNyBTIIV&KzsJyYu4QpphXb1P=JPkVkrsPFmqIPAqG&YDHi2x7VB0wfC0I7=cB0nYR8kjKW6krwG&m9F%2ByIhpZA0%2FXD5b=tcvZKqytVLJl213e&GUhngvWlFhi71wQn=xPx8qrvJh0TYL3X%2B&ndHsAW8eRqS17ndY=0EwW48n2oAnxPI73&YPyr7BQi6TPZgvWo=s4e6PoqjbBGIDEOb&dUwsDlke2AlZevHd=WKo3Nsz%2Fh63Wqvgy&5EYooFhffASUmQa6=F9vwt3MI4mo3ITGO&FAHHpk3KskC3v0kI=MzMJuDXFMqS9YLQ5&rfCCsexsBTHAj1rn=r36OGsAVpxvzXv22&fdZ0X95Np%2F2gUJQK=LoyRfOM97duAiAO7&94mzkefFdY1kkaDI=WvOlFR27piEDl5Cu&qK1RrxMHKmWTTDe5=dSKZDtQEUj3R2v7Y&8996CJqdKwLz5axv=j%2BxTDZZO3TYILOtQ&c3Z4i4hM72%2FPvPIa=gVjze36SpLWTU8KP&BxzB%2FUd6N8eceoC4=BpGU0XVYHvifl4aE&BX57GZBEX1R3Q8y3=%2Fwa2MPMgUkQmW9ej&pfKKgUIADydQZ5T%2F=8ftFbFemoYj5mSqV&M7hSrPXl0ZIbV%2BD0=624MObBiZAKm2dlw&Du3pGzhno4uLS3JW=Px697Mg9nOJwFwMJ&2yxDuw8%2F0RTqTVJY=FCQor7F2k1l%2F22iL&f3c3Z9BrZ2IdEiDL=QwVf5JjIGDWSU2PM&qhK57uodbnmLEYXm=Wv3VvYwtR%2Fk8m4ak&qyTqP3ZNaM8aupsU=WAaB7NJdJ94WOdlc&BAiBNGgwihRXw9RZ=B8C0yRtsgLWKcU%2F8&2H9wDqw42AEfhcaa=aXT%2BpU3sgz2ssF6g&1J4H%2FwoB%2BtStOywW=Ele86qjwlNgjq6iH&RQEX0iEJ857Oib%2FM=WtTKrYSlqXDqAXHQ&QoMxNYGORop348Yy=oA8DFhLqzC5lRBtc&jKRRbA8toVOpFElP=yvyMVYa9L3ozINNg&mNG%2FLqAOwSCiAuHm=OPe39JeZ6FcZ1Edn&M%2FLjXMLOmpPTGY%2Fh=umRpKFlF6nvwdK38&u%2F7WepZeZxgIqHFn=GRDic3FsGWxOytEc&RBVHXkHxN0ITpGCL=ZyJPTAlbRgFnR3y9&32X%2BgMlymYCLTfPM=hwNLvq1Fuy06gp5Y&11%2FCVohrIzZ%2FhxAk=TsBQYLT13JD76K2D&rmGhsSkWle7g0Crz=ijx4Ni6VJHeLJgsO&RmfE2nl9VbHYw9I8=4k7Xh3JC%2BHwYIXtZ&ZcYEAvNNG2F0Fd9i=tUxA7rZXIDBLQXQ3&ftGQDZKlyI2jT0dB=17Uurs0YH04qvpBC&7psYXwgmrIR%2FBlSV=AhsI%2F7b%2FogC%2BBq2J&UxaX4k7svW8LyqS2=%2B8tCTyqw9LSvpwEo&7eaOwMzFhMRI4r59=5Sbm68jFV51PzaUr&RCwIPxSiPrQxrSj5=wBTUpVeIR4evU5Y7&ga62T6E4zz0JavmY=7sw4i0hbOoxJx%2F%2FQ&vDjtMZlFDs6OWWZd=XloTjmZefivjAZhZ&Ps8ASfA%2FCDa30q3h=9OGR0E4bhHbY%2BGlJ&%2BZ36oQUZ1GUbd38D=xPZGKmMTyLVIXoSQ&C5uePwnB0vqp5wCF=mshwJxJD7COEGNLP&O5E8aSmko3SRjdn7=tdZeWdmb%2Fxk2l7lH&TsfcuQ2deA9WUJLr=0iECThwL7uaQBxA6&CcMFlPtBL9SIJoP9=4DjUAUjaYH4sOwV3&8I8FcLN8suSY%2FG4k=BfI7BSLwXF%2B%2BAb4E&9RoYhue9mBxBy3id=SLj2PC48b6ZT5apx&BKDCrgaW1mzGXEoH=AVKeXQHlV0QRcVLF&TM6DK8PG7w0Vd9U6=afXx%2FN32JjxAPTWM&5p%2BlF%2BZzE73ie6fi=shCUolsTzMPIxNjE&PBdlverdqSiRE7AS=l7XxJhoiBFaQoGKB&pCQ1aXmUlVYWmK5n=DiQk0exDWk5jsBxC&UmLiQ4JRskemaKXV=smYnu6QGNYVvaj4Y&6kaXT3ZOwrXf0Y0x=A%2FgSk7L%2B%2BWbrzt6i&eFp%2FxrOrIpzrAYJq=Mx8xT4zecboXiNO7&mhSo3GYuC1NVbqZi=pBouDxi972SfL%2Fou&V%2BbSPRYXwFRbnkGZ=3hpv3A8tpWASl8DN&4RD42grl1MsK2zf%2B=EWDc%2FQ5YXHVN8k%2Fd&%2Ft5ni%2FRPH6llihjy=tMj5AHeQYqUiZRZV&9vt7iW75Clt9gulE=vjWCSMgAE7W%2BQMWq&Bv1Oy01cT2ffJ2Ex=cgJe9wTFAt2r7Jru&MuyoAbVE5Q6ztfpj=x1OdToKdsdd802bo&eE634HkuRaCI4Drr=XJM3r5v1zykY8COG&1fZhpRs7kqHygQea=VMeM0PTUgcT%2BdH%2BK&PpL2MOlxzZunoV6j=H%2BwpizKKvqBGbDr3&3ydYqezO%2FPcVLnky=ri3TeTjUQEQHnV4M&CoBA7YxgIGL3tG7G=dw3zorKiLA01Fp4r&peLAOCZu5t8ATJnD=JBz0PYS6trTUou7c&pLUIKsYAKnql2kFp=wAvDtHKl4UvKz9YB&UNNYtJjUAFwzWTzh=ncmE7Nph%2BKHr8Dnw&1l0o8RprEJl7hx8l=UeBkorYftz9f1oSK&Inv1588UcoFdBs8D=S2xk0g3p1RQ9axTr&UUD8pbMBRMECJi9B=jS3Gf8RarXB5oKzE&58it2y0Lz%2F5061mC=3zP3TdYbEvvaeleU&%2BpF430eq3X2KNOJx=JTq6F8wwaws%2B8CgK&Rj%2FFCKamvqss4ytU=KMWuHTQLALwvd1O1&M%2FHv37Kg81Vhyq1W=EpnsAuD7t7BpMbV1&YODxD2g6uYaeLykP=y0s15hIcx8frjosX&gKAAJqPsJBtJEDFw=xHcBRW7uiUOINNhN&8z980CiKcsmdD%2Fdv=B%2BpXTlu206j7fMbn&93ysEfrMVJ5bL8jw=51nbB%2F%2FmhUGLQ1pI&mUgN3Ck9bOoWkiGg=ZNyMREkZAK93n2Cb&WehaoWGEumLqC7NZ=qdM%2BOJVnq4C02Xa%2F&2rQkNtbA5Y6uoR%2FC=10jgNk1g6EsxVHC8&KQ%2Fe9B0Qnq8tZHHC=%2FNHzMqcg0zEZydsx&9zIuikqN5kd77RwZ=r8isb7VRQNySUris&e3JUinPfFz2XA1BU=T7NTNXiXHslWDqme&9j078N11Kjocmwnr=pOs7N2ikKZZrlkhO&cKLPCivWSpD%2BFmU%2F=wHJDRGksYYMpObsM&XMSy9jxTXaWz5Jx5=g3H9P8xtwZIVZnpm&Awfvt5tcScnmOAL4=fOiOQScO7QO6Piso&12KYInL4rpwvxN1l=EgKxn9qjSFgyfNPo&TuJPAB6eC3inBRJo=CEZrQjktuo%2FX5gHA&sGVm71%2BC2PIQm6kV=6dgSlEwebyFQR66U&W5izwstn6bUjpD%2F%2F=8TmGrpdgZqcZQJpj&VwpbAY5dVGJow%2FaS=wQ5jcNiSKHkPAHPy&SI9gAMTPcDGv4cEJ=8CjZeIWPz%2F8mEC%2BC&GQiBWmsLemISc8%2FZ=9CRJzUBTJ7cC1Bl7&Fi%2F3SjV7ZZ5LfTdK=LEBltBUIEhb918%2Ft&SwKX2FWTJV2WeftY=KLf8ob7E0GQjIU%2Fo&xftlozGwS%2B%2FoY%2FZu=zJOCu%2BCjUwIFq21i&3mFKp%2F1ndfveJhwH=rsAwBHNqCXhDyuqd&gns%2FTf0cqsZ6RZtf=gFlZgVK21AX%2Ftgxq&niCYJVdWRhWFg5t%2F=v6ufpl7mkW2mVmpN&cKRS84YuvDNsSofw=bRny1HGoOnCb5Xpj&RpOPjrH77%2Fn9RCLR=5Fy6gd2TDIInvNAi&GuX6pfXd92Wim40f=yT%2FmsMw36mithaA1&t8XjPT4W5VO56Pzo=CMnw%2FymnGx8H%2FrGF&Ha211lfqoT9SagG1=bxJQkztZ465ihQq2&VWDIQlCxn3Z8fqtz=CfaRZ8%2FkhMzdkq1s&0Z0WNO%2Fh%2F8%2FxYeHj=ujDeNCZWd6gGbBLm&uNPfrOeIFby4YdIv=Ct4OfRyMeCz287T6&T7OiYHGh4wK8tIXu=Bx%2BH3KCp8T3nx1No&whanFaOv2uT6temW=YoiZzR%2BrIf7WrV9p&3Tiee5zQHjvGjtKW=Gz0nUAxkzlbR9KdV&o3v9WpltgliczGtJ=rzLh1SUkZhYi0OjE&B895RMCgRZLDe3r7=XhcktTK4MPCal0K8&sGihbSaFR8zGrKFD=KzBCRbDL%2F9B8Wl%2F6&ME06WWST5gbSqpEn=rcL%2BHN8HI8crGDm6&W1McbZ8u9j5H20gV=NicVigLxxMDSfDCm&hturNjFExp6BWPUj=0pxXqaY2%2BUP1vh9c&wXfKsXemmu5suBC4=7SFKkNY%2BZTIIPn8H&Sq4gr81g0SrjDvzA=tkjLSX%2FvGMMTGVxt&N%2FAfB5PksQfr05gS=6ztFy7SCLEeYwruR&Fjk818VHzRaTBGQt=1gU5diLE0E2nE6uo&I7oUqSWGrQIcpHfP=qJ0GIxMnQowKOmDz&6ln1%2F%2BpojOa7%2BPCg=NDGp8EZui1YUItpo&DU1n66Oi5UE%2F2%2FAM=mYZv6ixnm6J3MaxR&T%2BBs4%2Fixmj3Yftua=Tzb2x8emVRDtAbHj&2AKHO%2BYYWHxB%2BZaI=cw7XptpB8Q99wlIy&j%2BACVK0PUbpKsWB6=fNwU5k4ENN7gFe6s&VHe%2BiOrfx3M9qK1y=STV8Wy%2BPnI3dBMKD&VzsAwUfHef48b%2FOl=KTRtJvV%2FXAEk9Lf9&aTnHHLwiLJbMotwv=kCfno5eA5nuB8UZT&XTULI22BTgwbZXs8=a8ig72hjzfeG4SIm&sJhDfeTIt%2B4BU5Q1=H%2B6BrUI6eeWKKTOb&u440v9rmUjtkvu7g=QjPuoCJE2Z7fH3YW&gWyAQl0K4OR7QBFz=RcN574lA0eneUDrX&wk2JFHbt5rFxea1w=qQ8uQpq3KrFApOcE&61kB8hAzzvOgtR%2B6=eVUu9B%2BV%2Bm4eNaLJ&AyFCZBt6Vnylit2h=fqCzjmPnSeeDJJVT&WJfwSoxBef6aXFoy=6L3I0sSW0SDxpMDg&qEW1Kh1BcQzGDreC=XW13u4HpOjUgRaFi&Cz2fYFtj%2Bws7UwnF=ujyy5WtQO3rxVuhG&17300MVGMR6Mimk0=EQNydFBXQWRTN%2BDz&qVjIhpnOndQpuswp=ld2ok%2FInEA9%2FRU54&Zp5gZgv1DtVz24Jz=sihlkqBc2SdjUtca&qerpEa3Y44j9L85i=sIKOIZfMKsyYcQ49&IDhhg%2BgNaYsFoN59=w2jHv%2BtAFTQftoE6&blTlumOh44GgESeT=4XWAGtDDYXASJcT%2B&KU07sZ2fMj9Zcp5Y=l%2F4%2Bip0avwme5Kr5&6Rav%2FwJLXinMr37i=IgdtLHS%2B47cqJ5t%2F&g53%2BoPEUvxvUIU4l=%2FP%2BkFQlxZCgSJMkB&tU10zTZWndII1Tja=kp76Pm84rNYNOpav&M20qaN1%2BKu2PBZ9K=2B1bET%2FZ4RXatwdn&R%2F5ostQSZCMzEY0z=WqjupcFU%2BmCt36%2B1&bndrSEyrB0vtArzi=DkzDnyG2JaN8Maf1&tFDcWlH7MjctieOT=CPjFb%2BzJ1tz9amBV&lTPFf94S6gxZMZXy=b%2FcSjeRlLacgQ%2BRB&bn%2F5d%2FWOeMR0XxHB=QA9to60oFT5KwwZr&5H5Emk5oqSEL%2BtIK=6eNOcxFV99RwBdva&5ATPrLtbF1Nf5WUx=ShLjdi6JX4OQGMrC&rPCcTo3pIvh8f4Jt=efZqHzCoTeltVTXZ&nXzeD01%2B%2FVzHhA7n=1F%2F7ZUq4jICO8i2L&MASqimsZbAG5q8pk=1%2Bb7VtjbEA%2Bebkln&J3EQKPqr6QFp%2BZUd=5TPwLFzUW9rXXydi&0JP7Tq%2Fo%2BRRV%2BgX7=4b6abq8Gqi0XJon0&rRLvO4QOrhUSGJys=Bm7V6aTjvm1It57v&eVeMpW5UCehuBdvI=Rw9pB%2FwjzwmirSYO&%2F%2B9rKf1skkOPGSFo=ExVuEBgBdepBnviB&jqIQnqPsQd17hlAw=1FIk%2FFKSeTpNitoN&0lk63Y89RpwwagKM=wGUC8dWWdoXfTUYq&G8yMJm1YDiz8Mqb5=7UiBpXiBZ9kTdiQn&axab5rOqMRpbf1yS=aPtS5l%2B984b2C%2B1v&ulHgX%2BQexHQsaPAG=iurLJE6fRQYmWPBC&wTgFXtWPjCP5QkoH=H3xrYA61F5ch3II4&Ywea4JzbzXcOHa1Q=cOXnIlQfNIzFWSgP&WHBlEgZXlMo2452c=FhVoICD%2Bs2qUo6eM&BDz3cnQyeBW5%2BKYS=tRLAozZ6TXYXT%2BXW&qayTqpdBHPJqaGV4=i%2B3c5THzaII8McqY&Lo8iGSUCS2esCqSd=Ay%2Fc4C1rIWW9xaJY&wC7BsLJEKNDUB8eX=7854a6qyZd6EAo1A&tXLD%2FDN4kPkZOtPH=MDyA5H2NBLX42oMV&mobHEMo382yRLuT7=5Mq7JaBnCdJitlq2&6Autn242hgxaIpHD=xO6KGMcgIrA%2Fn%2FaE&enR4X%2Fr7F1QuNagf=GRkZiwGUfHK3fKaN&438yiS8%2FB4aQvaaO=yLit%2BZsSCWnZi46f&TtfqQgGyCRq5sgYn=3VBi%2BIez6zdD%2BMEg&tR7WTxDJuQQjSwe7=9z6OmPpjh7H5M7qR&KRnYaCCQu7o9oYti=HxRTLn2ro1Cb7MBz&fUEB9pUZ9CMywThj=y7eozjsfyBLxXZ4Z&bEZeQ6jlHGtM5bbK=i2bna4w3Dp8VSEW%2F&cfdUa9XaMUHKRn3z=lFrHafOOzElfWBaN&0tAU3aVPTMVzd0zb=%2BeiXGRtFL22hun%2Bt&m5t2iYGvDf9YRyPm=0B1mVumNHz9Tkv99&5PWJ1Y4Wtp5qVR%2Bp=AoqvZto0Ou0tc3ll&mWE%2BdMSdfUZM5rbI=rQhnSna2v02agqs9&iKKUd02%2FXDuLjwp9=Kqyjum%2Bfq5978oTr&Dur88%2BijdjQ%2FNdTm=sewk6JQ5RJ7LJ3Ki&a%2ByHdQ7yzcNMBFfF=M32vtCxU4GwZM8LP&podgTOcIWGu7PRj1=pUNh%2FxLbKWWWUZct&Svvmm7ohkH57pmla=T2A6Ui1QQ5M9rDHR&bC7y3JYLGCUD%2BvUP=zBjHOLm8Se5LOSX0&eC90ep7aOXaPtz7g=HM%2FoGbNQ1KIs%2FijJ&xs24DlcnmJwfp3sN=qXqlZH0gbkV%2BIfjd&tGnhQdz3cKGut1KV=27qm7YaSxlLZRVB0&8WztM%2BSGrcKWSbNl=mXbV%2FKdyp97xMheh&vgmyHESzRaYBmnuc=4%2BMYd7GJVEbYDCrP&jhokW%2BC%2Bw4%2BIPnH9=mKgQuMP%2FfDsNZver&HlKiKGanMIV47uGb=%2BLWYYotQ3ICgWmOt&zg1YmBBujXGU4hRo=OeWUi5xT2zbHqRlK&HGi4WKtXldyXs0q7=FD3Xnox1QFtqLEA0&XVVPH5HS7%2FzfCQxp=%2BsyOtpv%2FZxhSlbPN&pydcPbuN5tIW%2Bf4Y=%2BklivTINgXIp5784&cD253yxiglX0BfaJ=g0MmGqL8AO1Mt9nj&AZ7LUXaBWD0m2y7a=S7jH%2BjUf9TVEj6S%2F&47cTaKub7tVjEmRj=IRvmI6aNqE642puc&Uls9JoCh2nkoluqO=DQajJEVvoF17HEdw&ZgoGQOnn7APEXWW0=j%2B5bPN2oZiznco08&VaqHCxdIrlvAncDz=w8R2x8P5ynMlACmD&mqYiMm1SOwvWG0WU=HYwETuBTkkkL405Q&HIK8h10DbtasYnma=CQLrdiVs5d2jzexk&okuQlbv67uqOF6qB=oL7fuuCJME7drCfx&k0H4%2Bve556UmjyPL=MgpYVZ%2FY4boqDPEA&GK%2FzPaIOoMQBMPUX=MgZpcYQZInZL%2BLaX&qHgnulTF8l%2Fgv4va=jP5ge%2BpXNsuO4OHd&WyOEI8eDAI5P%2Bjos=5fVQq%2B3YbrOEJtzI&uKG0GlHIjxK%2F1Uaq=pKqQtk1OlWRCKLPw&cXtwrZqqhCncfcDh=j2tnNCKhvWGPz5tZ&GqUjHf%2BGsE6%2FNBJs=y%2BlFJXWBE%2BS3eN31&17aU2VkCkC8Fs7ak=whcK6261DYIqgd%2Bn&Irc%2F8eNlHfy10qAf=nGY9VSRqWxFQiEqR&QjKbtYyCD2A9h7Zd=vo%2BpenQAQKABBZrL&5xciAzp%2FUhYYyTqW=5vnoMiEvffjdCQxD&MD1NVAR7cpS7hlcB=xYnMU%2FH1lh0aAaAZ&GdljfLNU7xGbulyW=fWeEZ5qnP7n%2Bthma&fDiYID2YRVIp2neE=SPX0VL1SSHmz6%2FrI&%2BC0uVUdMyhEQFx%2Bh=ZCMWys0Fi%2FfHWWLt&Vs%2BGxFr4PU1jNc7J=NZufDiKnXldh0HGG&kJJe2t4yDfI3o3IP=fdS2D47qvu5vsDt5&hIK6zPdUdXwxbl5D=45VORRWnamNnDPrX&gJZ1IjKCaivdlZcP=09cqloaFcsKCQ0rT&Zca9tqTzz2TY4Jh%2F=owY5%2BGfZ%2BPiAs6of&%2BuOq1LBcMwiGDu3w=eI86brgtLYJtgfDj&maUMDN%2Feox8HYmQu=9hhMWqda7ajjDGqp&IcrrWuLyhip2pca3=XdS6ZIDfhC%2BU7OCk&x3kDIhoC8mtzJhOj=vhaTi6peMd2AIKo4&InTiRmuDGdFmz85c=eEyj6pVD%2BIxkJsfa&6YsBWzNBGtpdYfyZ=0Ne6kyiw7DQxHkRo&XHac3U%2B%2Fz%2FU4sfcH=l3rjytZzj%2BRGM63l&9NCFjikDiyv0BwkB=zIVp6bLbugUteblk&9RGSD8Mc%2F6wMh5Mr=IbvU%2B82S7tbJVvgP&bkOU846c1Vr3QKMj=TROLyUwanaKDbbCt&mkV9A8CNH0nytXbz=3hz2nJHWImfyBxzV&XHKmcQYmhocOBROV=f62%2FQgKuFx1QMuNl"
  },
  {
    "path": "bench/run_aiohttp.py",
    "content": "from aiohttp import web\nfrom urllib.parse import parse_qs\n\n\nasync def hello(request):\n    return web.Response(body=b'Hello World')\n\n\nasync def form(request):\n    data = await request.read()\n    body = data.decode()\n    form = parse_qs(data)\n    num_fields = len(list(form.keys()))\n    return web.Response(body=b'Found %d keys.' % num_fields)\n\n\napp = web.Application()\napp.router.add_route('GET', '/hello', hello)\napp.router.add_route('POST', '/form', form)\nweb.run_app(app, port=8000)\n"
  },
  {
    "path": "bench/run_albatross.py",
    "content": "from albatross import Server\n\n\nclass Handler:\n    async def on_get(self, req, res):\n        res.write('Hello World')\n\n\nclass FormHandler:\n    async def on_post(self, req, res):\n        num_fields = len(list(req.form.keys()))\n        res.write('Found %d keys.' % num_fields)\n\n\napp = Server()\napp.add_route('/hello', Handler())\napp.add_route('/form', FormHandler())\napp.serve(port=8000)\n"
  },
  {
    "path": "bench/run_flask.py",
    "content": "from flask import Flask\nfrom time import sleep\n\napp = Flask()\n\n\n@app.route('/hello')\ndef hello():\n    sleep(0.1)\n"
  },
  {
    "path": "bench/run_tornado.py",
    "content": "import tornado.ioloop\nimport tornado.web\n\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        self.write(\"Hello World\")\n\n\nclass FormHandler(tornado.web.RequestHandler):\n\n    def post(self):\n        num_fields = len(list(self.request.arguments.keys()))\n        self.write('Found %d keys.' % num_fields)\n\n\ndef make_app():\n    return tornado.web.Application([\n        (r'/hello', MainHandler),\n        (r'/form', FormHandler),\n    ])\n\n\nif __name__ == '__main__':\n    app = make_app()\n    app.listen(8000)\n    tornado.ioloop.IOLoop.current().start()\n"
  },
  {
    "path": "examples/basic.py",
    "content": "from albatross import Server\nfrom time import time\nimport asyncio\ntry:\n    import uvloop\n    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())\nexcept ImportError:\n    pass\n\n\nclass TimingMiddleware:\n    async def process_request(self, req, res, handler):\n        req._start_time = time()\n\n    async def process_response(self, req, res, handler):\n        duration = time() - req._start_time\n        print('Request took %.4fs' % duration)\n\n\nclass Handler:\n    async def on_get(self, req, res):\n        await asyncio.sleep(0.1)\n        res.write('OK')\n\n\napp = Server()\napp.add_route('/hello/{name}', Handler())\napp.add_regex_route('/.*', Handler())\napp.add_middleware(TimingMiddleware())\napp.serve()\n"
  },
  {
    "path": "setup.py",
    "content": "# Always prefer setuptools over distutils\nfrom setuptools import setup, find_packages\n# To use a consistent encoding\nfrom codecs import open\nfrom os import path\n\nhere = path.abspath(path.dirname(__file__))\n\ntry:\n    import pypandoc\n    long_description = pypandoc.convert('README.md', 'rst')\nexcept ImportError:\n    long_description = open('README.md').read()\n\n\nsetup(\n    name='albatross3',\n    # Versions should comply with PEP440.  For a discussion on single-sourcing\n    # the version across setup.py and the project code, see\n    # https://packaging.python.org/en/latest/single_source_version.html\n    version='0.6.3',\n    description='A modern async python3 web framework',\n    long_description=long_description,\n    url='https://github.com/kespindler/albatross',\n    author='Kurt Spindler',\n    author_email='kespindler@gmail.com',\n    license='MIT',\n    classifiers=[\n        # How mature is this project? Common values are\n        #   3 - Alpha\n        #   4 - Beta\n        #   5 - Production/Stable\n        'Development Status :: 3 - Alpha',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: MIT License',\n        'Programming Language :: Python :: 3.5',\n    ],\n    keywords='web http server async',\n    packages=['albatross'],\n    install_requires=[\n        'httptools',\n    ],\n    extras_require={\n        'ujson': ['ujson']\n    },\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_data_types.py",
    "content": "import unittest\nfrom albatross.data_types import (\n    ImmutableMultiDict, CaselessDict,\n    ImmutableCaselessMultiDict\n)\n\n\nclass ImmutableMultiDictTest(unittest.TestCase):\n    def test_immutable(self):\n        z = ImmutableMultiDict(**{'one': ['two', 'three']})\n        with self.assertRaises(TypeError):\n            z.update({})\n        assert z.get_all('one') == ['two', 'three']\n        assert z.get_all('two') is None\n\n\nclass CaselessDictTest(unittest.TestCase):\n    def test_caselesss(self):\n        z = CaselessDict(**{'One': 'two'})\n        assert z['one'] == 'two'\n        z.update({'Three': 'four'})\n        assert z['three'] == 'four'\n        z.update([('FIVE', 'six')])\n        assert z['five'] == 'six'\n        for k in z:\n            assert k in z\n\n        assert z.get('two') is None\n\n    def test_caseless_update(self):\n        z = CaselessDict(One='two')\n        z.update(two=2)\n        assert z['TWO'] == 2\n\n    def test_caseless_iterable_init(self):\n        z = CaselessDict([\n            ('ONE', 1),\n            ('TWO', 2),\n            ('THREE', 3),\n        ])\n        assert z['one'] == 1\n        assert z['tWO'] == 2\n        assert z['thRee'] == 3\n\n\nclass ImmutableCaselessMultiDictTest(unittest.TestCase):\n    def test_immutable_caseless_multi_dict(self):\n        z = ImmutableCaselessMultiDict([\n            ('one', 1),\n            ('ONE', 'I'),\n            ('tWO', 2),\n            ('TwO', 'II'),\n        ])\n\n        assert z['one'] == 1, z\n        assert z.get('one') == 1\n        assert z.get_all('one') == [1, 'I']\n\n        assert z['TWO'] == 2\n        assert z.get('Two') == 2\n        assert z.get_all('TWO') == [2, 'II']\n\n        assert z.get('three') is None\n        assert z.get_all('three') is None\n"
  },
  {
    "path": "tests/test_request.py",
    "content": "import unittest\nfrom albatross import Request\nfrom albatross.http_error import HTTPError\nfrom io import BytesIO\n\n\nclass RequestTest(unittest.TestCase):\n\n    def test_request(self):\n        r = Request()\n        r.method = 'POST'\n        r.on_url(b'/hello/test?foo=baz')\n        r.args = {'name': 'test'}\n        r.on_header(b'CONTENT-TYPE', b'application/x-www-form-urlencoded')\n        r.on_headers_complete()\n        r.on_body(b'one=two')\n        r.on_message_complete()\n        assert r.method == 'POST'\n        assert r.path == '/hello/test'\n        assert r.query_string == 'foo=baz'\n        assert r.form['one'] == 'two'\n        assert r.form.get('one') == 'two'\n        assert r.form.get('ONE') is None\n        with self.assertRaises(HTTPError):\n            assert r.form['ONE']\n\n    def test_request_cookie(self):\n        r = Request()\n        r.on_header(b'COOKIE', b'token=bizbaz; fizzle=bizzle')\n        r.on_headers_complete()\n        assert r.cookies['token'] == 'bizbaz'\n        assert r.cookies['fizzle'] == 'bizzle'\n        assert ' fizzle' not in r.cookies\n\n    def test_request_raw_body(self):\n        r = Request()\n        r.on_body(b'stream')\n        assert r.raw_body.getvalue() == b'stream'\n\n    def test_request_json(self):\n        r = Request()\n        r.on_header(b'Content-Type', b'application/json')\n        r.on_headers_complete()\n        r._parse_body(BytesIO(b'{\"my\":\"name\"}'))\n        assert r.form == {'my': 'name'}\n"
  },
  {
    "path": "tests/test_response.py",
    "content": "import unittest\nfrom albatross import Response, HTTPError\nfrom albatross.status_codes import HTTP_301, HTTP_302\n\n\nclass RequestTest(unittest.TestCase):\n\n    def test_request(self):\n        r = Response()\n        with self.assertRaises(HTTPError):\n            r.redirect('/')\n        assert r.headers['Location'] == '/'\n        assert r.status_code == HTTP_302\n\n        with self.assertRaises(HTTPError):\n            r.redirect('/another', permanent=True)\n        assert r.headers['Location'] == '/another'\n        assert r.status_code == HTTP_301\n"
  },
  {
    "path": "tests/test_server.py",
    "content": "import unittest\nimport asyncio\nfrom albatross import Server\nfrom albatross.compat import json\nfrom aiohttp import client\nimport socket\nfrom datetime import datetime\nfrom hashlib import md5\nfrom time import time\n\nBODY = b'--------------------------5969313f95a69716\\r\\nContent-Disposition: form-data; name=\"key1\"\\r\\n\\r\\nvalue1\\r\\n--------------------------5969313f95a69716\\r\\nContent-Disposition: form-data; name=\"upload\"; filename=\"test.txt\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nwhat a great file\\n\\r\\n--------------------------5969313f95a69716--\\r\\n'  # noqa\n\n\nclass Handler:\n    async def on_get(self, req, res):\n        res.write('Hello World')\n\n    async def on_post(self, req, res):\n        name = req.form['name']\n        res.write_json({'name': name})\n        res.cookies['success'] = 'true'\n        res.cookies['expires_at'] = ('test1', datetime.utcnow())\n        res.cookies['expires_in'] = ('test1', 100)\n\n    async def on_put(self, req, res):\n        m = md5()\n        m.update(req.form['upload'].value)\n        res.write_json({\n            'upload': req.form['upload'].filename,\n            'hash': m.hexdigest(),\n            'value': req.form['key1']\n        })\n\n\nclass TimingMiddleware:\n    async def process_request(self, req, res, handler):\n        req._start_time = time()\n\n    async def process_response(self, req, res, handler):\n        duration = time() - req._start_time\n        res.headers['Duration'] = duration\n\n\ndef get_free_port():\n    s = socket.socket()\n    s.bind(('', 0))\n    port = s.getsockname()[1]\n    s.close()\n    return port\n\n\nclass ServerIntegrationTest(unittest.TestCase):\n\n    def setUp(self):\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n        self.server = Server()\n        self.server.add_route('/hello', Handler())\n\n        self.port = get_free_port()\n        self.url = 'http://127.0.0.1:%d' % (self.port, )\n        self.async_server = self.loop.run_until_complete(\n            asyncio.start_server(\n                self.server._handle, '127.0.0.1', self.port, loop=self.loop\n            )\n        )\n\n    def tearDown(self):\n        self.async_server.close()\n\n    def request(self, method, path, data=None, headers=None):\n        async def go():\n            self.session = client.ClientSession(loop=self.loop)\n            response = await self.session.request(\n                method, self.url + path,\n                data=data, headers=headers\n            )\n            bytes = await response.read()\n            body = bytes.decode()\n            self.session.close()\n            return response, body\n        return self.loop.run_until_complete(go())\n\n    def test_hello_world(self):\n        response, body = self.request('GET', '/hello')\n        assert body == 'Hello World'\n\n    def test_hello_world_post(self):\n        response, body = self.request(\n            'POST', '/hello',\n            data='name=mouse', headers={\n                'Content-Type': 'application/x-www-form-urlencoded'\n            }\n        )\n        assert body == '{\"name\":\"mouse\"}', body\n        assert response.cookies['success'].value == 'true', response.cookies\n\n    def test_hello_world_put(self):\n        response, body = self.request(\n            'PUT', '/hello',\n            data=BODY, headers={\n                'Content-Type': 'multipart/form-data; boundary=------------------------5969313f95a69716'  # noqa\n            }\n        )\n        assert response.status == 200, response.status\n        assert json.loads(body) == {\n            'upload': 'test.txt',\n            'hash': 'f4b099a273213d89d4161f64c05aaf13',\n            'value': 'value1',\n        }\n\n    def test_malformed_boundary(self):\n        response, body = self.request(\n            'PUT', '/hello',\n            data='name=mouse', headers={\n                'Content-Type': 'multipart/form-data'\n            }\n        )\n        assert response.status == 500, response.status\n\n    def test_not_found(self):\n        response, body = self.request('GET', '/notfound')\n        assert response.status == 404\n\n    def test_with_middleware(self):\n        self.server.add_middleware(TimingMiddleware())\n        response, body = self.request('GET', '/hello')\n        assert float(response.headers['Duration'])\n\n    def test_expect_continue(self):\n        response, body = self.request(\n            'POST', '/hello',\n            data='{\"name\":\"test\"}', headers={\n                'Expect': '100-continue',\n                'Content-Type': 'application/json',\n            }\n        )\n        assert response.status == 200\n        assert body == '{\"name\":\"test\"}'\n\n\nclass FakeHandler:\n    def __init__(self, n):\n        self.n = n\n\n\nclass ServerUnitTest(unittest.TestCase):\n    def setUp(self):\n        self.server = Server()\n        self.server.add_route('/hello/{name}', FakeHandler(1))\n        self.server.add_route('/hello', FakeHandler(2))\n        self.server.add_route('/hello/{another}/motd', FakeHandler(3))\n\n    def test_simple_route(self):\n        handler, args = self.server.get_handler('/hello/test')\n        assert handler.n == 1\n        assert args == {'name': 'test'}, args\n\n        handler, args = self.server.get_handler('/hello')\n        assert handler.n == 2\n        assert args == {}\n\n        handler, args = self.server.get_handler('/hello/test/motd')\n        assert handler.n == 3\n        assert args == {'another': 'test'}\n\n        handler, args = self.server.get_handler('/hello/')\n        assert handler is None\n        assert args is None\n"
  }
]