Parameterized Route returns 404 instead of 405
eric-spitler opened this issue · comments
Observation / Problem
When a route is parametrized (e.g. /<identifier>
or /<identifier:int>
) and an HTTP method is used that is not defined, the response comes back as a 404 Not Found
instead of 405 Method Not Allowed
.
Simple routes w/o parameterization properly return a 405.
Test Setup
from sanic import Sanic, Blueprint
from sanic.response import HTTPResponse
from sanic.views import HTTPMethodView
app = Sanic(name='test')
@app.get(uri='/with-func', name='get-func')
def handler_str(request):
print(None)
return HTTPResponse()
@app.get(uri='/with-func/str/<identifier>', name='str-id-get-func')
def handler_str(request, identifier: str):
print(identifier, type(identifier))
return HTTPResponse()
@app.get(uri='/with-func/int/<identifier:int>', name='int-id-get-func')
def handler_int(request, identifier: int):
print(identifier, type(identifier))
return HTTPResponse()
class StrView(HTTPMethodView, attach=app, uri='/with-view', name='get-view'):
@staticmethod
def get(request):
print(None)
return HTTPResponse()
class StrView(HTTPMethodView, attach=app, uri='/with-view/str/<identifier>', name='str-id-get-view'):
@staticmethod
def get(request, identifier):
print(identifier, type(identifier))
return HTTPResponse()
class IntView(HTTPMethodView, attach=app, uri='/with-view/int/<identifier:int>', name='int-id-get-view'):
@staticmethod
def get(request, identifier: int):
print(identifier, type(identifier))
return HTTPResponse()
app.run(host='localhost', port=8080, single_process=True)
Test Execution
from httpx import get, post
paths = [
'/with-func',
'/with-func/str/test',
'/with-func/int/1',
'/with-view',
'/with-view/str/test',
'/with-view/int/1'
]
for path in paths:
print(path)
for method in [get, post]:
response = method(f'http://localhost:8080{path}')
print(f'\t{method.__name__:8}{response}')
App Logs
[2023-11-02 21:35:08 +0000] [32026] [INFO] Sanic v23.6.0
[2023-11-02 21:35:08 +0000] [32026] [INFO] Goin' Fast @ http://localhost:8080
[2023-11-02 21:35:08 +0000] [32026] [INFO] mode: production, single worker
[2023-11-02 21:35:08 +0000] [32026] [INFO] server: sanic, HTTP/1.1
[2023-11-02 21:35:08 +0000] [32026] [INFO] python: 3.11.6
[2023-11-02 21:35:08 +0000] [32026] [INFO] platform: Linux-3.10.0-1160.99.1.el7.x86_64-x86_64-with-glibc2.17
[2023-11-02 21:35:08 +0000] [32026] [INFO] packages: sanic-routing==23.6.0, sanic-testing==23.6.0, sanic-ext==23.6.0
[2023-11-02 21:35:08 +0000] [32026] [INFO] Sanic Extensions:
[2023-11-02 21:35:08 +0000] [32026] [INFO] > injection [0 dependencies; 0 constants]
[2023-11-02 21:35:08 +0000] [32026] [INFO] > openapi [http://localhost:8080/docs]
[2023-11-02 21:35:08 +0000] [32026] [INFO] > http
[2023-11-02 21:35:08 +0000] [32026] [INFO] Starting worker [32026]
None
test <class 'str'>
1 <class 'int'>
None
test <class 'str'>
1 <class 'int'>
Test Logs
/with-func
get <Response [200 OK]>
post <Response [405 Method Not Allowed]>
/with-func/str/test
get <Response [200 OK]>
post <Response [404 Not Found]>
/with-func/int/1
get <Response [200 OK]>
post <Response [404 Not Found]>
/with-view
get <Response [200 OK]>
post <Response [405 Method Not Allowed]>
/with-view/str/test
get <Response [200 OK]>
post <Response [404 Not Found]>
/with-view/int/1
get <Response [200 OK]>
post <Response [404 Not Found]>
Cause
This section catches NotFound
and NoMethod
sanic-routing/sanic_routing/router.py
Lines 89 to 99 in 8893c4a
but the value of self.exception
is <class 'sanic_routing.exceptions.NotFound'>
Suggestion
Add a path
parameter to the NoMethod
class
sanic-routing/sanic_routing/exceptions.py
Lines 22 to 31 in 8893c4a
and use the caught exception class to re-raise
raise e.__class__(str(e), path=path)
Thanks for bringing this up. I am stunned this has not been caught and there is no automated test for this already.