forked from orbit-oss/flask
Added support for automagic OPTIONS
This commit is contained in:
parent
a532568680
commit
5e1b1030e8
7 changed files with 74 additions and 18 deletions
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
|
- after request functions are now called in reverse order of
|
||||||
registration.
|
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
|
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
|
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
|
are handled like the `HTTP RFC`_ (the document describing the HTTP
|
||||||
protocol) demands, so you can completely ignore that part of 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
|
You have no idea what an HTTP method is? Worry not, here quick
|
||||||
introduction in HTTP methods and why they matter:
|
introduction in HTTP methods and why they matter:
|
||||||
|
|
@ -310,6 +311,11 @@ very common:
|
||||||
`DELETE`
|
`DELETE`
|
||||||
Remove the information that the given location.
|
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
|
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
|
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
|
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
|
.. versionchanged:: 0.2
|
||||||
`view_func` parameter added.
|
`view_func` parameter added.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6
|
||||||
|
`OPTIONS` is added automatically as method.
|
||||||
|
|
||||||
:param rule: the URL rule as string
|
:param rule: the URL rule as string
|
||||||
:param endpoint: the endpoint for the registered URL rule. Flask
|
:param endpoint: the endpoint for the registered URL rule. Flask
|
||||||
itself assumes the name of the view function as
|
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
|
:param view_func: the function to call when serving a request to the
|
||||||
provided endpoint
|
provided endpoint
|
||||||
:param options: the options to be forwarded to the underlying
|
: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:
|
if endpoint is None:
|
||||||
assert view_func is not None, 'expected view func if endpoint ' \
|
assert view_func is not None, 'expected view func if endpoint ' \
|
||||||
'is not provided.'
|
'is not provided.'
|
||||||
endpoint = view_func.__name__
|
endpoint = view_func.__name__
|
||||||
options['endpoint'] = endpoint
|
options['endpoint'] = endpoint
|
||||||
options.setdefault('methods', ('GET',))
|
methods = options.pop('methods', ('GET',))
|
||||||
self.url_map.add(Rule(rule, **options))
|
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:
|
if view_func is not None:
|
||||||
self.view_functions[endpoint] = view_func
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
|
|
@ -539,8 +554,10 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
:param rule: the URL rule as string
|
:param rule: the URL rule as string
|
||||||
:param methods: a list of methods this rule should be limited
|
:param methods: a list of methods this rule should be limited
|
||||||
to (``GET``, ``POST`` etc.). By default a rule
|
to (`GET`, `POST` etc.). By default a rule
|
||||||
just listens for ``GET`` (and implicitly ``HEAD``).
|
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
|
:param subdomain: specifies the rule for the subdomain in case
|
||||||
subdomain matching is in use.
|
subdomain matching is in use.
|
||||||
:param strict_slashes: can be used to disable the strict slashes
|
:param strict_slashes: can be used to disable the strict slashes
|
||||||
|
|
@ -650,7 +667,15 @@ class Flask(_PackageBoundObject):
|
||||||
try:
|
try:
|
||||||
if req.routing_exception is not None:
|
if req.routing_exception is not None:
|
||||||
raise req.routing_exception
|
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:
|
except HTTPException, e:
|
||||||
return self.handle_http_exception(e)
|
return self.handle_http_exception(e)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,9 @@ class _RequestContext(object):
|
||||||
self.flashes = None
|
self.flashes = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.request.endpoint, self.request.view_args = \
|
url_rule, self.request.view_args = \
|
||||||
self.url_adapter.match()
|
self.url_adapter.match(return_rule=True)
|
||||||
|
self.request.url_rule = url_rule
|
||||||
except HTTPException, e:
|
except HTTPException, e:
|
||||||
self.request.routing_exception = e
|
self.request.routing_exception = e
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import posixpath
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from time import time
|
from time import time
|
||||||
from zlib import adler32
|
from zlib import adler32
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
# try to load the best simplejson implementation available. If JSON
|
# try to load the best simplejson implementation available. If JSON
|
||||||
# is not installed, we add a failing class.
|
# is not installed, we add a failing class.
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ class Request(RequestBase):
|
||||||
:attr:`~flask.Flask.request_class` to your subclass.
|
:attr:`~flask.Flask.request_class` to your subclass.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: the endpoint that matched the request. This in combination with
|
#: the internal URL rule that matched the request. This can be
|
||||||
#: :attr:`view_args` can be used to reconstruct the same or a
|
#: useful to inspect which methods are allowed for the URL from
|
||||||
#: modified URL. If an exception happened when matching, this will
|
#: a before/after handler (``request.url_rule.methods``) etc.
|
||||||
#: be `None`.
|
#:
|
||||||
endpoint = None
|
#: .. versionadded:: 0.6
|
||||||
|
url_rule = None
|
||||||
|
|
||||||
#: a dict of view arguments that matched the request. If an exception
|
#: a dict of view arguments that matched the request. If an exception
|
||||||
#: happened when matching, this will be `None`.
|
#: happened when matching, this will be `None`.
|
||||||
|
|
@ -40,6 +41,16 @@ class Request(RequestBase):
|
||||||
#: something similar.
|
#: something similar.
|
||||||
routing_exception = None
|
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
|
@property
|
||||||
def module(self):
|
def module(self):
|
||||||
"""The name of the current module"""
|
"""The name of the current module"""
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,15 @@ class ContextTestCase(unittest.TestCase):
|
||||||
|
|
||||||
class BasicFunctionalityTestCase(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):
|
def test_request_dispatching(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
@ -124,7 +133,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/').data == 'GET'
|
assert c.get('/').data == 'GET'
|
||||||
rv = c.post('/')
|
rv = c.post('/')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||||
rv = c.head('/')
|
rv = c.head('/')
|
||||||
assert rv.status_code == 200
|
assert rv.status_code == 200
|
||||||
assert not rv.data # head truncates
|
assert not rv.data # head truncates
|
||||||
|
|
@ -132,7 +141,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/more').data == 'GET'
|
assert c.get('/more').data == 'GET'
|
||||||
rv = c.delete('/more')
|
rv = c.delete('/more')
|
||||||
assert rv.status_code == 405
|
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):
|
def test_url_mapping(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -148,7 +157,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/').data == 'GET'
|
assert c.get('/').data == 'GET'
|
||||||
rv = c.post('/')
|
rv = c.post('/')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||||
rv = c.head('/')
|
rv = c.head('/')
|
||||||
assert rv.status_code == 200
|
assert rv.status_code == 200
|
||||||
assert not rv.data # head truncates
|
assert not rv.data # head truncates
|
||||||
|
|
@ -156,7 +165,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/more').data == 'GET'
|
assert c.get('/more').data == 'GET'
|
||||||
rv = c.delete('/more')
|
rv = c.delete('/more')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||||
|
|
||||||
def test_session(self):
|
def test_session(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue