17
17
import logging
18
18
import urllib
19
19
from 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
+ )
20
32
21
33
import treq
22
34
from canonicaljson import encode_canonical_json
37
49
from twisted .web .client import Agent , HTTPConnectionPool , readBody
38
50
from twisted .web .http import PotentialDataLoss
39
51
from twisted .web .http_headers import Headers
52
+ from twisted .web .iweb import IResponse
40
53
41
54
from synapse .api .errors import Codes , HttpResponseException , SynapseError
42
55
from synapse .http import (
57
70
"synapse_http_client_responses" , "" , ["method" , "code" ]
58
71
)
59
72
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
+
60
86
61
87
def check_against_blacklist (ip_address , ip_whitelist , ip_blacklist ):
62
88
"""
@@ -285,13 +311,26 @@ def __getattr__(_self, attr):
285
311
ip_blacklist = self ._ip_blacklist ,
286
312
)
287
313
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 :
289
321
"""
290
322
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
+
295
334
"""
296
335
# A small wrapper around self.agent.request() so we can easily attach
297
336
# counters to it
@@ -324,6 +363,8 @@ async def request(self, method, uri, data=None, headers=None):
324
363
headers = headers ,
325
364
** self ._extra_treq_args
326
365
)
366
+ # we use our own timeout mechanism rather than treq's as a workaround
367
+ # for https://twistedmatrix.com/trac/ticket/9534.
327
368
request_deferred = timeout_deferred (
328
369
request_deferred ,
329
370
60 ,
@@ -353,18 +394,26 @@ async def request(self, method, uri, data=None, headers=None):
353
394
set_tag ("error_reason" , e .args [0 ])
354
395
raise
355
396
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 :
357
403
"""
358
404
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
363
408
364
409
Returns:
365
- object: parsed json
410
+ parsed json
366
411
367
412
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
+
368
417
HttpResponseException: On a non-2xx HTTP response.
369
418
370
419
ValueError: if the response was not JSON
@@ -398,19 +447,24 @@ async def post_urlencoded_get_json(self, uri, args={}, headers=None):
398
447
response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
399
448
)
400
449
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 :
402
453
"""
403
454
404
455
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
409
459
410
460
Returns:
411
- object: parsed json
461
+ parsed json
412
462
413
463
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
+
414
468
HttpResponseException: On a non-2xx HTTP response.
415
469
416
470
ValueError: if the response was not JSON
@@ -440,21 +494,22 @@ async def post_json_get_json(self, uri, post_json, headers=None):
440
494
response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
441
495
)
442
496
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.
445
501
446
502
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
454
506
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.
457
508
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
+
458
513
HttpResponseException On a non-2xx HTTP response.
459
514
460
515
ValueError: if the response was not JSON
@@ -466,22 +521,27 @@ async def get_json(self, uri, args={}, headers=None):
466
521
body = await self .get_raw (uri , args , headers = headers )
467
522
return json_decoder .decode (body .decode ("utf-8" ))
468
523
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.
471
532
472
533
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
481
538
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.
484
540
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
+
485
545
HttpResponseException On a non-2xx HTTP response.
486
546
487
547
ValueError: if the response was not JSON
@@ -513,21 +573,23 @@ async def put_json(self, uri, json_body, args={}, headers=None):
513
573
response .code , response .phrase .decode ("ascii" , errors = "replace" ), body
514
574
)
515
575
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.
518
580
519
581
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
527
585
Returns:
528
- Succeeds when we get *any* 2xx HTTP response, with the
586
+ Succeeds when we get a 2xx HTTP response, with the
529
587
HTTP body as bytes.
530
588
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
+
531
593
HttpResponseException on a non-2xx HTTP response.
532
594
"""
533
595
if len (args ):
@@ -552,16 +614,29 @@ async def get_raw(self, uri, args={}, headers=None):
552
614
# XXX: FIXME: This is horribly copy-pasted from matrixfederationclient.
553
615
# The two should be factored out.
554
616
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 ]:
556
624
"""GETs a file from a given URL
557
625
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
562
629
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
564
631
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.
565
640
"""
566
641
567
642
actual_headers = {b"User-Agent" : [self .user_agent ]}
0 commit comments