码迷,mamicode.com
首页 > Web开发 > 详细

002 web基础之手动实现简易web服务框架

时间:2019-10-22 01:04:32      阅读:247      评论:0      收藏:0      [点我收藏+]

标签:recv   输入   web基础   三次   rap   好的   fetchall   imp   应用   

一、软件开发架构

C/S 客户端 服务端

B/S 浏览器和服务端

注意:B/S 的本质也是C/S架构

二、实现一个简单的web服务

我们无需开发浏览器(本质即套接字客户端),只需要开发S端即可,S端的本质就是用套接字实现的,如下

# server 端
import socket

server = socket.socket() # 实例化一个server对象
server.bind(('127.0.0.1', 8080))# server对象绑定端口号
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''

# TCP半开连接是指发送了TCP连接请求,等待对方应答的状态,此时连接并没有完全建立起来,双方还无法进行通信交互的状态,此时就称为半连接。由于一个完整的TCP连接需要经过三次握手才能完成,这里把三次握手之前的连接都称之为半连接。
server.listen(5) 

while True:
   # 客户端/浏览器 连接服务端的时候,会产生客户端/浏览器 的地址和连接对象
    conn,addr = server.accept()
    
    # 由于是基于B/S 架构,所以接收浏览器的请求信息
    data = conn.recv(1024)
    
   
    # 接收了请求以后,给浏览器发送响应信息
    conn.send(b'hello world!')
    conn.close()
        
## 这个时候当浏览器访问服务器的地址时就会出现服务器和浏览器响应信息
127.0.0.1:8080 (尽量用谷歌浏览器)

注意:以上的S端已经可以正常接收浏览器发来的请求信息了,但是浏览器在接收到S端回复的响应消息b‘hello world’时却不能正常的解析,此处是因为浏览器和S端之间收发消息默认使用的应用层协议是Http协议,浏览器默认以http协议规定的格式发消息,而s端也必须按照http协议的格式回消息才行

2.1 S端修正版本

处理HTTP协议的请求信息,并按照HTTP协议的格式响应回复信息

# server端修正版
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''

server.listen(5) 

while True:

    conn,addr = server.accept()

    data = conn.recv(1024)
    
    conn.send(b'http://1.1 200 ok\r\n\r\n')# 此处是服务端给浏览器发送的协议
    conn.send(b'hello world!')
    conn.close()
        
## 这个时候当浏览器访问服务器的地址时就会出现服务器给浏览器正确响应信息
127.0.0.1:8080 (尽量用谷歌浏览器)
    
## 此时重新启动S端就可以看到解析正确的hello world了

2.2 S端将html标签返回

# server端返回html标签
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''

server.listen(5) 

while True:
    conn,addr = server.accept()
    data = conn.recv(1024)
    
    conn.send(b'http://1.1 200 ok\r\n\r\n')
    # 返回html标签
    conn.send(b'<p>hello world!</p>')
    conn.close()

2.3 浏览器携带参数访问S端时的处理

# 浏览器访问 127.0.0.1:8080/index
import socket

server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5)
while True:
    conn, addr = server.accept()

    # 接收浏览器的请求
    data = conn.recv(1024)
    current_path = data.decode('utf8')
    # print(current_path)
    
    # 在这里我们会发现用户访问服务端的一些信息
    '''
    请求头\r\n
    请求首行\r\n
    \r\n
    请求体
    '''
    # 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
    path_info = current_path.split(' ')[1]
    print(path_info)


    # 规定和浏览器使用的传输协议,我们都要遵守
    # tcp连接时传输时通过二进制的形式
    conn.send(b'http://1.1 200 ok \r\n\r\n')
    
    # 判断用户携带的参数是否有我服务端的权限,是否可以正确的与服务端之间进行通信
    if path_info=="/index":
        conn.send(b'hello index')
        conn.close()

    elif path_info=="/login":
        conn.send(b'<p>hello world!</p>')
        conn.close()

    else:
        conn.send(b'404')
        conn.close()

2.4 S端返回html页面给浏览器

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="python folder/bootstrap/js/bootstrap.min.js"></script>
    <link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <meta name="viewport" content="width=device-width, initial-scale=1 ">
    <title>index</title>
</head>
<body>
<p>hello 我是index</p>
</body>
</html>
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="python folder/bootstrap/js/bootstrap.min.js"></script>
    <link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <meta name="viewport" content="width=device-width, initial-scale=1 ">
    <title>lgoin</title>
</head>
<body>
<p>hello 我是login</p>
</body>
</html>
# server 端返回html文件渲染浏览器页面
import socket

server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5)

while True:

    conn, addr = server.accept()

    # 接收浏览器的请求
    data = conn.recv(1024)
    current_path = data.decode('utf8')
    # print(current_path)
    # 在这里我们会发现用户访问服务端的一些信息
    '''
    请求头\r\n
    请求首行\r\n
    \r\n
    请求体
    '''
    # 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
    path_info = current_path.split(' ')[1]
    print(path_info)

    conn.send(b'http://1.1 200 ok \r\n\r\n')
    # 判断用户携带的参数是否有我服务端的权限,是否可以正确的与服务端之间进行通信
    if path_info=="/index": # 如果浏览器携带的参数是可以访问服务器的就打开html文件,以rb读取内容,响应给浏览器,此时浏览器矿口会看到相应的html页面
        with open('index.html','rb') as fr:
            data = fr.read()
        conn.send(data)
        conn.close()

    elif path_info=="/login":
        with open('login.html','rb') as fr:
            data = fr.read()
        conn.send(data)
        conn.close()

    else:
        conn.send(b'404')
        conn.close()

2.5 减少代码冗余的server端

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="python folder/bootstrap/js/bootstrap.min.js"></script>
    <link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <meta name="viewport" content="width=device-width, initial-scale=1 ">
    <title>index</title>
</head>
<body>
<p>hello my index</p>
</body>
</html>
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="python folder/bootstrap/js/bootstrap.min.js"></script>
    <link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <meta name="viewport" content="width=device-width, initial-scale=1 ">
    <title>lgoin</title>
</head>
<body>
<p>hello my login</p>
</body>
</html>
# server减少代码冗余

import socket

server = socket.socket()

server.bind(("127.0.0.1", 8080))
server.listen(5)  # 相当于半连接池,只能一次接受5个客户端的访问,这个时候服务端处于监听的状态,等待客户端的连接


# 通过进行判断,执行任务的接口函数
def index():
    with open('index.html','r') as fr:
        data = fr.read()
    return data

def login():
    with open('login.html','r') as fr:
        data = fr.read()
    return data

def earro():
    return '404'


# 这是为了减少代码的冗余,把用户携带的参数放在一个固定的列表里
# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
    ('/index',index),
    ('/login',login)
]



while True:
    conn, addr = server.accept()

    #接收浏览器的请求
    data = conn.recv(1024)
    current_path = data.decode('utf8')
    conn.send(b'http://1.1 200 ok\r\n\r\n')
    # print(current_path)
    # 在这里我们会发现用户访问服务端的一些信息
    '''
    请求头\r\n
    请求首行\r\n
    \r\n
    请求体
    '''
    # 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
    path_info = current_path.split(' ')[1]
    # print(path_info)

    func = None # 定义一个获取用户携带参数的
    for url in urls_list:
        if path_info == url[0]:
            func = url[1]
            break #只要一匹配到,我退出循环,减少消耗的资源

    # 这里是判断func用户请求我的时候,有没有参数,
    # 若有则取执行响应的功能
    if func:
        res = func()
    else:
       res = earro()

    # 执行对应的接口函数过后,我们会接收到函数返回给我们的字符串值
    # 发给用户的时候,我们要转换为二进制的形式去发送
    conn.send(res.encode('utf8'))
    conn.close()

三、基于wsgrief模块实现简易web服务框架

这个模块实现了上面的所有手动去写原生怠慢的过程

根据每个部分功能不同,将他们分成不同的py文件

urls.py:只用来存放路由与视图函数对应的关系的

View.py:用来存放视图函数(函数、类)

拆分完成后,如果想要给一个应用添加应用,仅仅只需要在urls.py和View.py两个文件里改就可以了

并且使用wsgrief模块的本质是将不用我们自己去写server部分,而且我们是通过任务分发的方式去实现每个浏览器请求信息的,以下就是一个完整的例子

# urls.py文件

from views import *
# 此处是路由和功能函数的关系

# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
    ('/index', index),
    ('/login', login),
]
# views.py

# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
        data = fr.read()
    return data


def login(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
        data = fr.read()
    return data

def error(env):
    return '404'
# server 端

from wsgiref.simple_server import make_server
from url_list import *

# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
    '''
    :param env: 客户端请求的相关数据
    :param response: 服务端响应的所有数据
    :return:
    '''
    
    response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码

    current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
    # 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values

    # 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
    func = None
    for url in urls_list:
        if current_path == url[0]:
            func = url[1]  # 一旦匹配成功 就将匹配到的函数名赋值给func变量
            break  # 主动结束匹配
        # 判断func是否有值

    if func:
        res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
    else:
        res = error(env)
    return [res.encode('utf-8')]


if __name__ == '__main__':
    # 实时监听该地址  只要有客户端来连接 统一交给run函数去处理
    server = make_server('127.0.0.1', 8080, run)
    server.serve_forever()  # 启动服务端

3.1 通过后端获取当前时间展示到前端

# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
        data = fr.read()
    return data


def login(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
        data = fr.read()
    return data


def error(env):
    return '404'


# 用于显示当前时间的
import datetime
def get_time(env):
    current_time = datetime.datetime.now()
    current_time = current_time.strftime('%Y-%m-%d %X')

    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_time.html','r') as fr:
        data = fr.read()
        data = data.replace('time',current_time)

        # 我们也可以通过特定的语法来帮我们实现,一般都是用规定的字符
        # data = data.replace('$$time$$',current_time)
    return data
# urls.py
from views import *
# 此出是路由和功能函数的关系

# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
    ('/index', index),
    ('/login', login),
    ('/get_time', get_time),
    ('/get_user', get_user),
]
# server 端

from wsgiref.simple_server import make_server
from url_list import *

# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
    '''
    :param env: 客户端请求的相关数据
    :param response: 服务端响应的所有数据
    :return:
    '''
    
    response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码

    current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
    # 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values

    # 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
    func = None
    for url in urls_list:
        if current_path == url[0]:
            func = url[1]  # 一旦匹配成功 就将匹配到的函数名赋值给func变量
            break  # 主动结束匹配
        # 判断func是否有值

    if func:
        res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
    else:
        res = error(env)
    return [res.encode('utf-8')]


if __name__ == '__main__':
    # 实时监听该地址  只要有客户端来连接 统一交给run函数去处理
    server = make_server('127.0.0.1', 8080, run)
    server.serve_forever()  # 启动服务端

3.2 后端通过获取数据库的信息,展示到前端页面

此处提出一个概念:模板的渲染

模板的渲染:后端获取的数据 传递给前端页面就叫做模板的渲染

现在又有一个包可以帮我们实现这个任务了jinja2

安装方式:pip3 install jinja2

模板语法(极其贴近python后端语法)

# 这里是下面的一个例子可以将字典相应给前端页面,然后通过点取值的方式         
<p>{{ user }}</p>
<p>{{ user.name }}</p>
<p>{{ user['pwd'] }}</p>
<p>{{ user.get('hobby') }}</p>

?
? #通过获取数据库的数据,然后渲染到前端页面
? {% for user_dict in user_list %}
?
? {{ user_dict.id }}
? {{ user_dict.name }}
? {{ user_dict.pwd }}
?
? {% endfor %}

# viess.py
# Author:Cecilia


# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
        data = fr.read()
    return data


def login(env):
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
        data = fr.read()
    return data


def error(env):
    return '404'


# 用于显示当前时间的
import datetime
def get_time(env):
    current_time = datetime.datetime.now()
    current_time = current_time.strftime('%Y-%m-%d %X')

    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_time.html','r') as fr:
        data = fr.read()
        data = data.replace('time',current_time)

        # 我们也可以通过特定的语法来帮我们实现,一般都是用规定的字符
        # data = data.replace('$$time$$',current_time)
    return data




# 现在我们有一个需求,就是我后台有一个字典,我需要从后台去渲染前端的页面
# 并且让用户在浏览器通过输入字典的key九可以取值
# 这个时候我们需要导入一个jinja2的模块来帮助我们实现,这是封装好的方法,我们直接用就可以了
from jinja2 import Template
def get_user(env):
    d = {'name':'cecilia','age':18,'sex':'female','hobbies':['run','sing','play']}
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_user.html','r') as fr:
        data = fr.read()

    # 我们拿到get_userhtml文件的文本格式以后
    # 通过jinja2库的Template模块来帮我们完成
    temp = Template(data)
    res = temp.render(user=d)# 将字典d传递给前端页面 页面上通过变量名user就能够获取到该字典
    return res


#我们需要从数据库里面取值
import pymysql
def get_db(env):
    conn = pymysql.connect(
        host = '127.0.0.1',
        port = 3306,
        database = 'mydb',
        user = 'root',
        password = '123',
        charset = 'utf8',
        autocommit = True
    )

    # 获取游标对象
    curser = conn.cursor(pymysql.cursors.DictCursor)
    sql = 'select * from userinfo'
    curser.execute(sql)
    res = curser.fetchall()
    with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_db.html','r',encoding='utf8') as fr:
        data = fr.read()

    temp = Template(data)
    ret = temp.render(user_list = res) # 将从数据库里取出来的值,放进我们get_db html页面种
    return ret
# urls.py
from views import *

# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
    ('/index', index),
    ('/login', login),
    ('/get_time', get_time),
    ('/get_user', get_user),
    ('/get_db', get_db),
]
# server 端

from wsgiref.simple_server import make_server
from url_list import *

# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
    '''
    :param env: 客户端请求的相关数据
    :param response: 服务端响应的所有数据
    :return:
    '''
    
    response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码

    current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
    # 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values

    # 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
    func = None
    for url in urls_list:
        if current_path == url[0]:
            func = url[1]  # 一旦匹配成功 就将匹配到的函数名赋值给func变量
            break  # 主动结束匹配
        # 判断func是否有值

    if func:
        res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
    else:
        res = error(env)
    return [res.encode('utf-8')]


if __name__ == '__main__':
    # 实时监听该地址  只要有客户端来连接 统一交给run函数去处理
    server = make_server('127.0.0.1', 8080, run)
    server.serve_forever()  # 启动服务端

注意:在这里我们发现,不管我们添加多少个功能,我们的server页面都没有变过,所以这就是我们的web框架

注意2:在我们的功能逐渐变多时,我们此时可能要写很多个html页面,这样会影响到我们调式代码,所以在这里,我们把这些html页面单独拿出来放在一个文件夹里(templates文件夹)

002 web基础之手动实现简易web服务框架

标签:recv   输入   web基础   三次   rap   好的   fetchall   imp   应用   

原文地址:https://www.cnblogs.com/XuChengNotes/p/11717306.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!