This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch v2-4-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 5bf28504902ff9eded740ff9b7f9da98ac404f5a Author: Dakshin K <36289388+dakshi...@users.noreply.github.com> AuthorDate: Tue Oct 18 18:20:13 2022 +0530 Add separate error handler for 405(Method not allowed) errors (#26880) Co-authored-by: Dakshin K <dakshin...@gmail.com> (cherry picked from commit 8efb678e771c8b7e351220a1eb7eb246ae8ed97f) --- airflow/www/extensions/init_views.py | 10 ++++- .../airflow/{not_found.html => error.html} | 6 +-- airflow/www/views.py | 19 ++++++++- tests/api_connexion/test_error_handling.py | 47 ++++++++++++++++++---- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/airflow/www/extensions/init_views.py b/airflow/www/extensions/init_views.py index fcb2a87265..cad715085b 100644 --- a/airflow/www/extensions/init_views.py +++ b/airflow/www/extensions/init_views.py @@ -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, @@ -199,6 +198,13 @@ def init_api_connexion(app: Flask) -> None: 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 diff --git a/airflow/www/templates/airflow/not_found.html b/airflow/www/templates/airflow/error.html similarity index 91% rename from airflow/www/templates/airflow/not_found.html rename to airflow/www/templates/airflow/error.html index e9e1231e3a..cfb38a25b5 100644 --- a/airflow/www/templates/airflow/not_found.html +++ b/airflow/www/templates/airflow/error.html @@ -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> diff --git a/airflow/www/views.py b/airflow/www/views.py index 8a45a15444..e27ad84c9f 100644 --- a/airflow/www/views.py +++ b/airflow/www/views.py @@ -482,15 +482,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 ( diff --git a/tests/api_connexion/test_error_handling.py b/tests/api_connexion/test_error_handling.py index 8793e7c150..86effad88c 100644 --- a/tests/api_connexion/test_error_handling.py +++ b/tests/api_connexion/test_error_handling.py @@ -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