-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wsgi: Add middleware for delaying and rejecting requests
- Fix #7
- Loading branch information
1 parent
cf4754c
commit 1e8f428
Showing
9 changed files
with
226 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[bandit] | ||
exclude: /test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import os | ||
|
||
|
||
REQUESTS_SLOWDOWN_DATETIME = os.getenv( | ||
"KIWI_REQUESTS_SLOWDOWN_DATETIME", "2019-06-24T13:00:00" | ||
) | ||
REQUESTS_RESTRICT_DATETIME = os.getenv( | ||
"KIWI_REQUESTS_RESTRICT_DATETIME", "2019-08-01T13:00:00" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import re | ||
import time | ||
from datetime import datetime | ||
|
||
import webob | ||
from dateutil.parser import parse | ||
from webob.dec import wsgify | ||
|
||
from . import settings | ||
|
||
|
||
user_agent_re = re.compile( | ||
r"^(?P<name>\S.+?)\/(?P<version>\S.+?) " | ||
r"\(Kiwi\.com (?P<environment>\S.+?)\)(?: ?(?P<system_info>.*))$" | ||
) | ||
req_slowdown_datetime = parse(settings.REQUESTS_SLOWDOWN_DATETIME) | ||
req_restrict_datetime = parse(settings.REQUESTS_RESTRICT_DATETIME) | ||
|
||
|
||
def _refuse_request(req, app): | ||
msg = "Invalid User-Agent: does not comply with KW-RFC-22" | ||
return webob.exc.HTTPBadRequest(msg) | ||
|
||
|
||
def _slowdown_request(req, app): | ||
before_time = time.time() | ||
resp = req.get_response(app) | ||
seconds = time.time() - before_time | ||
time.sleep(seconds) | ||
return resp | ||
|
||
|
||
@wsgify.middleware | ||
def user_agent_middleware(req, app): | ||
"""Validate client's User-Agent header and modify response based on that. | ||
If the User-Agent header is invalid, there are three things that can happen: | ||
1. The current time is less then :obj:`settings.REQUESTS_SLOWDOWN_DATETIME`, | ||
do nothing in this case. | ||
2. The current time is less then :obj:`settings.REQUESTS_RESTRICT_DATETIME`, | ||
slow down the response twice the normal responce time. | ||
3. The current time is more then :obj:`settings.REQUESTS_RESTRICT_DATETIME`, | ||
refuse the request, return HTTP 400 to the client. | ||
Usage:: | ||
from your_app import wsgi_app | ||
wsgi_app = user_agent_middleware(wsgi_app) | ||
For example, in Flask, the middleware can be applied like this:: | ||
from flask import Flask | ||
app = Flask(__name__) | ||
app.wsgi_app = user_agent_middleware(app) | ||
app.run() | ||
In Django, you can apply the middleware like this:: | ||
from django.core.wsgi import get_wsgi_application | ||
application = user_agent_middleware(get_wsgi_application()) | ||
For more information see | ||
`Django docs <https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/>`_. | ||
.. warning:: | ||
The middleware slows down requests by calling :meth:`time.sleep()` | ||
(in the time frame when requests with invalid user-agent are being delayed). | ||
This can increase worker busyness which can overload a service. | ||
""" | ||
now = datetime.utcnow() | ||
|
||
if now < req_slowdown_datetime: | ||
return app | ||
|
||
is_valid = bool(user_agent_re.match(req.user_agent)) | ||
|
||
if is_valid: | ||
return app | ||
elif now < req_restrict_datetime: | ||
return _slowdown_request(req, app) | ||
else: | ||
return _refuse_request(req, app) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import time | ||
from datetime import datetime | ||
|
||
import pytest | ||
from webob.request import BaseRequest | ||
|
||
from kw.platform import wsgi as uut | ||
|
||
|
||
def create_app(sleep_seconds=0): | ||
def simple_app(environ, start_response): | ||
if sleep_seconds: | ||
time.sleep(sleep_seconds) | ||
start_response("200 OK", [("Content-Type", "text/html; charset=UTF-8")]) | ||
return ["OK"] | ||
|
||
return simple_app | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"user_agent,sleep_seconds,expected_time,expected_status,current_time", | ||
[ | ||
("invalid", 0, 0, 200, datetime(2019, 5, 21, 0, 0, 0)), | ||
("invalid", 0, 0, 400, datetime(2020, 1, 10, 0, 0, 0)), | ||
("mambo/1a (Kiwi.com dev)", 0, 0, 200, datetime(2020, 1, 10, 0, 0, 0)), | ||
("invalid", 0.1, 0.2, 200, datetime(2019, 7, 1, 0, 0, 0)), | ||
("mambo/1a (Kiwi.com dev)", 0.1, 0.1, 200, datetime(2019, 7, 1, 0, 0, 0)), | ||
], | ||
) | ||
def test_user_agent_middleware( | ||
mocker, user_agent, sleep_seconds, expected_time, expected_status, current_time | ||
): | ||
req = BaseRequest.blank("/") | ||
req.user_agent = user_agent | ||
|
||
mocker.patch( | ||
"kw.platform.wsgi.datetime", | ||
mocker.Mock(utcnow=mocker.Mock(return_value=current_time)), | ||
) | ||
app = create_app(sleep_seconds) | ||
app = uut.user_agent_middleware(app) # pylint: disable=no-value-for-parameter | ||
|
||
before_time = time.time() | ||
res = req.get_response(app) | ||
request_time = time.time() - before_time | ||
|
||
assert res.status_code == expected_status | ||
assert expected_time >= request_time |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters