1717import logging
1818import urllib
1919from io import BytesIO
20+ from typing import (
21+ Any ,
22+ BinaryIO ,
23+ Dict ,
24+ Iterable ,
25+ List ,
26+ Mapping ,
27+ Optional ,
28+ Sequence ,
29+ Tuple ,
30+ Union ,
31+ )
2032
2133import treq
2234from canonicaljson import encode_canonical_json
3749from twisted .web .client import Agent , HTTPConnectionPool , readBody
3850from twisted .web .http import PotentialDataLoss
3951from twisted .web .http_headers import Headers
52+ from twisted .web .iweb import IResponse
4053
4154from synapse .api .errors import Codes , HttpResponseException , SynapseError
4255from synapse .http import (
5770 "synapse_http_client_responses" , "" , ["method" , "code" ]
5871)
5972
73+ # the type of the headers list, to be passed to the t.w.h.Headers.
74+ # Actually we can mix str and bytes keys, but Mapping treats 'key' as invariant so
75+ # we simplify.
76+ RawHeaders = Union [Mapping [str , "RawHeaderValue" ], Mapping [bytes , "RawHeaderValue" ]]
77+
78+ # the value actually has to be a List, but List is invariant so we can't specify that
79+ # the entries can either be Lists or bytes.
80+ RawHeaderValue = Sequence [Union [str , bytes ]]
81+
82+ # the type of the query params, to be passed into `urlencode`
83+ QueryParamValue = Union [str , bytes , Iterable [Union [str , bytes ]]]
84+ QueryParams = Union [Mapping [str , QueryParamValue ], Mapping [bytes , QueryParamValue ]]
85+
6086
6187def check_against_blacklist (ip_address , ip_whitelist , ip_blacklist ):
6288 """
@@ -285,13 +311,26 @@ def __getattr__(_self, attr):
285311 ip_blacklist = self ._ip_blacklist ,
286312 )
287313
288- async def request (self , method , uri , data = None , headers = None ):
314+ async def request (
315+ self ,
316+ method : str ,
317+ uri : str ,
318+ data : Optional [bytes ] = None ,
319+ headers : Optional [Headers ] = None ,
320+ ) -> IResponse :
289321 """
290322 Args:
291- method (str): HTTP method to use.
292- uri (str): URI to query.
293- data (bytes): Data to send in the request body, if applicable.
294- headers (t.w.http_headers.Headers): Request headers.
323+ method: HTTP method to use.
324+ uri: URI to query.
325+ data: Data to send in the request body, if applicable.
326+ headers: Request headers.
327+
328+ Returns:
329+ Response object, once the headers have been read.
330+
331+ Raises:
332+ RequestTimedOutError if the request times out before the headers are read
333+
295334 """
296335 # A small wrapper around self.agent.request() so we can easily attach
297336 # counters to it
@@ -324,6 +363,8 @@ async def request(self, method, uri, data=None, headers=None):
324363 headers = headers ,
325364 ** self ._extra_treq_args
326365 )
366+ # we use our own timeout mechanism rather than treq's as a workaround
367+ # for https://twistedmatrix.com/trac/ticket/9534.
327368 request_deferred = timeout_deferred (
328369 request_deferred ,
329370 60 ,
@@ -353,18 +394,26 @@ async def request(self, method, uri, data=None, headers=None):
353394 set_tag ("error_reason" , e .args [0 ])
354395 raise
355396
356- async def post_urlencoded_get_json (self , uri , args = {}, headers = None ):
397+ async def post_urlencoded_get_json (
398+ self ,
399+ uri : str ,
400+ args : Mapping [str , Union [str , List [str ]]] = {},
401+ headers : Optional [RawHeaders ] = None ,
402+ ) -> Any :
357403 """
358404 Args:
359- uri (str):
360- args (dict[str, str|List[str]]): query params
361- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
362- header name to a list of values for that header
405+ uri: uri to query
406+ args: parameters to be url-encoded in the body
407+ headers: a map from header name to a list of values for that header
363408
364409 Returns:
365- object: parsed json
410+ parsed json
366411
367412 Raises:
413+ RequestTimedOutException: if there is a timeout before the response headers
414+ are received. Note there is currently no timeout on reading the response
415+ body.
416+
368417 HttpResponseException: On a non-2xx HTTP response.
369418
370419 ValueError: if the response was not JSON
@@ -398,19 +447,24 @@ async def post_urlencoded_get_json(self, uri, args={}, headers=None):
398447 response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
399448 )
400449
401- async def post_json_get_json (self , uri , post_json , headers = None ):
450+ async def post_json_get_json (
451+ self , uri : str , post_json : Any , headers : Optional [RawHeaders ] = None
452+ ) -> Any :
402453 """
403454
404455 Args:
405- uri (str):
406- post_json (object):
407- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
408- header name to a list of values for that header
456+ uri: URI to query.
457+ post_json: request body, to be encoded as json
458+ headers: a map from header name to a list of values for that header
409459
410460 Returns:
411- object: parsed json
461+ parsed json
412462
413463 Raises:
464+ RequestTimedOutException: if there is a timeout before the response headers
465+ are received. Note there is currently no timeout on reading the response
466+ body.
467+
414468 HttpResponseException: On a non-2xx HTTP response.
415469
416470 ValueError: if the response was not JSON
@@ -440,21 +494,22 @@ async def post_json_get_json(self, uri, post_json, headers=None):
440494 response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
441495 )
442496
443- async def get_json (self , uri , args = {}, headers = None ):
444- """ Gets some json from the given URI.
497+ async def get_json (
498+ self , uri : str , args : QueryParams = {}, headers : Optional [RawHeaders ] = None ,
499+ ) -> Any :
500+ """Gets some json from the given URI.
445501
446502 Args:
447- uri (str): The URI to request, not including query parameters
448- args (dict): A dictionary used to create query strings, defaults to
449- None.
450- **Note**: The value of each key is assumed to be an iterable
451- and *not* a string.
452- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
453- header name to a list of values for that header
503+ uri: The URI to request, not including query parameters
504+ args: A dictionary used to create query string
505+ headers: a map from header name to a list of values for that header
454506 Returns:
455- Succeeds when we get *any* 2xx HTTP response, with the
456- HTTP body as JSON.
507+ Succeeds when we get a 2xx HTTP response, with the HTTP body as JSON.
457508 Raises:
509+ RequestTimedOutException: if there is a timeout before the response headers
510+ are received. Note there is currently no timeout on reading the response
511+ body.
512+
458513 HttpResponseException On a non-2xx HTTP response.
459514
460515 ValueError: if the response was not JSON
@@ -466,22 +521,27 @@ async def get_json(self, uri, args={}, headers=None):
466521 body = await self .get_raw (uri , args , headers = headers )
467522 return json_decoder .decode (body .decode ("utf-8" ))
468523
469- async def put_json (self , uri , json_body , args = {}, headers = None ):
470- """ Puts some json to the given URI.
524+ async def put_json (
525+ self ,
526+ uri : str ,
527+ json_body : Any ,
528+ args : QueryParams = {},
529+ headers : RawHeaders = None ,
530+ ) -> Any :
531+ """Puts some json to the given URI.
471532
472533 Args:
473- uri (str): The URI to request, not including query parameters
474- json_body (dict): The JSON to put in the HTTP body,
475- args (dict): A dictionary used to create query strings, defaults to
476- None.
477- **Note**: The value of each key is assumed to be an iterable
478- and *not* a string.
479- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
480- header name to a list of values for that header
534+ uri: The URI to request, not including query parameters
535+ json_body: The JSON to put in the HTTP body,
536+ args: A dictionary used to create query strings
537+ headers: a map from header name to a list of values for that header
481538 Returns:
482- Succeeds when we get *any* 2xx HTTP response, with the
483- HTTP body as JSON.
539+ Succeeds when we get a 2xx HTTP response, with the HTTP body as JSON.
484540 Raises:
541+ RequestTimedOutException: if there is a timeout before the response headers
542+ are received. Note there is currently no timeout on reading the response
543+ body.
544+
485545 HttpResponseException On a non-2xx HTTP response.
486546
487547 ValueError: if the response was not JSON
@@ -513,21 +573,23 @@ async def put_json(self, uri, json_body, args={}, headers=None):
513573 response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
514574 )
515575
516- async def get_raw (self , uri , args = {}, headers = None ):
517- """ Gets raw text from the given URI.
576+ async def get_raw (
577+ self , uri : str , args : QueryParams = {}, headers : Optional [RawHeaders ] = None
578+ ) -> bytes :
579+ """Gets raw text from the given URI.
518580
519581 Args:
520- uri (str): The URI to request, not including query parameters
521- args (dict): A dictionary used to create query strings, defaults to
522- None.
523- **Note**: The value of each key is assumed to be an iterable
524- and *not* a string.
525- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
526- header name to a list of values for that header
582+ uri: The URI to request, not including query parameters
583+ args: A dictionary used to create query strings
584+ headers: a map from header name to a list of values for that header
527585 Returns:
528- Succeeds when we get *any* 2xx HTTP response, with the
586+ Succeeds when we get a 2xx HTTP response, with the
529587 HTTP body as bytes.
530588 Raises:
589+ RequestTimedOutException: if there is a timeout before the response headers
590+ are received. Note there is currently no timeout on reading the response
591+ body.
592+
531593 HttpResponseException on a non-2xx HTTP response.
532594 """
533595 if len (args ):
@@ -552,16 +614,29 @@ async def get_raw(self, uri, args={}, headers=None):
552614 # XXX: FIXME: This is horribly copy-pasted from matrixfederationclient.
553615 # The two should be factored out.
554616
555- async def get_file (self , url , output_stream , max_size = None , headers = None ):
617+ async def get_file (
618+ self ,
619+ url : str ,
620+ output_stream : BinaryIO ,
621+ max_size : Optional [int ] = None ,
622+ headers : Optional [RawHeaders ] = None ,
623+ ) -> Tuple [int , Dict [bytes , List [bytes ]], str , int ]:
556624 """GETs a file from a given URL
557625 Args:
558- url (str): The URL to GET
559- output_stream (file): File to write the response body to.
560- headers (dict[str|bytes, List[str|bytes]]|None): If not None, a map from
561- header name to a list of values for that header
626+ url: The URL to GET
627+ output_stream: File to write the response body to.
628+ headers: A map from header name to a list of values for that header
562629 Returns:
563- A (int,dict,string,int) tuple of the file length, dict of the response
630+ A tuple of the file length, dict of the response
564631 headers, absolute URI of the response and HTTP response code.
632+
633+ Raises:
634+ RequestTimedOutException: if there is a timeout before the response headers
635+ are received. Note there is currently no timeout on reading the response
636+ body.
637+
638+ SynapseError: if the response is not a 2xx, the remote file is too large, or
639+ another exception happens during the download.
565640 """
566641
567642 actual_headers = {b"User-Agent" : [self .user_agent ]}
0 commit comments