python miniweb框架学习总结(wsgi)


前言

python中的web开发,虽然python更倾向于一种脚本语言并不适合web开发,但了解相关web开发模式对本身的web开发思维还是有很大帮助。基于自己的了解和相关查阅,,总结一下学习.

miniweb


一、网络协议

计算机在网络上通信需要遵行一定的规则,可以想象一下城市的道路系统,计算机各个程序之间和各个服务器之间要通过“道路”到达自己的目的地,同时基于对道路的维护,计算机将通信这一行为分成抽象的结构,在现实世界中又实现了具体的层次。
百度百科中的介绍如下:
为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。
其中,物理层和数据链路层就是“基础设施”,网络层和传输层即“交通法”,会话层和表示层、应用层主要体现在到达目的之后的行为。
web开发中所用的协议表现在网络层和传输层中,它控制着计算机之间的通信和数据传输,比如人们熟知的TCP/IP协议。

1.三次握手、四次挥手

三次握手、四次挥手
在web开发中,客户端和服务端之间的通信建立在网络层上,客户端主动发起请求、服务端被动监听请求。第一次握手发生在客户端主动建立联系,服务端被动监听到请求。随后服务端向客户端确认是否建立联系(第二次握手),客户端接收到服务端的确认信息,给服务端反馈同意,服务端接受到反馈即建立相应的客户端监听(第三次握手),建立通信之后,可以进行数据传输工作,建立通信的过程即三次握手。数据传输完毕,客户端无数据传输,开始向服务端发送中止连接请求,同时不再发送数据,服务端接收到请求向应用层发送应用停止信号(一次挥手),同时给客户端发送“关闭中,请等待”。虽然客户端不再发送数据,但要回复服务端的信号,此时处于半关闭状态(二次挥手)。随后服务端给客户端发送连接中断确认(三次挥手),客户端回复反馈信息“确认”给服务端,服务端收到信息之后彻底断开连接(四次挥手)。但因为客户端这边无法再收到服务端的指令,会暂时处于“老大,您吩咐”的状态,2-3分钟后,确认服务端已经不理自己了,也全部断开。
简要理解,想要更深层次理解请看相关博客,或者B站上有更详细的网络通信的基础内容。
图片来源:图片博文

三次握手:
三次握手
四次挥手:
四次挥手

2.TCP、IP和端口

上述的计算机之间建立通信的过程,其基础的协议就是TCP协议。百度百科:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。把计算机比作出发地和目的地的话,IP就是两者的位置,即IP是计算机在网络中的地址。我们常用的本机地址:localhost和127.0.0.1即IP。同时,在因特网中我们所访问的www.baidu.com也是IP地址,不过他们代指的是计算机IP绑定的公网地址,以域名的方式体现在网络世界中,方便人们搜索也方便相关部门监管。
有关域名和公网相关内容可以自行搜索一下,简单说,公网是世界上公有财产,面向世界,也就是数量有限。域名即营业执照,需要在相关部门记录(备案)相关信息和用途。所以哈,那啥都是黑网站。。。危险!
有了通信规则和地址我们就能送货了吗?不行,因为不知道送到小区的哪一个住户,又是从那个“舔狗”送的。这种不确定的信息也是不被通信规则所接纳的,所以对应具体住户的方式:端口。在web开发中,最常见的端口即80,我们之所以访问www.baidu.com不用加端口,就是因为它绑定了80端口。在tomcat中固定端口是8080,在telnet协议通信中端口是23,ssh中是22,oracle是2151,mysql是3306,sqlserver是1433,FTP是21。这些都是常用的既定端口,当然为了安全我们也可以在此基础上更改对应用户的端口。客户端到服务端的端口通信时一对多的,服务端到客户端的通信是一对一。主动和被动结合IP和端口在一起理解一下应该就比较清楚了。
同样,端口也是有具体的数量的:端口号:65536个。计算机基础架构已经占用了一些端口,即知名端口:范围从0到1023。
在开发过程中,要注意端口的冲突问题。

3.数据

简单了解:
位:bit
字节:byte
1024字节: 1 kb
1024 kb : 1 Mb
1024 Mb : 1 Gb
1024 Gb : 1 T
编码:
unicode iso-8859-1 GBK UTF-8
数据传输过程中需要编码和解码:encode、decode

4.建立通信的工具:套接字(socket)

实际开发中不需要我们写三次握手和四次挥手的实现,站在巨人的肩膀上直接使用socket实现建立通信就行,想具体了解的可以问度娘。

二、客户端和服务端开发

1.客户端

代码如下(示例):

# 导入socket
import socket

# 定义连接方法
def web_connect():
	# 建立socket对象
	# 1. AF_INET:表示ipv4
    # 2. SOCK_STREAM: tcp传输协议
    web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 请求连接到服务器,连接不成功会报错,可以抛出异常,添加日志
    # 1. IP
    # 2. 端口
    web_socket.connect(("127.0.0.1", 8080))
    # 设置传输的数据,对数据进行编码
    send_data = "第一次连接尝试".encode("UTF-8")
    # 传输数据
    web_socket.send(send_data)
    # 接收服务端数据,每次接收1024 byte,编码为字节
    recv_data = web_socket.recv(1024)
    # 对数据进行解码
    contexnt = recv_data.decode("GBK")
    # 打印接收到的数据
    print(contexnt)
    # 关闭socket连接
    web_socket.close()

# 调用函数
web_connect()

2.服务端

# 导入socket模块
import socket
# 程序入口
if __name__ == "__main__":
	# 建立socket对象
	# 1. IPv4
	# 2. TCP传输协议
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口号立即释放
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 设置绑定的端口号
    # 1. 默认本机:127.0.0.1
    # 2. 端口号
    server_socket.bind(("",8339))
    # 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端,
    # 不需要让客户端进行等待建立连接
    # listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字来完成
    server_socket.listen(128)
    # 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行
    # 1. 专门和客户端通信的套接字: service_client_socket
    # 2. 客户端的ip地址和端口号: ip_port
    server_listen_socket, ip_port = server_socket.accept()
    # 接收客户端发送的数据, 这次接收数据的最大字节数是1024
    recv_data = server_listen_socket.recv(1024)
    # 对接收到的数据进行解码
    content = recv_data.decode("GBK")
    print("接收的数据是:", content)
    # 服务端数据,编码
    send_data = "收到!马上处理!".encode("UTF")
    # 发送数据
    server_listen_socket.send(send_data)
    # 关闭服务端和客户端连接套接字,中止和客户端通信
    server_listen_socket.close()
    # 关闭服务端套接字,不再接受客户端的连接请求
    server_socket.close()

3.多线程服务端

import socket
import threading


def http_socket(socket_client_tcp, ip_port):
    # 循环接收客户端数据
    while True:
        # 接收客户端数据,最大1024字节
        revc_data = socket_client_tcp.recv(1024)
        # 判断是否还有数据传输
        if not revc_data:
            print(f"客户端{ip_port}连接关闭")
            break
        else:
            # 解码接收到的数据
            content = revc_data.decode("GBK")
            print(content)
            # 编码发送的数据
            send_data = "收到!正在处理。".encode()
            # 发送数据
            socket_client_tcp.send(send_data)
    socket_client_tcp.close()


if __name__ == "__main__":
    # 建立socket对象
    # AF_INET:IPv4
    # SOCK_STREAM:TCP通信协议
    socket_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用
    socket_server_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,True)
    # 绑定IP和端口
    socket_server_tcp.bind(("", 8378))
    # 设置监听套接字
    socket_server_tcp.listen(128)
    while True:
        # 建立客户端服务端通信套接字,获取客户端ip和端口
        socket_client_tcp, ip_port = socket_server_tcp.accept()
        # 建立多线程
        socket_thread = threading.Thread(target=http_socket, args=(socket_client_tcp, ip_port))
        # 设置守护线程
        socket_thread.setDaemon(True)
        # 开启线程
        socket_thread.start()

三、静态、动态web开发

1.了解前端语言html和脚本语言javascript、ajax

因为不做前端的东西,这方面内容只做了解,对web后台开发来说,能够写出简单的html页面和了解ajax前后端数据交互就行,javascript可以理解成轻量的java语言,帮助前端人员做些脚本工作。

2.熟悉http协议

在web开发中我们需要对用户请求进行分析,返回相应的资源文件和数据库中相关数据,在数据传输中也有很多相关的协议,通常用的为:HTTP也称为超文本传输协议。超文本是超级文本的缩写,是指超越文本限制或者超链接,比如:图片、音乐、视频、超链接等等都属于超文本。HTTP 协议设计之前目的是传输网页数据的,现在允许传输任意类型的数据。传输 HTTP 协议格式的数据是基于 TCP 传输协议的,发送数据之前需要先建立连接。

在http协议中,用户的请求通常以url方式进行请求如:
https://news.163.com/18/1122/10/E178J2O4000189FH.html
URL的组成部分:
协议部分: https://、http://、ftp://
域名部分: news.163.com
资源路径部分: /18/1122/10/E178J2O4000189FH.html
请求通常有两种方式,不带参数get和带参数post,上述为不带参数。
https://news.163.com/hello.html?page=1&count=10
参数部分为:?page=1&count=10
其中?表示后面的都是参数,&表示and

3. 请求报文和响应报文

请求报文
用户的url请求通常被浏览器封装成特定的报文形式,即请求报文,其有两种方式:get和post
get方式:

---- 请求行 ----
GET / HTTP/1.1  # GET请求方式 请求资源路径 HTTP协议版本
---- 请求头 -----
Host: www.baidu.com  # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36  # 用户代理,也就是客户端的名称
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 # 可接受的数据类型
Accept-Encoding: gzip, deflate # 可接受的压缩格式
Accept-Language: zh-CN,zh;q=0.9 #可接受的语言
Cookie: pgv_pvi=1246921728; # 登录用户的身份标识

---- 空行 ----

观察请求报文的格式我们可以看出其由:
请求行和请求头和空行组成,每项之间用\r\n连接
通常get请求指向静态资源,即我们通过客户端获取的数据为上述内容,我们可以通过字符串操作和列表、字典操作抽取或者组装成我们需要的内容。如:获取静态资源的指向:

# 请求行
request_data = data.split("\r\n")
request_line = request_data.pop(0)
# 资源地址
request_path = request_line.split(" ")[1]
# 请求头
request_header = {}
for data in request_data:
	# 考率空行
	if data =="":
		continue
	request_header[data.split(":")[0]] = data.split(":")[1]
# 判断是否有查询字符串数据
request_query = request_path.split('?')
if len(request_query) > 1:
    request_path = request_query[0]
    query_str = request_query[1]
else:
    query_str = None

post方式:

---- 请求行 ----
POST /xmweb?host=mail.163.com&_t=1542884567319 HTTP/1.1 # POST请求方式 请求资源路径 HTTP协议版本
---- 请求头 ----
Host: mail.163.cn # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Content-Type: application/x-www-form-urlencoded  # 告诉服务端请求的数据类型
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客户端的名称
---- 空行 ----
---- 请求体 ----
username=hello&pass=hello # 请求参数

观察请求报文我们可以看出post方式的结构为:
请求行和请求头、空行、请求体
获取用户请求需要添加分析参数部分

request_data = data.split("\r\n")
request_line = request_data.pop(0)
# 资源地址
request_path = request_line.split(" ")[1]
# 请求头
request_header = {}
for data in request_data:
	# 考率空行
	if data =="":
		continue
	request_header[data.split(":")[0]] = data.split(":")[1]
# 判断是否有查询字符串数据
request_query = request_path.split('?')
if len(request_query) > 1:
    request_path = request_query[0]
    query_str = request_query[1]
else:
    query_str = None

响应报文

--- 响应行/状态行 ---
HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述
--- 响应头 ---
Server: Tengine # 服务器名称
Content-Type: text/html; charset=UTF-8 # 内容类型
Transfer-Encoding: chunked # 发送给客户端内容不确定内容长度,发送结束的标记是0\r\n, Content-Length表示服务端确定发送给客户端的内容大小,但是二者只能用其一。
Connection: keep-alive # 和客户端保持长连接
Date: Fri, 23 Nov 2018 02:01:05 GMT # 服务端的响应时间
--- 空行 ---
--- 响应体 ---
<!DOCTYPE html><html lang=“en”></html> # 响应给客户端的数据

响应报文格式统一由:
响应行和响应头、空行、响应体组成,之间用\r\n连接
组装响应报文方法:

# 应答行
response_line = "HTTP/1.1 200 OK\r\n"
# 应答头
response_header = "Server:pwd\r\nAccess-Control-Allow-Credentials:true\r\nAccess-Control-Allow-Origin:*\r\nAccess-Control-Allow-Methods:GET, POST, PUT\r\nAccess-Control-Allow-Headers:X-Custom-Header"
# 应答体
response_body = data
# 应答数据
response_data = response_line + response_header + "\r\n\r\n" + response_body

响应行中200为响应状态码。
200 请求成功
307 重定向
400 错误的请求,请求地址或者参数有误
404 请求资源在服务器不存在
500 服务器内部源代码出现错误

4. web静态开发

import sys
import threading
import socket


class WebHttpServer(object):
    def __init__(self, port):
        # 创建服务端socket对象
        self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口复用
        self.socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 设置服务端端口
        self.socket_server.bind(("", port))
        # 设置监听套接字
        self.socket_server.listen(128)

    def start(self):
        # 循环接收客户端连接
        while True:
            # 建立客户端套接字和ip端口
            socket_client, ip_port = self.socket_server.accept()
            # 建立连接成功
            print(f"{ip_port}建立连接成功")
            # 创建客户端套接字线程
            client_thread = threading.Thread(target=self.web_http, args=(socket_client, ip_port))
            # 设置守护线程
            client_thread.setDaemon(True)
            # 启动线程
            client_thread.start()

    @staticmethod
    def web_http(socket_client, ip_port):
        # 获取客户端传输数据
        recv_data = socket_client.recv(4096)
        # 获取请求数据
        data = recv_data.decode("GBK")
        # 获取请求行
        request_data = data.split("\r\n")
        request_line = request_data.pop(0)
        # 获取请求地址
        request_path = request_line.split(" ")[1]
        # 判断请求地址
        if request_path == "/":
            request_path = "/welcome.html"
            # 返回报文数据
            WebHttpServer.find_resource(request_path, socket_client)
        elif request_path == "/test.html":
            request_path = "/test.html"
            # 返回报文数据
            WebHttpServer.find_resource(request_path, socket_client)

    @staticmethod
    def find_resource(request_path, socket_client):
        try:
            # 报文格式为字节,以字节形式读取静态资源返回
            with open("./static" + request_path, "rb") as f:
                respond_data = f.read()
        except Exception as e:
            # 返回错误资源请求
            request_path = "/error.html"
            # 报文格式为字节,以字节形式读取静态资源返回
            with open("./static" + request_path, "rb") as f:
                respond_data = f.read()
            # 构造返回行
            respond_line = "HTTP/1.1 404 NOT FOUND\r\n"
            # 构造返回头
            respond_header = "Server:PWS1.0\r\n"
            # 返回体
            respond_body = respond_data
            # 构造返回数据
            send_data = (respond_line + respond_header + "\r\n").encode() + respond_body
            socket_client.send(send_data)
        else:
            # 正确反回行
            respond_line = "HTTP/1.1 200 OK\r\n"
            # 正确返回头
            respond_header = "Server:PWS1.0\r\n"
            # 正确返回体
            respond_body = respond_data
            # 正确返回数据
            send_data = (respond_line + respond_header + "\r\n").encode() + respond_body
            socket_client.send(send_data)
        finally:
            # 关闭套接字
            socket_client.close()


def main():
    # 命令行自定义端口执行,默认8080
    # 获取命令行参数,判断命令行参数是否为两位
    if len(sys.argv) != 2:
        print("执行xxx.py 8080")
        port = 8080
    # 端口是否纯数字
    elif not sys.argv[1].isdigit():
        print("执行xxx.py 8080")
        port = 8080
    else:
        # 获取自定义端口号
        port = int(sys.argv[1])
    # 启动服务端
    web_http_server = WebHttpServer(port)
    web_http_server.start()


if __name__ == "__main__":
    main()

5. web动态开发

1. wsgi协议

WSGI服务器网管接口(Web Server Gateway Interface)是为python语言定义的一种服务器与框架进行通信的简单接口,其接口原理就是实现application函数。
application函数格式如下:

def application(environ, func):
    func('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    return 'Hello MiniWeb'

函数的第一个参数environ类型是一个字典,用来接收封装到字典内的请求资源路径。
函数的第二个参数func类型是一个函数,用来接收调用时传递的函数引用。
application函数在是在框架中实现,框架就是处理数据的py文件;在服务器端调用。
实现application函数并且完成数据处理的py文件我们就可以称它是WSGI接口。
第一行定义了一个名为 app 的 callable,接受两个参数,environ 和 start_response,environ 是一个字典包含了 CGI 中的环境变量。
第二行调用了start_response,状态指定为“200 OK”,消息头指定为内容类型是“text/plain”。start_response 也是一个 callable,接受两个必须的参数,status(HTTP 状态)和 response_headers(响应消息的头)。
第三行将响应消息的消息体返回。

2. 路由表

为了方便对请求资源管理,通过建立路由表对资源进行管理,如:
请求路径 处理函数
/login login函数
/index index函数
/detail detail函数
利用装饰器可实现路由表中函数的调用

# 创建路由表
route_list = []
# 自动维护路由表
def route(path):
	# 装饰器函数
    def out_func(func):
    	# 添加路由表
        route_list.append((path, func))
        # 执行函数
        def inner(*args,**kwargs):
            body = func(*args,**kwargs)
            return body
        return inner
    return out_func

# 装饰器参数
@route("/index.html")
def index(environ):
	pass

@route("/login.html")
def login(environ):
	pass

# 框架接口函数
def application(environ,start_response)
	start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
	request_path = environ["request_path"]
	for path,func in route_list:
		if request_path == path:
			result = func()
			return result
	else:
		return "404 NOT FOUND"

3. 动态web实例

import threading
import socket
import frameweb


class WebHttpClient(object):
    def __init__(self):
        self.socket_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket_server_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.socket_server_tcp.bind(("", 8677))
        self.socket_server_tcp.listen(128)

    def start(self):
        while True:
            socket_client_tcp, ip_port = self.socket_server_tcp.accept()
            client_thread = threading.Thread(target=self.web_link, args=(socket_client_tcp, ip_port))
            client_thread.setDaemon(True)
            client_thread.start()

    @staticmethod
    def web_link(socket_client_tcp, ip_port):
        recv_data = socket_client_tcp.recv(5012).decode()
        if recv_data:
            print(f"{ip_port}下线了")
        request_data = recv_data.split("\r\n")
        request_line = request_data.pop(0)
        request_path = request_line.split(" ")[1]
        request_header = {}
        for data in request_data:
            if data == "":
                continue
            request_header[data.split(":")[0]] = data.split(":")[1]
        if "?" in request_path:
            path = request_path.split("?")[0]
            query_str = request_path.split("?")[1]
        else:
            path = request_path
            query_str = ''
        # 组合标注数据
        requests = {
            "path": path,
            "query_str": query_str,
            "header": request_header
        }
        # wsgi标注
        environ = {
            "request_path": path,
            "requests": requests
        }
        # 应答行
        respond_line = "HTTP/1.1 200 OK\r\n"
        # 应答头
        respond_header = "Server:PWS1.0\r\n" \
                         "Access-Control-Allow-Credentials:true\r\n" \
                         "Access-Control-Allow-Origin:*\r\n" \
                         "Access-Control-Allow-Methods:GET, POST, PUT\r\n" \
                         "Access-Control-Allow-Headers:X-Custom-Header\r\n"
        # 应答体
        respond_body = frameweb.application(environ)
        # 应答数据
        respond_data = respond_line + respond_header + "\r\n" + respond_body
        socket_client_tcp.send(respond_data.encode("utf-8"))
        socket_client_tcp.close()


if __name__ == "__main__":
    web_http = WebHttpClient()
    web_http.start()

实现模块

import pymssql
import json

# 定义路由列表
route_list = []


# 定义自动维护路由列表函数
def route(path):
    # 装饰器函数
    def decorator(func):
        # 自动维护路由列表函数
        route_list.append((path, func))

        # 路由函数
        def inner_func(*args, **kwargs):
            result = func(*args, **kwargs)
            return result

        return inner_func

    return decorator


@route('/')
def index(env):
    with open("index.html", 'r', encoding="utf-8") as f:
        result = f.read()
    return result


# 带有参数的路由处理例子
@route("/detail")
def detail(env):
    # 获取前端传递数据
    data = env['requests']['query_str']
    header = env['requests']['header']
    # 创建链接
    conn = pymssql.connect(host='localhost', port=3306, database='booksite', user='root', password='mysql',
                           charset='utf8')
    # 创建游标
    cursor = conn.cursor()
    # 执行sql语句 按照指定条件查询
    sql = "select *  from bookinfo where {0};".format(data)
    cursor.execute(sql)

    # 获取数据 元组  ((),())
    stock_data = cursor.fetchone()
    data_dict = {
        "id": stock_data[0],
        "name": stock_data[1],
        "auth": stock_data[2],
        "img_url": stock_data[3],
        "read": stock_data[5],
        "comment": stock_data[6],
        "score": stock_data[8],
        "content": stock_data[7],
        "synopsis": stock_data[9]
    }

    # 将阅读量更新后写入数据库
    read = stock_data[5] + 1
    sql = 'Update bookinfo set bread={0} where id={1};'.format(read, stock_data[0])
    cursor.execute(sql)
    conn.commit()

    # 在JSON数据转换时需要注意,json.dumps()会默认采用ASCII码进项解码转换,想要不使用ASCII进行转码就需要
    # 设置ensure_ascii参数,将它设置成False即可解决问题。
    json_str = json.dumps(data_dict, ensure_ascii=False)

    return json_str


# 接口函数
def application(environ):
    # 获取请求数据
    request_path = environ["request_path"]
    # 遍历路由列表
    for path, func in route_list:
        # 执行路由函数
        if path == request_path:
            result = func(environ)
            return result
    else:
        return "404 NOT FOUND!"

总结

整理了一下所学,没有具体的前后端项目,只是将开发框架深度理解了一下,还是很有价值的。
时光如水,人生逆旅矣。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值