Chuyển đến nội dung chính

Thiết kế một RESTful API bằng python và flask.


Như các bạn đã biết thì REST viết tắt bởi REpresentational State Transfer. Bạn có thể tham khảo thêm ở đây.

Vậy REST là gì:

Có 6 yếu tố của REST như sau:

1. Client-Server: phía server side phục vụ như một service còn phía client sẽ dùng service này.

2.Stateless:  Mỗi request từ client phải chứa tất cả các thông tin mà phía server yêu cầu phải có. Nói nôm na là mỗi request chứa các thông tin riêng rẽ không liên quan đến những request khác.

3. Cacheable : Phía server side phải cảnh báo chỉ ra cho client side biết là request đó có cache hay không.

4. Hệ thống phân lớp: Giao tiếp giữa server side và client side có thể thông qua lớp trung gian ở giữa để trả lời các request được gọi mà không cân phía client phải làm những động tác khác nữa.

5. Các cổng giao tiếp là giống nhau : Các phương thức giao tiếp giữa client và server là khuôn mẫu giống nhau.

6. Mã code linh động. Phía server có thể thực thi cho môt API gọi duy nhất mà không ảnh hưởng tới các API khác.

RESTful web service là gì :

Kiến trúc REST bắt nguồn từ việc thiết kế sao cho phù hợp với giao thức HTTP thông dụng nhất.

Các phương thức HTTP method có các định dạng sau:

HTTP Method

ActionExamples
GETLấy những thông tin từ URIs (resource)http://example.com/api/orders
(retrieve order list)
GETLấy những thông tin từ URIs (resource)http://example.com/api/orders/123
(retrieve order #123)
POSTTạo một resource mới http://example.com/api/orders
(create a new order, from data provided with the request)
PUTCập nhật một resource đã có  http://example.com/api/orders/123
(update order #123, from data provided with the request)
DELETEXóa một resource http://example.com/api/orders/123
(delete order #123)


REST không yêu cầu một định dạng data đặt biệt, thông thường thì trả về dạng JSON hoặc kiểu query string.

Thiết kế một API cơ bản.

Bây giờ ta viết một ứng dụng To Do List có URL như sau :

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


URIs bạn đặt sao cho developer có thể nhìn vào biết được đó là application gì, và version cho API đó là bao nhiêu để sau này các developer sau có thể dễ dàng update cho sau này.


Tiếp theo là ta chọn các resource cho ứng dụng này.

Thử viết ra một list các tác vụ API nhé.

HTTP Method

URIAction
GEThttp://[hostname]/todo/api/v1.0/tasksRetrieve list of tasks
GEThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Retrieve a task
POSThttp://[hostname]/todo/api/v1.0/tasksCreate a new task
PUThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Update an existing task
DELETEhttp://[hostname]/todo/api/v1.0/tasks/[task_id]Delete a task


Bạn thấy URIs 1 và 3 giống nhau nhưng phương thức khác nhau. Một cái là GET thông tin và cái còn lại là tạo một thông tin.

Các phương thức còn lại chắc dễ dàng phải không.

Trong mỗi tác vụ có các thông tin. Trong thông tin có các trường thông tin. Bạn hãy tưởng tượng 1 nhân viên công ty chẳng hạn có các thông tin gì?

Và đây là cácthông tin cơ bản :

  • id: số định danh duy nhất cho task. Numeric type.
  • title:  mô tả tóm gọn của task. String type.
  • description: các mô tả cụ thể hơn . Text type.
  • done: trạng thái của task. Boolean type.

Và bạn cần phải định nghĩa kiểu dữ liệu cho các trường thông tin trên .

Giới thiệu về flask microframework của python. 

Flask là một web framework của python. nhỏ gọn và mạnh mẽ.

Bài viết này mình chỉ hướng dẫn bạn cài flask trên linux :


$ mkdir todo-api
$ cd todo-api
$ virtualenv flask
New python executable in flask/bin/python
Installing setuptools............................done.
Installing pip...................done.
$ flask/bin/pip install flask


Trên windows bạn có thể cài Cygwin

Sau khi cài đặt xong flask trên máy tính bạn, bây giờ bạn có thể tạo một file python bất kỳ, sau đây mình đặt tên cho nó là app.py

Có nội dung sau :

#!flask/bin/python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True)

Bạn muốn dùng flask framework thì bạn phải import thư viện flask vào trong code.

Và định nghĩa  app = Flask(__name__)

Cuối đoạn code thì thực thi nó:

if __name__ == '__main__':
    app.run(debug=True)

Để chạy code đã viết bạn chạy như sau :

$ chmod a+x app.py
$ ./app.py
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader
 
 
 
Chmod cho file đó có quyền thực thi,

Khi chạy thì bạn truy cập  trên web browser http://localhost:5000
để thấy kết quả.

Bắt tay vào viết một API:

Viết một API thì dễ hơn là viết một viết một ứng dụng web. Trở lại với ứng dụng To Do List phía trên, thì phải thêm xóa, chỉnh sửa, lấy thông tin của tác vụ task, do vậy cần phải  lưu trữ các dữ liệu vào một database nhưng việc tích hợp database nằm ngoài bài viết này nên ta chỉ lưu dữ liệu trong memory.

#!flask/bin/python
from flask import Flask, jsonify

app = Flask(__name__)

tasks = [
    {
        'id': 1,
        'title': u'Buy groceries',
        '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)



Ta tạo một dãy các thư viện, mỗi một entry trong một dãy (array ) có các trường mà ta đã định nghĩa phía trên .

Thay vì để index cho trang đầu tiên ta có thể thay thế bằng tác vụ task với method  GETvới URI: /todo/api/v1.0/tasks

Response trả về trong trường hợp này là json, ta có thể dùng web browser để kiểm tra response trả về nhưng đó khọng phải là ý kiến tốt mà ta nên dùng lệnh curl để request là xem response trả về trong terminal và dùng Postman để test.

Bây giờ ta có thể impove tác vụ GET như sau:
 
from flask import abort

@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    task = [task for task in tasks if task['id'] == task_id]
    if len(task) == 0:
        abort(404)
    return jsonify({'task': task[0]}) 

chúng ta gán agument task_id  và dùng vòng lặp for để tìm đúng task ID cho tác vụ task nào đó.

Và lỗi 404 sẽ trả về nếu ta gán task_id không tồn tại trong database.

$ curl -i http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 151
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 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://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 238
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:21:52 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.</p><p>If you     entered the URL manually please check your spelling and try again.</p>
 
 
 
Phía trên ta test với task_id là 2 và 3
Với task_id là 3 ta sẽ nhận về lỗi 404.

Và như ta thấy thì 404 trả về theo dạng HTML, nếu ta muốn lỗi 404 trả về theo dạng json thì ta viết thêm hàm sau:

from flask import make_response

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

Và kiểm tra bằng curl:

$ curl -i http://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 26
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:36:54 GMT

{
  "error": "Not found"
}

Thấy chưa bạn đã thấy định dạng json trả về.

Bây giờ là viết một hàm POST như sau:

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(400)
    task = {
        'id': tasks[-1]['id'] + 1,
        'title': request.json['title'],
        'description': request.json.get('description', ""),
        'done': False
    }
    tasks.append(task)
    return jsonify({'task': task}), 201

Như đoạn code trên bạn chỉ thấy là task_id được +thêm 1. Và các thông số request.json, request.json.get

Hãy nhìn thông tin tạo như sau:

curl -i -H "Content-Type: application/json" -X POST -d '{"title":"Read a book"}' http://localhost:5000/todo/api/v1.0/tasks

 title được gán "Read a book"

Và bạn thấy kết quả trả vê:

HTTP/1.0 201 Created
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:56:21 GMT

{
  "task": {
    "description": "",
    "done": false,
    "id": 3,
    "title": "Read a book"
  }
}

 id được công thêm 1 với cái id cuối cùng.
Trường done mặc đinh là false vàdescription là empty.

Trên windows bạn dùng Cygwin chú ý tới các dấu nháy.

curl -i -H "Content-Type: application/json" -X POST -d "{"""title""":"""Read a book"""}" http://localhost:5000/todo/api/v1.0/tasks
 
Kiểm tra lại bạn sẽ thấy:

$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 423
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 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 Python"
    },
    {
      "description": "",
      "done": false,
      "id": 3,
      "title": "Read a book"
    }
  ]
}

id: 3đã được add vào.

Còn 2 function còn lại:

@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
    task = [task for task in tasks if task['id'] == task_id]
    if len(task) == 0:
        abort(404)
    if not request.json:
        abort(400)
    if 'title' in request.json and type(request.json['title']) != unicode:
        abort(400)
    if 'description' in request.json and type(request.json['description']) is not unicode:
        abort(400)
    if 'done' in request.json and type(request.json['done']) is not bool:
        abort(400)
    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 = [task for task in tasks if task['id'] == task_id]
    if len(task) == 0:
        abort(404)
    tasks.remove(task[0])
    return jsonify({'result': True})

 Trong đoạn code trên ta thấy nó phức tạp, thật chất là không vì nó chỉ lồng các điều kiện vào để redirect về các trang 404 , 400

Ví dụ ta test:

$ curl -i -H "Content-Type: application/json" -X PUT -d '{"done":true}' http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 170
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 07:10:16 GMT

{
  "task": [
    {
      "description": "Need to find a good Python tutorial on the web",
      "done": true,
      "id": 2,
      "title": "Learn Python"
    }
  ]
}


Bây giờ thì bạn có thể viết 1 REST API cơ bản rồi phải không ? Easy ! Happy coding. :) :) :)





Nhận xét

Bài đăng phổ biến từ blog này

Cách sử dụng sys.argv trong python.

Cách sử dụng sys.argv trong python.

sys.argv là môt danh sách [list] trong python, nó được sư dụng khi bạn chạy một lệnh command-line nào đó trên hệ thống.
Và argument này được đẩy vào script python để thực thi khi chạy câu lệnh.

Ví dụ: python sys.argv arg1 arg2

Trước tiên bạn phải import mô đun sys trong script.



import sys print"This is the name of the script: ", sys.argv[0]print"Number of arguments: ", len(sys.argv)print"The arguments are: ", str(sys.argv)

Tên của script này : sysargv.py
Số lượng arg là : 1
Arg là : ['sysargv.py']


python test1020.py 111 This is the name of the script:  test1020.py
Number of arguments:  2
The arguments are:  ['test1020.py', '111']

Cài đặt docker trên windows server 2016.

1. Cài đặt Windows Server 2016 Container Feature:  Mở PowerShell : Cài đặt các container feature dùng powershell: Dùng lệnh: InstallWindowsFeature containers Restart lại windows: 2. Cài đặt windows server 2016 base image:  Cài đặt mô đun : ContainerImage Dùng lệnh: Install-PackageProvider ContainerImage -force List các image có giá trị: Cài đặt Windows Server Core : 3. Cài đặt docker trên windows server 2016. Download script để cài đặt docker : Chạy script : Kiểm tra các image có trên server :