Repository: encode/starlette

Issue:
Config/Settings dump?
What's the best way to dump all of the settings using starlette.config? I want to create an endpoint to make it easy to review the settings being used but I don't want the Secrets to be viewable, e.g. things like:

    JWT_SECRET = config("JWT_SECRET", cast=starlette.datastructures.Secret)

We are currently using:

```
@router.get("/settings", tags=["Info"])
def get_settings() -> dict:
    dctconfig = dict(settings.config)
    return dctconfig
```
But it shows the the secrets (instead of Secret('**********')) along with everything else.


Discussion:

Patch:
diff --git a/docs/config.md b/docs/config.md
index baa9e38..7c5b2a6 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -9,13 +9,13 @@ that is not committed to source control.
 ```python
 from starlette.applications import Starlette
 from starlette.config import Config
-from starlette.datastructures import CommaSeparatedStrings, DatabaseURL, Secret
+from starlette.datastructures import CommaSeparatedStrings, Secret
 
 # Config will be read from environment variables and/or ".env" files.
 config = Config(".env")
 
 DEBUG = config('DEBUG', cast=bool, default=False)
-DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL)
+DATABASE_URL = config('DATABASE_URL', cast=URL)
 SECRET_KEY = config('SECRET_KEY', cast=Secret)
 ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)
 
@@ -62,7 +62,7 @@ Secret('**********')
 '98n349$%8b8-7yjn0n8y93T$23r'
 ```
 
-Similarly, the `URL` and `DatabaseURL` class will hide any password component
+Similarly, the `URL` class will hide any password component
 in their representations.
 
 ```python
@@ -128,7 +128,7 @@ application logic separated:
 
 ```python
 from starlette.config import Config
-from starlette.datastructures import DatabaseURL, Secret
+from starlette.datastructures import URL, Secret
 
 config = Config(".env")
 
@@ -136,7 +136,7 @@ DEBUG = config('DEBUG', cast=bool, default=False)
 TESTING = config('TESTING', cast=bool, default=False)
 SECRET_KEY = config('SECRET_KEY', cast=Secret)
 
-DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL)
+DATABASE_URL = config('DATABASE_URL', cast=URL)
 if TESTING:
     DATABASE_URL = DATABASE_URL.replace(database='test_' + DATABASE_URL.database)
 ```
@@ -180,6 +180,8 @@ app.add_middleware(
 @app.route('/', methods=['GET'])
 async def homepage(request):
     ...
+
+
 ```
 
 Now let's deal with our test configuration.
diff --git a/docs/database.md b/docs/database.md
index 5bab16d..39e605b 100644
--- a/docs/database.md
+++ b/docs/database.md
@@ -186,7 +186,7 @@ import pytest
 from starlette.config import environ
 from starlette.testclient import TestClient
 from sqlalchemy import create_engine
-from sqlalchemy_utils import database_exists, create_database
+from sqlalchemy_utils import database_exists, create_database, drop_database
 
 # This sets `os.environ`, but provides some additional protection.
 # If we placed it below the application import, it would raise an error
diff --git a/docs/requests.md b/docs/requests.md
index 317699b..7df7733 100644
--- a/docs/requests.md
+++ b/docs/requests.md
@@ -8,7 +8,7 @@ Signature: `Request(scope, receive=None)`
 
 ```python
 from starlette.requests import Request
-from starlette.response import Response
+from starlette.responses import Response
 
 
 class App:
diff --git a/docs/third-party-packages.md b/docs/third-party-packages.md
index 2153b7c..7869220 100644
--- a/docs/third-party-packages.md
+++ b/docs/third-party-packages.md
@@ -3,85 +3,29 @@ Starlette has a rapidly growing community of developers, building tools that int
 
 Here are some of those third party packages:
 
+
+## Backports
+
+### Python 3.5 port
+
+<a href="https://github.com/em92/starlette" target="_blank">Github</a>
+
 ## Plugins
 
 ### Starlette APISpec
 
 <a href="https://github.com/Woile/starlette-apispec" target="_blank">Github</a>
 
-Easy APISpec integration for Starlette.
-
-Document your REST API built with Starlette by declaring OpenAPI (Swagger) schemas in YAML format in your endpoints' docstrings.
+Simple APISpec integration for Starlette.
+Document your REST API built with Starlette by declaring OpenAPI (Swagger)
+schemas in YAML format in your endpoint's docstrings.
 
 ### Starlette API
 
 <a href="https://github.com/PeRDy/starlette-api" target="_blank">Github</a>
 
-That library aims to bring a layer on top of Starlette framework to provide useful mechanism for building APIs. It's
-based on API Star, inheriting some nice ideas like:
-
-* **Schema system** based on [Marshmallow](https://github.com/marshmallow-code/marshmallow/) that allows to **declare**
-the inputs and outputs of endpoints and provides a reliable way of **validate** data against those schemas.
-* **Dependency Injection** that ease the process of managing parameters needed in endpoints.
-* **Components** as the base of the plugin ecosystem, allowing you to create custom or use those already defined in
-your endpoints, injected as parameters.
-* **Starlette ASGI** objects like `Request`, `Response`, `Session` and so on are defined as components and ready to be
-injected in your endpoints.
-* **Auto generated API schema** using OpenAPI standard. It uses the schema system of your endpoints to extract all the
-necessary information to generate your API Schema.
-* **Auto generated docs** providing a [Swagger UI](https://swagger.io/tools/swagger-ui/) or
-[ReDocs](https://rebilly.github.io/ReDoc/) endpoint.
-
-
-```python
-from marshmallow import Schema, fields, validate
-from starlette_api.applications import Starlette
-
-
-# Data Schema
-class Puppy(Schema):
-    id = fields.Integer()
-    name = fields.String()
-    age = fields.Integer(validate=validate.Range(min=0))
-
-
-# Database
-puppies = [
-    {"id": 1, "name": "Canna", "age": 6},
-    {"id": 2, "name": "Sandy", "age": 12},
-]
-
-
-# Application
-app = Starlette(
-    components=[],      # Without custom components
-    title="Foo",        # API title
-    version="0.1",      # API version
-    description="Bar",  # API description
-    schema="/schema/",  # Path to expose OpenAPI schema
-    docs="/docs/",      # Path to expose Swagger UI docs
-    redoc="/redoc/",    # Path to expose ReDoc docs
-)
-
-
-# Views
-@app.route("/", methods=["GET"])
-def list_puppies(name: str = None) -> Puppy(many=True):
-    """
-    List the puppies collection. There is an optional query parameter that
-    specifies a name for filtering the collection based on it.
-
-    Request example:
-    GET http://example.com/?name=Sandy
-
-    Response example:
-    200
-    [
-        {"id": 2, "name": "Sandy", "age": 12}
-    ]
-    """
-    return [puppy for puppy in puppies if puppy["name"] == name]
-```
+That library aims to bring a layer on top of Starlette framework to provide useful mechanism for building APIs. Based on API Star. Some featuers: marshmallow schemas, dependency injection, auto generated api schemas,
+auto generated docs.
 
 ### webargs-starlette
 
@@ -93,84 +37,17 @@ of [webargs](https://github.com/marshmallow-code/webargs).
 Allows you to parse querystring, JSON, form, headers, and cookies using
 type annotations.
 
-```python
-import uvicorn
-from starlette.applications import Starlette
-from starlette.responses import JSONResponse
-from webargs_starlette import use_annotations
-
-app = Starlette()
-
-
-@app.route("/")
-@use_annotations(locations=("query",))
-async def index(request, name: str = "World"):
-    return JSONResponse({"Hello": name})
-
-
-if __name__ == "__main__":
-    uvicorn.run(app, port=5000)
-
-# curl 'http://localhost:5000/'
-# {"Hello": "World"}
-# curl 'http://localhost:5000/?name=Ada'
-# {"Hello": "Ada"}
-```
-
 ### Mangum
 
 <a href="https://github.com/erm/mangum" target="_blank">Github</a>
 
 Serverless ASGI adapter for AWS Lambda & API Gateway.
 
-```Python
-from starlette.applications import Starlette
-from starlette.responses import PlainTextResponse
-from mangum import Mangum
-
-
-app = Starlette()
-
-
-@app.route("/")
-def homepage(request):
-    return PlainTextResponse("Hello, world!")
-
-
-handler = Mangum(app)  # optionally set debug=True
-```
-
 ### Nejma ⭐
 
 <a href="https://github.com/taoufik07/nejma" target="_blank">Github</a>
 
-Helps you manage and send messages to groups of channels.
-
-```python
-
-from nejma.ext.starlette import WebSocketEndpoint
-
-@app.websocket_route("/ws")
-class Chat(WebSocketEndpoint):
-    encoding = "json"
-
-    async def on_receive(self, websocket, data):
-        room_id = data['room_id']
-        message = data['message']
-        username = data['username']
-
-        if message.strip():
-            group = f"group_{room_id}"
-
-            self.channel_layer.add(group, self.channel)
-
-            payload = {
-                "username": username,
-                "message": message,
-                "room_id": room_id
-            }
-            await self.channel_layer.group_send(group, payload)
-```
+Manage and send messages to groups of channels using websockets.
 Checkout <a href="https://github.com/taoufik07/nejma-chat" target="_blank">nejma-chat</a>, a simple chat application built using `nejma` and `starlette`.
 
 ## Frameworks
@@ -180,81 +57,16 @@ Checkout <a href="https://github.com/taoufik07/nejma-chat" target="_blank">nejma
 <a href="https://github.com/kennethreitz/responder" target="_blank">Github</a> |
 <a href="https://python-responder.org/en/latest/" target="_blank">Documentation</a>
 
-A familiar HTTP Service Framework for Python.
-
-* Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
-* Falcon's "every request and response is passed into to each view and mutated" methodology.
-* Support for YAML by default.
-* Several of Starlette's optional dependencies pre-installed, like:
-    * Production static file server.
-    * Uvicorn server.
-    * GraphQL support, via Graphene.
-
-```Python
-import responder
-
-api = responder.API()
-
-@api.route("/{greeting}")
-async def greet_world(req, resp, *, greeting):
-    resp.text = f"{greeting}, world!"
-
-if __name__ == '__main__':
-    api.run()
-```
+Async web service framework. Some Features: flask-style route expression,
+yaml support, OpenAPI schema generation, background tasks, graphql.
 
 ### FastAPI
 
 <a href="https://github.com/tiangolo/fastapi" target="_blank">Github</a> |
 <a href="https://fastapi.tiangolo.com/" target="_blank">Documentation</a>
 
-High performance, easy to learn, fast to code, ready for production.
-
-An API framework inspired by **APIStar**'s previous server system with type declarations for route parameters, based on the OpenAPI specification version 3.0.0+ (with JSON Schema), powered by **Pydantic** for the data handling.
-
-Use standard Python 3.6+ types as parameters to get:
-
-* Autocomplete everywhere.
-* Data conversion.
-* Data validation.
-* Automatic documentation with OpenAPI (and JSON Schema), based on the same Python types.
-
-Includes:
-
-* A simple but powerful **dependency injection** system.
-* Automatic interactive documentation (based on Swagger UI and ReDoc).
-* Security utilities, including **OAuth2** with **JWT tokens**.
-
-```Python
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-def read_root():
-    return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-def read_item(item_id: int, q: str = None):
-    return {"item_id": item_id, "q": q}
-```
-
-This code declares a path `/`, and a path `/items/{item_id}`.
-
-The path for `/items/{item_id}` has:
-
-* A path parameter `item_id` that will be validated as an `int`, with automatic errors when invalid.
-* An optional (by default `None`) query parameter `q` of type `str`.
-
-Calling `http://example.com/items/2?q=myquery` will return:
-
-```JSON
-{"item_id": 2, "q": "myquery"}
-```
-
-The same way you can declare JSON body schemas, headers, etc.
+High performance, easy to learn, fast to code, ready for production web API framework.
+Inspired by **APIStar**'s previous server system with type declarations for route parameters, based on the OpenAPI specification version 3.0.0+ (with JSON Schema), powered by **Pydantic** for the data handling.
 
 ### Bocadillo
 
@@ -262,30 +74,4 @@ The same way you can declare JSON body schemas, headers, etc.
 <a href="https://bocadilloproject.github.io" target="_blank">Documentation</a>
 
 A modern Python web framework filled with asynchronous salsa.
-
 Bocadillo is **async-first** and designed with productivity and simplicity in mind. It is not meant to be minimal: a **carefully chosen set of included batteries** helps you build performant web apps and services with minimal setup.
-
-Key features include:
-
-* Simple, powerful and familiar views and routing, inspired by the greatest (Flask, Falcon).
-* First-class support for both HTTP / REST and WebSocket.
-* Built-in CORS, HSTS, GZip, HTTP streaming, Jinja2 templates, background tasks, static files…
-
-… and more ahead, as depicted in the <a href="https://github.com/bocadilloproject/bocadillo/blob/master/ROADMAP.md" target="_blank">Roadmap</a>.
-
-The example below demonstrates a simple WebSocket echo server.
-
-```python
-from bocadillo import API, WebSocket
-
-api = API()
-
-@api.websocket_route("/echo")
-async def echo(ws: WebSocket):
-    async with ws:
-        async for message in ws:
-            await ws.send(message)
-
-if __name__ == "__main__":
-    api.run()
-```
diff --git a/starlette/middleware/cors.py b/starlette/middleware/cors.py
index 5021714..076708a 100644
--- a/starlette/middleware/cors.py
+++ b/starlette/middleware/cors.py
@@ -56,7 +56,7 @@ class CORSMiddleware:
         self.app = app
         self.allow_origins = allow_origins
         self.allow_methods = allow_methods
-        self.allow_headers = allow_headers
+        self.allow_headers = [h.lower() for h in allow_headers]
         self.allow_all_origins = "*" in allow_origins
         self.allow_all_headers = "*" in allow_headers
         self.allow_origin_regex = compiled_allow_origin_regex
@@ -117,7 +117,7 @@ class CORSMiddleware:
         if self.allow_all_headers and requested_headers is not None:
             headers["Access-Control-Allow-Headers"] = requested_headers
         elif requested_headers is not None:
-            for header in requested_headers.split(","):
+            for header in [h.lower() for h in requested_headers.split(",")]:
                 if header.strip() not in self.allow_headers:
                     failures.append("headers")
 


Test patch:
diff --git a/tests/middleware/test_cors.py b/tests/middleware/test_cors.py
index ddd0028..ecd32d2 100644
--- a/tests/middleware/test_cors.py
+++ b/tests/middleware/test_cors.py
@@ -55,7 +55,7 @@ def test_cors_allow_specific_origin():
     app.add_middleware(
         CORSMiddleware,
         allow_origins=["https://example.org"],
-        allow_headers=["X-Example"],
+        allow_headers=["X-Example", "Content-Type"],
     )
 
     @app.route("/")
@@ -68,13 +68,13 @@ def test_cors_allow_specific_origin():
     headers = {
         "Origin": "https://example.org",
         "Access-Control-Request-Method": "GET",
-        "Access-Control-Request-Headers": "X-Example",
+        "Access-Control-Request-Headers": "X-Example, Content-Type",
     }
     response = client.options("/", headers=headers)
     assert response.status_code == 200
     assert response.text == "OK"
     assert response.headers["access-control-allow-origin"] == "https://example.org"
-    assert response.headers["access-control-allow-headers"] == "X-Example"
+    assert response.headers["access-control-allow-headers"] == "X-Example, Content-Type"
 
     # Test standard response
     headers = {"Origin": "https://example.org"}
@@ -120,7 +120,9 @@ def test_cors_allow_origin_regex():
     app = Starlette()
 
     app.add_middleware(
-        CORSMiddleware, allow_headers=["X-Example"], allow_origin_regex="https://*"
+        CORSMiddleware,
+        allow_headers=["X-Example", "Content-Type"],
+        allow_origin_regex="https://*",
     )
 
     @app.route("/")
@@ -149,13 +151,13 @@ def test_cors_allow_origin_regex():
     headers = {
         "Origin": "https://another.com",
         "Access-Control-Request-Method": "GET",
-        "Access-Control-Request-Headers": "X-Example",
+        "Access-Control-Request-Headers": "X-Example, content-type",
     }
     response = client.options("/", headers=headers)
     assert response.status_code == 200
     assert response.text == "OK"
     assert response.headers["access-control-allow-origin"] == "https://another.com"
-    assert response.headers["access-control-allow-headers"] == "X-Example"
+    assert response.headers["access-control-allow-headers"] == "X-Example, Content-Type"
 
     # Test disallowed pre-flight response
     headers = {