Using flask to build RESTful APIs server side

Source: Internet
Author: User
Tags http authentication virtual environment virtualenv

This article was excerpted from: http://www.cnblogs.com/vovlie/p/4178077.html

In recent years, rest has become a standard architecture for Web services and APIs, and many app architectures are basically using restful forms.

This article will use the Python flask framework to easily implement a restful service.

Six Properties of rest:
    • Client-server: Server-side separation from client.
    • Stateless (stateless): Each client request must contain complete information, in other words, each request is independent.
    • Cacheable (cacheable): Server-side must specify which requests can be cached.
    • Layered System (Hierarchy): Server-side communication with clients must be standardized, and changes to the server do not affect the client.
    • Uniform Interface (Unified interface): The client-to-server communication method must be unified.
    • Code on Demand (do you want to execute codes on demand?) ): Can the server side execute code or script in context?

Servers can provide executable code or scripts for clients to execute in their context. This constraint are the only one, which is optional. (not seen)

What a RESTful Web service looks like

The rest architecture is designed for the HTTP protocol. The core concept of RESTful Web Services is managing resources. The resource is represented by URIs, and the client uses the HTTP ' POST, OPTIONS, GET, PUT, DELETE' method to send the request to the server and change the corresponding resource state.

The HTTP request method is often also appropriate to describe the action of the Operation resource:

HTTP method Action Example
GET Get resource information

Http://example.com/api/orders

(Retrieve order list)

GET Get resource information

Http://example.com/api/orders/123

(Retrieve order #123)

POST Create a secondary resource

Http://example.com/api/orders

(Use a request with data to create a new order)

PUT Update a resource

Http://example.com/api/orders/123

(Use request with data, update #123 order)

DELETE Delete a resource

Http://example.com/api/orders/123

Delete Order #123

A rest request does not require a specific data format, usually using JSON as the request body, or as part of the query parameters of the URL.

Design a simple Web service

The following tasks will practice the design of the rest guidelines as guidelines for manipulating resources through different request methods, identifying examples of resources.

We will write a to-do List application and design a Web service. The first step is to plan a root URL, for example:

http://[hostname]/todo/api/v1.0/

The URL above includes the name of the application, the API version, which is useful in providing namespace partitioning while differentiating from other systems. The version number is useful when upgrading new features, and does not affect older versions when a new feature is added below the new version.

The second step, planning the URL of the resource, this example is very simple, only the task list.

The plan is as follows:

HTTP method Uri Action
GET Http://[hostname]/todo/api/v1.0/tasks Retrieving a task List
GET HTTP://[HOSTNAME]/TODO/API/V1.0/TASKS/[TASK_ID] Retrieving a task
POST Http://[hostname]/todo/api/v1.0/tasks Create a new task
PUT HTTP://[HOSTNAME]/TODO/API/V1.0/TASKS/[TASK_ID] Update a task that already exists
DELETE HTTP://[HOSTNAME]/TODO/API/V1.0/TASKS/[TASK_ID] Delete a task

We define a task list with the following fields:

    • ID: Unique identity. Integral type.
    • Title: Short description of the task. String type.
    • Description: Complete description of the task. Text type.
    • Done: Task completion status. A Boolean-valued type.

The above basically completed the design part, next we will realize it!

Simple understanding of the flask framework

Flask is a good simple, but powerful Python web framework. Here is a series of tutorials flask mega-tutorial. (Note: django\tornado\web.py feel a lot of boxes: ()

Before we dive into the Web service, let's simply look at an example of the structure of a flask Web application.

Here are the demos below the Unix-like (Linux,mac OS X) operating system, but other systems can also run, such as the Cygwin under Windows. Maybe the order is a little different. (Note: Ignore Windows.) )

Use virtualenv to install a flask virtual environment first. If you do not have virtualenv installed, it is best to download and install the Python essentials. Https://pypi.python.org/pypi/virtualenv

$ mkdir todo-api$ cd todo-api$ virtualenv flasknew python executable in flask/bin/pythoninstalling setuptools ..... ........ done. Installing pip...................done.$ flask/bin/pip Install flask

This completes a flask development environment, starts to create a simple Web application, creates a app.py file in the current directory:

#!flask/bin/pythonfrom flask Import Flaskapp = Flask (__name__) @app. Route ('/') def index ():    return "Hello, world!" if __name__ = = ' __main__ ':    app.run (debug=true)

To execute app.py:

$ chmod a+x app.py$./app.py * Running on Http://127.0.0.1:5000/* Restarting with Reloader

Now you can open the browser, enter http://localhost:5000 to see this hello,world!

Well, it's very simple. We started switching to restful service!.

Using Python and flask to implement restful services

Building Web Services with flask is super simple.

Of course, there are many flask extensions that can help create restful services, but this example is simply too simple to use any extensions.

This Web service provides additions, deletions, and modifications to the task list, so we need to store the task list. The simplest way to do this is to use a small database, but the database is not too much for this article. You can refer to the full tutorial of the original author. Flask mega-tutorial Series

In this example, we store the task list in memory so that it can only be run in single-process and single-threaded, which is not suitable as a production server, unless it is necessary to use the database.

Now we are ready to implement the entry point for the first Web service:

#!flask/bin/pythonfrom flask Import flask, Jsonifyapp = Flask (__name__) tasks = [    {        ' id ': 1,        ' title ': U ' Buy gr Oceries ',        ' description ': U ' Milk, Cheese, Pizza, Fruit, Tylenol ',         ' done ': False    },    {        ' id ': 2,        ' title ': U ' learn python ',        ' description ': U ' need to find a good Python tutorial on the Web ',         ' done ': False    } ] @app. Route ('/todo/api/v1.0/tasks ', methods=[' GET ') def get_tasks ():    return jsonify ({' Tasks ': tasks}) if __name __ = = ' __main__ ':    app.run (debug=true)

As you can see, it doesn't change too much code. We store the task list in the list (memory), and the list holds two very simple array dictionaries. Each entity is the field we defined above.

The index entry point has a get_tasks function associated with the/todo/api/v1.0/tasks URI and only accepts the HTTP GET method.

This response is not a generic text, it is JSON-formatted data, and is formatted by the Jsonify module of the Flask framework.

It is not a good idea to use a browser to test the Web service, because to create HTTP requests of different classes, in fact, we will use the Curl command line. If you don't have curl installed, go ahead and install one.

Run app.py just as you did.

Open a terminal to run the following command:

$ curl-i http://localhost:5000/todo/api/v1.0/tasksHTTP/1.0 okcontent-type:application/jsoncontent-length: 294server:werkzeug/0.8.3 Python/2.7.3date:mon, 04:53:53 gmt{  "Tasks": [    {      "description": "Milk , Cheese, Pizza, Fruit, Tylenol ",      " done ": false,      " id ": 1,      " title ":" Buy Groceries "    },    {      " Description ":" Need to find a good Python tutorial on the Web ",      " done ": false,      " id ": 2,"      title ":" Learn Pyt Hon "    }  ]}

This invokes a RESTful service method!

Now, we write the second version of the Get method to get a specific task. To get a single task:

From flask import abort@app.route ('/todo/api/v1.0/tasks/<int:task_id> ', methods=[' GET ']) def get_task (task_id) :    task = filter (lambda t:t[' id '] = = task_id, tasks)    If Len (Task) = = 0:        abort (404)    return jsonify ({' Task ' : Task[0]})

The second function is slightly more complex. The ID of the task is contained within the URL, and flask passes the TASK_ID parameter inside the function.

Retrieves the tasks array by parameter. If the ID passed in the parameter does not exist in the array, we need to return error code 404, according to HTTP, 404 means "Resource not Found", the resource is not found.

If a task is found within an array of memory, we package the dictionary into JSON format via the Jsonify module and send the response to the client. It's like working with an entity dictionary.

Try using the Curl call:

$ curl-i http://localhost:5000/todo/api/v1.0/tasks/2HTTP/1.0 okcontent-type:application/jsoncontent-length: 151server:werkzeug/0.8.3 Python/2.7.3date:mon, 05:21:50 gmt{  "task": {    "description": "Need to find" A good Python tutorial on the Web "done    ": false,    "id": 2,    "title": "Learn Python"  }}$ curl-i Http://loc alhost:5000/todo/api/v1.0/tasks/3http/1.0 404 Not foundcontent-type:text/htmlcontent-length:238server:werkzeug/ 0.8.3 Python/2.7.3date:mon, 05:21:52 gmt<! DOCTYPE HTML public '-//w3c//dtd HTML 3.2 final//en ' ><title>404 not found</title>

When we request a resource for the # # ID, we can get it, but we return a 404 error when we request a resource of # #. And returned a strange HTML error, not the JSON we expected, because flask generated the default 404 response. The client needs to receive a JSON response, so we need to improve 404 error handling:

From flask import Make_response@app.errorhandler (404) def not_found (Error):    return Make_response (jsonify ({' Error ': ' Not Found '}), 404)

So we get a friendly API error response:

$ curl-i http://localhost:5000/todo/api/v1.0/tasks/3HTTP/1.0 404 Not foundcontent-type:application/ jsoncontent-length:26server:werkzeug/0.8.3 Python/2.7.3date:mon, 05:36:54 gmt{  "error": "Not Found"}

Next we implement the POST method, inserting a new task into the array:

From flask import request@app.route ('/todo/api/v1.0/tasks ', methods=[' POST ']) def create_task ():    if not Request.json or not ' title ' in Request.json:        abort (+)    task = {        ' id ': tasks[-1][' id '] + 1,        ' title ': requ est.json[' title '],        ' description ': Request.json.get (' description ', ""),        ' done ': False    }    Tasks.append (Task)    return jsonify ({' Task ': task}), 201

The Request.json contains the request data, and if it is not JSON or does not include the title field, it will return a 400 error code.

When creating a new task dictionary, use the last task ID value plus 1 as the new task ID (the simplest method produces a unique field). The Description field is allowed, and the Done field value is false by default.

Attaches the new task to the tasks array and returns the client 201 status code and the task content that was just added. HTTP defines a 201 status code of "Created".

Test the new features above:

$ curl-i-H "Content-type:application/json"-X post-d ' {"title": "Read a book"} ' Http://localhost:5000/todo/api/v1.0/tas kshttp/1.0 201 createdcontent-type:application/jsoncontent-length:104server:werkzeug/0.8.3 Python/2.7.3date:mon, 05:56:21 gmt{  "task": {    "description": "",    "done": false,    "id": 3,    "title": "Read a book"  }}

Note: If you use the native version of the Curl command-line prompt, the above command executes correctly. If you are using the Cygwin Bash version of Curl under Windows, you need to add double quotes to the body part:

Curl-i-H "Content-type:application/json"-X post-d "{" "" title "" ":" "" Read A Book "" "}" http://localhost:5000/todo/api/v 1.0/tasks

Basically, you need to use double quotes in Windows, including the body part, and you need three double-quote escape sequences.

To complete the above, you can see the contents of the list array after the update:

$ curl-i http://localhost:5000/todo/api/v1.0/tasksHTTP/1.0 okcontent-type:application/jsoncontent-length: 423server:werkzeug/0.8.3 Python/2.7.3date:mon, 05:57:44 gmt{  "Tasks": [    {      "description": "Milk , Cheese, Pizza, Fruit, Tylenol ",      " done ": false,      " id ": 1,      " title ":" Buy Groceries "    },    {      " Description ":" Need to find a good Python tutorial on the Web ",      " done ": false,      " id ": 2,"      title ":" Learn Pyth On "    },    {      " description ":" ",      " did ": false,      " id ": 3,"      title ":" Read a book "    }  ]}

The remaining two functions are as follows:

@app. Route ('/todo/api/v1.0/tasks/<int:task_id> ', methods=[' PUT ') def update_task (task_id): task = Filter (    Lambda t:t[' id '] = = task_id, tasks) If Len (Task) = = 0:abort (404) If not Request.json:abort (400) If ' title ' in Request.json and type (request.json[' title '))! = Unicode:abort (+) If ' description ' in Request.js On and type (request.json[' description ') are not unicode:abort (+) If ' done ' in Request.json and type (REQUEST.J son[' done ') are not bool:abort (+) task[0][' title ' = Request.json.get (' title ', task[0][' title ']) task[0][' Description '] = request.json.get (' description ', task[0][' description ']) task[0][' Done ' = request.json.get (' Done ', task[0][' Done ') return jsonify ({' Task ': Task[0]}) @app. Route ('/todo/api/v1.0/tasks/<int:task_id> ', methods=[ ' DELETE ']) def delete_task (task_id): task = filter (lambda t:t[' id ') = = task_id, tasks) If Len (Task) = = 0:abo RT (404) Tasks.remove (task[0]) returN jsonify ({' Result ': True}) 

The Delete_task function is nothing special. The Update_task function needs to check the input parameters to prevent the bug from producing an error. Ensure that the expected JSON format is written to the database.

The test changes the done field of the task # # to the Done state:

$ curl-i-H "Content-type:application/json"-X put-d ' {"Done": true} ' http://localhost:5000/todo/api/v1.0/tasks/2HTTP/ 1.0 okcontent-type:application/jsoncontent-length:170server:werkzeug/0.8.3 Python/2.7.3date:mon, May 2013 07:1 0:16 gmt{  "task": [    {      "description": "Need to find a good Python tutorial on the Web",      "Done": true,      " ID ": 2," "      title": "Learn Python"    }  ]}
Improving the Web service interface

Currently we also have a problem where the client may need to reconstruct the URI from the returned JSON, and if a new feature is added in the future, the client may need to be modified. (for example, a new version.) )

We can return the entire URI path to the client, not the task ID. For this function, create a small function to generate a "public" version of the task URI to return:

From flask import url_fordef make_public_task (Task):    new_task = {} for    field in task:        if field = = ' id ':            NE w_task[' uri '] = url_for (' get_task ', task_id=task[' id '], _external=true)        else:            New_task[field] = Task[field]    return New_task

With the Flask url_for module, when you get the task, replace the ID field in the task with the URI field and change the value to the URI value.

When we return to the list containing the task, after this function is processed, the full URI is returned to the client:

@app. Route ('/todo/api/v1.0/tasks ', methods=[' GET ') def get_tasks ():    return jsonify ({' Tasks ': Map (make_public_ task, Tasks)})

Now see the results of the search:

$ curl-i http://localhost:5000/todo/api/v1.0/tasksHTTP/1.0 okcontent-type:application/jsoncontent-length: 406server:werkzeug/0.8.3 Python/2.7.3date:mon, 18:16:28 gmt{  "Tasks": [    {      "title": "Buy groceri Es ",      " done ": false,      " description ":" Milk, Cheese, Pizza, Fruit, Tylenol ",      " uri ":" Http://localhost:5000/ Todo/api/v1.0/tasks/1 "    },    {      " title ":" Learn Python ",      " done ": false,      " description ":" Need to Find a good Python tutorial on the Web ",      " uri ":" Http://localhost:5000/todo/api/v1.0/tasks/2 "    }  ]}

This approach avoids compatibility with other functions and gets the full URI instead of an ID.

Secure authentication for RESTful Web service

We've done the whole thing, but we still have a problem. It is not a good idea for a Web service to be accessible to anyone.

The current service is all clients can connect, if someone else knows the API can write a client arbitrarily modify the data. Most tutorials do not have security-related content, which is a very serious problem.

The simplest approach is to allow only the user name and password authentication through the client connection in the Web service. In a regular web application, there should be a login form submitted for authentication, and the server creates a session process to communicate. This session ID is stored in the client's cookie. However, this violates the stateless rules in our rest, so we need clients to send their authentication information to the server each time.

For this reason we have two methods of form authentication, namely Basic and Digest.

Here is a small flask extension can be easily done. First you need to install Flask-httpauth:

$ flask/bin/pip Install Flask-httpauth

Assume that the Web service is accessible only to user ok and password for Python users. A basic HTTP authentication is set up below:

From Flask.ext.httpauth Import Httpbasicauthauth = Httpbasicauth () @auth. Get_passworddef Get_password (username):    If username = = ' OK ':        return ' python '    return None@auth.error_handlerdef Unauthorized ():    return Make_ Response (jsonify ({' Error ': ' Unauthorized Access '}), 401)

The Get_password function is a callback function that gets the password of a known user. In complex systems, functions need to be checked into the database, but this is just a small example.

When an authentication error occurs, the Error_Handler callback function sends the wrong code to the client. Here we customize an error code 401, which returns the JSON data instead of the HTML.

Add the @auth.login_required adorner to the function you want to validate:

@app. Route ('/todo/api/v1.0/tasks ', methods=[' GET ') @auth. Login_requireddef get_tasks ():    return jsonify ({' Tasks ': Tasks})

Now, try calling this function with curl:

$ curl-i http://localhost:5000/todo/api/v1.0/tasksHTTP/1.0 401 unauthorizedcontent-type:application/ Jsoncontent-length:36www-authenticate:basic realm= "Authentication Required" server:werkzeug/0.8.3 Python/2.7.3Date : Mon, May 06:41:14 gmt{  "error": "Unauthorized Access"}

This indicates that the validation is not passed, and the following is the authentication with the user name and password:

$ curl-u ok:python-i http://localhost:5000/todo/api/v1.0/tasksHTTP/1.0 okcontent-type:application/ jsoncontent-length:316server:werkzeug/0.8.3 Python/2.7.3date:mon, 06:46:45 gmt{  "Tasks": [    {      "title": "Buy groceries",      "done": false,      "description": "Milk, Cheese, Pizza, Fruit, Tylenol",      "uri": "http ://localhost:5000/todo/api/v1.0/tasks/1 "    },    {      " title ":" Learn Python ",      " done ": false,      " Description ":" Need to find a good Python tutorial on the Web ",      " uri ":" HTTP://LOCALHOST:5000/TODO/API/V1.0/TASKS/2 "    }  ]}

This certification extension is very flexible and can be validated with the specified APIs.

In order to ensure the security of the login information, the best way is to use HTTPS encrypted communication method, client and server-side transmission authentication information is encrypted, to prevent third-party people to see.

When using the browser to access this interface, will pop up an ugly login dialog box, if the password is wrong back to return 401 error code. To prevent the browser from ejecting the Validation dialog box, the client should handle this login request.

There is a small trick to avoid this problem, which is to modify the returned error code 401. For example, modifying to 403 ("Forbidden") will not pop up the validation dialog box.

@auth. Error_handlerdef Unauthorized ():    return Make_response (jsonify ({' Error ': ' Unauthorized Access '}), 403)

Of course, it also requires the client to know the meaning of this 403 error.

At last

There are many ways to improve this Web service.

In fact, a real web service should use a real database. There are a lot of restrictions on using memory data structures, not on actual applications.

On the other hand, multi-user processing. If the system supports multi-user authentication, the task list is also for multiple users. At the same time we need to have a second resource, user resources. A POST request is used when the user registers. Use get to return user information to the client. Use the PUT request to update the user profile, or email address. Use Delete to delete user accounts and so on.

There are many ways to extend a task list when it is retrieved through a GET request. First, you can add paging parameters so that the client requests only a portion of the data. Second, you can add filter keywords and so on. All of these elements can be added to the URL above the parameters.

Using flask to build RESTful APIs server side

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.