AX: AXIsolatedTree::updateChildren sometimes fails to update isolated subtrees when...
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / webdriver / webdriver / transport.py
1 import json
2 import select
3
4 from http.client import HTTPConnection
5 from typing import Dict, List, Mapping, Sequence, Tuple
6 from urllib import parse as urlparse
7
8 from . import error
9
10 """Implements HTTP transport for the WebDriver wire protocol."""
11
12
13 missing = object()
14
15
16 class ResponseHeaders(Mapping[str, str]):
17     """Read-only dictionary-like API for accessing response headers.
18
19     This class:
20       * Normalizes the header keys it is built with to lowercase (such that
21         iterating the items will return lowercase header keys).
22       * Has case-insensitive header lookup.
23       * Always returns all header values that have the same name, separated by
24         commas.
25     """
26     def __init__(self, items: Sequence[Tuple[str, str]]):
27         self.headers_dict: Dict[str, List[str]] = {}
28         for key, value in items:
29             key = key.lower()
30             if key not in self.headers_dict:
31                 self.headers_dict[key] = []
32             self.headers_dict[key].append(value)
33
34     def __getitem__(self, key):
35         """Get all headers of a certain (case-insensitive) name. If there is
36         more than one, the values are returned comma separated"""
37         values = self.headers_dict[key.lower()]
38         if len(values) == 1:
39             return values[0]
40         else:
41             return ", ".join(values)
42
43     def get_list(self, key, default=missing):
44         """Get all the header values for a particular field name as a list"""
45         try:
46             return self.headers_dict[key.lower()]
47         except KeyError:
48             if default is not missing:
49                 return default
50             else:
51                 raise
52
53     def __iter__(self):
54         for item in self.headers_dict:
55             yield item
56
57     def __len__(self):
58         return len(self.headers_dict)
59
60
61 class Response(object):
62     """
63     Describes an HTTP response received from a remote end whose
64     body has been read and parsed as appropriate.
65     """
66
67     def __init__(self, status, body, headers):
68         self.status = status
69         self.body = body
70         self.headers = headers
71
72     def __repr__(self):
73         cls_name = self.__class__.__name__
74         if self.error:
75             return "<%s status=%s error=%s>" % (cls_name, self.status, repr(self.error))
76         return "<% status=%s body=%s>" % (cls_name, self.status, json.dumps(self.body))
77
78     def __str__(self):
79         return json.dumps(self.body, indent=2)
80
81     @property
82     def error(self):
83         if self.status != 200:
84             return error.from_response(self)
85         return None
86
87     @classmethod
88     def from_http(cls, http_response, decoder=json.JSONDecoder, **kwargs):
89         try:
90             body = json.load(http_response, cls=decoder, **kwargs)
91             headers = ResponseHeaders(http_response.getheaders())
92         except ValueError:
93             raise ValueError("Failed to decode response body as JSON:\n" +
94                 http_response.read())
95
96         return cls(http_response.status, body, headers)
97
98
99 class HTTPWireProtocol(object):
100     """
101     Transports messages (commands and responses) over the WebDriver
102     wire protocol.
103
104     Complex objects, such as ``webdriver.Element``, ``webdriver.Frame``,
105     and ``webdriver.Window`` are by default not marshaled to enable
106     use of `session.transport.send` in WPT tests::
107
108         session = webdriver.Session("127.0.0.1", 4444)
109         response = transport.send("GET", "element/active", None)
110         print response.body["value"]
111         # => {u'element-6066-11e4-a52e-4f735466cecf': u'<uuid>'}
112
113     Automatic marshaling is provided by ``webdriver.protocol.Encoder``
114     and ``webdriver.protocol.Decoder``, which can be passed in to
115     ``HTTPWireProtocol.send`` along with a reference to the current
116     ``webdriver.Session``::
117
118         session = webdriver.Session("127.0.0.1", 4444)
119         response = transport.send("GET", "element/active", None,
120             encoder=protocol.Encoder, decoder=protocol.Decoder,
121             session=session)
122         print response.body["value"]
123         # => webdriver.Element
124     """
125
126     def __init__(self, host, port, url_prefix="/"):
127         """
128         Construct interface for communicating with the remote server.
129
130         :param url: URL of remote WebDriver server.
131         :param wait: Duration to wait for remote to appear.
132         """
133         self.host = host
134         self.port = port
135         self.url_prefix = url_prefix
136         self._conn = None
137         self._last_request_is_blocked = False
138
139     def __del__(self):
140         self.close()
141
142     def close(self):
143         """Closes the current HTTP connection, if there is one."""
144         if self._conn:
145             try:
146                 self._conn.close()
147             except OSError:
148                 # The remote closed the connection
149                 pass
150         self._conn = None
151
152     @property
153     def connection(self):
154         """Gets the current HTTP connection, or lazily creates one."""
155         if not self._conn:
156             conn_kwargs = {}
157             # We are not setting an HTTP timeout other than the default when the
158             # connection its created. The send method has a timeout value if needed.
159             self._conn = HTTPConnection(self.host, self.port, **conn_kwargs)
160
161         return self._conn
162
163     def url(self, suffix):
164         """
165         From the relative path to a command end-point,
166         craft a full URL suitable to be used in a request to the HTTPD.
167         """
168         return urlparse.urljoin(self.url_prefix, suffix)
169
170     def send(self,
171              method,
172              uri,
173              body=None,
174              headers=None,
175              encoder=json.JSONEncoder,
176              decoder=json.JSONDecoder,
177              timeout=None,
178              **codec_kwargs):
179         """
180         Send a command to the remote.
181
182         The request `body` must be JSON serialisable unless a
183         custom `encoder` has been provided.  This means complex
184         objects such as ``webdriver.Element``, ``webdriver.Frame``,
185         and `webdriver.Window`` are not automatically made
186         into JSON.  This behaviour is, however, provided by
187         ``webdriver.protocol.Encoder``, should you want it.
188
189         Similarly, the response body is returned au natural
190         as plain JSON unless a `decoder` that converts web
191         element references to ``webdriver.Element`` is provided.
192         Use ``webdriver.protocol.Decoder`` to achieve this behaviour.
193
194         The client will attempt to use persistent HTTP connections.
195
196         :param method: `GET`, `POST`, or `DELETE`.
197         :param uri: Relative endpoint of the requests URL path.
198         :param body: Body of the request.  Defaults to an empty
199             dictionary if ``method`` is `POST`.
200         :param headers: Additional dictionary of headers to include
201             in the request.
202         :param encoder: JSON encoder class, which defaults to
203             ``json.JSONEncoder`` unless specified.
204         :param decoder: JSON decoder class, which defaults to
205             ``json.JSONDecoder`` unless specified.
206         :param codec_kwargs: Surplus arguments passed on to `encoder`
207             and `decoder` on construction.
208
209         :return: Instance of ``webdriver.transport.Response``
210             describing the HTTP response received from the remote end.
211
212         :raises ValueError: If `body` or the response body are not
213             JSON serialisable.
214         """
215         if body is None and method == "POST":
216             body = {}
217
218         payload = None
219         if body is not None:
220             try:
221                 payload = json.dumps(body, cls=encoder, **codec_kwargs)
222             except ValueError:
223                 raise ValueError("Failed to encode request body as JSON:\n"
224                     "%s" % json.dumps(body, indent=2))
225
226         # When the timeout triggers, the TestRunnerManager thread will reuse
227         # this connection to check if the WebDriver its alive and we may end
228         # raising an httplib.CannotSendRequest exception if the WebDriver is
229         # not responding and this httplib.request() call is blocked on the
230         # runner thread. We use the boolean below to check for that and restart
231         # the connection in that case.
232         self._last_request_is_blocked = True
233         response = self._request(method, uri, payload, headers, timeout=None)
234         self._last_request_is_blocked = False
235         return Response.from_http(response, decoder=decoder, **codec_kwargs)
236
237     def _request(self, method, uri, payload, headers=None, timeout=None):
238         if isinstance(payload, str):
239             payload = payload.encode("utf-8")
240
241         if headers is None:
242             headers = {}
243         headers.update({"Connection": "keep-alive"})
244
245         url = self.url(uri)
246
247         if self._last_request_is_blocked or self._has_unread_data():
248             self.close()
249
250         self.connection.request(method, url, payload, headers)
251
252         # timeout for request has to be set just before calling httplib.getresponse()
253         # and the previous value restored just after that, even on exception raised
254         try:
255             if timeout:
256                 previous_timeout = self._conn.gettimeout()
257                 self._conn.settimeout(timeout)
258             response = self.connection.getresponse()
259         finally:
260             if timeout:
261                 self._conn.settimeout(previous_timeout)
262
263         return response
264
265     def _has_unread_data(self):
266         return self._conn and self._conn.sock and select.select([self._conn.sock], [], [], 0)[0]