-
Notifications
You must be signed in to change notification settings - Fork 3k
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
FastHttpUser does not pass headers correctly during a retry after a "failure exception" such as timeout #2894
Comments
Thanks for the deep analysis! We have a different ticket open for using geventhttpclient’s UserAgent #2681 Do you think that would that solve this issue? |
I might have been too hasty in my original analysis. geventhttpclient's UserAgent does not send a request when calling
I think the problem is with FastHttpSession relying on LocustUserAgent's and thus geventhttpclient UserAgent's internal implementation, namely On a side note, it looks like LocustUserAgent overrides geventhttpclient UserAgent's |
Yea the implementation in Locust was copied over to geventhttpclient (because it could be useful to others as well, not just Locust :) |
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 10 days. |
This issue was closed because it has been stalled for 10 days with no activity. This does not necessarily mean that the issue is bad, but it most likely means that nobody is willing to take the time to fix it. If you have found Locust useful, then consider contributing a fix yourself! |
Prerequisites
Description
Issue: We are testing a system with locust that cannot handle the intended load, and thus the system eventually starts producing timeouts and dropping some connections. During these tests, we started noticing errors where the system was complaining about us sending requests with invalid content-type (text/plain instead of application/json). We noticed that the content-type header was passed twice to the target app, once in Upper-Case and once in lower-case.
Investigation (TLDR at bottom): Here is an example entry from our locust logs:
Failure: POST <address>, code=400 - <cut content> headers: {'API_TOKEN': '<API_TOKEN>', 'Content-Type': 'application/json', 'USER_ID': '<testid>' Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'content-type': 'text/plain; charset=utf-8', 'content-length': 204}
FastHttpSession
retries sending requestsin_send_request_safe_mode
after aFAILURE_EXCEPTION
onfasthttp.py
row 167, in case the exception has no'response'
attribute:locust.contrib.fasthttp.py
:However, the second call uses an internal geventhttpclient UserAgent method,
_make_request
. After investigating further, the underlying geventhttpclient UserAgenturlopen
method takes the headers it receives, and uses aHeaders
object to update its internal default headers with the given headers (with the given headers containing the correct content-type in our case, application/json):geventhttpclient.useragent.py
:This works fine, since the
Headers
takes into consideration that we're passing content-type as uppercase,'Content-Type': 'application/json'
. The geventhttpclientHeaders
object tries to preserve the case of the original header, while still performingHeaders.get
case-insensitively. However, the_make_request
method does not convert the givendict
to aHeaders
object, and when used directly, we bypass the conversion from dict to aHeaders
-object. Thus the.get
below is done case-sensitively during the retry in locust's FastHttpSession.geventhttpclient.useragent.py
:Since the dict.get cannot find the upper-case
'Content-Type'
, the code ends in theelif not content_type and isinstance(payload, str):
block (cause the payload content has been converted to str earlier in locust). After this, headers will contain a lowercase'content-type': 'text/plain; charset=utf-8'
, added by the above block of code. This was a duplicate'content-type'
header in our case, and our system picked up that one instead of the correct'Content-Type': 'application/json'
TLDR: The problem can be solved by passing the headers to the
_make_request
as aHeaders
object. I think the problem was introduced by this geventhttpclient commit, though I think that the underlying issue is how locust utilizes geventhttpclient.I have a commit in a fork of locust which seems to solve the problem (for some reason the python 3.10 tests failed). However, the fix feels like a bit of a hack. It may be better to make the commit to geventhttpclient.UserAgent._make_request, since that method only exists because of locust. It would be even better if locust did not depend on the internals of geventhttpclient, as discussed in this issue.
Command line
locust -f mylocustfile.py -t 10s --headless
Locustfile contents
Python version
3.11.2
Locust version
2.35.1
Operating system
Ubuntu 20.04
The text was updated successfully, but these errors were encountered: