async def exception_handling_middleware(request, handler): try: return await handler(request) except ( web.HTTPNotFound, web.HTTPForbidden, web.HTTPMethodNotAllowed, asyncio.CancelledError, FDSNHTTPError, ) as err: raise err except web.HTTPRequestEntityTooLarge as err: raise FDSNHTTPError.create( 413, request, request_submitted=get_req_config(request, KEY_REQUEST_STARTTIME), error_desc_long=str(err), service_version=__version__, ) except Exception as err: exc_type, exc_value, exc_traceback = sys.exc_info() _logger = make_context_logger(logger, request) _logger.critical(f"Local Exception: {type(err)}") _logger.critical("Traceback information: " + repr( traceback.format_exception(exc_type, exc_value, exc_traceback))) raise FDSNHTTPError.create( 500, request, request_submitted=get_req_config(request, KEY_REQUEST_STARTTIME), service_version=__version__, )
async def run(self, route, req_method="GET", context=None, **req_kwargs): def route_with_single_stream(route): streams = set() for se in route.stream_epochs: streams.add(se.id()) return len(streams) == 1 url = route.url _sorted = sorted(route.stream_epochs) context = context or {} context["chunk_size"] = self._CHUNK_SIZE context["stream_epochs_record"] = copy.deepcopy(_sorted) # context logging try: logger = make_context_logger(self._logger, *context["logger_ctx"]) except (TypeError, KeyError): logger = self.logger finally: context["logger"] = logger with ThreadPoolExecutor(max_workers=1) as executor: assert route_with_single_stream( route ), "Cannot handle multiple streams within a single route." req_id = get_req_config(self.request, KEY_REQUEST_ID) async with AioSpooledTemporaryFile( max_size=self.config["buffer_rollover_size"], prefix=str(req_id) + ".", dir=self.config["tempdir"], executor=executor, ) as buf: await self._run( url, _sorted, req_method=req_method, buf=buf, splitting_factor=self.config["splitting_factor"], context=context, **req_kwargs, ) if await buf.tell(): async with self._lock: append = self._drain.prepared or False await self._flush( buf, self._drain, context, append=append, ) await self.finalize()
def __init__(self, request, **kwargs): self.request = request self._default_endtime = datetime.datetime.utcnow() self._post = False self._routed_urls = None self._response_sent = False self._await_on_close = [ self._gc_response_code_stats, ] self._logger = logging.getLogger(self.LOGGER) self.logger = make_context_logger(self._logger, self.request)
async def run( self, route, net, priority, req_method="GET", context=None, **req_kwargs, ): context = context or {} # context logging try: logger = make_context_logger(self._logger, *context["logger_ctx"]) except (TypeError, KeyError): logger = self.logger finally: context["logger"] = logger _buffer = {} logger.debug(f"Fetching data for network: {net!r}") # granular request strategy tasks = [ self._fetch( _route, parser_cb=self._parse_response, req_method=req_method, context={ "logger_ctx": create_job_context(self.request, parent_ctx=context.get("logger_ctx")) }, **req_kwargs, ) for _route in route ] results = await asyncio.gather(*tasks, return_exceptions=False) logger.debug(f"Processing data for network: {net!r}") for _route, data in results: if not data: continue se = _route.stream_epochs[0] _buffer[se.id()] = data if _buffer: serialized = self._dump(_buffer) await self._drain.drain((priority, serialized)) await self.finalize()
def __init__( self, request, session, drain, lock=None, **kwargs, ): self.request = request self._session = session self._drain = drain self._lock = lock self._logger = logging.getLogger(self.LOGGER) self.logger = make_context_logger(self._logger, self.request)
async def exception_handling_middleware(request, handler): try: return await handler(request) except ( web.HTTPBadRequest, web.HTTPNoContent, web.HTTPNotFound, web.HTTPForbidden, web.HTTPRequestEntityTooLarge, web.HTTPServiceUnavailable, web.HTTPGatewayTimeout, asyncio.CancelledError, ) as err: raise err except Exception as err: exc_type, exc_value, exc_traceback = sys.exc_info() _logger = make_context_logger(logger, request) _logger.critical(f"Local Exception: error={type(err)}, " f"url={request.url!r}, method={request.method!r}") _logger.critical("Traceback information: " + repr( traceback.format_exception(exc_type, exc_value, exc_traceback))) raise err
def __init__(self, request): super().__init__(request) self.config = self.request.config_dict[PROXY_BASE_ID]["config"] self._logger = logging.getLogger(self.LOGGER) self.logger = make_context_logger(self._logger, self.request)
async def run(self, route, req_method="GET", context=None, **req_kwargs): # context logging try: logger = make_context_logger(self._logger, *context["logger_ctx"]) except (TypeError, KeyError): logger = self.logger finally: context["logger"] = logger req_handler = self.REQUEST_HANDLER_CLS( **route._asdict(), query_params=self.query_params, headers=self.request_headers, ) req_handler.format = self.format req = getattr(req_handler, req_method.lower())(self._session) self._log_request(req_handler, req_method, logger=logger) resp_status = None try: async with req(**req_kwargs) as resp: resp.raise_for_status() resp_status = resp.status msg = ( f"Response: {resp.reason}: resp.status={resp_status}, " f"resp.request_info={resp.request_info}, " f"resp.url={resp.url}, resp.headers={resp.headers}" ) if resp_status == 200: logger.debug(msg) # XXX(damb): Read the entire response into memory text = await resp.read() # strip header data = text[(text.find(b"\n") + 1) :] if data: async with self._lock: await self._drain.drain(data) elif resp_status in FDSNWS_NO_CONTENT_CODES: logger.info(msg) else: await self.handle_error(msg=msg, context=context) except aiohttp.ClientResponseError as err: resp_status = err.status msg = ( f"Error while executing request: {err.message}: " f"error={type(err)}, resp.status={resp_status}, " f"resp.request_info={err.request_info}, " f"resp.headers={err.headers}" ) if resp_status == 413: await self.handle_413() elif resp_status in FDSNWS_NO_CONTENT_CODES: logger.info(msg) # https://github.com/aio-libs/aiohttp/issues/3641 elif ( resp_status == 400 and "invalid constant string" == err.message ): resp_status = 204 logger.info( "Excess found in read (reset status code to " f"{resp_status}). Original aiohttp error: {msg}" ) else: await self.handle_error(msg=msg, context=context) except (aiohttp.ClientError, asyncio.TimeoutError) as err: resp_status = 503 msg = ( f"Error while executing request: error={type(err)}, " f"req_handler={req_handler!r}, method={req_method}" ) if isinstance(err, aiohttp.ClientOSError): msg += f", errno={err.errno}" await self.handle_error(msg=msg, context=context) finally: if resp_status is not None: await self.update_cretry_budget(req_handler.url, resp_status) await self.finalize()
async def _fetch( self, route, parser_cb=None, req_method="GET", context=None, **kwargs, ): parser_cb = _coroutine_or_raise(parser_cb) # context logging try: logger = make_context_logger(self._logger, *context["logger_ctx"]) except (TypeError, KeyError): logger = self.logger req_handler = self.REQUEST_HANDLER_CLS( **route._asdict(), query_params=self.query_params, headers=self.request_headers, ) req_handler.format = self.format req = getattr(req_handler, req_method.lower())(self._session) self._log_request(req_handler, req_method, logger=logger) resp_status = None try: async with req(**kwargs) as resp: resp_status = resp.status resp.raise_for_status() msg = ( f"Response: {resp.reason}: resp.status={resp_status}, " f"resp.request_info={resp.request_info}, " f"resp.url={resp.url}, resp.headers={resp.headers}" ) if resp_status != 200: if resp_status in FDSNWS_NO_CONTENT_CODES: logger.info(msg) else: await self.handle_error(msg=msg, context=context) return route, None logger.debug(msg) if parser_cb is None: return route, await resp.read() return route, await parser_cb(resp) except aiohttp.ClientResponseError as err: resp_status = err.status msg = ( f"Error while executing request: {err.message}: " f"error={type(err)}, resp.status={resp_status}, " f"resp.request_info={err.request_info}, " f"resp.headers={err.headers}" ) if resp_status == 413: await self.handle_413(context=context) elif resp_status in FDSNWS_NO_CONTENT_CODES: logger.info(msg) # https://github.com/aio-libs/aiohttp/issues/3641 elif ( resp_status == 400 and "invalid constant string" == err.message ): resp_status = 204 logger.info( "Excess found in read (reset status code to " f"{resp_status}). Original aiohttp error: {msg}" ) else: await self.handle_error(msg=msg, context=context) return route, None except (aiohttp.ClientError, asyncio.TimeoutError) as err: msg = ( f"Error while executing request: error={type(err)}, " f"req_handler={req_handler!r}, method={req_method}" ) if isinstance(err, aiohttp.ClientOSError): msg += f", errno={err.errno}" await self.handle_error(msg=msg, context=context) resp_status = 503 return route, None finally: if resp_status is not None: await self.update_cretry_budget(req_handler.url, resp_status)
async def run(self, route, net, req_method="GET", context=None, **req_kwargs): context = context or {} context["buffer"] = {} # context logging try: logger = make_context_logger(self._logger, *context["logger_ctx"]) except (TypeError, KeyError): logger = self.logger finally: context["logger"] = logger logger.debug( f"Fetching data for network: {net!r} (num_routes={len(route)})") # TODO(damb): Currently, limiting the number of concurrent connection # is guaranteed by sharing an aiohttp.TCPConnector instance. Though, # instead of limiting the connection based on a HTTP connection pool it # would be better to limit the overall number of tasks in order to keep # the memory footprint low. -> Create bound number of tasks by means of # globally sharing an task pool. Similar to # https://docs.aiohttp.org/en/stable/client_reference.html# # aiohttp.TCPConnector, # to be able to limit task creation on a) overall and b) on a per host # basis. # granular request strategy tasks = [ self._fetch( _route, parser_cb=self._parse_response, req_method=req_method, context={ "logger_ctx": create_job_context(self.request, parent_ctx=context.get("logger_ctx")) }, **req_kwargs, ) for _route in route ] results = await asyncio.gather(*tasks, return_exceptions=False) logger.debug(f"Merging StationXML network element (net={net!r}, " f"level={self.level!r}) ...") for _, station_xml in results: if station_xml is None: continue for net_element in station_xml.iter(STATIONXML_TAGS_NETWORK): self._merge_net_element(net_element, level=self.level, context=context) for ( net_element, sta_elements, ) in context["buffer"].values(): serialized = self._serialize_net_element(net_element, sta_elements) async with self._lock: await self._drain.drain(serialized) await self.finalize()