Python:mini-web 自建web服务器和web框架,实现WSGI协议(含代码实例)

1.web服务器示意图

在这里插入图片描述
当浏览器请求的是一个静态页面时,服务器直接调用静态资源再发送给浏览器即可

当浏览器请求的是需要展示动态页面时,则需要将要展示的动态内容重新写在一个文件里,这个文件就是一个web框架,浏览器发来的页面请求信息传入给框架中,在框架中进行资源的寻找调用,并最后返回给服务器一个body内容,服务器再将body和header信息结合后发送给浏览器。

·

·

2.关于WSGI协议的流程

WSGI协议是为了匹配服务器使用框架的规则,也就是说所有的框架只要符合WSGI协议的规定,就可以被服务器拿来使用
·

在这里插入图片描述
·

·

定义WSGI接口:

def application(environ, start_response):
	start_response('200 OK', [('Content-Type', 'text/html')])
	return body(传给服务器请求页面的内容)

·
上面的 application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

··environ:一个包含所有HTTP请求信息的dict对象;
··start_response :一个发送HTTP响应的函数。

·

在服务器中会自建一个函数,用来保存框架给出的HTTP协议header头信息,在服务器调用框架的application()函数时,会将这个函数传进去,也就是传给start_response,在框架中会在application()函数里,对接收到的这个函数传入实参,也就是传入HTTP协议的header头信息,这样在服务器中,这个函数便会收到并保存框架给的头信息,浏览器请求的页面文件名字会以字典的形式传入environ,这样在框架中可以通过environ这个参数得到浏览器请求的页面文件名字。

在框架中的application()函数中执行了所有任务后,最后一定会返回一个值,这个值就是HTTP协议的body信息,服务器调用框架的application()函数后就可以得到这个body内容,并和之前得到的header信息一起发送给浏览器。

在这里插入图片描述
·

(这里的文件为自定义文件,需自行补充)

服务器代码实例:

WSGI_http_socket.py

import socket
import view.mini_frame
import time
import multiprocessing
import re


class http_server(object):
    def __init__(self):
        # 创建套接字
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置先让服务器断开连接
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定本地ip和端口
        self.tcp_socket.bind(("",8080))
        # 设置套接字改主动为被动状态
        self.tcp_socket.listen(128)


    def handle_data(self, new_client_socket):
        # 接受浏览器最大为1024个字节的请求信息并将字节流解码为字符串形式
        recv_data = new_client_socket.recv(1024).decode("utf-8")
        recv_data_list = recv_data.splitlines()
        # 利用正则表达式得到浏览器请求的页面文件
        find_dire = re.match(r"[^/]+(/[^ ]*)", recv_data_list[0])
        find_data = find_dire.group(1)
        if find_data == "/":
            find_data = "./templates/1.html"
        
        # 判断浏览器请求的页面是否以.py为结尾
        if not find_data.endswith(".py"):
            try:
                f = open("./static" + find_data, "rb")
            except:
                responds = "HTTP/1.1 404 NOT FOUND\r\n"
                responds += "\r\n"
                responds += "file not found"
                new_client_socket.send(responds.encode("gbk"))
            else:
                content = f.read()
                f.close()
                responds = "HTTP/1.1 200 OK \r\n"
                responds += "\r\n"
                new_client_socket.send(responds.encode("utf-8"))
                new_client_socket.send(content)
        else:
            # 将文件名保存到字典中传到框架
            env = dict()
            env["Path_file"] = find_data
            # 将函数实例函数set_response_header传给框架是为了用实例函数接收header信息,body用来接收框架运行结束后返回的要显示的内容
            body = view.mini_frame.application(env, self.set_response_header)
            print(body)
            print("**")
            # 在服务器中确定HTTP协议的版本号"HTTP/1.1"
            header = "HTTP/1.1 %s\r\n" % self.status
            for i in self.headers:
                header += "%s:%s\r\n" % (i[0], i[1])
            header += "\r\n"
            responds = header + body
            new_client_socket.send(responds.encode("utf-8"))
        new_client_socket.close()

    def set_response_header(self, status, headers):
        self.status = status
        # 在服务器中添加服务器名称版本
        self.headers = [("server", "mini-webv1.0")]
        self.headers += headers

    def runserver(self):
        while True:
            print("等待浏览器链接")
            new_client_socket,client_addr = self.tcp_socket.accept()
            p = multiprocessing.Process(target=self.handle_data, args=(new_client_socket,))
            p.start()
            new_client_socket.close()
        self.tcp_socket.close()

def main():
    server = http_server()
    server.runserver()


if __name__ == "__main__":
    main()

·

框架代码实例:

mini_frame.py

def index():
    with open("./templates/index.html") as f:
        content = f.read()
    return content

def center():
    with open("./templates/center.html", "r") as f:
        content = f.read()
    return content

def application(env, start_response):
	# 给服务器中保存头信息的函数传入实参(header信息)
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    # 得到浏览器请求的页面文件名
    file_name = env['Path_file']
    if file_name == "/index.py":
        return index()
    elif file_name == "/center.py":
        return center()
    else:
        return 'Hello World!我爱你中国'

·

·

优化服务器代码,使服务器与框架基本解耦

(将所有路径存入配置文件中,将端口号,和调用哪个模块里的哪个函数在运行程序时传入)

文件路径配置代码:

web_server.conf

{
"static_path":"./static",
"view_path":"./view"
}

·

框架代码:

mini_frame.py

def index():
    with open("./templates/index.html") as f:
        content = f.read()
    return content

def center():
    with open("./templates/center.html", "r") as f:
        content = f.read()
    return content

def application(env, start_response):
    # 给服务器中保存头信息的函数传入实参(header信息)
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    # 给服务器中保存头信息的函数传入实参(header信息)
    file_name = env['Path_file']
    if file_name == "/index.py":
        return index()
    elif file_name == "/center.py":
        return center()
    else:
        return 'Hello World!我爱你中国'

·

服务器代码:

WSGI_http_socket.py

import socket
import sys
# import view.mini_frame
import time
import multiprocessing
import re


# 默认端口号为8080
PORT = 8080

class http_server(object):
    def __init__(self, PORT, app, static_path):
        # 创建套接字
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置先让服务器断开连接
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定本地ip和端口
        self.tcp_socket.bind(("", PORT))
        # 设置套接字改主动为被动状态
        self.tcp_socket.listen(128)
        self.application = app
        self.static_path = static_path
    def handle_data(self, new_client_socket):
        # 接受浏览器最大为1024个字节的请求信息并将字节流解码为字符串形式
        recv_data = new_client_socket.recv(1024).decode("utf-8")
        recv_data_list = recv_data.splitlines()
        # 利用正则表达式得到浏览器请求的页面文件
        find_dire = re.match(r"[^/]+(/[^ ]*)", recv_data_list[0])
        find_data = find_dire.group(1)
        if find_data == "/":
            find_data = "/1.html"
        
        # 判断浏览器请求的页面是否以.py为结尾
        if not find_data.endswith(".py"):
            try:
                f = open(self.static_path+find_data, "rb")
            except:
                responds = "HTTP/1.1 404 NOT FOUND\r\n"
                responds += "\r\n"
                responds += "file not found"
                new_client_socket.send(responds.encode("gbk"))
            else:
                content = f.read()
                f.close()
                responds = "HTTP/1.1 200 OK \r\n"
                responds += "\r\n"
                new_client_socket.send(responds.encode("utf-8"))
                new_client_socket.send(content)
        else:
            # 将文件名保存到字典中传到框架
            env = dict()
            env["Path_file"] = find_data
            # 将函数实例函数set_response_header传给框架是为了用实例函数接收header信息,body用来接收框架运行结束后返回的要显示的内容
            body = self.application(env, self.set_response_header)
            print(body)
            print("**")
            # 在服务器中确定HTTP协议的版本号"HTTP/1.1"
            header = "HTTP/1.1 %s\r\n" % self.status
            for i in self.headers:
                header += "%s:%s\r\n" % (i[0], i[1])
            header += "\r\n"
            responds = header + body
            new_client_socket.send(responds.encode("utf-8"))
        new_client_socket.close()

    def set_response_header(self, status, headers):
        self.status = status
        # 在服务器中添加服务器名称版本
        self.headers = [("server", "mini-webv1.0")]
        self.headers += headers

    def runserver(self):
        while True:
            print("等待浏览器链接")
            new_client_socket,client_addr = self.tcp_socket.accept()
            p = multiprocessing.Process(target=self.handle_data, args=(new_client_socket,))
            p.start()
            new_client_socket.close()
        self.tcp_socket.close()

def main():
    global PORT
    frame_app_name = ""
    frame_name = ""
    if len(sys.argv) == 3:
        try:
            PORT = int(sys.argv[1])
            frame_app_name = sys.argv[2]
        except:
            print("端口输入有误")
    # 得到框架名和要调用的函数名
    ret = re.match(r"([^:]+):(.*)", frame_app_name)
    if ret:
        frame_name = ret.group(1)
        app_name = ret.group(2)
    else:
        print("请按照:python3 xxx.py 端口号 框架名:函数名")

    with open("./web_server.conf", "r") as f:
        # eval()函数将字符串当作有效的表达式,这里将读出的内容转变成有效的字典存入content中
        conf_info = eval(f.read())

    # 向系统变量中添加路径,使得程序在用import导入模块时也会在导入的路径中寻找该模块
    sys.path.append(conf_info["view_path"]) 

    # 通过import+变量会在路径中直接找该变量名,如果用__import__(变量)会先解析该变量,再去引入解析后的模块名,返回值将会标记着导入的模块
    frame = __import__(frame_name)

    # getattr()函数,使app指向了mini—frame模块中的application函数
    app = getattr(frame, app_name)

    server = http_server(PORT, app, conf_info["static_path"])
    server.runserver()


if __name__ == "__main__":
    main()

·

·

优化框架代码,通过使用装饰器传参,实现路由功能

对于不同的请求,安排调用不同的函数并进行回应的这种机制也叫做路由功能

框架代码:

URL_DICT = dict()


def dict_url(url):
    def add_dict(func):
        # 裝飾器傳的參數保存在url,func指向被裝飾的函數
        URL_DICT[url] = func
        def call_func(*args, **kwargs):
            return func(*args, **kwargs)
        return call_func
    return add_dict

@dict_url("/index.py")
def index():
    with open("./templates/index.html") as f:
        content = f.read()
    return content

@dict_url("/center.py")
def center():
    with open("./templates/center.html", "r") as f:
        content = f.read()
    return content

def add_dict(func):
    def call_func(*args, **kwargs):
        return func(*args, **kwargs)
    return call_func

def application(env, start_response):
    # 给服务器中保存头信息的函数传入实参(header信息)
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    # 给服务器中保存头信息的函数传入实参(header信息)
    file_name = env['Path_file']
    try:
        # 利用字典,key保存路徑名,value保存函數的引用,加括號表示函數的調用
        return URL_DICT[file_name]()
    except Exception as ret:
        return "輸入的地址不正確:%s" % str(ret)

·

·

·

3.在框架中添加对数据库的增删改查功能,并增添log日志

框架代码:

import re
import logging
from pymysql import *
from urllib.parse import unquote


URL_DICT = dict()
template_root = "./templates"


def dict_url(url):
    def add_dict(func):
        # 裝飾器傳的參數保存在url,func指向被裝飾的函數
        URL_DICT[url] = func
        def call_func(*args, **kwargs):
            return func(*args, **kwargs)
        return call_func
    return add_dict

@dict_url(r"/index.html")
def index(ret, file_name):
    """返回index.py需要的页面内容"""
    try:
        f = open(template_root + file_name)
    except Exception as res:
        return "%s" % res
    else:
        content = f.read()
        f.close()
 
    conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
    cs = conn.cursor()
    cs.execute("select * from info;")
    stock_info = cs.fetchall()
    cs.close()
    conn.close()
    
    # 定義展示內容的格式
    tr = """<tr>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>%s</td>
        <td>
            <input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="%s">
        </td>
        </tr>
    """
    html = ""
    for i in stock_info:
        html += tr % (i[0], i[1], i[2] ,i[3], i[4], i[5], i[6], i[7], i[1])

    # 用查詢到的信息內容(強轉爲字符串)去替換content裏的\{%content%\}
    content = re.sub(r"\{%content%\}", html, content)
    return content

@dict_url(r"/center.html")
def center(ret, file_name):
    """返回center.py需要的页面内容"""
    # return "hahha" + os.getcwd()  # for test 路径问题
    try:
        f = open(template_root + file_name)
    except Exception as res:
        return "%s" % res
    else:
        content = f.read()
        f.close()

    conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")

    cs = conn.cursor()
    cs.execute("select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info from info as i inner join focus as f on i.id=f.info_id;")
    text = cs.fetchall()
    cs.close()
    conn.close()

    tr = """
        etr>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>
                <a type="button" class="btn btn-default btn-xs" href="/update/%s.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a>
            </td>
            <td>
                <input type="button" value="刪除" id="toDel" name="toDel" systemidvaule="%s">
            </td>
        </tr>
    """

    html = ""
    for i in text:
        html += tr % (i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[0], i[0])
    content = re.sub(r"\{%content%\}", html, content)
    return content


@dict_url(r"/update/(\d+)\.html")
def update(ret, file_name):
    """显示 更新页面的内容"""
    try:
        f = open("./templates/update.html")
    except Exception as ret:
        return "%s,,,没有找到%s" % (ret, template_file_name)
    else:
        content = f.read()
        f.close()
 
        if ret:
            stock_code = ret.group(1)
        else:
            stock_code = 0
 
        conn = connect(host='localhost',port=3306,user='root',password='mysql',database='gupiao',charset='utf8')
        cursor = conn.cursor()
        # 会出现sql注入,怎样修改呢? 参数化
        sql = """select focus.note_info from focus inner join info on focus.info_id=info.id where info.code=%s;""" % stock_code
        cursor.execute(sql)
        stock_note_info = cursor.fetchone()
        cursor.close()
        conn.close()
 
        content = re.sub(r"\{%code%\}", stock_code, content)
        content = re.sub(r"\{%note_info%\}", str(stock_note_info[0]), content)
 
        return content
 
 
@dict_url(r"/update/(\d*)/(.*)\.html")
def update_note_info(ret, file_name):
    """进行数据的真正更新"""
    stock_code = 0
    stock_note_info = ""
 
    if ret:
        stock_code = ret.group(1)
        stock_note_info = ret.group(2)
        stock_note_info = unquote(stock_note_info)
 
    conn = connect(host='localhost',port=3306,user='root',password='mysql',database='gupiao',charset='utf8')
    cursor = conn.cursor()
    # 会出现sql注入,怎样修改呢? 参数化
    sql = """update focus inner join info on focus.info_id=info.id set focus.note_info=%s where info.code=%s;"""
    cursor.execute(sql, (stock_note_info, stock_code))
    conn.commit()
    cursor.close()
    conn.close()
 
    return "修改成功"


@dict_url(r"/add/(\d+)\.html")
def add_focus(ret, file_name):
    
    stock_code = ret.group(1)
    conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
    cs = conn.cursor()

    cs.execute("select * from focus as f inner join info as i on f.info_id=i.id where i.code=%s;", (stock_code,))
    if cs.fetchone():
        cs.close()
        conn.close()
        return "该信息已经关注过啦"
    
    cs.execute("insert into focus(info_id) select id from info where code=%s;", (stock_code,))
    conn.commit()
    cs.close()
    conn.close()

    return "消息增加成功"

@dict_url(r"/del/(\d+)\.html")
def del_focus(ret, file_name):
    stock_code = ret.group(1)
    conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
    cs = conn.cursor()
    cs.execute("delete from focus where info_id=(select id from info where code=%s)", (stock_code,))
    conn.commit()
    cs.close()
    conn.close()


def application(env, start_response):
    # 给服务器中保存头信息的函数传入实参(header信息)
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    # 给服务器中保存头信息的函数传入实参(header信息)
    file_name = env['Path_file']
    
    # 添加log日志打印,打印到文件中
    logging.basicConfig(level=logging.INFO,
            filename="./log.txt",
            filemode="a",
            format="%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")

    logging.info("访问的文件是:%s" % file_name)
    try:
        # 利用字典,key保存路徑名,value保存函數的引用,加括號表示函數的調用
        for url,call_func in URL_DICT.items():
            print(url)
            ret = re.match(url, file_name)
            if ret:
                return call_func(ret, file_name)
                break

        else:
            logging.warning("访问不到页面")
            return "访问不到页面--%s" % file_name

    except Exception as ret:
        return "%s" % str(ret)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

此时一位小白路过

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值