Repository: kespindler/albatross Branch: master Commit: 35b9a7a370eb Files: 26 Total size: 69.6 KB Directory structure: gitextract_m4qwufff/ ├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── albatross/ │ ├── __init__.py │ ├── compat.py │ ├── data_types.py │ ├── http_error.py │ ├── request.py │ ├── response.py │ ├── server.py │ └── status_codes.py ├── bench/ │ ├── benchmark.sh │ ├── client.py │ ├── data.txt │ ├── run_aiohttp.py │ ├── run_albatross.py │ ├── run_flask.py │ └── run_tornado.py ├── examples/ │ └── basic.py ├── setup.py └── tests/ ├── __init__.py ├── test_data_types.py ├── test_request.py ├── test_response.py └── test_server.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea __pycache__ dist albatross3.egg-info/ build/ .DS_Store .coverage cover bench/output ================================================ FILE: .travis.yml ================================================ language: python python: - "3.5" install: - pip install aiohttp ujson - python3 setup.py install script: nosetests ================================================ FILE: Makefile ================================================ COVERAGE=98 TEST=nosetests -s --with-coverage --cover-package=albatross --cover-branches $(TARGET) upload: git push python3 setup.py sdist bdist_wheel upload test: $(TEST) --cover-min-percentage $(COVERAGE) cover: $(TEST) --cover-html which open && open cover/index.html .PHONY: cover ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/kespindler/albatross.svg?branch=master)](https://travis-ci.org/kespindler/albatross) # Albatross A modern, fast, simple, natively-async web framework. (Python3.5 only) ```python from albatross import Server import asyncio class Handler: async def on_get(self, req, res): await asyncio.sleep(0.1) res.write('Hello, %s' % req.args['name']) app = Server() app.add_route('/{name})', Handler()) app.serve() ``` ### Notes for Usage For now (pre 1.0.0), I'm making no claims about API stability (but will try to avoid changes). That said, I'm using this framework for some small projects, and it is a joy to work in! Reach out if you want to use this, as I'm happy to incorporate your feedback! ## Install pip3 install albatross3 ## Features - You can read the entire codebase in about 30 minutes. - It's natively async. Doing `await` database calls or controller calls in your views just works! - This works with the `uvloop` project, to make your server fast! ## Benchmarks - My benchmarks indicate that albatross is as fast as aiohttp, both of which are twice as fast as tornado. You can run the benchmarks by poking around in the `bench/` folder. ================================================ FILE: albatross/__init__.py ================================================ from albatross.request import Request from albatross.response import Response from albatross.server import Server from albatross.status_codes import * from albatross.http_error import HTTPError ================================================ FILE: albatross/compat.py ================================================ try: import ujson as json except ImportError: import json ================================================ FILE: albatross/data_types.py ================================================ from albatross.http_error import HTTPError from albatross.status_codes import HTTP_400 def caseless_pairs(seq): for k, v in seq: yield k.lower(), v class Immutable: def __setitem__(self, k, v): raise TypeError('Cannot set item on %s' % self.__class__) def update(self, E=None, **F): raise TypeError('Cannot update on %s' % self.__class__) class ImmutableMultiDict(Immutable, dict): def __getitem__(self, k): if k in self: return super(ImmutableMultiDict, self).__getitem__(k)[0] raise HTTPError(HTTP_400, 'Must provide parameter \'%s\'.' % k) def get(self, k, d=None): if k in self: return super(ImmutableMultiDict, self).__getitem__(k)[0] return d def get_all(self, k, d=None): if k in self: return super(ImmutableMultiDict, self).__getitem__(k) return d class CaselessDict(dict): def __init__(self, it=None, **kwargs): it = caseless_pairs(it) if it else [] if kwargs: kwargs = {k.lower(): v for k, v in kwargs.items()} super(CaselessDict, self).__init__(it, **kwargs) def __contains__(self, k): return super(CaselessDict, self).__contains__(k.lower()) def __getitem__(self, k): return super(CaselessDict, self).__getitem__(k.lower()) def __iter__(self): for k in super(CaselessDict, self).__iter__(): yield k.lower() def __setitem__(self, k, v): super(CaselessDict, self).__setitem__(k.lower(), v) def get(self, k, d=None): if k in self: return super(CaselessDict, self).__getitem__(k.lower()) return d def update(self, other=None, **kwargs): updates = {k.lower(): v for k, v in kwargs.items()} if other: if hasattr(other, 'items'): other = other.items() updates.update(caseless_pairs(other)) return super(CaselessDict, self).update(updates) class ImmutableCaselessDict(Immutable, CaselessDict): pass class ImmutableCaselessMultiDict(ImmutableMultiDict, CaselessDict): def __init__(self, it=None, **kwargs): it = caseless_pairs(it) if it else [] if kwargs: kwargs = {k.lower(): [v] for k, v in kwargs.items()} for k, v in it: if k in kwargs: kwargs[k].append(v) else: kwargs[k] = [v] super(ImmutableCaselessMultiDict, self).__init__(**kwargs) ================================================ FILE: albatross/http_error.py ================================================ class HTTPError(Exception): def __init__(self, code, message=None): self.status_code = code self.message = message or code ================================================ FILE: albatross/request.py ================================================ from albatross.data_types import ( ImmutableMultiDict, ImmutableCaselessMultiDict ) from albatross.compat import json import urllib.parse as parse from httptools import parse_url import cgi import io REQUEST_STATE_PROCESSING = 0 REQUEST_STATE_CONTINUE = 1 REQUEST_STATE_COMPLETE = 2 def trim_keys(d): return {k.strip(): v for k, v in d.items()} class FileStorage: def __init__(self, field_storage): self.filename = field_storage.filename self.value = field_storage.value class Request: """ Attributes: method (str): One of GET, POST, PUT, DELETE path (str): Full request path query_string (str): Full query string query (dict): dict of the request query body (str): Request body args (dict): Dictionary of named parameters in route regex form (dict): Dictionary of body parameters """ def __init__(self, method=None, path=None, query_string='', args=None, headers=None, form=None, cookies=None): self._header_list = [] self._state = REQUEST_STATE_PROCESSING self.method = method self.path = path self.query_string = query_string self.query = None self.args = args self.headers = ImmutableCaselessMultiDict() self.cookies = ImmutableMultiDict() self.raw_body = io.BytesIO() self.form = form if query_string: self.query = ImmutableMultiDict(parse.parse_qs(self.query_string)) if headers: self.headers = ImmutableCaselessMultiDict(**headers) if cookies: self.cookies = ImmutableMultiDict(**cookies) def _parse_cookie(self, value): cookies = trim_keys(parse.parse_qs(value)) return ImmutableMultiDict(cookies) def _parse_form(self, body_stream): # TODO theres probably a way to not read whole body first) env = {'REQUEST_METHOD': 'POST'} form = cgi.FieldStorage(body_stream, headers=self.headers, environ=env) d = {} for k in form.keys(): if form[k].filename: d[k] = [FileStorage(form[k])] else: d[k] = [form[k].value] return ImmutableMultiDict(d) def _parse_body(self, body_stream): content_type = self.headers.get('Content-Type', '') if content_type == 'application/json': data = body_stream.getvalue().decode() self.form = json.loads(data) elif content_type.startswith('multipart/form-data'): self.form = self._parse_form(body_stream) elif content_type == 'application/x-www-form-urlencoded': data = body_stream.getvalue().decode() self.form = ImmutableMultiDict(parse.parse_qs(data)) body_stream.seek(0) # HTTPRequestParser protocol methods def on_url(self, url: bytes): parsed = parse_url(url) self.path = parsed.path.decode() self.query_string = (parsed.query or b'').decode() self.query = ImmutableMultiDict(parse.parse_qs(self.query_string)) def on_header(self, name: bytes, value: bytes): self._header_list.append((name.decode(), value.decode())) if name.lower() == b'expect' and value == b'100-continue': self._state = REQUEST_STATE_CONTINUE def on_headers_complete(self): self.headers = ImmutableCaselessMultiDict(self._header_list) cookie_value = self.headers.get('Cookie') if cookie_value: self.cookies = self._parse_cookie(cookie_value) def on_body(self, body: bytes): self.raw_body.write(body) def on_message_complete(self): self._state = REQUEST_STATE_COMPLETE self.raw_body.seek(0) self._parse_body(self.raw_body) @property def finished(self): return self._state == REQUEST_STATE_COMPLETE @property def needs_write_continue(self): return self._state == REQUEST_STATE_CONTINUE def reset_state(self): self._state = REQUEST_STATE_PROCESSING ================================================ FILE: albatross/response.py ================================================ from albatross import status_codes from albatross.compat import json from albatross.data_types import CaselessDict from albatross.http_error import HTTPError class Response: """ Attributes: status_code (str): HTTP status code headers (dict): Be careful about case-sensitivity here. body (str): """ def __init__(self): self.status_code = status_codes.HTTP_200 self._chunks = [] self.headers = CaselessDict([ ('Content-Type', 'text/html') ]) self.cookies = {} def clear(self): self._chunks = [] def write(self, string): self._chunks.append(string.encode()) def write_bytes(self, bytes): self._chunks.append(bytes) def write_json(self, data): self.headers['Content-Type'] = 'application/json' self._chunks.append(json.dumps(data).encode()) def redirect(self, location, permanent=False): self.headers['Location'] = location if permanent: self.status_code = status_codes.HTTP_301 else: self.status_code = status_codes.HTTP_302 raise HTTPError(self.status_code) ================================================ FILE: albatross/server.py ================================================ import re import asyncio from datetime import datetime from albatross import Request, Response from albatross.status_codes import HTTP_404, HTTP_500, HTTP_405 from albatross.http_error import HTTPError from httptools import HttpRequestParser import traceback def write_cookie(writer, key, value): if isinstance(value, tuple): value, duration = value if isinstance(duration, datetime): format = duration.strftime('%a %d %b %Y %H:%M:%S GMT').encode() writer.write(b'Set-Cookie: %s=%s;expires=%s\r\n' % ( key.encode(), str(value).encode(), format) ) elif isinstance(duration, int): writer.write(b'Set-Cookie: %s=%s;max-age=%d\r\n' % ( key.encode(), str(value).encode(), duration) ) else: writer.write(b'Set-Cookie: %s=%s\r\n' % ( key.encode(), str(value).encode()) ) class Server: """The core albatross server Attributes: _handlers (list): a list of route-handler tuples _middleware (list): a list of middlewares to process requests """ def __init__(self): self._handlers = [] self._middleware = [] self.spoof_options = True def get_handler(self, path): for route, handler in self._handlers: match = route.match(path) if match: return handler, match.groupdict() return None, None def add_regex_route(self, route, handler): route += '$' compiled = re.compile(route) self._handlers.append((compiled, handler)) def add_route(self, route, handler): route = re.sub('{([-_a-zA-Z]+)}', '(?P<\g<1>>[^/?]+)', route) self.add_regex_route(route, handler) def add_middleware(self, middleware): self._middleware.append(middleware) async def _parse_request(self, request_reader, response_writer): limit = 2 ** 16 req = Request() parser = HttpRequestParser(req) while True: data = await request_reader.read(limit) parser.feed_data(data) if req.finished or not data: break elif req.needs_write_continue: response_writer.write(b'HTTP/1.1 100 (Continue)\r\n\r\n') req.reset_state() if req.path is None: # connected without a formed HTTP request return handler, args = self.get_handler(req.path) req.method = parser.get_method().decode().upper() req.args = args return req, handler async def _route_request(self, handler, req, res): method = req.method if handler is None: raise HTTPError(HTTP_404) elif method == 'GET' and hasattr(handler, 'on_get'): await handler.on_get(req, res) elif method == 'POST' and hasattr(handler, 'on_post'): await handler.on_post(req, res) elif method == 'PUT' and hasattr(handler, 'on_put'): await handler.on_put(req, res) elif method == 'DELETE' and hasattr(handler, 'on_delete'): await handler.on_delete(req, res) elif method == 'OPTIONS': if hasattr(handler, 'on_options'): await handler.on_options(req, res) elif self.spoof_options: res.headers['Allow'] = 'GET,POST,DELETE,PUT' else: raise HTTPError(HTTP_405) else: raise HTTPError(HTTP_405) async def _handle(self, request_reader, response_writer): """Takes reader and writer from asyncio loop server and writes the response to the request. :param request_reader: :param response_writer: :return: """ res = Response() try: req, handler = await self._parse_request( request_reader, response_writer) for middleware in self._middleware: await middleware.process_request(req, res, handler) try: await self._route_request(handler, req, res) except HTTPError as e: self.handle_error(res, e) for middleware in self._middleware: await middleware.process_response(req, res, handler) except Exception as e: self.handle_error(res, e) self._write_response(res, response_writer) await response_writer.drain() response_writer.close() def handle_error(self, res, e): res.clear() if isinstance(e, HTTPError): res.status_code = e.status_code res.write(e.message) else: res.status_code = HTTP_500 res.write(res.status_code) traceback.print_exc() def _write_response(self, res, writer): writer.write(b'HTTP/1.1 %s\r\n' % res.status_code.encode()) if 'Content-Length' not in res.headers: length = sum(len(x) for x in res._chunks) res.headers['Content-Length'] = str(length) for key, value in res.headers.items(): writer.write(key.encode() + b': ' + str(value).encode() + b'\r\n') for key, value in res.cookies.items(): write_cookie(writer, key, value) writer.write(b'\r\n') for chunk in res._chunks: writer.write(chunk) writer.write_eof() async def initialize(self): pass def serve(self, port=8000, host='0.0.0.0'): loop = asyncio.get_event_loop() loop.run_until_complete(self.initialize()) print('Serving on %s:%d' % (host, port)) loop.create_task(asyncio.start_server(self._handle, host, port)) loop.run_forever() loop.close() ================================================ FILE: albatross/status_codes.py ================================================ # Copied from https://github.com/falconry/falcon/blob/master/falcon/status_codes.py HTTP_100 = '100 Continue' HTTP_CONTINUE = HTTP_100 HTTP_101 = '101 Switching Protocols' HTTP_SWITCHING_PROTOCOLS = HTTP_101 HTTP_200 = '200 OK' HTTP_OK = HTTP_200 HTTP_201 = '201 Created' HTTP_CREATED = HTTP_201 HTTP_202 = '202 Accepted' HTTP_ACCEPTED = HTTP_202 HTTP_203 = '203 Non-Authoritative Information' HTTP_NON_AUTHORITATIVE_INFORMATION = HTTP_203 HTTP_204 = '204 No Content' HTTP_NO_CONTENT = HTTP_204 HTTP_205 = '205 Reset Content' HTTP_RESET_CONTENT = HTTP_205 HTTP_206 = '206 Partial Content' HTTP_PARTIAL_CONTENT = HTTP_206 HTTP_226 = '226 IM Used' HTTP_IM_USED = HTTP_226 HTTP_300 = '300 Multiple Choices' HTTP_MULTIPLE_CHOICES = HTTP_300 HTTP_301 = '301 Moved Permanently' HTTP_MOVED_PERMANENTLY = HTTP_301 HTTP_302 = '302 Found' HTTP_FOUND = HTTP_302 HTTP_303 = '303 See Other' HTTP_SEE_OTHER = HTTP_303 HTTP_304 = '304 Not Modified' HTTP_NOT_MODIFIED = HTTP_304 HTTP_305 = '305 Use Proxy' HTTP_USE_PROXY = HTTP_305 HTTP_307 = '307 Temporary Redirect' HTTP_TEMPORARY_REDIRECT = HTTP_307 HTTP_308 = '308 Permanent Redirect' HTTP_PERMANENT_REDIRECT = HTTP_308 HTTP_400 = '400 Bad Request' HTTP_BAD_REQUEST = HTTP_400 HTTP_401 = '401 Unauthorized' # <-- Really means "unauthenticated" HTTP_UNAUTHORIZED = HTTP_401 HTTP_402 = '402 Payment Required' HTTP_PAYMENT_REQUIRED = HTTP_402 HTTP_403 = '403 Forbidden' # <-- Really means "unauthorized" HTTP_FORBIDDEN = HTTP_403 HTTP_404 = '404 Not Found' HTTP_NOT_FOUND = HTTP_404 HTTP_405 = '405 Method Not Allowed' HTTP_METHOD_NOT_ALLOWED = HTTP_405 HTTP_406 = '406 Not Acceptable' HTTP_NOT_ACCEPTABLE = HTTP_406 HTTP_407 = '407 Proxy Authentication Required' HTTP_PROXY_AUTHENTICATION_REQUIRED = HTTP_407 HTTP_408 = '408 Request Time-out' HTTP_REQUEST_TIMEOUT = HTTP_408 HTTP_409 = '409 Conflict' HTTP_CONFLICT = HTTP_409 HTTP_410 = '410 Gone' HTTP_GONE = HTTP_410 HTTP_411 = '411 Length Required' HTTP_LENGTH_REQUIRED = HTTP_411 HTTP_412 = '412 Precondition Failed' HTTP_PRECONDITION_FAILED = HTTP_412 HTTP_413 = '413 Payload Too Large' HTTP_REQUEST_ENTITY_TOO_LARGE = HTTP_413 HTTP_414 = '414 URI Too Long' HTTP_REQUEST_URI_TOO_LONG = HTTP_414 HTTP_415 = '415 Unsupported Media Type' HTTP_UNSUPPORTED_MEDIA_TYPE = HTTP_415 HTTP_416 = '416 Range Not Satisfiable' HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = HTTP_416 HTTP_417 = '417 Expectation Failed' HTTP_EXPECTATION_FAILED = HTTP_417 HTTP_418 = "418 I'm a teapot" HTTP_IM_A_TEAPOT = HTTP_418 HTTP_422 = '422 Unprocessable Entity' HTTP_UNPROCESSABLE_ENTITY = HTTP_422 HTTP_426 = '426 Upgrade Required' HTTP_UPGRADE_REQUIRED = HTTP_426 HTTP_428 = '428 Precondition Required' HTTP_PRECONDITION_REQUIRED = HTTP_428 HTTP_429 = '429 Too Many Requests' HTTP_TOO_MANY_REQUESTS = HTTP_429 HTTP_431 = '431 Request Header Fields Too Large' HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = HTTP_431 HTTP_451 = '451 Unavailable For Legal Reasons' HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = HTTP_451 HTTP_500 = '500 Internal Server Error' HTTP_INTERNAL_SERVER_ERROR = HTTP_500 HTTP_501 = '501 Not Implemented' HTTP_NOT_IMPLEMENTED = HTTP_501 HTTP_502 = '502 Bad Gateway' HTTP_BAD_GATEWAY = HTTP_502 HTTP_503 = '503 Service Unavailable' HTTP_SERVICE_UNAVAILABLE = HTTP_503 HTTP_504 = '504 Gateway Time-out' HTTP_GATEWAY_TIMEOUT = HTTP_504 HTTP_505 = '505 HTTP Version not supported' HTTP_HTTP_VERSION_NOT_SUPPORTED = HTTP_505 HTTP_511 = '511 Network Authentication Required' HTTP_NETWORK_AUTHENTICATION_REQUIRED = HTTP_511 # 70X - Inexcusable HTTP_701 = '701 Meh' HTTP_702 = '702 Emacs' HTTP_703 = '703 Explosion' # 71X - Novelty Implementations HTTP_710 = '710 PHP' HTTP_711 = '711 Convenience Store' HTTP_712 = '712 NoSQL' HTTP_719 = '719 I am not a teapot' # 72X - Edge Cases HTTP_720 = '720 Unpossible' HTTP_721 = '721 Known Unknowns' HTTP_722 = '722 Unknown Unknowns' HTTP_723 = '723 Tricky' HTTP_724 = '724 This line should be unreachable' HTTP_725 = '725 It works on my machine' HTTP_726 = "726 It's a feature, not a bug" HTTP_727 = '727 32 bits is plenty' # 74X - Meme Driven HTTP_740 = '740 Computer says no' HTTP_741 = '741 Compiling' HTTP_742 = '742 A kitten dies' HTTP_743 = '743 I thought I knew regular expressions' HTTP_744 = '744 Y U NO write integration tests?' HTTP_745 = ("745 I don't always test my code, but when I do" 'I do it in production') HTTP_748 = '748 Confounded by Ponies' HTTP_749 = '749 Reserved for Chuck Norris' # 75X - Syntax Errors HTTP_750 = "750 Didn't bother to compile it" HTTP_753 = '753 Syntax Error' HTTP_754 = '754 Too many semi-colons' HTTP_755 = '755 Not enough semi-colons' HTTP_759 = '759 Unexpected T_PAAMAYIM_NEKUDOTAYIM' # 77X - Predictable Problems HTTP_771 = '771 Cached for too long' HTTP_772 = '772 Not cached long enough' HTTP_773 = '773 Not cached at all' HTTP_774 = '774 Why was this cached?' HTTP_776 = '776 Error on the Exception' HTTP_777 = '777 Coincidence' HTTP_778 = '778 Off By One Error' HTTP_779 = '779 Off By Too Many To Count Error' # 78X - Somebody Else's Problem HTTP_780 = '780 Project owner not responding' HTTP_781 = '781 Operations' HTTP_782 = '782 QA' HTTP_783 = '783 It was a customer request, honestly' HTTP_784 = '784 Management, obviously' HTTP_785 = '785 TPS Cover Sheet not attached' HTTP_786 = '786 Try it now' # 79X - Internet crashed HTTP_791 = '791 The Internet shut down due to copyright restrictions' HTTP_792 = '792 Climate change driven catastrophic weather event' HTTP_797 = '797 This is the last page of the Internet. Go back' HTTP_799 = '799 End of the world' ================================================ FILE: bench/benchmark.sh ================================================ function run_ab() { python3 run_$1.py & sleep 1 PID=$! mkdir -p output/$2 time ab -c 100 -n 5000 http://127.0.0.1:8000/$2 > output/$2/$1.$2.log kill $PID wait $PID } function run_ab_post() { python3 run_$1.py & sleep 1 PID=$! mkdir -p output/$2 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 kill $PID wait $PID } run_ab albatross hello run_ab tornado hello run_ab aiohttp hello run_ab_post albatross form run_ab_post tornado form run_ab_post aiohttp form ================================================ FILE: bench/client.py ================================================ #!/usr/local/bin/python3.5 import asyncio from aiohttp import ClientSession from time import time url = 'http://localhost:8000/name' async def hello(): async with ClientSession() as session: print('getting') start = time() async with session.get(url) as response: response = await response.read() dur = time() - start print('read response, took %.5f' % dur) loop = asyncio.get_event_loop() tasks = [] for i in range(10): task = asyncio.ensure_future(hello()) tasks.append(task) loop.run_until_complete(asyncio.wait(tasks)) ================================================ FILE: bench/data.txt ================================================ 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>QqqCbtjP0MxdgJ=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>Hi4izbGHikGkoM=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 ================================================ FILE: bench/run_aiohttp.py ================================================ from aiohttp import web from urllib.parse import parse_qs async def hello(request): return web.Response(body=b'Hello World') async def form(request): data = await request.read() body = data.decode() form = parse_qs(data) num_fields = len(list(form.keys())) return web.Response(body=b'Found %d keys.' % num_fields) app = web.Application() app.router.add_route('GET', '/hello', hello) app.router.add_route('POST', '/form', form) web.run_app(app, port=8000) ================================================ FILE: bench/run_albatross.py ================================================ from albatross import Server class Handler: async def on_get(self, req, res): res.write('Hello World') class FormHandler: async def on_post(self, req, res): num_fields = len(list(req.form.keys())) res.write('Found %d keys.' % num_fields) app = Server() app.add_route('/hello', Handler()) app.add_route('/form', FormHandler()) app.serve(port=8000) ================================================ FILE: bench/run_flask.py ================================================ from flask import Flask from time import sleep app = Flask() @app.route('/hello') def hello(): sleep(0.1) ================================================ FILE: bench/run_tornado.py ================================================ import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello World") class FormHandler(tornado.web.RequestHandler): def post(self): num_fields = len(list(self.request.arguments.keys())) self.write('Found %d keys.' % num_fields) def make_app(): return tornado.web.Application([ (r'/hello', MainHandler), (r'/form', FormHandler), ]) if __name__ == '__main__': app = make_app() app.listen(8000) tornado.ioloop.IOLoop.current().start() ================================================ FILE: examples/basic.py ================================================ from albatross import Server from time import time import asyncio try: import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) except ImportError: pass class TimingMiddleware: async def process_request(self, req, res, handler): req._start_time = time() async def process_response(self, req, res, handler): duration = time() - req._start_time print('Request took %.4fs' % duration) class Handler: async def on_get(self, req, res): await asyncio.sleep(0.1) res.write('OK') app = Server() app.add_route('/hello/{name}', Handler()) app.add_regex_route('/.*', Handler()) app.add_middleware(TimingMiddleware()) app.serve() ================================================ FILE: setup.py ================================================ # Always prefer setuptools over distutils from setuptools import setup, find_packages # To use a consistent encoding from codecs import open from os import path here = path.abspath(path.dirname(__file__)) try: import pypandoc long_description = pypandoc.convert('README.md', 'rst') except ImportError: long_description = open('README.md').read() setup( name='albatross3', # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html version='0.6.3', description='A modern async python3 web framework', long_description=long_description, url='https://github.com/kespindler/albatross', author='Kurt Spindler', author_email='kespindler@gmail.com', license='MIT', classifiers=[ # How mature is this project? Common values are # 3 - Alpha # 4 - Beta # 5 - Production/Stable 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3.5', ], keywords='web http server async', packages=['albatross'], install_requires=[ 'httptools', ], extras_require={ 'ujson': ['ujson'] }, ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/test_data_types.py ================================================ import unittest from albatross.data_types import ( ImmutableMultiDict, CaselessDict, ImmutableCaselessMultiDict ) class ImmutableMultiDictTest(unittest.TestCase): def test_immutable(self): z = ImmutableMultiDict(**{'one': ['two', 'three']}) with self.assertRaises(TypeError): z.update({}) assert z.get_all('one') == ['two', 'three'] assert z.get_all('two') is None class CaselessDictTest(unittest.TestCase): def test_caselesss(self): z = CaselessDict(**{'One': 'two'}) assert z['one'] == 'two' z.update({'Three': 'four'}) assert z['three'] == 'four' z.update([('FIVE', 'six')]) assert z['five'] == 'six' for k in z: assert k in z assert z.get('two') is None def test_caseless_update(self): z = CaselessDict(One='two') z.update(two=2) assert z['TWO'] == 2 def test_caseless_iterable_init(self): z = CaselessDict([ ('ONE', 1), ('TWO', 2), ('THREE', 3), ]) assert z['one'] == 1 assert z['tWO'] == 2 assert z['thRee'] == 3 class ImmutableCaselessMultiDictTest(unittest.TestCase): def test_immutable_caseless_multi_dict(self): z = ImmutableCaselessMultiDict([ ('one', 1), ('ONE', 'I'), ('tWO', 2), ('TwO', 'II'), ]) assert z['one'] == 1, z assert z.get('one') == 1 assert z.get_all('one') == [1, 'I'] assert z['TWO'] == 2 assert z.get('Two') == 2 assert z.get_all('TWO') == [2, 'II'] assert z.get('three') is None assert z.get_all('three') is None ================================================ FILE: tests/test_request.py ================================================ import unittest from albatross import Request from albatross.http_error import HTTPError from io import BytesIO class RequestTest(unittest.TestCase): def test_request(self): r = Request() r.method = 'POST' r.on_url(b'/hello/test?foo=baz') r.args = {'name': 'test'} r.on_header(b'CONTENT-TYPE', b'application/x-www-form-urlencoded') r.on_headers_complete() r.on_body(b'one=two') r.on_message_complete() assert r.method == 'POST' assert r.path == '/hello/test' assert r.query_string == 'foo=baz' assert r.form['one'] == 'two' assert r.form.get('one') == 'two' assert r.form.get('ONE') is None with self.assertRaises(HTTPError): assert r.form['ONE'] def test_request_cookie(self): r = Request() r.on_header(b'COOKIE', b'token=bizbaz; fizzle=bizzle') r.on_headers_complete() assert r.cookies['token'] == 'bizbaz' assert r.cookies['fizzle'] == 'bizzle' assert ' fizzle' not in r.cookies def test_request_raw_body(self): r = Request() r.on_body(b'stream') assert r.raw_body.getvalue() == b'stream' def test_request_json(self): r = Request() r.on_header(b'Content-Type', b'application/json') r.on_headers_complete() r._parse_body(BytesIO(b'{"my":"name"}')) assert r.form == {'my': 'name'} ================================================ FILE: tests/test_response.py ================================================ import unittest from albatross import Response, HTTPError from albatross.status_codes import HTTP_301, HTTP_302 class RequestTest(unittest.TestCase): def test_request(self): r = Response() with self.assertRaises(HTTPError): r.redirect('/') assert r.headers['Location'] == '/' assert r.status_code == HTTP_302 with self.assertRaises(HTTPError): r.redirect('/another', permanent=True) assert r.headers['Location'] == '/another' assert r.status_code == HTTP_301 ================================================ FILE: tests/test_server.py ================================================ import unittest import asyncio from albatross import Server from albatross.compat import json from aiohttp import client import socket from datetime import datetime from hashlib import md5 from time import time BODY = 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 class Handler: async def on_get(self, req, res): res.write('Hello World') async def on_post(self, req, res): name = req.form['name'] res.write_json({'name': name}) res.cookies['success'] = 'true' res.cookies['expires_at'] = ('test1', datetime.utcnow()) res.cookies['expires_in'] = ('test1', 100) async def on_put(self, req, res): m = md5() m.update(req.form['upload'].value) res.write_json({ 'upload': req.form['upload'].filename, 'hash': m.hexdigest(), 'value': req.form['key1'] }) class TimingMiddleware: async def process_request(self, req, res, handler): req._start_time = time() async def process_response(self, req, res, handler): duration = time() - req._start_time res.headers['Duration'] = duration def get_free_port(): s = socket.socket() s.bind(('', 0)) port = s.getsockname()[1] s.close() return port class ServerIntegrationTest(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.server = Server() self.server.add_route('/hello', Handler()) self.port = get_free_port() self.url = 'http://127.0.0.1:%d' % (self.port, ) self.async_server = self.loop.run_until_complete( asyncio.start_server( self.server._handle, '127.0.0.1', self.port, loop=self.loop ) ) def tearDown(self): self.async_server.close() def request(self, method, path, data=None, headers=None): async def go(): self.session = client.ClientSession(loop=self.loop) response = await self.session.request( method, self.url + path, data=data, headers=headers ) bytes = await response.read() body = bytes.decode() self.session.close() return response, body return self.loop.run_until_complete(go()) def test_hello_world(self): response, body = self.request('GET', '/hello') assert body == 'Hello World' def test_hello_world_post(self): response, body = self.request( 'POST', '/hello', data='name=mouse', headers={ 'Content-Type': 'application/x-www-form-urlencoded' } ) assert body == '{"name":"mouse"}', body assert response.cookies['success'].value == 'true', response.cookies def test_hello_world_put(self): response, body = self.request( 'PUT', '/hello', data=BODY, headers={ 'Content-Type': 'multipart/form-data; boundary=------------------------5969313f95a69716' # noqa } ) assert response.status == 200, response.status assert json.loads(body) == { 'upload': 'test.txt', 'hash': 'f4b099a273213d89d4161f64c05aaf13', 'value': 'value1', } def test_malformed_boundary(self): response, body = self.request( 'PUT', '/hello', data='name=mouse', headers={ 'Content-Type': 'multipart/form-data' } ) assert response.status == 500, response.status def test_not_found(self): response, body = self.request('GET', '/notfound') assert response.status == 404 def test_with_middleware(self): self.server.add_middleware(TimingMiddleware()) response, body = self.request('GET', '/hello') assert float(response.headers['Duration']) def test_expect_continue(self): response, body = self.request( 'POST', '/hello', data='{"name":"test"}', headers={ 'Expect': '100-continue', 'Content-Type': 'application/json', } ) assert response.status == 200 assert body == '{"name":"test"}' class FakeHandler: def __init__(self, n): self.n = n class ServerUnitTest(unittest.TestCase): def setUp(self): self.server = Server() self.server.add_route('/hello/{name}', FakeHandler(1)) self.server.add_route('/hello', FakeHandler(2)) self.server.add_route('/hello/{another}/motd', FakeHandler(3)) def test_simple_route(self): handler, args = self.server.get_handler('/hello/test') assert handler.n == 1 assert args == {'name': 'test'}, args handler, args = self.server.get_handler('/hello') assert handler.n == 2 assert args == {} handler, args = self.server.get_handler('/hello/test/motd') assert handler.n == 3 assert args == {'another': 'test'} handler, args = self.server.get_handler('/hello/') assert handler is None assert args is None