Skip to content

Commit

Permalink
Apply rate limits to search
Browse files Browse the repository at this point in the history
Due to excess spamming, we are applying rate limits to search endpoints.
Which means a given user that requests this same endpoint X times in one minute
will get 429 (200/minute). This is applied globally to all search,
because otherwise same IP hitting the different endpoint could still
consume Search API quota and thus take down search
  • Loading branch information
carkod committed Feb 8, 2023
1 parent 5da7c6a commit 71e8dff
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 40 deletions.
93 changes: 56 additions & 37 deletions canonicalwebteam/search/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

# Local
from canonicalwebteam.search.models import get_search_results
from limits import storage, strategies, parse

memory_storage = storage.MemoryStorage()
fixed_window = strategies.MovingWindowRateLimiter(memory_storage)
# Rate limit requests to protect from spamming
# To adjust this rate visit https://limits.readthedocs.io/en/latest/quickstart.html#examples
one_per_minute = parse("3/minute")


class NoAPIKeyError(Exception):
Expand Down Expand Up @@ -45,52 +52,64 @@ def search_view():
"""
Get search results from Google Custom Search
"""

# API key should always be provided as an environment variable
search_api_key = os.getenv("SEARCH_API_KEY")

if not search_api_key:
raise NoAPIKeyError("Unable to search: No API key provided")

params = flask.request.args
query = params.get("q")
start = params.get("start")
num = params.get("num")
site_search = site or params.get("siteSearch") or params.get("domain")
results = None

if query:
results = get_search_results(
session=session,
api_key=search_api_key,
search_engine_id=search_engine_id,
siteSearch=site_search,
site_restricted_search=site_restricted_search,
query=query,
start=start,
num=num,
# Rate limit requests to protect from spamming
# To adjust this rate visit
rate_limit = fixed_window.hit(one_per_minute)
if rate_limit:
# API key should always be provided as an environment variable
search_api_key = os.getenv("SEARCH_API_KEY")

if not search_api_key:
raise NoAPIKeyError("Unable to search: No API key provided")

params = flask.request.args
query = params.get("q")
start = params.get("start")
num = params.get("num")
site_search = (
site or params.get("siteSearch") or params.get("domain")
)
results = None

return (
flask.render_template(
if query:
results = get_search_results(
session=session,
api_key=search_api_key,
search_engine_id=search_engine_id,
siteSearch=site_search,
site_restricted_search=site_restricted_search,
query=query,
start=start,
num=num,
)

return (
flask.render_template(
template_path,
query=query,
start=start,
num=num,
results=results,
siteSearch=site_search,
),
{"X-Robots-Tag": "noindex"},
)

else:
return flask.render_template(
template_path,
query=query,
start=start,
num=num,
results=results,
siteSearch=site_search,
),
{"X-Robots-Tag": "noindex"},
)

)
else:
return flask.render_template(
template_path,
query=query,
start=start,
num=num,
results=results,
siteSearch=site_search,
return (
flask.render_template(
"429.html",
),
429,
)

return search_view
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name="canonicalwebteam.search",
version="1.2.8",
version="1.3.8",
author="Canonical webteam",
author_email="webteam@canonical.com",
url="https://github.com/canonical/canonicalwebteam.search",
Expand All @@ -15,6 +15,6 @@
packages=find_packages(),
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
install_requires=["Flask>=1.0.2", "user-agents>=2.0.0"],
tests_require=["httpretty", "Flask>=1.0.2", "user-agents>=2.0.0"],
install_requires=["Flask>=1.0.2", "user-agents>=2.0.0", "limits>=3.2.0"],
tests_require=["httpretty"],
)

0 comments on commit 71e8dff

Please sign in to comment.