mirror of https://github.com/pallets/flask.git
Added support for automagic OPTIONS
This commit is contained in:
parent
a532568680
commit
5e1b1030e8
3
CHANGES
3
CHANGES
|
@ -10,6 +10,9 @@ Release date to be announced, codename to be decided.
|
|||
|
||||
- after request functions are now called in reverse order of
|
||||
registration.
|
||||
- OPTIONS is now automatically implemented by Flask unless the
|
||||
application explictly adds 'OPTIONS' as method to the URL rule.
|
||||
In this case no automatic OPTIONS handling kicks in.
|
||||
|
||||
Version 0.5.1
|
||||
-------------
|
||||
|
|
|
@ -269,7 +269,8 @@ If `GET` is present, `HEAD` will be added automatically for you. You
|
|||
don't have to deal with that. It will also make sure that `HEAD` requests
|
||||
are handled like the `HTTP RFC`_ (the document describing the HTTP
|
||||
protocol) demands, so you can completely ignore that part of the HTTP
|
||||
specification.
|
||||
specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you
|
||||
as well automatically.
|
||||
|
||||
You have no idea what an HTTP method is? Worry not, here quick
|
||||
introduction in HTTP methods and why they matter:
|
||||
|
@ -310,6 +311,11 @@ very common:
|
|||
`DELETE`
|
||||
Remove the information that the given location.
|
||||
|
||||
`OPTIONS`
|
||||
Provides a quick way for a requesting client to figure out which
|
||||
methods are supported by this URL. Starting with Flask 0.6, this
|
||||
is implemented for you automatically.
|
||||
|
||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
||||
form might submit to the server are `GET` and `POST`. But with JavaScript
|
||||
and future HTML standards you can use other methods as well. Furthermore
|
||||
|
|
37
flask/app.py
37
flask/app.py
|
@ -464,6 +464,9 @@ class Flask(_PackageBoundObject):
|
|||
.. versionchanged:: 0.2
|
||||
`view_func` parameter added.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
`OPTIONS` is added automatically as method.
|
||||
|
||||
:param rule: the URL rule as string
|
||||
:param endpoint: the endpoint for the registered URL rule. Flask
|
||||
itself assumes the name of the view function as
|
||||
|
@ -471,15 +474,27 @@ class Flask(_PackageBoundObject):
|
|||
:param view_func: the function to call when serving a request to the
|
||||
provided endpoint
|
||||
:param options: the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object
|
||||
:class:`~werkzeug.routing.Rule` object. A change
|
||||
to Werkzeug is handling of method options. methods
|
||||
is a list of methods this rule should be limited
|
||||
to (`GET`, `POST` etc.). By default a rule
|
||||
just listens for `GET` (and implicitly `HEAD`).
|
||||
Starting with Flask 0.6, `OPTIONS` is implicitly
|
||||
added and handled by the standard request handling.
|
||||
"""
|
||||
if endpoint is None:
|
||||
assert view_func is not None, 'expected view func if endpoint ' \
|
||||
'is not provided.'
|
||||
endpoint = view_func.__name__
|
||||
options['endpoint'] = endpoint
|
||||
options.setdefault('methods', ('GET',))
|
||||
self.url_map.add(Rule(rule, **options))
|
||||
methods = options.pop('methods', ('GET',))
|
||||
provide_automatic_options = False
|
||||
if 'OPTIONS' not in methods:
|
||||
methods = tuple(methods) + ('OPTIONS',)
|
||||
provide_automatic_options = True
|
||||
rule = Rule(rule, methods=methods, **options)
|
||||
rule.provide_automatic_options = provide_automatic_options
|
||||
self.url_map.add(rule)
|
||||
if view_func is not None:
|
||||
self.view_functions[endpoint] = view_func
|
||||
|
||||
|
@ -539,8 +554,10 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
:param rule: the URL rule as string
|
||||
:param methods: a list of methods this rule should be limited
|
||||
to (``GET``, ``POST`` etc.). By default a rule
|
||||
just listens for ``GET`` (and implicitly ``HEAD``).
|
||||
to (`GET`, `POST` etc.). By default a rule
|
||||
just listens for `GET` (and implicitly `HEAD`).
|
||||
Starting with Flask 0.6, `OPTIONS` is implicitly
|
||||
added and handled by the standard request handling.
|
||||
:param subdomain: specifies the rule for the subdomain in case
|
||||
subdomain matching is in use.
|
||||
:param strict_slashes: can be used to disable the strict slashes
|
||||
|
@ -650,7 +667,15 @@ class Flask(_PackageBoundObject):
|
|||
try:
|
||||
if req.routing_exception is not None:
|
||||
raise req.routing_exception
|
||||
return self.view_functions[req.endpoint](**req.view_args)
|
||||
rule = req.url_rule
|
||||
# if we provide automatic options for this URL and the
|
||||
# request came with the OPTIONS method, reply automatically
|
||||
if rule.provide_automatic_options and req.method == 'OPTIONS':
|
||||
rv = self.response_class()
|
||||
rv.allow.update(rule.methods)
|
||||
return rv
|
||||
# otherwise dispatch to the handler for that endpoint
|
||||
return self.view_functions[rule.endpoint](**req.view_args)
|
||||
except HTTPException, e:
|
||||
return self.handle_http_exception(e)
|
||||
|
||||
|
|
|
@ -38,8 +38,9 @@ class _RequestContext(object):
|
|||
self.flashes = None
|
||||
|
||||
try:
|
||||
self.request.endpoint, self.request.view_args = \
|
||||
self.url_adapter.match()
|
||||
url_rule, self.request.view_args = \
|
||||
self.url_adapter.match(return_rule=True)
|
||||
self.request.url_rule = url_rule
|
||||
except HTTPException, e:
|
||||
self.request.routing_exception = e
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import posixpath
|
|||
import mimetypes
|
||||
from time import time
|
||||
from zlib import adler32
|
||||
from functools import wraps
|
||||
|
||||
# try to load the best simplejson implementation available. If JSON
|
||||
# is not installed, we add a failing class.
|
||||
|
|
|
@ -24,11 +24,12 @@ class Request(RequestBase):
|
|||
:attr:`~flask.Flask.request_class` to your subclass.
|
||||
"""
|
||||
|
||||
#: the endpoint that matched the request. This in combination with
|
||||
#: :attr:`view_args` can be used to reconstruct the same or a
|
||||
#: modified URL. If an exception happened when matching, this will
|
||||
#: be `None`.
|
||||
endpoint = None
|
||||
#: the internal URL rule that matched the request. This can be
|
||||
#: useful to inspect which methods are allowed for the URL from
|
||||
#: a before/after handler (``request.url_rule.methods``) etc.
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
url_rule = None
|
||||
|
||||
#: a dict of view arguments that matched the request. If an exception
|
||||
#: happened when matching, this will be `None`.
|
||||
|
@ -40,6 +41,16 @@ class Request(RequestBase):
|
|||
#: something similar.
|
||||
routing_exception = None
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
"""The endpoint that matched the request. This in combination with
|
||||
:attr:`view_args` can be used to reconstruct the same or a
|
||||
modified URL. If an exception happened when matching, this will
|
||||
be `None`.
|
||||
"""
|
||||
if self.url_rule is not None:
|
||||
return self.url_rule.endpoint
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
"""The name of the current module"""
|
||||
|
|
|
@ -111,6 +111,15 @@ class ContextTestCase(unittest.TestCase):
|
|||
|
||||
class BasicFunctionalityTestCase(unittest.TestCase):
|
||||
|
||||
def test_options_work(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
return 'Hello World'
|
||||
rv = app.test_client().open('/', method='OPTIONS')
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||
assert rv.data == ''
|
||||
|
||||
def test_request_dispatching(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
|
@ -124,7 +133,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
|
@ -132,7 +141,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||
|
||||
def test_url_mapping(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
@ -148,7 +157,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
|
@ -156,7 +165,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||
|
||||
def test_session(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
Loading…
Reference in New Issue