Falcon Documentation

Add to My manuals
70 Pages

advertisement

Falcon Documentation | Manualzz

Falcon Documentation, Release 0.2.0rc1

Why are trailing slashes trimmed from req.path?

Falcon normalizes incoming URI paths to simplify later processing and improve the predictability of application logic.

In addition to stripping a trailing slashes, if any, Falcon will convert empty paths to “/”.

Note also that routing is also normalized, so adding a route for “/foo/bar” also implicitly adds a route for “/foo/bar/”.

Requests coming in for either path will be sent to the same resource.

Why are field names in URI templates restricted to certain characters?

Field names are restricted to the ASCII characters in the set [a-zA-Z_]. Using a restricted set of characters allows the framework to make simplifying assumptions that reduce the overhead of parsing incoming requests.

Why is my query parameter missing from the req object?

If a query param does not have a value, Falcon will by default ignore that parameter. For example, passing ‘foo’ or

‘foo=’ will result in the parameter being ignored.

If you would like to recognize such parameters, you must set the keep_blank_qs_values request option to True.

Request options are set globally for each instance of falcon.API through the req_options attribute. For example: api .

req_options .

keep_blank_qs_values = True

5.2 User Guide

5.2.1 Introduction

Falcon is a minimalist, high-performance web framework for building RESTful services and app backends with

Python. Falcon works with any WSGI container that is compliant with PEP-3333, and works great with Python

2.6, Python 2.7, Python 3.3, Python 3.4 and PyPy, giving you a wide variety of deployment options.

How is Falcon different?

First, Falcon is one of the fastest WSGI frameworks available. When there is a conflict between saving the developer a few keystrokes and saving a few microseconds to serve a request, Falcon is strongly biased toward the latter. That being said, Falcon strives to strike a good balance between usability and speed.

Second, Falcon is lean. It doesn’t try to be everything to everyone, focusing instead on a single use case: HTTP

APIs. Falcon doesn’t include a template engine, form helpers, or an ORM (although those are easy enough to add yourself). When you sit down to write a web service with Falcon, you choose your own adventure in terms of async

I/O, serialization, data access, etc. In fact, Falcon only has two dependencies: six , to make it easier to support both

Python 2 and 3, and mimeparse for handling complex Accept headers. Neither of these packages pull in any further dependencies of their own.

Third, Falcon eschews magic. When you use the framework, it’s pretty obvious which inputs lead to which outputs.

Also, it’s blatantly obvious where variables originate. All this makes it easier to reason about the code and to debug edge cases in large-scale deployments of your application.

16 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

About Apache 2.0

Falcon is released under the terms of the Apache 2.0 License . This means that you can use it in your commercial applications without having to also open-source your own code. It also means that if someone happens to contribute code that is associated with a patent, you are granted a free license to use said patent. That’s a pretty sweet deal.

Now, if you do make changes to Falcon itself, please consider contributing your awesome work back to the community.

Falcon License

Copyright 2012 by Rackspace Hosting, Inc.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an

“AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

License for the specific language governing permissions and limitations under the License.

5.2.2 Installation

Install from PyPI

If available, Falcon will compile itself with Cython for an extra speed boost. The following will make sure Cython is installed first, and that you always have the latest and greatest.

$ pip install --upgrade cython falcon

Note that if you are running on PyPy, you won’t need Cython, so you can just type:

$ pip install --upgrade falcon

Installing Cython on OS X

In order to get Cython working on OS X Mavericks with Xcode 5.1, you will first need to set up Xcode Command

Line Tools. Install them with this command:

$ xcode-select --install

The Xcode 5.1 CLang compiler treats unrecognized command-line options as errors; this can cause problems under

Python 2.6, for example: clang: error: unknown argument: '-mno-fused-madd' [-Wunused-command-line-argument-hard-error-in-future]

You can work around errors caused by unused arguments by setting some environment variables:

$ export CFLAGS=-Qunused-arguments

$ export CPPFLAGS=-Qunused-arguments

$ pip install cython falcon

5.2. User Guide 17

Falcon Documentation, Release 0.2.0rc1

WSGI Server

Falcon speaks WSGI. If you want to actually serve a Falcon app, you will want a good WSGI server. Gunicorn and uWSGI are some of the more popular ones out there, but anything that can load a WSGI app will do. Gevent is an async library that works well with both Gunicorn and uWSGI.

$ pip install --upgrade gevent [gunicorn|uwsgi]

Source Code

Falcon lives on GitHub , making the code easy to browse, download, fork, etc. Pull requests are always welcome!

Also, please remember to star the project if it makes you happy.

Once you have cloned the repro or downloaded a tarball from GitHub, you can install Falcon like this:

$ cd falcon

$ pip install .

Or, if you want to edit the code, first fork the main repo, clone the fork to your desktop, and then run the following to install it using symbolic linking, so that when you change your code, the changes will be automagically available to your app without having to reinstall the package:

$ cd falcon

$ pip install -e .

Did we mention we love pull requests? :)

5.2.3 Quickstart

If you haven’t done so already, please take a moment to

install

the Falcon web framework before continuing.

The Big Picture

Learning by Example

Here is a simple example from Falcon’s README, showing how to get started writing an API:

# things.py

# Let's get this party started

import falcon

# Falcon follows the REST architectural style, meaning (among

# other things) that you think in terms of resources and state

# transitions, which map to HTTP verbs.

class ThingsResource

:

def

on_get ( self , req, resp):

"""Handles GET requests""" resp .

status = falcon .

HTTP_200 # This is the default status resp .

body = ( '\nTwo things awe me most, the starry sky '

'above me and the moral law within me.\n'

18 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

'\n'

' ~ Immanuel Kant\n\n' )

# falcon.API instances are callable WSGI apps app = falcon .

API()

# Resources are represented by long-lived class instances things = ThingsResource()

# things will handle all requests to the '/things' URL path app .

add_route( '/things' , things)

You can run the above example using any WSGI server, such as uWSGI or Gunicorn. For example:

$ pip install gunicorn

$ gunicorn things:app

Then, in another terminal:

$ curl localhost:8000/things

More Features

Here is a more involved example that demonstrates reading headers and query parameters, handling errors, and working with request and response bodies.

import json import logging import uuid from wsgiref import

simple_server

import falcon import requests class StorageEngine

( object ):

def

get_things ( self , marker, limit):

return

[{ 'id' : str (uuid .

uuid4()), 'color' : 'green' }]

def

add_thing ( self , thing): thing[ 'id' ] = str (uuid .

uuid4())

return

thing

class StorageError

( Exception ):

@staticmethod def

handle (ex, req, resp, params): description = ( 'Sorry, couldn\'t write your thing to the '

'database. It worked on my box.' )

raise

falcon .

HTTPError(falcon .

HTTP_725,

'Database Error' , description)

5.2. User Guide 19

Falcon Documentation, Release 0.2.0rc1

class SinkAdapter

( object ): engines = {

'ddg' : 'https://duckduckgo.com' ,

'y' : 'https://search.yahoo.com/search' ,

}

def

__call__ ( self , req, resp, engine): url = self .

engines[engine] params = { 'q' : req .

get_param( 'q' , True )} result = requests .

get(url, params = params) resp .

status = str (result .

status_code) + ' ' + result .

reason resp .

content_type = result .

headers[ 'content-type' ] resp .

body = result .

text

class AuthMiddleware

( object ):

def

process_request ( self , req, resp): token = req .

get_header( 'X-Auth-Token' ) project = req .

get_header( 'X-Project-ID' )

if

token

is

None : description = ( 'Please provide an auth token '

'as part of the request.' )

raise

falcon .

HTTPUnauthorized( 'Auth token required' , description, href = 'http://docs.example.com/auth' )

if not

self .

_token_is_valid(token, project): description = ( 'The provided auth token is not valid. '

'Please request a new token and try again.' )

raise

falcon .

HTTPUnauthorized( 'Authentication required' , description, href = 'http://docs.example.com/auth' , scheme = 'Token; UUID' )

def

_token_is_valid ( self , token, project):

return

True # Suuuuuure it's valid...

class RequireJSON

( object ):

def

process_request ( self , req, resp):

if not

req .

client_accepts_json:

raise

falcon .

HTTPNotAcceptable(

'This API only supports responses encoded as JSON.' , href = 'http://docs.examples.com/api/json' )

if

req .

method

in

( 'POST' , 'PUT' ):

if

'application/json'

not in

req .

content_type:

raise

falcon .

HTTPUnsupportedMediaType(

'This API only supports requests encoded as JSON.' , href = 'http://docs.examples.com/api/json' )

20 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

class JSONTranslator

( object ):

def

process_request ( self , req, resp):

# req.stream corresponds to the WSGI wsgi.input environ variable,

# and allows you to read bytes from the request body.

#

# See also: PEP 3333

if

req .

content_length

in

( None , 0 ):

# Nothing to do

return

body = req .

stream .

read()

if not

body:

raise

falcon .

HTTPBadRequest( 'Empty request body' ,

'A valid JSON document is required.' )

try

: req .

context[ 'doc' ] = json .

loads(body .

decode( 'utf-8' ))

except

( ValueError , UnicodeDecodeError ):

raise

falcon .

HTTPError(falcon .

HTTP_753,

'Malformed JSON' ,

'Could not decode the request body. The '

'JSON was incorrect or not encoded as '

'UTF-8.' )

def

process_response ( self , req, resp, resource):

if

'result'

not in

req .

context:

return

resp .

body = json .

dumps(req .

context[ 'result' ])

def

max_body (limit):

def

hook (req, resp, resource, params): length = req .

content_length

if

length

is not

None and length > limit: msg = ( 'The size of the request is too large. The body must not '

'exceed ' + str (limit) + ' bytes in length.' )

raise

falcon .

HTTPRequestEntityTooLarge(

'Request body is too large' , msg)

return

hook

class ThingsResource

:

def

__init__ ( self , db): self .

db = db self .

logger = logging .

getLogger( 'thingsapp.' + __name__)

def

on_get ( self , req, resp, user_id): marker = req .

get_param( 'marker' )

or

'' limit = req .

get_param_as_int( 'limit' )

or

50

try

:

5.2. User Guide 21

Falcon Documentation, Release 0.2.0rc1

result = self .

db .

get_things(marker, limit)

except

Exception as ex: self .

logger .

error(ex) description = ( 'Aliens have attacked our base! We will '

'be back as soon as we fight them off. '

'We appreciate your patience.' )

raise

falcon .

HTTPServiceUnavailable(

'Service Outage' , description,

30 )

# An alternative way of doing DRY serialization would be to

# create a custom class that inherits from falcon.Request. This

# class could, for example, have an additional 'doc' property

# that would serialize to JSON under the covers.

req .

context[ 'result' ] = result resp .

set_header( 'X-Powered-By' , 'Small Furry Creatures' ) resp .

status = falcon .

HTTP_200

@falcon.before

(max_body( 64

*

1024 ))

def

on_post ( self , req, resp, user_id):

try

: doc = req .

context[ 'doc' ]

except

KeyError :

raise

falcon .

HTTPBadRequest(

'Missing thing' ,

'A thing must be submitted in the request body.' ) proper_thing = self .

db .

add_thing(doc) resp .

status = falcon .

HTTP_201 resp .

location = '/ %s /things/ %s ' % (user_id, proper_thing[ 'id' ])

# Configure your WSGI server to load "things.app" (app is a WSGI callable) app = falcon .

API(middleware = [

AuthMiddleware(),

RequireJSON(),

JSONTranslator(),

]) db = StorageEngine() things = ThingsResource(db) app .

add_route( '/{user_id}/things' , things)

# If a responder ever raised an instance of StorageError, pass control to

# the given handler.

app .

add_error_handler(StorageError, StorageError .

handle)

# Proxy some things to another service; this example shows how you might

# send parts of an API off to a legacy system that hasn't been upgraded

# yet, or perhaps is a single cluster that all data centers have to share.

sink = SinkAdapter() app .

add_sink(sink, r'/search/(?P<engine>ddg|y)\Z' )

22 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

# Useful for debugging problems in your API; works with pdb.set_trace()

if

__name__ == '__main__' : httpd = simple_server .

make_server( '127.0.0.1' , 8000 , app) httpd .

serve_forever()

5.2.4 Tutorial

In this tutorial we’ll walk through building an API for a simple image sharing service. Along the way, we’ll discuss

Falcon’s major features and introduce the terminology used by the framework.

The Big Picture

First Steps

Before continuing, be sure you’ve got Falcon

installed . Then, create a new project folder called “look” and cd into it:

$ mkdir look

$ cd look

Next, let’s create a new file that will be the entry point into your app:

$ touch app.py

Open that file in your favorite text editor and add the following lines:

import falcon

api = application = falcon .

API()

That creates your WSGI application and aliases it as api. You can use any variable names you like, but we’ll use application since that is what Gunicorn expects it to be called, by default.

A WSGI application is just a callable with a well-defined signature so that you can host the application with any web server that understands the WSGI protocol . Let’s take a look at the falcon.API class.

First, install IPython (if you don’t already have it), and fire it up:

$ pip install ipython

$ ipython

Now, type the following to introspect the falcon.API callable:

In [1]: import falcon

In [2]: falcon.API.__call__?

Alternatively, you can use the built-in help function:

In [3]: help(falcon.API.__call__)

Note the method signature. env and start_response are standard WSGI params. Falcon adds a thin abstraction on top of these params so you don’t have to interact with them directly.

5.2. User Guide 23

Falcon Documentation, Release 0.2.0rc1

The Falcon framework contains extensive inline documentation that you can query using the above technique. The team has worked hard to optimize the docstrings for readability, so that you can quickly scan them and find what you need.

Tip: bpython is another super- powered REPL that is good to have in your toolbox when exploring a new library.

Hosting Your App

Now that you have a simple Falcon app, you can take it for a spin with a WSGI server. Python includes a reference server for self-hosting, but let’s use something that you would actually deploy in production.

$ pip install gunicorn

$ gunicorn app

Now try querying it with curl:

$ curl localhost:8000 -v

You should get a 404. That’s actually OK, because we haven’t specified any routes yet. Note that Falcon includes a default 404 response handler that will fire for any requested path that doesn’t match any routes.

Curl is a bit of a pain to use, so let’s install HTTPie and use it from now on.

$ pip install --upgrade httpie

$ http localhost:8000

Creating Resources

Falcon borrows some of its terminology from the REST architectural style, so if you are familiar with that mindset,

Falcon should be familiar. On the other hand, if you have no idea what REST is, no worries; Falcon was designed to be as intuitive as possible for anyone who understands the basics of HTTP.

In Falcon, you map incoming requests to things called “Resources”. A Resource is just a regular Python class that includes some methods that follow a certain naming convention. Each of these methods corresponds to an action that the API client can request be performed in order to fetch or transform the resource in question.

Since we are building an image-sharing API, let’s create an “images” resource. Create a new file, images.py within your project directory, and add the following to it:

import falcon class Resource

( object ):

def

on_get ( self , req, resp): resp .

body = '{"message": "Hello world!"}' resp .

status = falcon .

HTTP_200

As you can see, Resource is just a regular class. You can name the class anything you like. Falcon uses duck-typing, so you don’t need to inherit from any sort of special base class.

The image resource above defines a single method, on_get. For any HTTP method you want your resource to support, simply add an on_x class method to the resource, where x is any one of the standard HTTP methods, lowercased (e.g., on_get, on_put, on_head, etc.).

24 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

We call these well-known methods “responders”. Each responder takes (at least) two params, one representing the

HTTP request, and one representing the HTTP response to that request. By convention, these are called req and resp , respectively. Route templates and hooks can inject extra params, as we shall see later on.

Right now, the image resource responds to GET requests with a simple 200 OK and a JSON body. Falcon’s Internet media type defaults to application/json but you can set it to whatever you like. See serialization with

MessagePack for example:

def

on_get ( self , req, resp): resp .

data = msgpack .

packb({ 'message' : 'Hello world!' }) resp .

content_type = 'application/msgpack' resp .

status = falcon .

HTTP_200

Note the use of resp.data in lieu of resp.body. If you assign a bytestring to the latter, Falcon will figure it out, but you can get a little performance boost by assigning directly to resp.data.

OK, now let’s wire up this resource and see it in action. Go back to app.py and modify it so it looks something like this:

import falcon import images

api = application = falcon .

API() images = images .

Resource() api .

add_route( '/images' , images)

Now, when a request comes in for “/images”, Falcon will call the responder on the images resource that corresponds to the requested HTTP method.

Restart gunicorn, and then try sending a GET request to the resource:

$ http GET localhost:8000/images

Request and Response Objects

Each responder in a resource receives a request object that can be used to read the headers, query parameters, and body of the request. You can use the help function mentioned earlier to list the Request class members:

In [1]: import falcon

In [2]: help(falcon.Request)

Each responder also receives a response object that can be used for setting the status code, headers, and body of the response. You can list the Response class members using the same technique used above:

In [3]: help(falcon.Response)

Let’s see how this works. When a client POSTs to our images collection, we want to create a new image resource.

First, we’ll need to specify where the images will be saved (for a real service, you would want to use an object storage service instead, such as Cloud Files or S3).

Edit your images.py file and add the following to the resource:

def

__init__ ( self , storage_path): self .

storage_path = storage_path

5.2. User Guide 25

Falcon Documentation, Release 0.2.0rc1

Then, edit app.py and pass in a path to the resource initializer.

Next, let’s implement the POST responder:

import os import time import uuid import falcon def

_media_type_to_ext (media_type):

# Strip off the 'image/' prefix

return

media_type[ 6 :]

def

_generate_id ():

return

str (uuid .

uuid4())

class Resource

( object ):

def

__init__ ( self , storage_path): self .

storage_path = storage_path

def

on_post ( self , req, resp): image_id = _generate_id() ext = _media_type_to_ext(req .

content_type) filename = image_id + '.' + ext image_path = os .

path .

join( self .

storage_path, filename)

with

open (image_path, 'wb' )

as

image_file:

while

True : chunk = req .

stream .

read( 4096 )

if not

chunk:

break

image_file .

write(chunk) resp .

status = falcon .

HTTP_201 resp .

location = '/images/' + image_id

As you can see, we generate a unique ID and filename for the new image, and then write it out by reading from req.stream

. It’s called stream instead of body to emphasize the fact that you are really reading from an input stream; Falcon never spools or decodes request data, instead giving you direct access to the incoming binary stream provided by the WSGI server.

Note that we are setting the HTTP response status code to “201 Created”. For a full list of predefined status strings, simply call help on falcon.status_codes:

In [4]: help(falcon.status_codes)

The last line in the on_post responder sets the Location header for the newly created resource. (We will create a route for that path in just a minute.) Note that the Request and Response classes contain convenience attributes for reading and setting common headers, but you can always access any header by name with the req.get_header

and resp.set_header methods.

Restart gunicorn, and then try sending a POST request to the resource (substituting test.jpg for a path to any JPEG you like.)

26 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

$ http POST localhost:8000/images Content-Type:image/jpeg < test.jpg

Now, if you check your storage directory, it should contain a copy of the image you just POSTed.

Serving Images

Now that we have a way of getting images into the service, we need a way to get them back out. What we want to do is return an image when it is requested using the path that came back in the Location header, like so:

$ http GET localhost:8000/images/87db45ff42

Now, we could add an on_get responder to our images resource, and that is fine for simple resources like this, but that approach can lead to problems when you need to respond differently to the same HTTP method (e.g., GET), depending on whether the user wants to interact with a collection of things, or a single thing.

With that in mind, let’s create a separate class to represent a single image, as opposed to a collection of images. We will then add an on_get responder to the new class.

Go ahead and edit your images.py file to look something like this:

import os import time import uuid import falcon def

_media_type_to_ext (media_type):

# Strip off the 'image/' prefix

return

media_type[ 6 :]

def

_ext_to_media_type (ext):

return

'image/' + ext

def

_generate_id ():

return

str (uuid .

uuid4())

class Collection

( object ):

def

__init__ ( self , storage_path): self .

storage_path = storage_path

def

on_post ( self , req, resp): image_id = _generate_id() ext = _media_type_to_ext(req .

content_type) filename = image_id + '.' + ext image_path = os .

path .

join( self .

storage_path, filename)

with

open (image_path, 'wb' )

as

image_file:

while

True : chunk = req .

stream .

read( 4096 )

if not

chunk:

break

5.2. User Guide 27

Falcon Documentation, Release 0.2.0rc1

image_file .

write(chunk) resp .

status = falcon .

HTTP_201 resp .

location = '/images/' + filename

class Item

( object ):

def

__init__ ( self , storage_path): self .

storage_path = storage_path

def

on_get ( self , req, resp, name): ext = os .

path .

splitext(name)[ 1 ][ 1 :] resp .

content_type = _ext_to_media_type(ext) image_path = os .

path .

join( self .

storage_path, name) resp .

stream = open (image_path, 'rb' ) resp .

stream_len = os .

path .

getsize(image_path)

As you can see, we renamed Resource to Collection and added a new Item class to represent a single image resource. Also, note the name parameter for the on_get responder. Any URI parameters that you specify in your routes will be turned into corresponding kwargs and passed into the target responder as such. We’ll see how to specify

URI parameters in a moment.

Inside the on_get responder, we set the Content-Type header based on the filename extension, and then stream out the image directly from an open file handle. Note the use of resp.stream_len. Whenever using resp.stream

instead of resp.body or resp.data, you have to also specify the expected length of the stream so that the web client knows how much data to read from the response.

Note: If you do not know the size of the stream in advance, you can work around that by using chunked encoding, but that’s beyond the scope of this tutorial.

If resp.status is not set explicitly, it defaults to 200 OK, which is exactly what we want the on_get responder to do.

Now, let’s wire things up and give this a try. Go ahead and edit app.py to look something like this:

import falcon import images

api = application = falcon .

API() storage_path = '/usr/local/var/look' image_collection = images .

Collection(storage_path) image = images .

Item(storage_path) api .

add_route( '/images' , image_collection) api .

add_route( '/images/{name}' , image)

As you can see, we specified a new route, /images/{name}. This causes Falcon to expect all associated responders to accept a name argument.

28 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

Note: Falcon currently supports Level 1 URI templates , and support for higher levels is planned.

Now, restart gunicorn and post another picture to the service:

$ http POST localhost:8000/images Content-Type:image/jpeg < test.jpg

Make a note of the path returned in the Location header, and use it to try GETing the image:

$ http localhost:8000/images/6daa465b7b.jpeg

HTTPie won’t download the image by default, but you can see that the response headers were set correctly. Just for fun, go ahead and paste the above URI into your web browser. The image should display correctly.

Query Strings

Coming soon...

Introducing Hooks

At this point you should have a pretty good understanding of the basic parts that make up a Falcon-based API. Before we finish up, let’s just take a few minutes to clean up the code and add some error handling.

First of all, let’s check the incoming media type when something is posted to make sure it is a common image type.

We’ll do this by using a Falcon before hook.

First, let’s define a list of media types our service will accept. Place this constant near the top, just after the import statements in images.py:

ALLOWED_IMAGE_TYPES = (

'image/gif' ,

'image/jpeg' ,

'image/png' ,

)

The idea here is to only accept GIF, JPEG, and PNG images. You can add others to the list if you like.

Next, let’s create a hook that will run before each request to post a message. Add this method below the definition of

ALLOWED_IMAGE_TYPES

:

def

validate_image_type (req, resp, params):

if

req .

content_type

not in

ALLOWED_IMAGE_TYPES: msg = 'Image type not allowed. Must be PNG, JPEG, or GIF'

raise

falcon .

HTTPBadRequest( 'Bad request' , msg)

And then attach the hook to the on_post responder like so:

@falcon.before(validate_image_type) def on_post(self, req, resp):

Now, before every call to that responder, Falcon will first invoke the validate_image_type method. There isn’t anything special about that method, other than it must accept three arguments. Every hook takes, as its first two arguments, a reference to the same req and resp objects that are passed into responders. The third argument, named params by convention, is a reference to the kwarg dictionary Falcon creates for each request. params will contain the route’s URI template params and their values, if any.

5.2. User Guide 29

Falcon Documentation, Release 0.2.0rc1

As you can see in the example above, you can use req to get information about the incoming request. However, you can also use resp to play with the HTTP response as needed, and you can even inject extra kwargs for responders in a DRY way, e.g.,:

def

extract_project_id (req, resp, params):

"""Adds `project_id` to the list of params for all responders.

Meant to be used as a `before` hook.

""" params[ 'project_id' ] = req .

get_header( 'X-PROJECT-ID' )

Now, you can imagine that such a hook should apply to all responders for a resource, or even globally to all resources.

You can apply hooks to an entire resource like so:

@falcon.before(extract_project_id) class Message(object):

# ...

And you can apply hooks globally by passing them into the API class initializer: falcon .

API(before = [extract_project_id])

To learn more about hooks, take a look at the docstring for the API class, as well the docstrings for the falcon.before

and falcon.after decorators.

Now that you’ve added a hook to validate the media type when an image is POSTed, you can see it in action by passing in something nefarious:

$ http POST localhost:8000/images Content-Type:image/jpx < test.jpx

That should return a 400 Bad Request status and a nicely structured error body. When something goes wrong, you usually want to give your users some info to help them resolve the issue. The exception to this rule is when an error occurs because the user is requested something they are not authorized to access. In that case, you may wish to simply return 404 Not Found with an empty body, in case a malicious user is fishing for information that will help them crack your API.

Tip: Please take a look at our new sister project, Talons , for a collection of useful Falcon hooks contributed by the community. Also, If you create a nifty hook that you think others could use, please consider contributing to the project yourself.

Error Handling

Generally speaking, Falcon assumes that resource responders (on_get, on_post, etc.) will, for the most part, do the right thing. In other words, Falcon doesn’t try very hard to protect responder code from itself.

This approach reduces the number of (often) extraneous checks that Falcon would otherwise have to perform, making the framework more efficient. With that in mind, writing a high-quality API based on Falcon requires that:

1. Resource responders set response variables to sane values.

2. Your code is well-tested, with high code coverage.

3. Errors are anticipated, detected, and handled appropriately within each responder.

30 Chapter 5. Documentation

Falcon Documentation, Release 0.2.0rc1

Tip: Falcon will re-raise errors that do not inherit from falcon.HTTPError unless you have registered a custom error handler for that type (see also:

falcon.API

).

Speaking of error handling, when something goes horribly (or mildly) wrong, you could manually set the error status, appropriate response headers, and even an error body using the resp object. However, Falcon tries to make things a bit easier by providing a set of exceptions you can raise when something goes wrong. In fact, if Falcon catches any exception your responder throws that inherits from falcon.HTTPError, the framework will convert that exception to an appropriate HTTP error response.

You may raise an instance of falcon.HTTPError, or use any one of a number of predefined error classes that try to do “the right thing” in setting appropriate headers and bodies. Have a look at the docs for any of the following to get more information on how you can use them in your API: falcon .

HTTPBadGateway falcon .

HTTPBadRequest falcon .

HTTPConflict falcon .

HTTPError falcon .

HTTPForbidden falcon .

HTTPInternalServerError falcon .

HTTPLengthRequired falcon .

HTTPMethodNotAllowed falcon .

HTTPNotAcceptable falcon .

HTTPNotFound falcon .

HTTPPreconditionFailed falcon .

HTTPRangeNotSatisfiable falcon .

HTTPServiceUnavailable falcon .

HTTPUnauthorized falcon .

HTTPUnsupportedMediaType falcon .

HTTPUpgradeRequired

For example, you could handle a missing image file like this:

try

: resp .

stream = open (image_path, 'rb' )

except

IOError :

raise

falcon .

HTTPNotFound()

Or you could handle a bogus filename like this:

VALID_IMAGE_NAME = re .

compile( r'[a-f0-9]{10}\.(jpeg|gif|png)$' )

# ...

class Item

( object ):

def

__init__ ( self , storage_path): self .

storage_path = storage_path

def

on_get ( self , req, resp, name):

if not

VALID_IMAGE_NAME .

match(name):

raise

falcon .

HTTPNotFound()

Sometimes you don’t have much control over the type of exceptions that get raised. To address this, Falcon lets you create custom handlers for any type of error. For example, if your database throws exceptions that inherit from

NiftyDBError, you can install a special error handler just for NiftyDBError, so you don’t have to copy-paste your handler code across multiple responders.

Have a look at the docstring for falcon.API.add_error_handler for more information on using this feature

5.2. User Guide 31

advertisement

Related manuals

advertisement