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 separate error handler for 405(Method not allowed) errors #26880

Merged
merged 1 commit into from Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions airflow/www/extensions/init_views.py
Expand Up @@ -188,8 +188,7 @@ def init_api_connexion(app: Flask) -> None:
from airflow.www import views

@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
def _handle_api_not_found(ex):
if request.path.startswith(base_path):
# 404 errors are never handled on the blueprint level
# unless raised from a view func so actual 404 errors,
Expand All @@ -199,6 +198,13 @@ def _handle_api_error(ex):
else:
return views.not_found(ex)

@app.errorhandler(405)
def _handle_method_not_allowed(ex):
if request.path.startswith(base_path):
return common_error_handler(ex)
else:
return views.method_not_allowed(ex)

spec_dir = path.join(ROOT_APP_DIR, 'api_connexion', 'openapi')
connexion_app = App(__name__, specification_dir=spec_dir, skip_error_handlers=True)
connexion_app.app = app
Expand Down
Expand Up @@ -20,14 +20,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Airflow 404</title>
<title>Airflow {{ status_code }}</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='pin_32.png') }}">
</head>
<body>
<div style="font-family: verdana; text-align: center; margin-top: 200px;">
<img src="{{ url_for('static', filename='pin_100.png') }}" width="50px" alt="pin-logo" />
<h1>Airflow 404</h1>
<p>Page cannot be found.</p>
<h1>Airflow {{ status_code }}</h1>
<p>{{ error_message }}</p>
<a href="/">Return to the main page</a>
<p>{{ hostname }}</p>
</div>
Expand Down
19 changes: 18 additions & 1 deletion airflow/www/views.py
Expand Up @@ -489,15 +489,32 @@ def not_found(error):
"""Show Not Found on screen for any error in the Webserver"""
return (
render_template(
'airflow/not_found.html',
'airflow/error.html',
hostname=get_hostname()
if conf.getboolean('webserver', 'EXPOSE_HOSTNAME', fallback=True)
else 'redact',
status_code=404,
error_message='Page cannot be found.',
),
404,
)


def method_not_allowed(error):
"""Show Method Not Allowed on screen for any error in the Webserver"""
return (
render_template(
'airflow/error.html',
hostname=get_hostname()
if conf.getboolean('webserver', 'EXPOSE_HOSTNAME', fallback=True)
else 'redact',
status_code=405,
error_message='Received an invalid request.',
),
405,
)


def show_traceback(error):
"""Show Traceback for a given error"""
return (
Expand Down
47 changes: 40 additions & 7 deletions tests/api_connexion/test_error_handling.py
Expand Up @@ -21,22 +21,55 @@ def test_incorrect_endpoint_should_return_json(minimal_app_for_api):
client = minimal_app_for_api.test_client()

# Given we have application with Connexion added
# When we hitting incorrect endpoint in API path
# When we are hitting incorrect endpoint in API path

resp_json = client.get("/api/v1/incorrect_endpoint").json
resp = client.get("/api/v1/incorrect_endpoint")

# Then we have parsable JSON as output

assert 404 == resp_json["status"]
assert 'Not Found' == resp.json["title"]
assert 404 == resp.json["status"]
assert 404 == resp.status_code


def test_incorrect_endpoint_should_return_html(minimal_app_for_api):
client = minimal_app_for_api.test_client()

# When we are hitting non-api incorrect endpoint

resp_json = client.get("/incorrect_endpoint").json
resp = client.get("/incorrect_endpoint")

# Then we do not have JSON as response, rather standard HTML

assert resp_json is None
assert resp.json is None
assert resp.mimetype == 'text/html'
assert resp.status_code == 404


def test_incorrect_method_should_return_json(minimal_app_for_api):
client = minimal_app_for_api.test_client()

# Given we have application with Connexion added
# When we are hitting incorrect HTTP method in API path

resp = client.put("/api/v1/version")

resp_json = client.put("/api/v1/variables").json
# Then we have parsable JSON as output

assert 'Method Not Allowed' == resp.json["title"]
assert 405 == resp.json["status"]
assert 405 == resp.status_code


def test_incorrect_method_should_return_html(minimal_app_for_api):
client = minimal_app_for_api.test_client()

# When we are hitting non-api incorrect HTTP method

resp = client.put("/")

# Then we do not have JSON as response, rather standard HTML

assert 'Method Not Allowed' == resp_json["title"]
assert resp.json is None
assert resp.mimetype == 'text/html'
assert resp.status_code == 405