User Login Principles

User login and registration functions have almost become standard in Web applications. Therefore, it is necessary to add a user management module to Todo List to learn how users log in.

The HTTP protocol is stateless, which means that each complete HTTP request-response process is relatively independent, and the Web server cannot tell whether two consecutive requests were sent by the same user (client). In order for the server to remember the user, there is a technology called cookies. I drew a diagram to illustrate how Cookie works:

First, the browser sends a GET request to the server/path. When the server returns a response to the browser, two keys are Set in the response header as the set-cookie header field. Then the browser automatically saves the set-cookie header field after receiving it. The browser automatically adds the Cookie request header field to all subsequent requests.

This is the general working principle of Cookie. In summary, Cookie has the following characteristics:

  • CookieThe server and browser need to work togetherSet-CookieSet in the response header fieldCookieAnd you can set more than one at a timeSet-CookieResponse header field. Browser receivedSet-CookieIn response to the header fieldautomaticsaveCookieContent will be passed on subsequent requestsautomaticaddCookieRequest header field to carryCookieContent to the server.
  • CookieAlways in the form of key-value pairs, such asa=123,b=456More than,Cookieuse;Space.
  • CookieDoes not restrict request methods, anyHTTPEither request method can be usedCookie.
  • For safety reasons,CookieThere are domain name restrictions. Any vendor’s browser in the saveCookieI keep track of thisCookieWhich domain name does it belong to? When sending a request to the server, only the domain name belongs to this domain nameCookieTo be carried. Such as access to127.0.0.1:8000The browser will only carry the ones under the domain nameCookieIf the browser also saves itwww.jd.comUnder the domain ofCookieIs not to be carried to127.0.0.1:8000Server.
  • CookieYou can set the expiration time, as shown aboveCookieValue is the keya=123In this case, the browser’s default handling mechanism is to delete this automatically when the browser is closedCookie. You can do this if you need to set the expiration timeSet-Cookie: a=123; Max-Age=60At this time,a=123This articleCookieThe expiration time is 60 seconds, after which the browser will automatically save this itemCookieDelete it.

How do I view cookies in Chrome? Open Chrome developer Tools, select the Application TAB, and click on Cookies to see all the Cookies recorded under the current domain name.

If cookies are used to implement user login, the process is as follows:

  1. The login page for accessing Web applications is displayed.
  2. Enter the user name and password on the login page and click the login button to log in.
  3. The server receives the user name and password from the browser, checks the database for the existence of the user, and, once authenticated, adds it to the response returned to the browserSet-Cookie: username=zhangsanResponse header field.
  4. The browser receives a message withSet-Cookie: username=zhangsanAfter the response to the header field, theusername=zhangsanSave it.
  5. When the browser requests a page of the Web application againautomaticcarryCookie: username=zhangsanRequest header field.
  6. The server receives the browser request and parses itCookieRequest header field, the server knows that the request iszhangsanIt’s on its way.

Although Cookie can be used to achieve user login, it is not reliable to directly store user information username=zhangsan to Cookie. Because usernames are easy to reveal and easy to guess. If the attacker knows that zhangsan exists in our system, he does not need to know zhangsan’s password. You only need to add the Cookie username=zhangsan to the 127.0.0.1:8000 domain name in the browser (you can manually change the Cookie in the Chorme developer tools). The browser will automatically carry this Cookie when it requests 127.0.0.1:8000 server next time. After receiving this request, the server will think that the request is zhangsan from this user, so that the website attacker can cheat the login mechanism of the server. Therefore, in order to solve this problem, someone proposed a concept called Session.

Session is not a technical implementation, but an idea. When cookies are used to realize user login, user information username=zhangsan is directly stored in the browser Cookie in plaintext. With the Session mechanism, user information can be saved to the server (in any storage medium), for example, it can be saved as a JSON object in a file:

{
    "6d78289e237c48719201640736226e39": "zhangsan"."3cdb8c488abb4eccad2c7cc8c3c9d065": "lisi"
}
Copy the code

The key of the object is a random string called session_id and the value is the user name. Instead of sending the user name directly to the browser in plain text when the server returns the response, session_ID is placed in the set-cookie field at the head of the response: Session_id = 6 d78289e237c48719201640736226e39 sent to the browser. This change doesn’t change anything on the browser side, the browser still saves the Cookie locally and carries it with it the next time it sends a request. However, the addition of Session mechanism makes the user login mechanism more secure. Because session_id is a randomly generated string, a malicious user cannot forge the session_id even if he knows the user name.

However, with the addition of the Session mechanism, the server’s mechanism for authenticating users’ cookies has to be slightly modified. In the past, the server only needs to parse the Cookie sent by the browser to get the user name Zhangsan, but now after parsing the Cookie to get session_id, The server also needs to look up the value of the session_id key in the JSON object where all sessions are stored to get the current login user name.

After the Session mechanism is added, the login process is as follows:

  1. The login page for accessing Web applications is displayed.
  2. Enter the user name and password on the login page and click the login button to log in.
  3. The server receives the user name and password transmitted by the browser, and then searches the database for the existence of the user. After the authentication is passedzhangsanThe user generates a randomsession_idAnd thensession_idAnd user names are stored as key-value pairsJSONFile, and then add to the response returned to the browserSet-Cookie: session_id=6d78289e237c48719201640736226e39Response header field.
  4. The browser receives a message withSet-Cookie: session_id=6d78289e237c48719201640736226e39After the response to the header field, thesession_id=6d78289e237c48719201640736226e39Save it.
  5. When the browser requests a page of the Web application againautomaticcarryCookie: session_id=6d78289e237c48719201640736226e39Request header field.
  6. The server receives the browser request and parses itCookieThe request header field getssession_idAnd then to store all of themSessionJSONFind in file6d78289e237c48719201640736226e39thissession_idThe corresponding user namezhangsanThe server knows that the request iszhangsanIt’s on its way.

The above is the most common mechanism to realize user login by using Session + Cookie. Session is just an idea, and it can also be used without Cookie. There are many ways to realize user login, and interested readers can explore them according to their own needs.

User management function design

Todo List is a Todo List application with user management.

  • For the model layer, it needs to be addedUser,SessionThe two model classes deal with users andSession.
  • For the view layer, it needs to be addedregister.html(Registration),login.html(Login) Two HTML pages.
  • For the controller layer, implementation is requiredregister(Registration),login(Login) Two view functions.

Except for the functionality that needs to be added to each layer of the MVC pattern. We also need to add two JSON files user.json and session.json to store user and session information respectively.

In addition, when the Todo List program is added to the user management function, only logged in users can view and manage their own Todo. So we need to make some changes to the existing view functions in the Todo management section.

User management function coding implementation

Based on the above analysis of user management functions, the current Todo List program directory structure is designed as follows:

Todo_list ├ ─ ─ for server py ├ ─ ─ tests │ ├ ─ ─ test_controllers. Py └ ─ ─ todo ├ ─ ─ just set py ├ ─ ─ config. Py ├ ─ ─ controllers │ ├ ─ ─ Set py │ ├ ─ ─ auth. Py │ ├ ─ ─ the static. Py │ └ ─ ─ todo. Py ├ ─ ─ the db │ ├ ─ ─ the session. The json │ ├ ─ ─ todo. Json │ └ ─ ─ the user. The json ├ ─ ─ Logs │ └ ─ ─ todo. Log ├ ─ ─ models │ ├ ─ ─ just set py │ ├ ─ ─ session. Py │ ├ ─ ─ todo. Py │ └ ─ ─ user. Py ├ ─ ─ the static │ ├ ─ ─ CSS │ │ └ ─ ─ style.css. CSS │ └ ─ ─ the favicon. Ico ├ ─ ─ templates │ ├ ─ ─ auth │ │ ├ ─ ─ the login. The HTML │ │ └ ─ ─ the register. The HTML │ └ ─ ─ todo │ ├ ─ ─ Edit the HTML │ └ ─ ─ index. HTML └ ─ ─ utils ├ ─ ─ just set py ├ ─ ─ error. Py ├ ─ ─ HTTP. Py ├ ─ ─ logging. Py └ ─ ─ templating. PyCopy the code

The Session model class is written in the models/session.py file:

# todo_list/todo/models/session.py

import uuid
import datetime

from . import Model


class Session(Model) :
    Session model class

    def __init__(self, **kwargs) :
        For security purposes, the Session ID does not use an incremented number, but instead uses a UUID
        self.id = kwargs.get('id')
        if self.id is None:
            self.id = uuid.uuid4().hex

        self.user_id = kwargs.get('user_id', -1)
        self.expire_in = kwargs.get('expire_in')
        
        if self.expire_in is None:
            now = datetime.datetime.now()
            expire_in = now + datetime.timedelta(days=1)
            self.expire_in = expire_in.strftime('%Y-%m-%d %H:%M:%S')

    def is_expired(self) :
        """ Determine whether the Session is expired ""
        now = datetime.datetime.now()
        return datetime.datetime.strptime(self.expire_in, '%Y-%m-%d %H:%M:%S') <= now

    def save(self) :
        """ overrides the parent class's save method, filtering out sessions that have expired when saved.
        models = [model.__dict__ for model in self.all(a)if model.id! = self.id and not model.is_expired()]
        if not self.is_expired():
            models.append(self.__dict__)
        self._save_db(models)
Copy the code

The Session Model class inherits from Model. Unlike the Todo model class, which implements only __init__ methods, the Session model class also implements is_EXPIRED and save methods. The is_expired method is used to determine whether the current Session is expired, since the user’s login time is usually limited. The save method filters out expired sessions while saving the current Session object.

I designed the following JSON object to store the Session data of user login:

{
    "id": "6d78289e237c48719201640736226e39"."user_id": 2."expire_in": "The 2020-05-31 22:27:55"
}
Copy the code

Id is session_id, user_id corresponds to the ID of the current login user. In this way, the user can be found through the Session object. Expire_in indicates the expiration time of the Session.

To implement a random session_id, get a random string from the UUID in the Session model’s __init__ method.

The User model class is written in the models/user.py file:

# todo/models/user.py

import hashlib

from . import Model
from todo.config import SECRET


class User(Model) :
    "" User model class ""

    def __init__(self, **kwargs) :
        self.id = kwargs.get('id')
        self.username = kwargs.get('username'.' ')
        self.password = kwargs.get('password'.' ')

    @classmethod
    def generate_password(cls, raw_password) :
        """ Generate password """
        md5 = hashlib.md5()
        md5.update(SECRET.encode('utf-8'))
        md5.update(raw_password.encode('utf-8'))
        return md5.hexdigest()

    @classmethod
    def validate_password(cls, raw_password, password) :
        """ Verify password """
        md5 = hashlib.md5()
        md5.update(SECRET.encode('utf-8'))
        md5.update(raw_password.encode('utf-8'))
        return md5.hexdigest() == password
Copy the code

For security reasons, passwords are usually not stored in the database as clear text. This way, if our Web application is dragged to the library, the plaintext password reduces the risk of the user being hit by the library.

Since passwords are not stored in files in plain text, the User model implements two methods: generating passwords and checking passwords. The generate_password method is called to pass in the original password, and the result is an encrypted string that can be stored in a file and cannot be decrypted. During authentication, pass the original password (the password entered by the user) and the encrypted character string into the validate_password method to verify the original password.

The MD5 algorithm is used to encrypt the user password. Md5 algorithm is a widely used hash algorithm, but it has the risk of collision, so it is salted in the code, which can greatly reduce the probability of collision.

During password authentication, you do not need to decrypt the encrypted password stored in the file. You only need to encrypt the original password in the same way and compare the two encrypted strings to see if they are equal. Because the MD5 algorithm must get the same output for the same input, that is, the encryption result of the same data is the same each time.

Strictly speaking, MD5 is not an encryption algorithm, but a hashing algorithm. Because the encrypted data can be decrypted through the encryption algorithm, and the hash algorithm is a summary of information, can not be reversed through the summary of the original data. But many people are used to the MD5 algorithm is called the encryption algorithm, so I also use the encryption algorithm to introduce it. For more knowledge about MD5 algorithm, you can search related materials to learn by yourself.

The user information will be stored in a DB /user.json file in the following format:

{
    "id": 1."username": "user"."password": "7fff062fcb96c6f041df7dbc3fa0dcaf"
}
Copy the code

Having created the Session and User models, we will implement the User registration function.

The HTML for the registration page is as follows:

<! -- todo_list/todo/templates/auth/register.html -->

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="UTF-8">
    <title>Todo List | Register</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<h1 class="container">Register</h1>
<div class="container">
    <form class="register" action="/register" method="post">
        <input type="text" name="username" placeholder="Username">
        <input type="password" name="password" placeholder="Password">
        <button>registered</button>
    </form>
</div>
</body>
</html>
Copy the code

Append the CSS code for the registration page to the style.css file:

/* todo_list/todo/static/css/style.css */

.register {
    width: 100%;
    max-width: 600px;
    text-align: center;
}

.register input {
    width: 100%;
    height: 40px;
    padding: 0 4px;
}

.register button {
    margin-top: 20px;
}
Copy the code

At the controller level, write a register view function to handle the business logic of user registration:

# todo_list/todo/controllers/auth.py

def register(request) :
    """ Register view function ""
    if request.method == 'POST':
        Get the username and password from the form
        form = request.form
        logger(f'form: {form}')
        username = form.get('username')
        raw_password = form.get('password')

        Verify that the username and password are valid
        if not username or not raw_password:
            return 'Invalid username or password'.encode('utf-8')
        user = User.find_by(username=username, ensure_one=True)
        if user:
            return 'Username already exists'.encode('utf-8')

        Hash the password, create and save the user information
        password = User.generate_password(raw_password)
        user = User(username=username, password=password)
        user.save()
        Redirect to login page after successful registration
        return redirect('/login')

    return render_template('auth/register.html')
Copy the code

The registered view function can receive two types of requests, GET or POST. If it is a GET request, it indicates that the user wants to access the registration page and directly returns the CORRESPONDING HTML of the registration page. If the request is POST, the user clicks the registration button on the registration page, and the registration logic needs to be processed.

After successful registration, the user will be redirected to the login page, so we will implement the user login function.

The HTML of the login page is as follows:

<! -- todo_list/todo/templates/auth/login.html -->

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="UTF-8">
    <title>Todo List | Login</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<h1 class="container">Login</h1>
<div class="container">
    <form class="login" action="/login" method="post">
        <input type="text" name="username" placeholder="Username">
        <input type="password" name="password" placeholder="Password">
        <button>The login</button>
    </form>
</div>
</body>
</html>
Copy the code

Append the CSS code for the login page to the style.css file:

/* todo_list/todo/static/css/style.css */

.login {
    width: 100%;
    max-width: 600px;
    text-align: center;
}

.login input {
    width: 100%;
    height: 40px;
    padding: 0 4px;
}

.login button {
    margin-top: 20px;
}
Copy the code

At the controller level, write a login view function to handle the business logic of the user login:

# todo_list/todo/controllers/auth.py

def login(request) :
    """ Login view function ""
    If the user is already logged in, redirect directly to the home page
    if current_user(request):
        return redirect('/index')

    if request.method == 'POST':
        message = 'Incorrect username or password'.encode('utf-8')

        Get the username and password from the form
        form = request.form
        logger(f'form: {form}')
        username = form.get('username')
        raw_password = form.get('password')
        
        Verify that username and password are correct
        if not username or not raw_password:
            return message
        user = User.find_by(username=username, ensure_one=True)
        if not user:
            return message
        password = user.password
        if not User.validate_password(raw_password, password):
            return message

        Create Session and write session_ID to Cookie
        session = Session(user_id=user.id)
        session.save()
        cookies = {
            'session_id': session.id,}return redirect('/index', cookies=cookies)

    return render_template('auth/login.html')
Copy the code

Login view functions can also receive both GET and POST requests. If the request is a GET request, it indicates that the user wants to access the login page and directly returns the CORRESPONDING HTML of the login page. If the request is POST, the user clicks the login button on the login page, and the login logic needs to be processed.

The current_user function is called in the logon view function to determine whether the user is currently logged in. The current_user function is implemented as follows:

# todo_list/todo/utils/auth.py

def current_user(request) :
    """ Get the current logged-in user ""
    Get session_ID from Cookie
    cookies = request.cookies
    logger(f'cookies: {cookies}')
    session_id = cookies.get('session_id')

    Find Session and verify whether it is expired
    session = Session.get(session_id)
    if not session:
        return None
    if session.is_expired():
        session.delete()
        return None

    Find the current logged-in user
    user = User.get(session.user_id)
    if not user:
        return None
    return user
Copy the code

In the current_user function, the cookies attribute of the request object is used to obtain the Cookie information carried in the current request. The code for how the Request class parses the Cookie information carried in the request can be viewed in the source repository in this section.

To implement user login, create a Session object based on user_id and write the session_ID of the Session object into the browser Cookie. So make the following changes to the redirect function and Response class:

# todo_list/todo/utils/http.py

def redirect(url, status=302, cookies=None) :
    """ "Redirect """
    headers = {
        'Location': url,
    }
    body = ' '
    return Response(body, headers=headers, status=status, cookies=cookies)


class Response(object) :
    """ Response class ""

    Get the reason phrase based on the status code
    reason_phrase = {
        200: 'OK'.302: 'FOUND'.405: 'METHOD NOT ALLOWED',}def __init__(self, body, headers=None, status=200, cookies=None) :
        The default response header specifies that the type of the response content is HTML
        _headers = {
            'Content-Type': 'text/html; charset=utf-8',}if headers is not None:
            _headers.update(headers)
        self.headers = _headers  # response headers
        self.body = body  # response body
        self.status = status  # status code
        self.cookies = cookies  # Cookie

    def __bytes__(self) :
        """ Construct response message """
        # status line 'HTTP/1.1 200 OK\r\n'
        header = F 'HTTP / 1.1{self.status} {self.reason_phrase.get(self.status, "")}\r\n'
        # response header
        header += ' '.join(f'{k}: {v}\r\n' for k, v in self.headers.items())
        # Cookie
        if self.cookies:
            header += 'Set-Cookie: ' + \
                      '; '.join(f'{k}={v}' for k, v in self.cookies.items())
        # a blank line
        blank_line = '\r\n'
        # response body
        body = self.body

        # body supports either STR or bytes
        if isinstance(body, str):
            body = body.encode('utf-8')
        response_message = (header + blank_line).encode('utf-8') + body
        return response_message
Copy the code

Then, when the login view function finishes processing the login logic and executes the return redirect(‘/index’, cookies=cookies) on the last line, you can redirect to the home page and complete the login.

Now you can test the registration and login functions in the browser:

After a successful login, the user is redirected to the home page to display all the toDo of the current user.

Todo is not currently associated with the user, and in order todo so, you need to change the todo model and the JSON object format in which todo is stored.

The __init__ method of the Todo model needs to be able to accept user_id:

# todo_list/todo/models/todo.py

from . import Model


class Todo(Model) :
    """
    Todo 模型类
    """

    def __init__(self, **kwargs) :
        self.id = kwargs.get('id')
        self.user_id = kwargs.get('user_id', -1)
        self.content = kwargs.get('content'.' ')
Copy the code

We need to store the user_id in the JSON object that stores todo:

{
    "id": 4."user_id": 1."content": "hello world"
}
Copy the code

This will query all toDos associated with the current user’s user_id.

Modify the Todo List home page view function to get the current login user and query all toDos associated with it.

# todo_list/todo/controllers/todo.py

def index(request) :
    """ Home view function ""
    user = current_user(request)
    todo_list = Todo.find_by(user_id=user.id, sort=True, reverse=True)
    context = {
        'todo_list': todo_list,
    }
    return render_template('todo/index.html', **context)
Copy the code

The home page of the program will display the todo of the current user after successful login:

You can sign up for another account, open the Todo List program in another browser, and try adding a few ToDos to another user to see what happens.

After todo is associated with the user, new operations on toDO need to verify that the user is logged in. For toDo delete, modify, and query operations, only the creator of the todo has permission, so verify not only that the user is logged in, but also that the toDO of the operation belongs to the current logged-in user.

We could certainly put all the validation operations in view functions, but if you look closely, you’ll see that all the operations on Todo have one thing in common: they all need to verify that the user is logged in. So it would be more elegant to write a decorator that validates the login, so that all view functions that need to validate the user’s login only need to type this decorator.

# todo_list/todo/utils/auth.py

def login_required(func) :
    """ Validate login decorator """

    @functools.wraps(func)
    def wrapper(request) :
        user = current_user(request)
        if not user:
            return redirect('/login')
        result = func(request)
        return result

    return wrapper
Copy the code

Using the Todo List home page view function as an example, verify that the login decorator is used as follows:

# todo_list/todo/controllers/auth.py

@login_required
def index(request) :
    """ Home view function ""
    user = current_user(request)
    todo_list = Todo.find_by(user_id=user.id, sort=True, reverse=True)
    context = {
        'todo_list': todo_list,
    }
    return render_template('todo/index.html', **context)
Copy the code

You don’t need to make any changes to the code inside the index view function, just add the @login_required decoration to the function definition.

The Todo belonging only to the currently logged user is queried inside the view function by passing the user_id keyword argument to the find_by method of the Todo model.

The other view functions related to Todo are listed below and will not be covered here.

# todo_list/todo/controllers/auth.py

@login_required
def new(request) :
    """ New Todo view function """
    form = request.form
    logger(f'form: {form}')

    content = form.get('content')
    if content:
        user = current_user(request)
        if user:
            todo = Todo(content=content, user_id=user.id)
            todo.save()
    return redirect('/index')


@login_required
def edit(request) :
    """ Edit the Todo view function
    if request.method == 'POST':
        form = request.form
        logger(f'form: {form}')

        id = int(form.get('id', -1))
        content = form.get('content')

        if id! = -1 and content:
            user = current_user(request)
            if user:
                todo = Todo.find_by(id=id, user_id=user.id, ensure_one=True)
                if todo:
                    todo.content = content
                    todo.save()
        return redirect('/index')

    args = request.args
    logger(f'args: {args}')

    id = int(args.get('id', -1))
    if id= = -1:
        return redirect('/index')

    user = current_user(request)
    if not user:
        return redirect('/index')

    todo = Todo.find_by(id=id, user_id=user.id, ensure_one=True)
    if not todo:
        return redirect('/index')

    context = {
        'todo': todo,
    }
    return render_template('todo/edit.html', **context)


@login_required
def delete(request) :
    """ Delete todo view function """
    form = request.form
    logger(f'form: {form}')

    id = int(form.get('id', -1))
    if id! = -1:
        user = current_user(request)
        if user:
            todo = Todo.find_by(id=id, user_id=user.id, ensure_one=True)
            if todo:
                todo.delete()
    return redirect('/index')
Copy the code

Perfect project

A more complete Web project should include a mechanism for global exception handling, because you cannot enumerate all possible exceptions in every function of the program. Exceptions that are not caught are likely to reveal important information about a program if exposed directly to the user.

Here I designed two exception pages, 404 page to tell the user that the page visited does not exist, 500 page to tell the user that the server has an unknown error.

The HTML code for the 404 page is as follows:

<! -- todo_list/todo/templates/error/404.html -->

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="UTF-8">
    <title>Todo List | Page Not Found</title>
</head>
<body>
<p>Page Not Found</p>
</body>
</html>
Copy the code

The HTML code for page 500 is as follows:

<! -- todo_list/todo/templates/error/500.html -->

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="UTF-8">
    <title>Todo List | Internal Server Error</title>
</head>
<body>
<p>Internal Server Error</p>
</body>
</html>
Copy the code

Create a new error/ directory in the Templates template directory to house the 404 and 500 global exception pages.

Here are the 404 and 500 page handlers:

# todo_list/todo/utils/error.py

from todo.utils.templating import render_template
from utils.http import Response


def page_not_found() :
    """ Handle 400 exception """
    body = render_template('error/404.html')
    return Response(body, status=400)


def internal_server_error() :
    """ Handle 500 exception """
    body = render_template('error/500.html')
    return Response(body, status=500)


errors = {
    404: page_not_found,
    500: internal_server_error,
}
Copy the code

Because server.py is the entry and exit of the server program, this file is a good place to write code for global exception catching.

# todo_list/server.py

def process_connection(client) :
    """ Processing client requests """
    request_bytes = b''
    while True:
        chunk = client.recv(BUFFER_SIZE)
        request_bytes += chunk
        if len(chunk) < BUFFER_SIZE:
            break

    Request message
    request_message = request_bytes.decode('utf-8')
    logger(f'request_message: {request_message}')

    Parse request packets
    request = Request(request_message)
    try:
        Construct a response message from the request message
        response_bytes = make_response(request)
    except Exception as e:
        logger(e)
        # Return to user 500 page
        response_bytes = bytes(errors[500] ())# return response
    client.sendall(response_bytes)

    # close the connection
    client.close()


def make_response(request, headers=None) :
    """ Construct response message """
    The default status code is 200
    status = 200
    Handle static resource requests
    if request.path.startswith('/static'):
        route, methods = routes.get('/static')
    else:
        try:
            route, methods = routes.get(request.path)
        except TypeError:
            Return the user to the 404 page
            return bytes(errors[404] ())If the request method is not allowed to return the 405 status code
    if request.method not in methods:
        status = 405
        data = 'Method Not Allowed'
    else:
        Route is actually the index view function we defined in controllers. Py
        data = route(request)

    If the Response object is returned, the Response message is directly obtained
    if isinstance(data, Response):
        response_bytes = bytes(data)
    else:
        The Response object must be constructed first, and then the Response packet must be obtained
        response = Response(data, headers=headers, status=status)
        response_bytes = bytes(response)

    logger(f'response_bytes: {response_bytes}')
    return response_bytes
Copy the code

If the URL path accessed by the user does not match the view function, the user can be returned with a 404 page. When an uncaught exception occurs before the response is returned, it is caught by global exception handling in the process_Connection function and can be returned to the user 500 page. Remember to write real exception information into the log for easy troubleshooting.

Here is a screenshot of the page when a 404 or 500 exception is encountered:

At this point, all the functions of the Todo List program are developed. Finally, leave an assignment for readers to try to implement the user logout function.

This chapter source: chapter8

Contact me: