Provide a RFC 7231 compliant user agent string
The current default of "keystoneauth1" doesn't convey enough information, and additionally when the user of a Session supplies their own user agent, it stomps on any notion of keystoneauth1 being there. Per RFC 7231 Section 5.5.3 (https://tools.ietf.org/html/rfc7231#section-5.5.3), user agents should basically be a space-delimited list of product/version pairs in decreasing order of importance. This change makes the default user agent something like the following: keystoneauth1/2.1.1 python-requests/2.8.1 CPython/3.4.1+ Due to the decreasing order of importance, when a user creates a Session with something like Session(user_agent="my-product/1.0"), 'my-product/1.0' is then prepended to the above list. The only time this is not the case is if a user agent is provided directly to Session.request. In that case, the User-Agent header is set to whatever the provided argument is, verbatim. This was a change we had originally made to the Transport class in python-openstacksdk (I80ca26fff3f2522b8232472676396abb86166f91), but upon moving to keystoneauth instead of our own implementation, it was noticed that we lost this, and keystoneauth is a better place for this than for us to re-implement it inside of python-openstacksdk. Change-Id: I46f336f25fac5b524547bb13e4f5438ebf1d4320
This commit is contained in:
parent
30f9a02fa2
commit
bbd85fded7
@ -0,0 +1,16 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo('keystoneauth1').version_string()
|
@ -15,6 +15,7 @@ import functools
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
@ -23,6 +24,7 @@ import requests
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
import keystoneauth1
|
||||
from keystoneauth1 import _utils as utils
|
||||
from keystoneauth1 import exceptions
|
||||
|
||||
@ -36,7 +38,9 @@ try:
|
||||
except ImportError:
|
||||
osprofiler_web = None
|
||||
|
||||
USER_AGENT = 'keystoneauth1'
|
||||
DEFAULT_USER_AGENT = 'keystoneauth1/%s %s %s/%s' % (
|
||||
keystoneauth1.__version__, requests.utils.default_user_agent(),
|
||||
platform.python_implementation(), platform.python_version())
|
||||
|
||||
_logger = utils.get_logger(__name__)
|
||||
|
||||
@ -95,8 +99,12 @@ class Session(object):
|
||||
of seconds or 0 for no timeout. (optional, defaults
|
||||
to 0)
|
||||
:param string user_agent: A User-Agent header string to use for the
|
||||
request. If not provided a default is used.
|
||||
(optional, defaults to 'keystoneauth1')
|
||||
request. If not provided, a default of
|
||||
:attr:`~keystoneauth1.session.DEFAULT_USER_AGENT`
|
||||
is used, which contains the keystoneauth1 version
|
||||
as well as those of the requests library and
|
||||
which Python is being used. When a non-None value
|
||||
is passed, it will be prepended to the default.
|
||||
:param int/bool redirect: Controls the maximum number of redirections that
|
||||
can be followed by a request. Either an integer
|
||||
for a specific count or True/False for
|
||||
@ -127,7 +135,11 @@ class Session(object):
|
||||
|
||||
# don't override the class variable if none provided
|
||||
if user_agent is not None:
|
||||
self.user_agent = user_agent
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent
|
||||
# should be ordered by decreasing significance.
|
||||
# If a user sets their product, we prepend it to the KSA
|
||||
# version, requests version, and then the Python version.
|
||||
self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT)
|
||||
|
||||
self._json = _JSONEncoder()
|
||||
|
||||
@ -367,7 +379,7 @@ class Session(object):
|
||||
elif self.user_agent:
|
||||
user_agent = headers.setdefault('User-Agent', self.user_agent)
|
||||
else:
|
||||
user_agent = headers.setdefault('User-Agent', USER_AGENT)
|
||||
user_agent = headers.setdefault('User-Agent', DEFAULT_USER_AGENT)
|
||||
|
||||
if self.original_ip:
|
||||
headers.setdefault('Forwarded',
|
||||
|
@ -89,12 +89,15 @@ class SessionTests(utils.TestCase):
|
||||
self.assertRequestBodyIs(json={'hello': 'world'})
|
||||
|
||||
def test_user_agent(self):
|
||||
session = client_session.Session(user_agent='test-agent')
|
||||
custom_agent = 'custom-agent/1.0'
|
||||
session = client_session.Session(user_agent=custom_agent)
|
||||
self.stub_url('GET', text='response')
|
||||
resp = session.get(self.TEST_URL)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual('User-Agent', 'test-agent')
|
||||
self.assertRequestHeaderEqual(
|
||||
'User-Agent',
|
||||
'%s %s' % (custom_agent, client_session.DEFAULT_USER_AGENT))
|
||||
|
||||
resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'})
|
||||
self.assertTrue(resp.ok)
|
||||
|
@ -2,6 +2,7 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr>=1.6
|
||||
argparse
|
||||
iso8601>=0.1.9
|
||||
requests>=2.8.1
|
||||
|
Loading…
Reference in New Issue
Block a user