Views ===== A view function in chalice is the function attached to an ``@app.route()`` decorator. In the example below, ``index`` is the view function: .. code-block:: python from chalice import Chalice app = Chalice(app_name='helloworld') @app.route('/') def index(): return {'view': 'index'} View Function Parameters ------------------------ A view function's parameters correspond to the number of captured URL parameters specified in the ``@app.route`` call. In the example above, the route ``/`` specifies no captured parameters so the ``index`` view function accepts no parameters. However, in the view function below, a single URL parameter, ``{city}`` is specified, so the view function must accept a single parameter: .. code-block:: python from chalice import Chalice app = Chalice(app_name='helloworld') @app.route('/cities/{city}') def index(city): return {'city': city} This indicates that the value of ``{city}`` is variable, and whatever value is provided in the URL is passed to the ``index`` view function. For example:: GET /cities/seattle --> index('seattle') GET /cities/portland --> index('portland') If you want to access any other metdata of the incoming HTTP request, you can use the ``app.current_request`` property, which is an instance of the the :class:`Request` class. View Function Return Values --------------------------- The response returned back to the client depends on the behavior of the view function. There are several options available: * Returning an instance of :class:`Response`. This gives you complete control over what gets returned back to the customer. * A ``bytes`` type response body must have a ``Content-Type`` header value that is present in the ``app.api.binary_types`` list in order to be handled properly. * Any other return value will be serialized as JSON and sent back as the response body with content type ``application/json``. * Any subclass of ``ChaliceViewError`` will result in an HTTP response being returned with the status code associated with that response, and a JSON response body containing a ``Code`` and a ``Message``. This is discussed in more detail below. * Any other exception raised will result in a 500 HTTP response. The body of that response depends on whether debug mode is enabled. Error Handling -------------- Chalice provides a built in set of exception classes that map to common HTTP errors including: * ``BadRequestError``- returns a status code of 400 * ``UnauthorizedError``- returns a status code of 401 * ``ForbiddenError``- returns a status code of 403 * ``NotFoundError``- returns a status code of 404 * ``ConflictError``- returns a status code of 409 * ``TooManyRequestsError``- returns a status code of 429 * ``ChaliceViewError``- returns a status code of 500 You can raise these anywhere in your view functions and chalice will convert these to the appropriate HTTP response. The default chalice error responses will send the error back as ``application/json`` with the response body containing a ``Code`` corresponding to the exception class name and a ``Message`` key corresponding to the string provided when the exception was instantiated. For example: .. code-block:: python from chalice import Chalice from chalice import BadRequestError app = Chalice(app_name="badrequest") @app.route('/badrequest') def badrequest(): raise BadRequestError("This is a bad request") This view function will generate the following HTTP response:: $ http https://endpoint/api/badrequest HTTP/1.1 400 Bad Request { "Code": "BadRequestError", "Message": "This is a bad request" } In addition to the built in chalice exceptions, you can use the :class:`Response` class to customize the HTTP errors if you prefer to either not have JSON error responses or customize the JSON response body for errors. For example: .. code-block:: python from chalice import Chalice, Response app = Chalice(app_name="badrequest") @app.route('/badrequest') def badrequest(): return Response(body='Plain text error message', headers={'Content-Type': 'text/plain'}, status_code=400) Specifying HTTP Methods ----------------------- So far, our examples have only allowed GET requests. It's actually possible to support additional HTTP methods. Here's an example of a view function that supports PUT: .. code-block:: python @app.route('/resource/{value}', methods=['PUT']) def put_test(value): return {"value": value} We can test this method using the ``http`` command:: $ http PUT https://endpoint/api/resource/foo HTTP/1.1 200 OK { "value": "foo" } Note that the ``methods`` kwarg accepts a list of methods. Your view function will be called when any of the HTTP methods you specify are used for the specified resource. For example: .. code-block:: python @app.route('/myview', methods=['POST', 'PUT']) def myview(): pass The above view function will be called when either an HTTP POST or PUT is sent to ``/myview`` as shown below:: POST /myview --> myview() PUT /myview --> myview() Alternatively if you do not want to share the same view function across multiple HTTP methods for the same route url, you may define separate view functions to the same route url but have the view functions differ by HTTP method. For example: .. code-block:: python @app.route('/myview', methods=['POST']) def myview_post(): pass @app.route('/myview', methods=['PUT']) def myview_put(): pass This setup will route all HTTP POST's to ``/myview`` to the ``myview_post()`` view function and route all HTTP PUT's to ``/myview`` to the ``myview_put()`` view function as shown below:: POST /myview --> myview_post() PUT /myview --> myview_put() If you do chose to use separate view functions for the same route path, it is important to know: * View functions that share the same route cannot have the same names. For example, two view functions that both share the same route path cannot both be named ``view()``. * View functions that share the same route cannot overlap in supported HTTP methods. For example if two view function both share the same route path, they both cannot contain ``'PUT'`` in their route ``methods`` list. * View functions that share the same route path and have CORS configured cannot have differing CORS configuration. For example, if two view functions that both share the same route path, the route configuration for one of the view functions cannot set ``cors=True`` while having the route configuration of the other view function be set to ``cors=app.CORSConfig(allow_origin='https://foo.example.com')``. Binary Content -------------- Chalice supports binary payloads through its ``app.api.binary_types`` list. Any type in this list is considered a binary ``Content-Type``. Whenever a request with a ``Content-Type`` header is encountered that matches an entry in the ``binary_types`` list, its body will be available as a ``bytes`` type on the property ``app.current_request.raw_body``. Similarly, in order to send binary data back in a response, simply set your ``Content-Type`` header to something present in the ``binary_types`` list. Note that you can override the default types by modifying the ``app.api.binary_types`` list at the module level. Here is an example app which simply echos back binary content: .. code-block:: python from chalice import Chalice, Response app = Chalice(app_name="binary-response") @app.route('/bin-echo', methods=['POST'], content_types=['application/octet-stream']) def bin_echo(): raw_request_body = app.current_request.raw_body return Response(body=raw_request_body, status_code=200, headers={'Content-Type': 'application/octet-stream'}) You can see this app echo back binary data sent to it:: $ echo -n -e "\xFE\xED" | http POST $(chalice url)bin-echo \ Accept:application/octet-stream Content-Type:application/octet-stream | xxd 0000000: feed .. Note that both the ``Accept`` and ``Content-Type`` header are required. If you fail to set the ``Content-Type`` header on the request will result in a ``415 UnsupportedMediaType`` error. Care must be taken when configuring what ``content_types`` a route accepts, they must all be valid binary types, or they must all be non-binary types. The ``Accept`` header must also be set if the data returned is to be the raw binary, if is omitted the call return a ``400`` Bad Request response. For example, here is the same call as above without the ``Accept`` header:: $ echo -n -e "\xFE\xED" | http POST $(chalice url)bin-echo \ Content-Type:application/octet-stream HTTP/1.1 400 Bad Request Connection: keep-alive Content-Length: 270 Content-Type: application/json Date: Sat, 27 May 2017 07:09:51 GMT { "Code": "BadRequest", "Message": "Request did not specify an Accept header with application/octet-stream, The response has a Content-Type of application/octet-stream. If a response has a binary Content-Type then the request must specify an Accept header that matches." } Usage Recommendations --------------------- If you want to return a JSON response body, just return the corresponding python types directly. You don't need to use the :class:`Response` class. Chalice will automatically convert this to a JSON HTTP response as a convenience for you. Use the :class:`Response` class when you want to return non-JSON content, or when you want to inject custom HTTP headers to your response. For errors, raise the built in ``ChaliceViewError`` subclasses (e.g ``BadRequestError``, ``NotFoundError``, ``ConflictError`` etc) when you want to return a HTTP error response with a preconfigured JSON body containing a ``Code`` and ``Message``. Use the :class:`Response` class when you want to customize the error responses to either return a different JSON error response body, or to return an HTTP response that's not ``application/json``.