Я работаю над службой шлюза API с aiohttp, которая выполняет совокупную операцию для ряда внутренних служб и упаковывает их в групповой ответ 207.
Внутренне мы используем файл cookie сеанса Django в качестве основного средства аутентификации (планируем перейти на JWT во втором квартале этого года), что означает, что для сервисов, получающих доступ к этому шлюзу, мне нужно пройти через cookie из запрос браузера или API остальных.
Кажется, что aiohttp не делает этого вообще, и при этом не делает доступными какие-либо файлы cookie в заголовках Postman Cookies
. Однако, если я попал в сервис с теми же файлами cookie, я получу ожидаемый ответ.
Код сейчас:
class AggregateView(BaseView):
async def post(self):
requests = await self.request.json()
logger.debug(f'Making [{len(requests)}] service calls at [{datetime.now()}]')
queries = []
async with ClientSession(cookie_jar=CookieJar(unsafe=True)) as session:
for req in requests:
queries.append(self._retrieve(req, session))
logger.debug(f'Async calls created [{datetime.now()}]')
results = await asyncio.gather(*queries)
logger.debug(f'Async calls completed and aggregated [{datetime.now()}]')
return web.json_response(
status=207,
data=results
)
class BaseView(web.View):
@cache
async def _retrieve(self, req, session):
"""
Unpacks a request body and executes the appropriate service call
:param request: request body as dictionary
:param session: aiohttp ClientSession
:return:
"""
url, path, query_params = await self._unpack_request(req)
logger.info(f'{url}/{path}\t{query_params}')
logger.info(f'Starting request for url [{url}/{path}] at [{datetime.now()}]')
logger.info(f'with query params {query_params}')
func = session.get
params = {
'url': f'{url}/{path}',
'params': query_params,
'headers': {
'Vary': 'Accept, Origin, Accept, Accept-Encoding, Authorization, Cookie'
}
}
async with AsyncBreaker(circuit_breaker, params['url'], func, **params) as resp:
logger.info(f'received request for url [{url}/{path}] at [{datetime.now()}]')
logger.info(resp.headers)
logger.info(resp.cookies)
return {
'url': f'{url}/{path}',
'status': resp.status,
'response': await resp.json()
}
def cache(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
request = args[1]
access_logger.info(args[2].cookie_jar._cookies.keys())
requested_service = request.get('service').upper()
service = SERVICES.get(requested_service)
if service and service.get('cache'):
access_logger.info('This response is cacheable.')
pass
return await func(*args, **kwargs)
return wrapper
dict_keys([]) # output in #cache for cookie jar inspection
This response is cacheable.
Starting request for url [https://service.company.com/api/v1/home-editorial/]
at [2019-01-11 15:01:03.983993]
with query params None
received request for url [https://service.company.com/api/v1/home-editorial/]
at [2019-01-11 15:01:04.434696]
Header output: <CIMultiDictProxy('Server': 'nginx/1.11.3', 'Content-Type':
'application/json', 'Www-Authenticate': 'Token', 'Allow': 'GET, HEAD,
OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security':
'max-age=15724800; includeSubDomains; preload', 'Accept-Ranges': 'bytes',
'Content-Length': '58', 'Accept-Ranges': 'bytes', 'Date':
'Fri, 11 Jan 2019 15:01:04 GMT', 'Via': '1.1 varnish', 'Connection':
'keep-alive', 'X-Client-IP': '192.150.73.149', 'X-Served-By':
'cache-bos8232-BOS', 'X-Cache': 'MISS', 'X-Cache-Hits': '0',
'X-Timer': 'S1547218864.246313,VS0,VE152', 'Vary': 'Accept, Origin')>
Примечания:
AsyncBreaker
использует aiobreaker
для использования шаблона автоматического выключателя для защиты служб от перегрузки
@cache
в настоящее время декоратор просто упаковывает метод извлечения и используется для проверки атрибутов AggregateView
и ClientSession
. Он будет более полнофункциональным с aiocache, как только эта проблема будет решена.