Flask restful api series (1), flaskrestful
Before that, we would like to explain that our entire framework uses flask + sqlalchemy + redis. If you have not developed a web application, let's take a look at it first. Here we will only introduce how to switch from the development web to the development mobile terminal. If you are not familiar with flask, I suggest you go to this website to learn more. It is very simple. Http://dormousehole.readthedocs.org/en/latest/
I always wanted to write something special so that everyone could learn and discuss it. However, for many blogs on the internet, I read them in accordance with the official document or directly move the code without explaining anything. I wrote this series of blogs to let everyone go from simple to deep, step by step to complex structures, and why do you want to do this? Can other methods be used.
Currently, mobile development is the most popular, and our python is the flask web framework that is most suitable for mobile development. This web framework is very clear, simple to use, and complex to use. In the simplest case, a py file can be used as a project. In complicated cases, the blueprint is used for various version control, and the code structure is completely controlled and free.
First, we need to know that at present, we are basically using restful APIs for mobile development. What is restful api? Baidu Encyclopedia: The most important REST principle for Web applications is that interactions between clients and servers are stateless between requests. Each request from the client to the server must contain the information necessary to understand the request. If the server restarts at any time point between requests, the client will not be notified. In addition, stateless requests can be answered by any available server, which is suitable for cloud computing and other environments. The client can cache data to improve performance.
To put it bluntly, we cannot use cookies or sessions. People with a little experience in http know that, in many cases, we put some basic content in cookies. Every time the server reads or writes data, the server can directly set the session, so that every time the client directly carries its own cookie value, we will know who it is and how to give it data. But the restful api style does not allow this. What solution should the server adopt?
At present, most of the practices on the Internet are in the token mode. When you log on for the first time, submit the user name and password. After the server collects the information, verify it first. If the verification is successful, at this time, the server generates a token value using md5, des, aes, and other encryption methods based on the user name, password, and current timestamp, and then stores the token value in redis, record which user it corresponds to and send the token value to the client. After the client receives the token value, it will directly carry this token when it accesses any interface on the server next time, so that the server will know who it is and what data it should give it. In what way do I provide it to the server? The common practice is to put the token value in the header. Of course, this is not an absolute thing. You can also put it in the body. This is a matter of benevolence and wisdom. Well, we should put it in the header first.
As mentioned above, everyone is also bored. Let's go to the code and explain it again.
First, create the entire project. For the sake of simplicity and convenience, we will keep the following py files directly.
Check the code in model. py:
# coding:utf-8from sqlalchemy import create_engine, ForeignKey, Column, Integer, String, Text, DateTime,\ and_, or_, SmallInteger, Float, DECIMAL, desc, asc, Table, join, eventfrom sqlalchemy.orm import relationship, backref, sessionmaker, scoped_session, aliased, mapperfrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.ext.hybrid import hybrid_property, hybrid_methodfrom sqlalchemy.orm.collections import attribute_mapped_collectionimport datetimeengine = create_engine("mysql://root:a12345678@127.0.0.1:3306/blog01?charset=utf8", pool_recycle=7200)Base = declarative_base()db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))Base.query = db_session.query_property()class User(Base): __tablename__ = 'user' id = Column('id', Integer, primary_key=True) phone_number = Column('phone_number', String(11), index=True) password = Column('password', String(30)) nickname = Column('nickname', String(30), index=True, nullable=True) register_time = Column('register_time', DateTime, index=True, default=datetime.datetime.now)if __name__ == '__main__': Base.metadata.create_all(engine)
Run the command to create the user table.
The user table contains the phone number, phone_number, password, password, nickname, nickname, register_time, and registration time.
The following is the view. py code.
1 # coding: UTF-8 2 from flask import Flask, request, jsonify 3 from model import User, db_session 4 import hashlib 5 import time 6 import redis 7 8 app = Flask (_ name _) 9 redis_store = redis. redis (host = 'localhost', port = 6380, db = 4, password = 'dahai123 ') 10 11 12 @ app. route ('/') 13 def hello_world (): 14 return 'Hello World! '1516 17 @ app. route ('/login', methods = ['post']) 18 def login (): 19 phone_number = request. get_json (). get ('phone _ number') 20 password = request. get_json (). get ('Password') 21 user = User. query. filter_by (phone_number = phone_number ). first () 22 if not user: 23 return jsonify ({'code': 0, 'message': 'No user'}) 24 25 if user. password! = Password: 26 return jsonify ({'code': 0, 'message': 'incorrect password'}) 27 28 m = hashlib. md5 () 29 m. update (phone_number) 30 m. update (password) 31 m. update (str (int (time. time () 32 token = m. hexdigest () 33 34 redis_store.hmset ('user: % s' % user. phone_number, {'Token': token, 'nickname': user. nickname, 'app _ online': 1}) 35 redis_store.set ('token: % s' % token, user. phone_number) 36 redis_store.expire ('token: % s' % token, 3600*24*30) 37 38 return jsonify ({'code': 1, 'message': 'logon successful ', 'nickname': user. nickname, 'Token': token}) 39 40 41 @ app. route ('/user') 42 def user (): 43 token = request. headers. get ('Token') 44 if not token: 45 return jsonify ({'code': 0, 'message': 'verification required '}) 46 phone_number = redis_store.get ('token: % s' % token) 47 if not phone_number or token! = Redis_store.hget ('user: % s' % phone_number, 'Token'): 48 return jsonify ({'code': 2, 'message': 'verification information error '}) 49 50 nickname = redis_store.hget ('user: % s' % phone_number, 'nickname') 51 return jsonify ({'code': 1, 'nickname': nickname, 'Phone _ number': phone_number}) 52 53 54 @ app. route ('/logout') 55 def logout (): 56 token = request. headers. get ('Token') 57 if not token: 58 return jsonify ({'code': 0, 'message ':' Verification required '}) 59 phone_number = redis_store.get ('token: % s' % token) 60 if not phone_number or token! = Redis_store.hget ('user: % s' % phone_number, 'Token'): 61 return jsonify ({'code': 2, 'message': 'verification information error '}) 62 63 redis_store.delete ('token: % s' % token) 64 redis_store.hmset ('user: % s' % phone_number, {'app _ online': 0 }) 65 return jsonify ({'code': 1, 'message': 'deregistered successfully '}) 66 68 @ app. teardown_request69 def handle_teardown_request (exception): 70 db_session.remove () 71 72 if _ name _ = '_ main _': 73 app. run (debug = True, host = '0. 0.0.0 ', port = 5001)
Next we will explain one by one,
First, there is no need to explain several imports. Note that both User and db_session are imported, and then a redis connection is defined.
Next, we will use the login interface and direct post method to obtain json-format data, which includes phone_number and password. Next, we will query the database. If this user does not exist, a json format will be returned, next we will compare the password. If both phone_number and password are correct, we will use the md5 function to generate a token, which contains the str (int (time. time () is generated and then returned.
Look at each of the items I return. First, they are in json format. This is the default format returned by most mobile development. Second, each response must have a code. Currently, there are two values, it can be seen that 0 represents failure, 1 represents success, and 0 represents a message. Each return code is required, but the value can be defined by yourself. Many developers can use the http code directly. For example, if the code is returned successfully, the code is 200. If the code is not returned, the code is 404. If the code is not returned, the code is 403. If the code is disabled, the code is. You can specify a value as long as the development on the server and the development on the client start. The specific value can be used as long as the development is fast.
Well, the returned format is explained first, and we will continue to expand later. Let's look at the next redis.
The first line uses the user: 13765505223 type as the key for each user. The value is something basic. app_online indicates that the app is online. This app_online is actually very important, because mobile development is different from web development, it is necessary to record whether the mobile is logged on or logged out. If you are in the login status, we can push the data from the server. We will talk about the push in the future and put it here first.
The second line uses the token: token as the key. The value in the key is the user's phone number.
Row 3: Set an expiration time for the token. If the expiration time is exceeded, the token will be deleted. You can set the token for this app. If the token of your app remains unchanged, you can comment out this line of code.
Now, the login function is basically complete.
Next, let's take a look at the authentication function user and the logout function logout,
The user function, which is used to verify whether there is any data after logon. It doesn't make much sense.
Perform row-by-row analysis. First, locate the token in the header. If no token exists, the system returns an error. Second, verify redis. If the key value pair of the token contains no value or the value is incorrect, failure is returned. Then return the specific data. A very simple function.
The following logout functions are similar. They are also used for verification. Then, the token key value pair is deleted, and app_online is set to 0, indicating that the current logout status is logged out.
The last one is very important. Remember to write this function. Without this function, db_session will not be cleared after each session. In many cases, the database is changed and cannot be found at the front end, or the database has not been changed because it has already been submitted, or there is no access interface for a long time, such as mysql gong away. In short, it must be added.
Okay. The entire process has been completed. The verification status is displayed below. First, create a new user and store it in the database. Then, write a script to verify the user.
>>> From model import User, db_session >>> new_user = User (phone_number = '000000', password = '000000', nickname = u'test User 1') >>> db_session.add (new_user) >>> db_session.commit ()
One user has been created, and the next step is the test. There are two methods for this test. One is to test the test software that comes with IDE, and pycharm has a good test software. The other is to test it with a small script, since we will keep writing examples in the future, we can test it with a small script, and the process is very simple.
1 # coding:utf-8 2 import requests 3 import json 4 5 6 class APITest(object): 7 def __init__(self, base_url): 8 self.base_url = base_url 9 self.headers = {}10 self.token = None11 12 def login(self, phone_number, password, path='/login'):13 payload = {'phone_number': phone_number, 'password': password}14 self.headers = {'content-type': 'application/json'}15 response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)16 response_data = json.loads(response.content)17 self.token = response_data.get('token')18 return response_data19 20 def user(self, path='/user'):21 self.headers = {'token': self.token}22 response = requests.get(url=self.base_url + path, headers=self.headers)23 response_data = json.loads(response.content)24 return response_data25 26 def logout(self, path='/logout'):27 self.headers = {'token': self.token}28 response = requests.get(url=self.base_url + path, headers=self.headers)29 response_data = json.loads(response.content)30 return response_data
Write a very simple script and you can test it in the command line. Let's try it.
>>> From client import APITest >>> api = APITest ('HTTP: // 127.0.0.1: 100') >>> data = api. login ('100', '100')> print data. get ('message') Password error >>> data = api. login ('100', '100')> print data. get ('message') successfully logged on >>> data = api. user () >>> print data {u'phone _ number': u'000000', u'code': 1, u'nickname ': u' \ u6d4b \ u8bd5 \ u7528 \ u62371 '} >>> print nicknameTraceback (most recent call last): File "<input>", line 1, in <module> NameError: name 'nickname' is not defined >>> print data. get ('nickname') test user 1 >>> data = api. logout () >>> print data {u'message': U' \ u6210 \ u529f \ u6ce8 \ u9500 ', u'code ': 1 }>>> print messageTraceback (most recent call last): File "<input>", line 1, in <module> NameError: name 'message' is not defined >>> print data. get ('message') is successfully logged out
When the login is successful, let's go to redis to check the redis data format.
127.0.0.1:6380[4]> keys *1) "token:bbf73ab651a13a5bc5601cf01add2564"2) "user:12345678901"127.0.0.1:6380[4]> hgetall user:123456789011) "token"2) "bbf73ab651a13a5bc5601cf01add2564"3) "nickname"4) "\xe6\xb5\x8b\xe8\xaf\x95\xe7\x94\xa8\xe6\x88\xb71"5) "app_online"6) "1"127.0.0.1:6380[4]> get token:bbf73ab651a13a5bc5601cf01add2564"12345678901"127.0.0.1:6380[4]>
Well, everything is normal, but we can't do everything in development. There are a lot of improvements to this Code, especially the token verification, can we improve it? When redis is set, what if an error occurs or if redis is set to half? We will continue to solve these problems in the next chapter.