Skip to content

Commit

Permalink
[WIP] Make webdriver client handle headers case insensitively (web-pl…
Browse files Browse the repository at this point in the history
…atform-tests#25680)

Closes web-platform-tests#25039

Co-authored-by: Robert Ma <robertma@chromium.org>

Co-authored-by: Robert Ma <robertma@chromium.org>
  • Loading branch information
2 people authored and ziransun committed Oct 6, 2020
1 parent 712efca commit 6146ad2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 11 deletions.
53 changes: 52 additions & 1 deletion tools/webdriver/webdriver/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import select

from six import text_type, PY3
from six.moves.collections_abc import Mapping
from six.moves.http_client import HTTPConnection
from six.moves.urllib import parse as urlparse

Expand All @@ -10,6 +11,56 @@
"""Implements HTTP transport for the WebDriver wire protocol."""


missing = object()


class ResponseHeaders(Mapping):
"""Read-only dictionary-like API for accessing response headers.
This class:
* Normalizes the header keys it is built with to lowercase (such that
iterating the items will return lowercase header keys).
* Has case-insensitive header lookup.
* Always returns all header values that have the same name, separated by
commas.
It does not ensure header types (e.g. binary vs string).
"""
def __init__(self, items):
self.headers_dict = {}
for key, value in items:
key = key.lower()
if key not in self.headers_dict:
self.headers_dict[key] = []
self.headers_dict[key].append(value)

def __getitem__(self, key):
"""Get all headers of a certain (case-insensitive) name. If there is
more than one, the values are returned comma separated"""
values = self.headers_dict[key.lower()]
if len(values) == 1:
return values[0]
else:
return ", ".join(values)

def get_list(self, key, default=missing):
"""Get all the header values for a particular field name as a list"""
try:
return self.headers_dict[key.lower()]
except KeyError:
if default is not missing:
return default
else:
raise

def __iter__(self):
for item in self.headers_dict:
yield item

def __len__(self):
return len(self.headers_dict)


class Response(object):
"""
Describes an HTTP response received from a remote end whose
Expand Down Expand Up @@ -40,7 +91,7 @@ def error(self):
def from_http(cls, http_response, decoder=json.JSONDecoder, **kwargs):
try:
body = json.load(http_response, cls=decoder, **kwargs)
headers = dict(http_response.getheaders())
headers = ResponseHeaders(http_response.getheaders())
except ValueError:
raise ValueError("Failed to decode response body as JSON:\n" +
http_response.read())
Expand Down
13 changes: 3 additions & 10 deletions webdriver/tests/support/asserts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import imghdr
import struct

from six import ensure_binary, text_type, PY3
from six import ensure_binary, text_type

from webdriver import Element, NoSuchAlertException, WebDriverException

Expand Down Expand Up @@ -83,15 +83,8 @@ def assert_response_headers(headers):
"""
assert 'cache-control' in headers
assert 'no-cache' == headers['cache-control']
# In Python 2, HTTPResponse normalizes header keys to lowercase, whereas
# Python 3 preserves the case. See
# https://github.com/web-platform-tests/wpt/pull/22858#issuecomment-612656097
if PY3:
assert 'Content-Type' in headers
assert 'application/json; charset=utf-8' == headers['Content-Type']
else:
assert 'content-type' in headers
assert 'application/json; charset=utf-8' == headers['content-type']
assert 'content-type' in headers
assert 'application/json; charset=utf-8' == headers['content-type']


def assert_dialog_handled(session, expected_text, expected_retval):
Expand Down

0 comments on commit 6146ad2

Please sign in to comment.