Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for custom SSLContext when using FastHttpUser #2113

Merged
merged 7 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion locust/contrib/fasthttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ def __init__(
user: Optional[User],
insecure=True,
client_pool: Optional[HTTPClientPool] = None,
ssl_context: Optional[gevent.ssl.SSLContext] = None,
**kwargs,
):
self.environment = environment
self.base_url = base_url
self.cookiejar = CookieJar()
self.user = user
if insecure:
if ssl_context and self._check_ssl_context(ssl_context):
ssl_context_factory = lambda: ssl_context
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesnt it make more sense to just pass a function instead of an instantiated SSLContext that we make a fake function for? (I made a comment and then deleted it, but after some more thought I think my initial comment was correct :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely it does. I was trying to make use of ssl_context as an actual context then the caller could set it accordingly, but making it a factory keeps the way it does in the background. I will rename it to ssl_context_factory and type-hint it as Callable. Thanks for reviewing it.

elif insecure:
ssl_context_factory = insecure_ssl_context_factory
else:
ssl_context_factory = gevent.ssl.create_default_context
Expand All @@ -107,6 +110,13 @@ def __init__(
# store authentication header (we construct this by using _basic_auth_str() function from requests.auth)
self.auth_header = _construct_basic_auth_str(parsed_url.username, parsed_url.password)

def _check_ssl_context(ssl_context):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this deserves its own method :)

if not isinstance(ssl_context, gevent.ssl.SSLContext):
raise TypeError(
'You must provide a valid SSLContext. Expected type is gevent.ssl.SSLContext '
f'but you provided a ${type(ssl_context)}')
return True

def _build_url(self, path):
"""prepend url with hostname unless it's already an absolute URL"""
if absolute_http_url_regexp.match(path):
Expand Down Expand Up @@ -318,6 +328,9 @@ class by using the :py:func:`@task decorator <locust.task>` on the methods, or b
client_pool: Optional[HTTPClientPool] = None
"""HTTP client pool to use. If not given, a new pool is created per single user."""

ssl_context: Optional[gevent.ssl.SSLContext] = None
"""A pre-configured SSL context for overriding the default context context created by the FastHttpSession."""

abstract = True
"""Dont register this as a User class that can be run by itself"""

Expand All @@ -341,6 +354,7 @@ def __init__(self, environment):
concurrency=self.concurrency,
user=self,
client_pool=self.client_pool,
ssl_context=self.ssl_context
)
"""
Instance of HttpSession that is created upon instantiation of User.
Expand Down
20 changes: 20 additions & 0 deletions locust/test/test_fasthttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,23 @@ def test_ssl_request_insecure(self):
self.assertEqual(200, r.status_code)
self.assertIn("<title>Locust for None</title>", r.content.decode("utf-8"))
self.assertIn("<p>Script: <span>None</span></p>", r.text)

def test_custom_ssl_context(self):
def create_custom_context():
context = gevent.ssl.create_default_context()
context.check_hostname = False
context.verify_mode = gevent.ssl.CERT_NONE
return context

s = FastHttpSession(self.environment, "https://127.0.0.1:%i" % self.web_port, ssl_context=create_custom_context(), user=None)
r = s.get("/")
self.assertEqual(200, r.status_code)
self.assertIn("<title>Locust for None</title>", r.content.decode("utf-8"))
self.assertIn("<p>Script: <span>None</span></p>", r.text)

def test_custom_ssl_context_error_fail(self):
def create_custom_context():
return {'ssl_context': {'cert': 'mycert.pem', 'key': 'mykey.key'}}

with FastHttpSession(self.environment, "https://127.0.0.1:%i" % self.web_port, ssl_context=create_custom_context(), user=None):
self.assertRaises(TypeError)