codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=h1) Report
> Merging [#1722](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/4df46939c2ae11fe0b2625ef371f7eca959e0c2f&el=desc) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1722/graphs/tree.svg?width=650&height=150&src=pr&token=zOtkfNQ3h6)](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1722   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           49        49           
  Lines         3911      3939   +28     
  Branches       610       619    +9     
=========================================
+ Hits          3911      3939   +28     
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/asgi/app.py](https://codecov.io/gh/falconry/falcon/pull/1722/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvYXBwLnB5) | `100.00% <ø> (ø)` | |
| [falcon/middlewares.py](https://codecov.io/gh/falconry/falcon/pull/1722/diff?src=pr&el=tree#diff-ZmFsY29uL21pZGRsZXdhcmVzLnB5) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=footer). Last update [4df4693...7be9e95](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

CaselIT: I've updated the pr:
- improved the docs for allow_credentials
- rename allow_origin parameter to allow_origins, making it plural like the other parameters
- allow_credentials now can accept None (behaves like false) and a str (behaves like a list of one element), similar to the other parameters.


CaselIT: Hopefully I've not added other typos. Need to go towards the 🛌
vytas7: As someone also situated the CEST timezone, I have to agree wrt heading towards the :sleeping_bed: Same here.
kgriffs: Nice work!
diff --git a/docs/api/cors.rst b/docs/api/cors.rst
index 18e0e73..59b85cb 100644
--- a/docs/api/cors.rst
+++ b/docs/api/cors.rst
@@ -16,12 +16,17 @@ By default, Falcon's built-in CORS support is disabled, so that any cross-origin
 requests will be blocked by the browser. Passing ``cors_enable=True`` will
 cause the framework to include the necessary response headers to allow access
 from any origin to any route in the app. Individual responders may override this
-behavior by setting the Access-Control-Allow-Origin header explicitly.
+behavior by setting the ``Access-Control-Allow-Origin`` header explicitly.
 
 Whether or not you implement a CORS policy, we recommend also putting a robust
 AuthN/Z layer in place to authorize individual clients, as needed, to protect
 sensitive resources.
 
+Directly passing the :class:`falcon.CORSMiddleware` middleware to the application
+allows customization of the CORS policy applied. The middleware allows customizing
+the allowed origins, if credentials should be allowed and if additional headers
+can be exposed.
+
 Usage
 -----
 
@@ -36,6 +41,10 @@ Usage
             # Enable a simple CORS policy for all responses
             app = falcon.App(cors_enable=True)
 
+            # Enable CORS policy for example.com and allows credentials
+            app = falcon.App(middleware=falcon.CORSMiddleware(
+                allow_origins='example.com', allow_credentials='*'))
+
     .. tab:: ASGI
 
         .. code:: python
@@ -44,3 +53,12 @@ Usage
 
             # Enable a simple CORS policy for all responses
             app = falcon.asgi.App(cors_enable=True)
+
+            # Enable CORS policy for example.com and allows credentials
+            app = falcon.asgi.App(middleware=falcon.CORSMiddleware(
+                allow_origins='example.com', allow_credentials='*'))
+
+CORSMiddleware
+--------------
+
+.. autoclass:: falcon.CORSMiddleware
diff --git a/falcon/__init__.py b/falcon/__init__.py
index c979777..0596ab4 100644
--- a/falcon/__init__.py
+++ b/falcon/__init__.py
@@ -36,6 +36,7 @@ from falcon.errors import *  # NOQA
 from falcon.redirects import *  # NOQA
 from falcon.http_error import HTTPError  # NOQA
 from falcon.http_status import HTTPStatus  # NOQA
+from falcon.middlewares import CORSMiddleware  # NOQA
 
 # NOTE(kgriffs): Ensure that "from falcon import uri" will import
 # the same front-door module as "import falcon.uri". This works by
diff --git a/falcon/app.py b/falcon/app.py
index 015f638..72ed68d 100644
--- a/falcon/app.py
+++ b/falcon/app.py
@@ -152,7 +152,9 @@ class App:
 
         cors_enable (bool): Set this flag to ``True`` to enable a simple
             CORS policy for all responses, including support for preflighted
-            requests (default ``False``).
+            requests. An instance of :py:class:`~.CORSMiddleware` can instead be
+            passed to the middleware argument to customize its behaviour.
+            (default ``False``).
             (See also: :ref:`CORS <cors>`)
 
     Attributes:
diff --git a/falcon/asgi/app.py b/falcon/asgi/app.py
index 0e86d86..29d018c 100644
--- a/falcon/asgi/app.py
+++ b/falcon/asgi/app.py
@@ -212,7 +212,9 @@ class App(falcon.app.App):
 
         cors_enable (bool): Set this flag to ``True`` to enable a simple
             CORS policy for all responses, including support for preflighted
-            requests (default ``False``).
+            requests. An instance of :py:class:`..CORSMiddleware` can instead be
+            passed to the middleware argument to customize its behaviour.
+            (default ``False``).
             (See also: :ref:`CORS <cors>`)
 
     Attributes:
diff --git a/falcon/middlewares.py b/falcon/middlewares.py
index 37539d6..099a251 100644
--- a/falcon/middlewares.py
+++ b/falcon/middlewares.py
@@ -1,16 +1,101 @@
+from typing import Iterable, Optional, Union
+
+from .request import Request
+from .response import Response
+
 
 class CORSMiddleware(object):
-    def process_response(self, req, resp, resource, req_succeeded):
-        """Implement a simple blanket CORS policy for all routes.
+    """CORS Middleware
+
+    This middleware provides a simple out-of-the box CORS policy, including handling
+    of preflighted requests from the browser.
+
+    See also:
+
+    * https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+    * https://www.w3.org/TR/cors/#resource-processing-model
+
+    Keyword Arguments:
+        allow_origins (Union[str, Iterable[str]]): List of origins to allow (case
+            sensitive). The string ``'*'`` acts as a wildcard, matching every origin.
+            (default ``'*'``).
+        expose_headers (Optional[Union[str, Iterable[str]]]): List of additional response
+            headers to expose via the ``Access-Control-Expose-Headers`` header.
+            These headers are in addition to the CORS-safelisted ones:
+            ``Cache-Control``, ``Content-Language``, ``Content-Length``, ``Content-Type``,
+            ``Expires``, ``Last-Modified``, ``Pragma``. (default ``None``).
+
+            See also:
+            https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
+        allow_credentials (Optional[Union[str, Iterable[str]]]): List of origins (case sensitive)
+            for which to allow credentials via the ``Access-Control-Allow-Credentials`` header.
+            The string ``'*'`` acts as a wildcard, matching every allowed origin, while
+            ``None`` disallows all origins. This parameter takes effect only
+            if the origin is allowed by the ``allow_origins`` argument. (Default ``None``).
+
+    """
+    def __init__(
+        self,
+        allow_origins: Union[str, Iterable[str]] = '*',
+        expose_headers: Optional[Union[str, Iterable[str]]] = None,
+        allow_credentials: Optional[Union[str, Iterable[str]]] = None,
+    ):
+        if allow_origins == '*':
+            self.allow_origins = allow_origins
+        else:
+            if isinstance(allow_origins, str):
+                allow_origins = [allow_origins]
+            self.allow_origins = frozenset(allow_origins)
+            if '*' in self.allow_origins:
+                raise ValueError(
+                    'The wildcard string "*" may only be passed to allow_origins as a '
+                    'string literal, not inside an iterable.'
+                )
+
+        if expose_headers is not None and not isinstance(expose_headers, str):
+            expose_headers = ', '.join(expose_headers)
+        self.expose_headers = expose_headers
+
+        if allow_credentials is None:
+            allow_credentials = frozenset()
+        elif allow_credentials != '*':
+            if isinstance(allow_credentials, str):
+                allow_credentials = [allow_credentials]
+            allow_credentials = frozenset(allow_credentials)
+            if '*' in allow_credentials:
+                raise ValueError(
+                    'The wildcard string "*" may only be passed to allow_credentials as a '
+                    'string literal, not inside an iterable.'
+                )
+        self.allow_credentials = allow_credentials
+
+    def process_response(self, req: Request, resp: Response, resource, req_succeeded):
+        """Implement the CORS policy for all routes.
 
         This middleware provides a simple out-of-the box CORS policy,
         including handling of preflighted requests from the browser.
 
         See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+
+        See also: https://www.w3.org/TR/cors/#resource-processing-model
         """
 
+        origin = req.get_header('Origin')
+        if origin is None:
+            return
+
+        if self.allow_origins != '*' and origin not in self.allow_origins:
+            return
+
         if resp.get_header('Access-Control-Allow-Origin') is None:
-            resp.set_header('Access-Control-Allow-Origin', '*')
+            set_origin = '*' if self.allow_origins == '*' else origin
+            if self.allow_credentials == '*' or origin in self.allow_credentials:
+                set_origin = origin
+                resp.set_header('Access-Control-Allow-Credentials', 'true')
+            resp.set_header('Access-Control-Allow-Origin', set_origin)
+
+        if self.expose_headers:
+            resp.set_header('Access-Control-Expose-Headers', self.expose_headers)
 
         if (req_succeeded and
                 req.method == 'OPTIONS' and

Additional options to the internal cors middleware
The current cors middleware is a bit too limited for use in anything other than the most simple setups.

As discussed on gitter, I think we could add a bit more options, without complicating it too much.
In particular I think that the `App.cors_enable` flag should still add the middleware configured as is, but when manually adding it to the application more options should be exposed.
Options:
- `Access-Control-Allow-Origin`: default to '*', a str or iterable of strings. 
- `Access-Control-Expose-Headers`: default to not set, a str or iterable of strings.
- ` Access-Control-Allow-Credentials`: default to not set, True to always enable, an iterable of strings to match against the `Origin`.

Changes to the processing, ref https://www.w3.org/TR/cors/#resource-processing-model:
- Do nothing if the `Origin` header is not present.
- if  `Access-Control-Allow-Origin` is not '*', check if the `Origin` is in it. If not terminate
- If adding credential, set `Access-Control-Allow-Origin` to the origin
- Add `Access-Control-Expose-Headers` if defined
diff --git a/tests/test_cors_middleware.py b/tests/test_cors_middleware.py
new file mode 100644
index 0000000..d8ef5dc
--- /dev/null
+++ b/tests/test_cors_middleware.py
@@ -0,0 +1,201 @@
+import pytest
+
+import falcon
+from falcon import testing
+
+from _util import create_app, disable_asgi_non_coroutine_wrapping  # NOQA
+
+
+@pytest.fixture
+def client(asgi):
+    app = create_app(asgi)
+    return testing.TestClient(app)
+
+
+@pytest.fixture(scope='function')
+def cors_client(asgi):
+    # NOTE(kgriffs): Disable wrapping to test that built-in middleware does
+    #   not require it (since this will be the case for non-test apps).
+    with disable_asgi_non_coroutine_wrapping():
+        app = create_app(asgi, cors_enable=True)
+    return testing.TestClient(app)
+
+
+class CORSHeaderResource:
+
+    def on_get(self, req, resp):
+        resp.body = "I'm a CORS test response"
+
+    def on_delete(self, req, resp):
+        resp.set_header('Access-Control-Allow-Origin', 'example.com')
+        resp.body = "I'm a CORS test response"
+
+
+class TestCorsMiddleware:
+
+    def test_disabled_cors_should_not_add_any_extra_headers(self, client):
+        client.app.add_route('/', CORSHeaderResource())
+        result = client.simulate_get(headers={'Origin': 'localhost'})
+        h = dict(result.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Origin'.lower() not in h
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_enabled_cors_no_origin(self, client):
+        client.app.add_route('/', CORSHeaderResource())
+        result = client.simulate_get()
+        h = dict(result.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Origin'.lower() not in h
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_enabled_cors_should_add_extra_headers_on_response(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_get(headers={'Origin': 'localhost'})
+        assert 'Access-Control-Allow-Origin'.lower() in dict(
+            result.headers.lower_items()).keys()
+
+    def test_enabled_cors_should_accept_all_origins_requests(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+
+        result = cors_client.simulate_get(headers={'Origin': 'localhost'})
+        assert result.headers['Access-Control-Allow-Origin'] == '*'
+
+        result = cors_client.simulate_delete(headers={'Origin': 'localhost'})
+        assert result.headers['Access-Control-Allow-Origin'] == 'example.com'
+
+    def test_enabled_cors_handles_preflighting(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_options(headers=(
+            ('Origin', 'localhost'),
+            ('Access-Control-Request-Method', 'GET'),
+            ('Access-Control-Request-Headers', 'X-PINGOTHER, Content-Type'),
+        ))
+        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
+        assert result.headers['Access-Control-Allow-Headers'] == 'X-PINGOTHER, Content-Type'
+        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
+
+    def test_enabled_cors_handles_preflighting_no_headers_in_req(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_options(headers=(
+            ('Origin', 'localhost'),
+            ('Access-Control-Request-Method', 'POST'),
+        ))
+        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
+        assert result.headers['Access-Control-Allow-Headers'] == '*'
+        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
+
+
+@pytest.fixture(scope='function')
+def make_cors_client(asgi):
+    def make(middleware):
+        app = create_app(asgi, middleware=middleware)
+        return testing.TestClient(app)
+    return make
+
+
+class TestCustomCorsMiddleware:
+
+    def test_raises(self):
+        with pytest.raises(ValueError, match='passed to allow_origins'):
+            falcon.CORSMiddleware(allow_origins=['*'])
+        with pytest.raises(ValueError, match='passed to allow_credentials'):
+            falcon.CORSMiddleware(allow_credentials=['*'])
+
+    @pytest.mark.parametrize('allow, fail_origins, success_origins', (
+        ('*', [None], ['foo', 'bar']),
+        ('test', ['other', 'Test', 'TEST'], ['test']),
+        (['foo', 'bar'], ['foo, bar', 'foobar', 'foo,bar', 'Foo', 'BAR'], ['foo', 'bar']),
+    ))
+    def test_allow_origin(self, make_cors_client, allow, fail_origins, success_origins):
+        client = make_cors_client(falcon.CORSMiddleware(allow_origins=allow))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in fail_origins:
+            h = {'Origin': origin} if origin is not None else {}
+            res = client.simulate_get(headers=h)
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Origin'.lower() not in h
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        for origin in success_origins:
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == '*' if allow == '*' else origin
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_allow_credential_wildcard(self, make_cors_client):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_get(headers={'Origin': 'localhost'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'localhost'
+        assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+
+    @pytest.mark.parametrize('allow, successOrigin', (
+        (['foo', 'bar'], ['foo', 'bar']),
+        ('foo', ['foo']),
+    ))
+    def test_allow_credential_list_or_str(self, make_cors_client, allow, successOrigin):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials=allow))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in ('foo, bar', 'foobar', 'foo,bar', 'Foo', 'BAR'):
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == '*'
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        for origin in successOrigin:
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == origin
+            assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_allow_credential_existing_origin(self, make_cors_client):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_delete(headers={'Origin': 'something'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'example.com'
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+
+    def test_allow_origin_allow_credential(self, make_cors_client):
+        client = make_cors_client(
+            falcon.CORSMiddleware(allow_origins='test', allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in ['foo', 'TEST']:
+            res = client.simulate_get(headers={'Origin': origin})
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Origin'.lower() not in h
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        res = client.simulate_get(headers={'Origin': 'test'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'test'
+        assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    @pytest.mark.parametrize('attr, exp', (
+        ('foo', 'foo'),
+        ('foo, bar', 'foo, bar'),
+        (['foo', 'bar'], 'foo, bar'),
+    ))
+    def test_expose_headers(self, make_cors_client, attr, exp):
+        client = make_cors_client(
+            falcon.CORSMiddleware(expose_headers=attr, allow_credentials=None)
+        )
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_get(headers={'Origin': 'something'})
+        assert res.headers['Access-Control-Allow-Origin'] == '*'
+        assert res.headers['Access-Control-Expose-Headers'] == exp
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
diff --git a/tests/test_headers.py b/tests/test_headers.py
index d42efe3..41f23f8 100644
--- a/tests/test_headers.py
+++ b/tests/test_headers.py
@@ -6,7 +6,7 @@ import pytest
 import falcon
 from falcon import testing
 
-from _util import create_app, disable_asgi_non_coroutine_wrapping  # NOQA
+from _util import create_app  # NOQA
 
 
 SAMPLE_BODY = testing.rand_string(0, 128 * 1024)
@@ -18,15 +18,6 @@ def client(asgi):
     return testing.TestClient(app)
 
 
-@pytest.fixture(scope='function')
-def cors_client(asgi):
-    # NOTE(kgriffs): Disable wrapping to test that built-in middleware does
-    #   not require it (since this will be the case for non-test apps).
-    with disable_asgi_non_coroutine_wrapping():
-        app = create_app(asgi, cors_enable=True)
-    return testing.TestClient(app)
-
-
 class XmlResource:
     def __init__(self, content_type):
         self.content_type = content_type
@@ -245,16 +236,6 @@ class ExpiresHeaderResource:
         resp.expires = self._expires
 
 
-class CORSHeaderResource:
-
-    def on_get(self, req, resp):
-        resp.body = "I'm a CORS test response"
-
-    def on_delete(self, req, resp):
-        resp.set_header('Access-Control-Allow-Origin', 'example.com')
-        resp.body = "I'm a CORS test response"
-
-
 class CustomHeaders:
     def items(self):
         return [('test-header', 'test-value')]
@@ -769,46 +750,6 @@ class TestHeaders:
         content_length = str(len(falcon.HTTPNotFound().to_json()))
         assert result.headers['Content-Length'] == content_length
 
-    def test_disabled_cors_should_not_add_any_extra_headers(self, client):
-        client.app.add_route('/', CORSHeaderResource())
-        result = client.simulate_get()
-        assert 'Access-Control-Allow-Origin'.lower() not in dict(
-            result.headers.lower_items()).keys()
-
-    def test_enabled_cors_should_add_extra_headers_on_response(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_get()
-        assert 'Access-Control-Allow-Origin'.lower() in dict(
-            result.headers.lower_items()).keys()
-
-    def test_enabled_cors_should_accept_all_origins_requests(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-
-        result = cors_client.simulate_get()
-        assert result.headers['Access-Control-Allow-Origin'] == '*'
-
-        result = cors_client.simulate_delete()
-        assert result.headers['Access-Control-Allow-Origin'] == 'example.com'
-
-    def test_enabled_cors_handles_preflighting(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_options(headers=(
-            ('Access-Control-Request-Method', 'GET'),
-            ('Access-Control-Request-Headers', 'X-PINGOTHER, Content-Type'),
-        ))
-        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
-        assert result.headers['Access-Control-Allow-Headers'] == 'X-PINGOTHER, Content-Type'
-        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
-
-    def test_enabled_cors_handles_preflighting_no_headers_in_req(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_options(headers=(
-            ('Access-Control-Request-Method', 'POST'),
-        ))
-        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
-        assert result.headers['Access-Control-Allow-Headers'] == '*'
-        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
-
     def test_set_headers_with_custom_class(self, client):
         client.app.add_route('/', CustomHeadersResource())
 
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index e2a20de..612af84 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -957,7 +957,7 @@ class TestCORSMiddlewareWithAnotherMiddleware(TestMiddleware):
         app = create_app(asgi, middleware=mw, cors_enable=True)
         app.add_route('/', TestCorsResource())
         client = testing.TestClient(app)
-        result = client.simulate_get()
+        result = client.simulate_get(headers={'Origin': 'localhost'})
         assert result.headers['Access-Control-Allow-Origin'] == '*'
 
 

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc libunwind-dev
3.8
requirements/tests
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiofiles==24.1.0
anyio==4.5.2
asgiref==3.8.1
attrs==25.3.0
autobahn==23.1.2
Automat==24.8.1
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
constantly==23.10.4
coverage==7.6.1
cryptography==44.0.2
daphne==4.1.2
exceptiongroup==1.2.2
execnet==2.1.1
-e git+https://github.com/falconry/falcon.git@0fc91cbf028779cf0f7a8761dded149683429adb#egg=falcon
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
iniconfig==2.1.0
msgpack==1.1.0
mujson==1.4
orjson==3.10.15
packaging==24.2
pluggy==1.5.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
pyOpenSSL==25.0.0
pytest==8.3.5
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-rapidjson==1.20
PyYAML==6.0.2
requests==2.32.3
service-identity==24.2.0
sniffio==1.3.1
testtools==2.7.2
tomli==2.2.1
Twisted==24.11.0
txaio==23.1.1
typing_extensions==4.13.0
ujson==5.10.0
urllib3==2.2.3
uvicorn==0.33.0
zope.interface==7.2

name: falcon
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiofiles==24.1.0
      - anyio==4.5.2
      - asgiref==3.8.1
      - attrs==25.3.0
      - autobahn==23.1.2
      - automat==24.8.1
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - constantly==23.10.4
      - coverage==7.6.1
      - cryptography==44.0.2
      - daphne==4.1.2
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - falcon==3.0.0a1
      - h11==0.14.0
      - httpcore==1.0.7
      - httpx==0.28.1
      - hyperlink==21.0.0
      - idna==3.10
      - incremental==24.7.2
      - iniconfig==2.1.0
      - msgpack==1.1.0
      - mujson==1.4
      - orjson==3.10.15
      - packaging==24.2
      - pluggy==1.5.0
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycparser==2.22
      - pyopenssl==25.0.0
      - pytest==8.3.5
      - pytest-asyncio==0.24.0
      - pytest-cov==5.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-rapidjson==1.20
      - pyyaml==6.0.2
      - requests==2.32.3
      - service-identity==24.2.0
      - sniffio==1.3.1
      - testtools==2.7.2
      - tomli==2.2.1
      - twisted==24.11.0
      - txaio==23.1.1
      - typing-extensions==4.13.0
      - ujson==5.10.0
      - urllib3==2.2.3
      - uvicorn==0.33.0
      - zope-interface==7.2
prefix: /opt/conda/envs/falcon

tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_raises tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[wsgi-*-fail_origins0-success_origins0] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[wsgi-test-fail_origins1-success_origins1] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[wsgi-allow2-fail_origins2-success_origins2] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_wildcard[wsgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_list_or_str[wsgi-allow0-successOrigin0] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_list_or_str[wsgi-foo-successOrigin1] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_existing_origin[wsgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin_allow_credential[wsgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[wsgi-foo-foo] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[wsgi-foo, tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[wsgi-attr2-foo,
tests/test_headers.py::TestHeaders::test_override_default_media_type[text/plain; tests/test_headers.py::TestHeaders::test_override_default_media_type[text/plain-Hello tests/test_cors_middleware.py::TestCorsMiddleware::test_disabled_cors_should_not_add_any_extra_headers[asgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_no_origin[asgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_should_add_extra_headers_on_response[asgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_should_accept_all_origins_requests[asgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_handles_preflighting[asgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_handles_preflighting_no_headers_in_req[asgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[asgi-*-fail_origins0-success_origins0] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[asgi-test-fail_origins1-success_origins1] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin[asgi-allow2-fail_origins2-success_origins2] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_wildcard[asgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_list_or_str[asgi-allow0-successOrigin0] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_list_or_str[asgi-foo-successOrigin1] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_credential_existing_origin[asgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_allow_origin_allow_credential[asgi] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[asgi-foo-foo] tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[asgi-foo, tests/test_cors_middleware.py::TestCustomCorsMiddleware::test_expose_headers[asgi-attr2-foo, tests/test_headers.py::TestHeaders::test_content_length[asgi] tests/test_headers.py::TestHeaders::test_declared_content_length_on_head[asgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overridden_by_no_body[asgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overriden_by_body_length[asgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overriden_by_data_length[asgi] tests/test_headers.py::TestHeaders::test_expires_header[asgi] tests/test_headers.py::TestHeaders::test_default_value[asgi] tests/test_headers.py::TestHeaders::test_unset_header[asgi-True] tests/test_headers.py::TestHeaders::test_unset_header[asgi-False] tests/test_headers.py::TestHeaders::test_required_header[asgi] tests/test_headers.py::TestHeaders::test_no_content_length[asgi-204 tests/test_headers.py::TestHeaders::test_no_content_length[asgi-304 tests/test_headers.py::TestHeaders::test_passthrough_request_headers[asgi] tests/test_headers.py::TestHeaders::test_headers_as_list[asgi] tests/test_headers.py::TestHeaders::test_default_media_type[asgi] tests/test_headers.py::TestHeaders::test_override_default_media_type_missing_encoding[True] tests/test_headers.py::TestHeaders::test_response_header_helpers_on_get[asgi] tests/test_headers.py::TestHeaders::test_unicode_location_headers[asgi] tests/test_headers.py::TestHeaders::test_unicode_headers_convertable[asgi] tests/test_headers.py::TestHeaders::test_response_set_and_get_header[asgi] tests/test_headers.py::TestHeaders::test_response_append_header[asgi] tests/test_headers.py::TestHeaders::test_vary_star[asgi] tests/test_headers.py::TestHeaders::test_vary_header[asgi-vary0-accept-encoding] tests/test_headers.py::TestHeaders::test_vary_header[asgi-vary1-accept-encoding, tests/test_headers.py::TestHeaders::test_content_type_no_body[asgi] tests/test_headers.py::TestHeaders::test_no_content_type[asgi-204 tests/test_headers.py::TestHeaders::test_no_content_type[asgi-304 tests/test_headers.py::TestHeaders::test_custom_content_type[asgi] tests/test_headers.py::TestHeaders::test_add_link_single[asgi] tests/test_headers.py::TestHeaders::test_add_link_multiple[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_title[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_title_star[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_anchor[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_hreflang[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_hreflang_multi[asgi] tests/test_headers.py::TestHeaders::test_add_link_with_type_hint[asgi] tests/test_headers.py::TestHeaders::test_add_link_complex[asgi] tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-None-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-anonymous-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-Anonymous-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-AnOnYmOUs-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-Use-Credentials-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[asgi-use-credentials-</related/thing>; tests/test_headers.py::TestHeaders::test_set_headers_with_custom_class[asgi] tests/test_headers.py::TestHeaders::test_headers_with_custom_class_not_callable[asgi] tests/test_headers.py::TestHeaders::test_request_multiple_header[asgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_skip_process_resource[asgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_response_middleware_raises_exception[asgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_log_get_request[asgi-True] tests/test_middleware.py::TestRequestTimeMiddleware::test_log_get_request[asgi-False] tests/test_middleware.py::TestTransactionIdMiddleware::test_generate_trans_id_with_request[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_generate_trans_id_and_time_with_request[asgi-True] tests/test_middleware.py::TestSeveralMiddlewares::test_generate_trans_id_and_time_with_request[asgi-False] tests/test_middleware.py::TestSeveralMiddlewares::test_legacy_middleware_called_with_correct_args[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_middleware_execution_order[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_independent_middleware_execution_order[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_multiple_reponse_mw_throw_exception[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_throw_exception[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_throw_exception_while_processing_resp[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_with_ex_handler_throw_exception[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_outer_mw_with_ex_handler_throw_exception[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_resp[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_resp[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_req[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_req[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_rsrc[asgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_rsrc[asgi] tests/test_middleware.py::TestRemoveBasePathMiddleware::test_base_path_is_removed_before_routing[asgi] tests/test_middleware.py::TestResourceMiddleware::test_can_access_resource_params[asgi-True] tests/test_middleware.py::TestResourceMiddleware::test_can_access_resource_params[asgi-False] tests/test_middleware.py::TestEmptySignatureMiddleware::test_dont_need_params_in_signature[asgi] tests/test_middleware.py::TestErrorHandling::test_error_composed_before_resp_middleware_called[asgi] tests/test_middleware.py::TestErrorHandling::test_http_status_raised_from_error_handler[asgi] tests/test_middleware.py::TestShortCircuiting::test_process_request_not_cached[asgi] tests/test_middleware.py::TestShortCircuiting::test_process_request_cached[asgi-True] tests/test_middleware.py::TestShortCircuiting::test_process_request_cached[asgi-False] tests/test_middleware.py::TestShortCircuiting::test_process_resource_cached[asgi-True] tests/test_middleware.py::TestShortCircuiting::test_process_resource_cached[asgi-False] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[asgi-mw0] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[asgi-mw1] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[asgi-mw2] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[asgi-mw3]
tests/test_cors_middleware.py::TestCorsMiddleware::test_disabled_cors_should_not_add_any_extra_headers[wsgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_no_origin[wsgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_should_add_extra_headers_on_response[wsgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_should_accept_all_origins_requests[wsgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_handles_preflighting[wsgi] tests/test_cors_middleware.py::TestCorsMiddleware::test_enabled_cors_handles_preflighting_no_headers_in_req[wsgi] tests/test_headers.py::TestHeaders::test_content_length[wsgi] tests/test_headers.py::TestHeaders::test_declared_content_length_on_head[wsgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overridden_by_no_body[wsgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overriden_by_body_length[wsgi] tests/test_headers.py::TestHeaders::test_declared_content_length_overriden_by_data_length[wsgi] tests/test_headers.py::TestHeaders::test_expires_header[wsgi] tests/test_headers.py::TestHeaders::test_default_value[wsgi] tests/test_headers.py::TestHeaders::test_unset_header[wsgi-True] tests/test_headers.py::TestHeaders::test_unset_header[wsgi-False] tests/test_headers.py::TestHeaders::test_required_header[wsgi] tests/test_headers.py::TestHeaders::test_no_content_length[wsgi-204 tests/test_headers.py::TestHeaders::test_no_content_length[wsgi-304 tests/test_headers.py::TestHeaders::test_content_header_missing[asgi] tests/test_headers.py::TestHeaders::test_content_header_missing[wsgi] tests/test_headers.py::TestHeaders::test_passthrough_request_headers[wsgi] tests/test_headers.py::TestHeaders::test_headers_as_list[wsgi] tests/test_headers.py::TestHeaders::test_default_media_type[wsgi] tests/test_headers.py::TestHeaders::test_override_default_media_type_missing_encoding[False] tests/test_headers.py::TestHeaders::test_response_header_helpers_on_get[wsgi] tests/test_headers.py::TestHeaders::test_unicode_location_headers[wsgi] tests/test_headers.py::TestHeaders::test_unicode_headers_convertable[wsgi] tests/test_headers.py::TestHeaders::test_response_set_and_get_header[wsgi] tests/test_headers.py::TestHeaders::test_response_append_header[wsgi] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-ValueError-Set-Cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-ValueError-set-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-ValueError-seT-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-HeaderNotSupported-Set-Cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-HeaderNotSupported-set-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[asgi-HeaderNotSupported-seT-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-ValueError-Set-Cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-ValueError-set-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-ValueError-seT-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-HeaderNotSupported-Set-Cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-HeaderNotSupported-set-cookie] tests/test_headers.py::TestHeaders::test_set_cookie_disallowed[wsgi-HeaderNotSupported-seT-cookie] tests/test_headers.py::TestHeaders::test_vary_star[wsgi] tests/test_headers.py::TestHeaders::test_vary_header[wsgi-vary0-accept-encoding] tests/test_headers.py::TestHeaders::test_vary_header[wsgi-vary1-accept-encoding, tests/test_headers.py::TestHeaders::test_content_type_no_body[wsgi] tests/test_headers.py::TestHeaders::test_no_content_type[wsgi-204 tests/test_headers.py::TestHeaders::test_no_content_type[wsgi-304 tests/test_headers.py::TestHeaders::test_custom_content_type[wsgi] tests/test_headers.py::TestHeaders::test_add_link_single[wsgi] tests/test_headers.py::TestHeaders::test_add_link_multiple[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_title[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_title_star[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_anchor[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_hreflang[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_hreflang_multi[wsgi] tests/test_headers.py::TestHeaders::test_add_link_with_type_hint[wsgi] tests/test_headers.py::TestHeaders::test_add_link_complex[wsgi] tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-None-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-anonymous-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-Anonymous-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-AnOnYmOUs-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-Use-Credentials-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_crossorigin[wsgi-use-credentials-</related/thing>; tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[*] tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[Allow-all] tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[Lax] tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[MUST-REVALIDATE] tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[Strict] tests/test_headers.py::TestHeaders::test_add_link_invalid_crossorigin_value[deny] tests/test_headers.py::TestHeaders::test_content_length_options[asgi] tests/test_headers.py::TestHeaders::test_content_length_options[wsgi] tests/test_headers.py::TestHeaders::test_set_headers_with_custom_class[wsgi] tests/test_headers.py::TestHeaders::test_headers_with_custom_class_not_callable[wsgi] tests/test_headers.py::TestHeaders::test_request_multiple_header[wsgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_skip_process_resource[wsgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_add_invalid_middleware[asgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_add_invalid_middleware[wsgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_response_middleware_raises_exception[wsgi] tests/test_middleware.py::TestRequestTimeMiddleware::test_log_get_request[wsgi-True] tests/test_middleware.py::TestRequestTimeMiddleware::test_log_get_request[wsgi-False] tests/test_middleware.py::TestTransactionIdMiddleware::test_generate_trans_id_with_request[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_generate_trans_id_and_time_with_request[wsgi-True] tests/test_middleware.py::TestSeveralMiddlewares::test_generate_trans_id_and_time_with_request[wsgi-False] tests/test_middleware.py::TestSeveralMiddlewares::test_legacy_middleware_called_with_correct_args[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_middleware_execution_order[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_independent_middleware_execution_order[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_multiple_reponse_mw_throw_exception[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_throw_exception[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_throw_exception_while_processing_resp[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_inner_mw_with_ex_handler_throw_exception[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_outer_mw_with_ex_handler_throw_exception[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_resp[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_resp[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_req[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_req[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_rsrc[wsgi] tests/test_middleware.py::TestSeveralMiddlewares::test_order_independent_mw_executed_when_exception_in_rsrc[wsgi] tests/test_middleware.py::TestRemoveBasePathMiddleware::test_base_path_is_removed_before_routing[wsgi] tests/test_middleware.py::TestResourceMiddleware::test_can_access_resource_params[wsgi-True] tests/test_middleware.py::TestResourceMiddleware::test_can_access_resource_params[wsgi-False] tests/test_middleware.py::TestEmptySignatureMiddleware::test_dont_need_params_in_signature[wsgi] tests/test_middleware.py::TestErrorHandling::test_error_composed_before_resp_middleware_called[wsgi] tests/test_middleware.py::TestErrorHandling::test_http_status_raised_from_error_handler[wsgi] tests/test_middleware.py::TestShortCircuiting::test_process_request_not_cached[wsgi] tests/test_middleware.py::TestShortCircuiting::test_process_request_cached[wsgi-True] tests/test_middleware.py::TestShortCircuiting::test_process_request_cached[wsgi-False] tests/test_middleware.py::TestShortCircuiting::test_process_resource_cached[wsgi-True] tests/test_middleware.py::TestShortCircuiting::test_process_resource_cached[wsgi-False] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[wsgi-mw0] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[wsgi-mw1] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[wsgi-mw2] tests/test_middleware.py::TestCORSMiddlewareWithAnotherMiddleware::test_api_initialization_with_cors_enabled_and_middleware_param[wsgi-mw3] tests/test_middleware.py::test_async_postfix_method_must_be_coroutine
falcon/__init__.py falcon/middlewares.py falcon/app.py docs/api/cors.rst falcon/asgi/app.py
falcon/__init__.py falcon/middlewares.py falcon/app.py docs/api/cors.rst falcon/asgi/app.py