async def get_json( self, uri: str, args: Optional[QueryParams] = None, headers: Optional[RawHeaders] = None, ) -> Any: """Gets some json from the given URI. Args: uri: The URI to request, not including query parameters args: A dictionary used to create query string headers: a map from header name to a list of values for that header Returns: Succeeds when we get a 2xx HTTP response, with the HTTP body as JSON. Raises: RequestTimedOutError: if there is a timeout before the response headers are received. Note there is currently no timeout on reading the response body. HttpResponseException On a non-2xx HTTP response. ValueError: if the response was not JSON """ actual_headers = {b"Accept": [b"application/json"]} if headers: actual_headers.update(headers) # type: ignore body = await self.get_raw(uri, args, headers=headers) return json_decoder.decode(body.decode("utf-8"))
async def on_claim_client_keys(self, origin: str, content: JsonDict) -> Dict[str, Any]: query = [] for user_id, device_keys in content.get("one_time_keys", {}).items(): for device_id, algorithm in device_keys.items(): query.append((user_id, device_id, algorithm)) log_kv({ "message": "Claiming one time keys.", "user, device pairs": query }) results = await self.store.claim_e2e_one_time_keys(query) json_result = {} # type: Dict[str, Dict[str, dict]] for user_id, device_keys in results.items(): for device_id, keys in device_keys.items(): for key_id, json_str in keys.items(): json_result.setdefault(user_id, {})[device_id] = { key_id: json_decoder.decode(json_str) } logger.info( "Claimed one-time-keys: %s", ",".join(("%s for %s:%s" % (key_id, user_id, device_id) for user_id, user_keys in json_result.items() for device_id, device_keys in user_keys.items() for key_id, _ in device_keys.items())), ) return {"one_time_keys": json_result}
async def get_json(self, uri, args={}, headers=None): """ Gets some json from the given URI. Args: uri (str): The URI to request, not including query parameters args (dict): A dictionary used to create query strings, defaults to None. **Note**: The value of each key is assumed to be an iterable and *not* a string. headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from header name to a list of values for that header Returns: Succeeds when we get *any* 2xx HTTP response, with the HTTP body as JSON. Raises: HttpResponseException On a non-2xx HTTP response. ValueError: if the response was not JSON """ actual_headers = {b"Accept": [b"application/json"]} if headers: actual_headers.update(headers) body = await self.get_raw(uri, args, headers=headers) return json_decoder.decode(body.decode("utf-8"))
def from_line(cls: Type["UserIpCommand"], line: str) -> "UserIpCommand": user_id, jsn = line.split(" ", 1) access_token, ip, user_agent, device_id, last_seen = json_decoder.decode( jsn) return cls(user_id, access_token, ip, user_agent, device_id, last_seen)
async def _fetch_well_known(self, server_name: bytes) -> Tuple[bytes, float]: """Actually fetch and parse a .well-known, without checking the cache Args: server_name: name of the server, from the requested url Raises: _FetchWellKnownFailure if we fail to lookup a result Returns: The lookup result and cache period. """ had_valid_well_known = self._had_valid_well_known_cache.get( server_name, False) # We do this in two steps to differentiate between possibly transient # errors (e.g. can't connect to host, 503 response) and more permanent # errors (such as getting a 404 response). response, body = await self._make_well_known_request( server_name, retry=had_valid_well_known) try: if response.code != 200: raise Exception("Non-200 response %s" % (response.code, )) parsed_body = json_decoder.decode(body.decode("utf-8")) logger.info("Response from .well-known: %s", parsed_body) result = parsed_body["m.server"].encode("ascii") except defer.CancelledError: # Bail if we've been cancelled raise except Exception as e: logger.info("Error parsing well-known for %s: %s", server_name, e) raise _FetchWellKnownFailure(temporary=False) cache_period = _cache_period_from_headers( response.headers, time_now=self._reactor.seconds) if cache_period is None: cache_period = WELL_KNOWN_DEFAULT_CACHE_PERIOD # add some randomness to the TTL to avoid a stampeding herd every 24 hours # after startup cache_period *= random.uniform( 1 - WELL_KNOWN_DEFAULT_CACHE_PERIOD_JITTER, 1 + WELL_KNOWN_DEFAULT_CACHE_PERIOD_JITTER, ) else: cache_period = min(cache_period, WELL_KNOWN_MAX_CACHE_PERIOD) cache_period = max(cache_period, WELL_KNOWN_MIN_CACHE_PERIOD) # We got a success, mark as such in the cache self._had_valid_well_known_cache.set( server_name, bool(result), cache_period + WELL_KNOWN_REMEMBER_DOMAIN_HAD_VALID, ) return result, cache_period
def from_line(cls, line): user_id, jsn = line.split(" ", 1) access_token, ip, user_agent, device_id, last_seen = json_decoder.decode( jsn) return cls(user_id, access_token, ip, user_agent, device_id, last_seen)
def to_synapse_error(self) -> SynapseError: """Make a SynapseError based on an HTTPResponseException This is useful when a proxied request has failed, and we need to decide how to map the failure onto a matrix error to send back to the client. An attempt is made to parse the body of the http response as a matrix error. If that succeeds, the errcode and error message from the body are used as the errcode and error message in the new synapse error. Otherwise, the errcode is set to M_UNKNOWN, and the error message is set to the reason code from the HTTP response. Returns: SynapseError: """ # try to parse the body as json, to get better errcode/msg, but # default to M_UNKNOWN with the HTTP status as the error text try: j = json_decoder.decode(self.response.decode("utf-8")) except ValueError: j = {} if not isinstance(j, dict): j = {} errcode = j.pop("errcode", Codes.UNKNOWN) errmsg = j.pop("error", self.msg) return ProxiedRequestError(self.code, errmsg, errcode, j)
async def on_GET(self, request: SynapseRequest, room_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) pagination_config = await PaginationConfig.from_request( self.store, request, default_limit=10) # Twisted will have processed the args by now. assert request.args is not None as_client_event = b"raw" not in request.args filter_str = parse_string(request, "filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter: Optional[Filter] = Filter( self._hs, json_decoder.decode(filter_json)) if (event_filter and event_filter.filter_json.get( "event_format", "client") == "federation"): as_client_event = False else: event_filter = None msgs = await self.pagination_handler.get_messages( room_id=room_id, requester=requester, pagin_config=pagination_config, as_client_event=as_client_event, event_filter=event_filter, ) return 200, msgs
def span_context_from_string(carrier): """ Returns: The active span context decoded from a string. """ carrier = json_decoder.decode(carrier) return opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
def parse_json_value_from_request(request, allow_empty_body=False): """Parse a JSON value from the body of a twisted HTTP request. Args: request: the twisted HTTP request. allow_empty_body (bool): if True, an empty body will be accepted and turned into None Returns: The JSON value. Raises: SynapseError if the request body couldn't be decoded as JSON. """ try: content_bytes = request.content.read() except Exception: raise SynapseError(400, "Error reading JSON content.") if not content_bytes and allow_empty_body: return None try: content = json_decoder.decode(content_bytes.decode("utf-8")) except Exception as e: logger.warning("Unable to parse JSON: %s", e) raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON) return content
async def on_GET(self, request, room_id): requester = await self.auth.get_user_by_req(request, allow_guest=True) pagination_config = await PaginationConfig.from_request( self.store, request, default_limit=10) as_client_event = b"raw" not in request.args filter_str = parse_string(request, b"filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter = Filter( json_decoder.decode(filter_json)) # type: Optional[Filter] if (event_filter and event_filter.filter_json.get( "event_format", "client") == "federation"): as_client_event = False else: event_filter = None msgs = await self.pagination_handler.get_messages( room_id=room_id, requester=requester, pagin_config=pagination_config, as_client_event=as_client_event, event_filter=event_filter, ) return 200, msgs
async def on_GET(self, request: SynapseRequest, room_id: str, event_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=False) await assert_user_is_admin(self.auth, requester.user) limit = parse_integer(request, "limit", default=10) # picking the API shape for symmetry with /messages filter_str = parse_string(request, "filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter: Optional[Filter] = Filter( self._hs, json_decoder.decode(filter_json)) else: event_filter = None event_context = await self.room_context_handler.get_event_context( requester, room_id, event_id, limit, event_filter, use_admin_priviledge=True, ) if not event_context: raise SynapseError(HTTPStatus.NOT_FOUND, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() results = { "events_before": self._event_serializer.serialize_events( event_context.events_before, time_now, bundle_aggregations=event_context.aggregations, ), "event": self._event_serializer.serialize_event( event_context.event, time_now, bundle_aggregations=event_context.aggregations, ), "events_after": self._event_serializer.serialize_events( event_context.events_after, time_now, bundle_aggregations=event_context.aggregations, ), "state": self._event_serializer.serialize_events(event_context.state, time_now), "start": event_context.start, "end": event_context.end, } return HTTPStatus.OK, results
async def get(self, cache_name: str, key: str) -> Optional[Any]: """Look up a key/value in the named cache.""" if self._redis_connection is None: return None with opentracing.start_active_span( "ExternalCache.get", tags={opentracing.SynapseTags.CACHE_NAME: cache_name}, ): with response_timer.labels("get").time(): result = await make_deferred_yieldable( self._redis_connection.get( self._get_redis_key(cache_name, key))) logger.debug("Got cache result %s %s: %r", cache_name, key, result) get_counter.labels(cache_name, result is not None).inc() if not result: return None # For some reason the integers get magically converted back to integers if isinstance(result, int): return result return json_decoder.decode(result)
def db_to_json(db_content: Union[memoryview, bytes, bytearray, str]) -> Any: """ Take some data from a database row and return a JSON-decoded object. Args: db_content: The JSON-encoded contents from the database. Returns: The object decoded from JSON. """ # psycopg2 on Python 3 returns memoryview objects, which we need to # cast to bytes to decode if isinstance(db_content, memoryview): db_content = db_content.tobytes() # Decode it to a Unicode string before feeding it to the JSON decoder, since # Python 3.5 does not support deserializing bytes. if isinstance(db_content, (bytes, bytearray)): db_content = db_content.decode("utf8") try: return json_decoder.decode(db_content) except Exception: logging.warning("Tried to decode '%r' as JSON and failed", db_content) raise
async def on_GET(self, request, room_id, event_id): requester = await self.auth.get_user_by_req(request, allow_guest=True) limit = parse_integer(request, "limit", default=10) # picking the API shape for symmetry with /messages filter_str = parse_string(request, b"filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter = Filter( json_decoder.decode(filter_json)) # type: Optional[Filter] else: event_filter = None results = await self.room_context_handler.get_event_context( requester.user, room_id, event_id, limit, event_filter) if not results: raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() results[ "events_before"] = await self._event_serializer.serialize_events( results["events_before"], time_now) results["event"] = await self._event_serializer.serialize_event( results["event"], time_now) results[ "events_after"] = await self._event_serializer.serialize_events( results["events_after"], time_now) results["state"] = await self._event_serializer.serialize_events( results["state"], time_now) return 200, results
def from_line(cls, line): stream_name, instance_name, token, row_json = line.split(" ", 3) return cls( stream_name, instance_name, None if token == "batch" else int(token), json_decoder.decode(row_json), )
def from_line(cls: Type["RdataCommand"], line: str) -> "RdataCommand": stream_name, instance_name, token, row_json = line.split(" ", 3) return cls( stream_name, instance_name, None if token == "batch" else int(token), json_decoder.decode(row_json), )
def span_context_from_string( carrier: str) -> Optional["opentracing.SpanContext"]: """ Returns: The active span context decoded from a string. """ payload: Dict[str, str] = json_decoder.decode(carrier) return opentracing.tracer.extract(opentracing.Format.TEXT_MAP, payload)
async def post_urlencoded_get_json( self, uri: str, args: Mapping[str, Union[str, List[str]]] = {}, headers: Optional[RawHeaders] = None, ) -> Any: """ Args: uri: uri to query args: parameters to be url-encoded in the body headers: a map from header name to a list of values for that header Returns: parsed json Raises: RequestTimedOutError: if there is a timeout before the response headers are received. Note there is currently no timeout on reading the response body. HttpResponseException: On a non-2xx HTTP response. ValueError: if the response was not JSON """ # TODO: Do we ever want to log message contents? logger.debug("post_urlencoded_get_json args: %s", args) query_bytes = urllib.parse.urlencode(encode_urlencode_args(args), True).encode("utf8") actual_headers = { b"Content-Type": [b"application/x-www-form-urlencoded"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) response = await self.request("POST", uri, headers=Headers(actual_headers), data=query_bytes) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body)
async def put_json( self, uri: str, json_body: Any, args: Optional[QueryParams] = None, headers: RawHeaders = None, ) -> Any: """Puts some json to the given URI. Args: uri: The URI to request, not including query parameters json_body: The JSON to put in the HTTP body, args: A dictionary used to create query strings headers: a map from header name to a list of values for that header Returns: Succeeds when we get a 2xx HTTP response, with the HTTP body as JSON. Raises: RequestTimedOutError: if there is a timeout before the response headers are received. Note there is currently no timeout on reading the response body. HttpResponseException On a non-2xx HTTP response. ValueError: if the response was not JSON """ if args: query_str = urllib.parse.urlencode(args, True) uri = "%s?%s" % (uri, query_str) json_str = encode_canonical_json(json_body) actual_headers = { b"Content-Type": [b"application/json"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) # type: ignore response = await self.request("PUT", uri, headers=Headers(actual_headers), data=json_str) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body)
def start_active_span_from_edu( edu_content, operation_name, references: Optional[list] = None, tags=None, start_time=None, ignore_active_span=False, finish_on_close=True, ): """ Extracts a span context from an edu and uses it to start a new active span Args: edu_content (dict): and edu_content with a `context` field whose value is canonical json for a dict which contains opentracing information. For the other args see opentracing.tracer """ references = references or [] if opentracing is None: return noop_context_manager() # type: ignore[unreachable] carrier = json_decoder.decode(edu_content.get("context", "{}")).get( "opentracing", {} ) context = opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier) _references = [ opentracing.child_of(span_context_from_string(x)) for x in carrier.get("references", []) ] # For some reason jaeger decided not to support the visualization of multiple parent # spans or explicitly show references. I include the span context as a tag here as # an aid to people debugging but it's really not an ideal solution. references += _references scope = opentracing.tracer.start_active_span( operation_name, child_of=context, references=references, tags=tags, start_time=start_time, ignore_active_span=ignore_active_span, finish_on_close=finish_on_close, ) scope.span.set_tag("references", carrier.get("references", [])) return scope
async def post_json_get_json(self, uri: str, post_json: Any, headers: Optional[RawHeaders] = None) -> Any: """ Args: uri: URI to query. post_json: request body, to be encoded as json headers: a map from header name to a list of values for that header Returns: parsed json Raises: RequestTimedOutError: if there is a timeout before the response headers are received. Note there is currently no timeout on reading the response body. HttpResponseException: On a non-2xx HTTP response. ValueError: if the response was not JSON """ json_str = encode_canonical_json(post_json) logger.debug("HTTP POST %s -> %s", json_str, uri) actual_headers = { b"Content-Type": [b"application/json"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) # type: ignore response = await self.request("POST", uri, headers=Headers(actual_headers), data=json_str) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body)
async def on_GET(self, request: SynapseRequest, room_id: str, event_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=False) await assert_user_is_admin(self.auth, requester.user) limit = parse_integer(request, "limit", default=10) # picking the API shape for symmetry with /messages filter_str = parse_string(request, "filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter = Filter( json_decoder.decode(filter_json)) # type: Optional[Filter] else: event_filter = None results = await self.room_context_handler.get_event_context( requester, room_id, event_id, limit, event_filter, use_admin_priviledge=True, ) if not results: raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() results[ "events_before"] = await self._event_serializer.serialize_events( results["events_before"], time_now) results["event"] = await self._event_serializer.serialize_event( results["event"], time_now) results[ "events_after"] = await self._event_serializer.serialize_events( results["events_after"], time_now) results["state"] = await self._event_serializer.serialize_events( results["state"], time_now, # No need to bundle aggregations for state events bundle_aggregations=False, ) return 200, results
async def put_json(self, uri, json_body, args={}, headers=None): """ Puts some json to the given URI. Args: uri (str): The URI to request, not including query parameters json_body (dict): The JSON to put in the HTTP body, args (dict): A dictionary used to create query strings, defaults to None. **Note**: The value of each key is assumed to be an iterable and *not* a string. headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from header name to a list of values for that header Returns: Succeeds when we get *any* 2xx HTTP response, with the HTTP body as JSON. Raises: HttpResponseException On a non-2xx HTTP response. ValueError: if the response was not JSON """ if len(args): query_bytes = urllib.parse.urlencode(args, True) uri = "%s?%s" % (uri, query_bytes) json_str = encode_canonical_json(json_body) actual_headers = { b"Content-Type": [b"application/json"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) response = await self.request( "PUT", uri, headers=Headers(actual_headers), data=json_str ) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body )
async def check_auth(self, authdict: dict, clientip: str) -> Any: try: user_response = authdict["response"] except KeyError: # Client tried to provide captcha but didn't give the parameter: # bad request. raise LoginError(400, "Captcha response is required", errcode=Codes.CAPTCHA_NEEDED) logger.info("Submitting recaptcha response %s with remoteip %s", user_response, clientip) # TODO: get this from the homeserver rather than creating a new one for # each request try: assert self._secret is not None resp_body = await self._http_client.post_urlencoded_get_json( self._url, args={ "secret": self._secret, "response": user_response, "remoteip": clientip, }, ) except PartialDownloadError as pde: # Twisted is silly data = pde.response resp_body = json_decoder.decode(data.decode("utf-8")) if "success" in resp_body: # Note that we do NOT check the hostname here: we explicitly # intend the CAPTCHA to be presented by whatever client the # user is using, we just care that they have completed a CAPTCHA. logger.info( "%s reCAPTCHA from hostname %s", "Successful" if resp_body["success"] else "Failed", resp_body.get("hostname"), ) if resp_body["success"]: return True raise LoginError(401, "Captcha authentication failed", errcode=Codes.UNAUTHORIZED)
async def post_urlencoded_get_json(self, uri, args={}, headers=None): """ Args: uri (str): args (dict[str, str|List[str]]): query params headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from header name to a list of values for that header Returns: object: parsed json Raises: HttpResponseException: On a non-2xx HTTP response. ValueError: if the response was not JSON """ # TODO: Do we ever want to log message contents? logger.debug("post_urlencoded_get_json args: %s", args) query_bytes = urllib.parse.urlencode(encode_urlencode_args(args), True).encode( "utf8" ) actual_headers = { b"Content-Type": [b"application/x-www-form-urlencoded"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) response = await self.request( "POST", uri, headers=Headers(actual_headers), data=query_bytes ) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body )
async def post_json_get_json(self, uri, post_json, headers=None): """ Args: uri (str): post_json (object): headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from header name to a list of values for that header Returns: object: parsed json Raises: HttpResponseException: On a non-2xx HTTP response. ValueError: if the response was not JSON """ json_str = encode_canonical_json(post_json) logger.debug("HTTP POST %s -> %s", json_str, uri) actual_headers = { b"Content-Type": [b"application/json"], b"User-Agent": [self.user_agent], b"Accept": [b"application/json"], } if headers: actual_headers.update(headers) response = await self.request( "POST", uri, headers=Headers(actual_headers), data=json_str ) body = await make_deferred_yieldable(readBody(response)) if 200 <= response.code < 300: return json_decoder.decode(body.decode("utf-8")) else: raise HttpResponseException( response.code, response.phrase.decode("ascii", errors="replace"), body )
async def get(self, cache_name: str, key: str) -> Optional[Any]: """Look up a key/value in the named cache.""" if self._redis_connection is None: return None result = await make_deferred_yieldable( self._redis_connection.get(self._get_redis_key(cache_name, key))) logger.debug("Got cache result %s %s: %r", cache_name, key, result) get_counter.labels(cache_name, result is not None).inc() if not result: return None # For some reason the integers get magically converted back to integers if isinstance(result, int): return result return json_decoder.decode(result)
async def send_new_transaction( self, destination: str, pdus: List[EventBase], edus: List[Edu], ) -> None: """ Args: destination: The destination to send to (e.g. 'example.org') pdus: In-order list of PDUs to send edus: List of EDUs to send """ # Make a transaction-sending opentracing span. This span follows on from # all the edus in that transaction. This needs to be done since there is # no active span here, so if the edus were not received by the remote the # span would have no causality and it would be forgotten. span_contexts = [] keep_destination = whitelisted_homeserver(destination) for edu in edus: context = edu.get_context() if context: span_contexts.append( extract_text_map(json_decoder.decode(context))) if keep_destination: edu.strip_context() with start_active_span_follows_from("send_transaction", span_contexts): logger.debug("TX [%s] _attempt_new_transaction", destination) txn_id = str(self._next_txn_id) logger.debug( "TX [%s] {%s} Attempting new transaction (pdus: %d, edus: %d)", destination, txn_id, len(pdus), len(edus), ) transaction = Transaction.create_new( origin_server_ts=int(self.clock.time_msec()), transaction_id=txn_id, origin=self._server_name, destination=destination, pdus=pdus, edus=edus, ) self._next_txn_id += 1 logger.info( "TX [%s] {%s} Sending transaction [%s], (PDUs: %d, EDUs: %d)", destination, txn_id, transaction.transaction_id, len(pdus), len(edus), ) # Actually send the transaction # FIXME (erikj): This is a bit of a hack to make the Pdu age # keys work # FIXME (richardv): I also believe it no longer works. We (now?) store # "age_ts" in "unsigned" rather than at the top level. See # https://github.com/matrix-org/synapse/issues/8429. def json_data_cb(): data = transaction.get_dict() now = int(self.clock.time_msec()) if "pdus" in data: for p in data["pdus"]: if "age_ts" in p: unsigned = p.setdefault("unsigned", {}) unsigned["age"] = now - int(p["age_ts"]) del p["age_ts"] return data try: response = await self._transport_layer.send_transaction( transaction, json_data_cb) except HttpResponseException as e: code = e.code response = e.response set_tag(tags.ERROR, True) logger.info("TX [%s] {%s} got %d response", destination, txn_id, code) raise logger.info("TX [%s] {%s} got 200 response", destination, txn_id) for e_id, r in response.get("pdus", {}).items(): if "error" in r: logger.warning( "TX [%s] {%s} Remote returned error for %s: %s", destination, txn_id, e_id, r, ) if pdus and destination in self._federation_metrics_domains: last_pdu = pdus[-1] last_pdu_ts_metric.labels(server_name=destination).set( last_pdu.origin_server_ts / 1000)
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: # This will always be set by the time Twisted calls us. assert request.args is not None if b"from" in request.args: # /events used to use 'from', but /sync uses 'since'. # Lets be helpful and whine if we see a 'from'. raise SynapseError( 400, "'from' is not a valid query parameter. Did you mean 'since'?" ) requester = await self.auth.get_user_by_req(request, allow_guest=True) user = requester.user device_id = requester.device_id timeout = parse_integer(request, "timeout", default=0) since = parse_string(request, "since") set_presence = parse_string( request, "set_presence", default="online", allowed_values=self.ALLOWED_PRESENCE, ) filter_id = parse_string(request, "filter") full_state = parse_boolean(request, "full_state", default=False) logger.debug( "/sync: user=%r, timeout=%r, since=%r, " "set_presence=%r, filter_id=%r, device_id=%r", user, timeout, since, set_presence, filter_id, device_id, ) request_key = (user, timeout, since, filter_id, full_state, device_id) if filter_id is None: filter_collection = DEFAULT_FILTER_COLLECTION elif filter_id.startswith("{"): try: filter_object = json_decoder.decode(filter_id) set_timeline_upper_limit( filter_object, self.hs.config.server.filter_timeline_limit ) except Exception: raise SynapseError(400, "Invalid filter JSON") self.filtering.check_valid_filter(filter_object) filter_collection = FilterCollection(filter_object) else: try: filter_collection = await self.filtering.get_user_filter( user.localpart, filter_id ) except StoreError as err: if err.code != 404: raise # fix up the description and errcode to be more useful raise SynapseError(400, "No such filter", errcode=Codes.INVALID_PARAM) sync_config = SyncConfig( user=user, filter_collection=filter_collection, is_guest=requester.is_guest, request_key=request_key, device_id=device_id, ) since_token = None if since is not None: since_token = await StreamToken.from_string(self.store, since) # send any outstanding server notices to the user. await self._server_notices_sender.on_user_syncing(user.to_string()) affect_presence = set_presence != PresenceState.OFFLINE if affect_presence: await self.presence_handler.set_state( user, {"presence": set_presence}, True ) context = await self.presence_handler.user_syncing( user.to_string(), affect_presence=affect_presence ) with context: sync_result = await self.sync_handler.wait_for_sync_for_user( requester, sync_config, since_token=since_token, timeout=timeout, full_state=full_state, ) # the client may have disconnected by now; don't bother to serialize the # response if so. if request._disconnected: logger.info("Client has disconnected; not serializing response.") return 200, {} time_now = self.clock.time_msec() # We know that the the requester has an access token since appservices # cannot use sync. response_content = await self.encode_response( time_now, sync_result, requester.access_token_id, filter_collection ) logger.debug("Event formatting complete") return 200, response_content