1 制作服务器代码

前面我们做过简单的基于tcp的服务器,我们这里选用多进程的服务器进行接下来的测试。

我们初次做的服务器流程

python 编译为浏览器插件 python制作浏览器_web service


我们可以看图1中所示浏览器发送请求

浏览器带有请求头如代码:

可以看到第一行:其中/logo1.PNG 就是浏览器需要请求得文件。
这个时候服务器就收到后提取出来这段地址,然后拼接我们的文件地址
./xxx/xxx/logo1.PNG然后通过打开文件读取文件返回给浏览器就行了。
GET /logo1.PNG HTTP/1.1
Host: 127.0.0.1:8880
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.9 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8880/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

当服务器返回浏览器,这个时候浏览器怎么知道用什么编码,怎么解析呢?
这个时候服务器需要返回的就是又一个返回的头

//告诉浏览器用什么编码和怎么解析
Content-Type: text/html;charset=UTF-8
//服务器的类型和版本
server: mini_fram-1.0

然后头后面加上我们的内容就可以了

python实现服务器的代码(多进程)

==请求头和数据之间需要各一行
例如:

返回的header
# 这一行隔开,我们通过转义字符\r\n进行转义,也就是header后面跟\r\n\r\n跟两对才行。
返回的data
import socket
import re
import multiprocessing 

# get /sdgs/sdgs  HTTP/1.1 200 ok
# 定义一个处理客户端事件的函数
def deal_task(client_socket,client_addr):
    
    try:
    	# 读取浏览器发来的请求,提取第一行数据
        recv_data = client_socket.recv(1024).decode('utf8')
        print('-'*20)
        print(recv_data)
        recv_data = recv_data.splitlines()
        # 通过正则提取数据
        request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
        # 判断数据请求的路径
        if request_path == '/':
            request_path = '/index.html'
        print('请求路径', './wwwroot'+request_path)
        # 读取数据,以二进制读出
        f = open("./wwwroot"+request_path, 'rb')
    except Exception as ex:
    	#准备请求头发送
        response_header = 'HTTP/1.1 404 NO FOUND\r\n'
        response_header += '\r\n'
        response_body = '-----no found-----'
        response_data = response_header+response_body
        client_socket.send(response_data.encode("utf8"))
        # print(ex)
    else:
    	# 读取的数据
        response_body = f.read()
        f.close()
        response_header = 'HTTP/1.1 200 OK\r\n'
        response_header += '\r\n'
        response_data = response_header.encode("utf8")+response_body
        client_socket.send(response_data)
    client_socket.close()

def main():
    # 创建一个套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 刷新端口,避免服务器三次握手等待占用端口
    server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    # 绑定一个端口
    local_addr = ("",8888)
    server_socket.bind(local_addr)
    # 设置为被动连接
    server_socket.listen(128)
    # 等待连接
    print('------服务器启动-----')
    while True:
        client_socket,client_addr = server_socket.accept()
        pro = multiprocessing.Process(target=deal_task,args=[client_socket,client_addr])
        pro.start()
        # 因为这个套接字公用一个资源,主进程关闭之后,如果子进程也关闭那么就是关闭成功。只有两个同时关闭才会成功
        client_socket.close()

if __name__ == "__main__":
    main()

上面的代码就是我们做的一个服务器,但是美中不足,我们可不可进行封装。在主函数中直接几行简单的代码就行。
下面我们进行优化

import socket
import re
import multiprocessing 

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 创建一个套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定一个端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 设置为被动连接
        self.server_socket.listen(128)
        # 等待连接
        print('------服务器启动-----')


    def sever_client(self,client_socket):
        try:
            recv_data = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_data)
            recv_data = recv_data.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('请求路径', './wwwroot'+request_path)
            f = open("./wwwroot"+request_path, 'rb')
        except Exception as ex:
            response_header = 'HTTP/1.1 404 NO FOUND\r\n'
            response_header += '\r\n'
            response_body = '-----no found-----'
            response_data = response_header+response_body
            client_socket.send(response_data.encode("utf8"))
            # print(ex)
        else:
            response_body = f.read()
            f.close()
            response_header = 'HTTP/1.1 200 OK\r\n'
            response_header += '\r\n'
            response_data = response_header.encode("utf8")+response_body
            client_socket.send(response_data)
        client_socket.close()

    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

我们接着改,我们发现就是它的耦合性太高了,还有就是它是一个静态的网站。我们如何做一个动态的呢,就是当他请求得是比如是一个XXXX.py的时候我们根据内容给与反馈一定的数据。

大概思路就是

if 请求的不是xxx.py:
	显示一个原有的界面
else 如果是xxx.py:
	我们返回我们自己想让显示的界面

这个时候我们进行改进
web服务器段代码

import socket
import re
import multiprocessing 
import time
import mini_fram

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 创建一个套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定一个端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 设置为被动连接
        self.server_socket.listen(128)
        # 等待连接
        print('------服务器启动-----')


    def sever_client(self,client_socket):
        
            recv_data = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_data)
            recv_data = recv_data.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('请求路径', './wwwroot'+request_path)
            # 如果以.py结尾的我们就认定为静态资源
            if not request_path.endswith('.py'):
                try:    
                    f = open("./wwwroot"+request_path, 'rb')
                except Exception as ex:
                    response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                    response_header += '\r\n'
                    response_body = '-----no found-----'
                    response_data = response_header+response_body
                    client_socket.send(response_data.encode("utf8"))
                    # print(ex)
                else:
                    response_body = f.read()
                    f.close()
                    response_header = 'HTTP/1.1 200 OK\r\n'
                    response_header += '\r\n'
                    response_data = response_header.encode("utf8")+response_body
                    client_socket.send(response_data)
            else:
                # 如果是以.py为结尾的我们认为是动态资源
                # 请求头
                response_header = "HTTP/1.1 200 OK\r\n"
                response_header += "\r\n"
                # 调用mini_fram中的application函数,请求函数,让这个函数判断我们的请求,并且给与返回
                response_body = mini_fram.application(request_path)
                # 组合数据发送
                response_data = response_header+response_body
                client_socket.send(response_data.encode('gb2312'))
            client_socket.close()

    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

mini_fram.py代码

import time


def login():
    current_time = time.ctime()
    response_body = f"<h1>登录界面:{current_time}</h1>"
    return response_body


def register():
    current_time = time.ctime()
    response_body = f"""
    <body>
    注册时间{current_time}<br>
    <form>
    
        First name: <input type="text" name="firstname"><br>
        Last name: <input type="text" name="lastname">
    </form>
    <body>
    
    """
    return response_body


# 这个函数进行判断请求,并给与反馈
def application(request_path):
    if request_path == '/login.py':
        return login()
    elif request_path == '/register.py':
        return register()
    else:
        return "臣妾做不到"

看了上面的代码我们有的同学会问,为什么用application当函数名,可以用其他的吗,
我回复,当然可以啦,但是这个是一个协议的规定,这个协议就是WSGI
什么是WSGI呢?

Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。

我们所了解的很多框架,都是遵循这个协议跟很多web服务器进行交流的。

支持WSGI的Web应用框架有很多:

BlueBream、bobo、Bottle、CherryPy、Django、Flask、Google App Engine’s webapp2

Gunicorn、prestans、Pylons、Pyramid、restlite、Tornado、Trac、TurboGears、Uliweb、web.py、web2py、weblayer、Werkzeug

python 编译为浏览器插件 python制作浏览器_python_02


我们知道了这个WSGI(不知道的,自己再搜一下)然后它的大体协议是什么呢?

用Python语言写的一个符合WSGI的“Hello World”应用程序如下所示:
def application(environ, start_response): 
	start_response('200 OK', [('Content-Type', 'text/plain')]) yield "Hello 	world!\n"
其中
第一行定义了一个名为 application的 callable,接受两个参数,environ 和 start_response,environ 是一个字典包含了 CGI 中的环境变量。
第二行调用了start_response,状态指定为“200 OK”,消息头指定为内容类型是“text/plain”。start_response 也是一个 callable,接受两个必须的参数,status(HTTP 状态)和 response_headers(响应消息的头)。
第三行将响应消息的消息体返回。

那么接下来我们修改我们的框架,加上相应的函数的参数
我们的目录结构,

dynamic:放入我们的框架,就是我们的application函数所在的py文件
static:中我们放入我们需要的css js images font等等我们的静态代码
templates:中是我们的动态网页我们的框架调用的网页文件等。

python 编译为浏览器插件 python制作浏览器_python_03


python 编译为浏览器插件 python制作浏览器_socket_04

import socket
import re
import multiprocessing 
import time
import dynamic.mini_fram_wsgi as mini_fram_wsgi

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 创建一个套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定一个端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 设置为被动连接
        self.server_socket.listen(128)
        # 等待连接
        print('------服务器启动-----')


    def sever_client(self,client_socket):
        
            recv_datas = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_datas)
            recv_data = recv_datas.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('请求路径', './static'+request_path)
            # 如果以.py结尾的我们就认定为静态资源
            if not request_path.endswith('.py'):
                try:    
                    f = open("./static"+request_path, 'rb')
                except Exception as ex:
                    response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                    response_header += '\r\n'
                    response_body = '-----no found-----'
                    response_data = response_header+response_body
                    client_socket.send(response_data.encode("utf8"))
                    # print(ex)
                else:
                    response_body = f.read()
                    f.close()
                    response_header = 'HTTP/1.1 200 OK\r\n'
                    response_header += '\r\n'
                    response_data = response_header.encode("utf8")+response_body
                    client_socket.send(response_data)
            else:
                # 如果是以.py为结尾的我们认为是动态资源
                # # 请求头
                # response_header = "HTTP/1.1 200 OK\r\n"
                # response_header += "\r\n"
                # 调用请求函数
                environ = dict()
                environ["PATH_INFO"] = request_path
                这里我们加入的调用函数
                response_body = mini_fram_wsgi.application(
                    environ, self.set_response_header)
                # 整合数据
                response_header = "HTTP/1.1 %s\r\n" % self.status
                for temp in self.headers:
                    response_header += "%s:%s\r\n" % (temp[0],temp[1])
                response_header +='\r\n'
                # 组合数据发送
                response_data = response_header+response_body
                client_socket.send(response_data.encode('utf8'))
            client_socket.close()
	==再这里我们加上我们收到来自函数application的请求==
    def set_response_header(self,status,headers):
        self.status = status
        self.headers =[("server","mini_fram-1.0")]
        self.headers += headers


    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

dynamci中的文件

import time


def index():
    with open("./templates/index.html",'r') as f:
        response_body = f.read()
    return response_body


def about():
    with open("./templates/about.html", 'r',encoding='utf8') as f:
        response_body = f.read()
    return response_body


def single():
    with open("./templates/single.html", 'r', encoding='utf8') as f:
        response_body = f.read()
    return response_body


def contact():
    with open("./templates/contact.html", 'r') as f:
        response_body = f.read()
    return response_body


def register():
    current_time = time.ctime()
    response_body = f"""
    <body>
    注册时间{current_time}<br>
    <form>
    
        First name: <input type="text" name="firstname"><br>
        Last name: <input type="text" name="lastname">
    </form>
    <body>
    
    """
    return response_body



def application(environ,start_response):
    print(environ["PATH_INFO"])
    if environ["PATH_INFO"] == '/register.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'),("server", "大哥")])
        return register()

    elif environ["PATH_INFO"] == '/index.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return index()

    elif environ["PATH_INFO"] == '/about.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return about()

    elif environ["PATH_INFO"] == '/contact.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return contact()

    elif environ["PATH_INFO"] == '/single.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return single()

    else:
        start_response(
            '404 NO', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return "<h1>没有的</h1>"

我们看到这个框架竟然可以设计我们的服务器版本,这就有点不好了吧
这个时候我们需要再服务器中设计好,这个属性。这样降低耦合性就更好了
我们再加上配置文件。配置文件中

{	# 静态文件路径
    "static_path":"./static",
    # 动态框架文件路径
    "dynamic_path":"./dynamic",
    # 端口号
    "port":"8880",
    # 框架文件名,用于导入
    "web_fram_name":"mini_fram_wsgi",
    # 框架的函数名
    "web_app_name":"application"
}

所以最终的版本
文件目录

python 编译为浏览器插件 python制作浏览器_web service_05


主要改服务器的代码

import socket
import re
import multiprocessing
import time
import dynamic.mini_fram_wsgi as mini_fram_wsgi
import sys


# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer(object):
    def __init__(self, port, app, static_path):
        self.static_path = static_path
        # 创建一个套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定一个端口
        local_addr = ("", port)
        self.server_socket.bind(local_addr)
        # 设置为被动连接
        self.server_socket.listen(128)
        # 等待连接
        self.application = app

        print('------服务器启动-----')

    def sever_client(self, client_socket):

        recv_datas = client_socket.recv(1024 * 1024).decode('utf8')
        print('-' * 20)
        print(recv_datas)
        recv_data = recv_datas.splitlines()
        request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
        if request_path == '/':
            request_path = '/index.html'
        print('请求路径', self.static_path + request_path)
        # 如果以.py结尾的我们就认定为静态资源
        if not request_path.endswith('.py'):
            try:
                f = open(self.static_path + request_path, 'rb')
            except Exception as ex:
                response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                response_header += '\r\n'
                response_body = '-----no found-----'
                response_data = response_header + response_body
                client_socket.send(response_data.encode("utf8"))
                # print(ex)
            else:
                response_body = f.read()
                f.close()
                response_header = 'HTTP/1.1 200 OK\r\n'
                response_header += '\r\n'
                response_data = response_header.encode("utf8") + response_body
                client_socket.send(response_data)
        else:
            # 如果是以.py为结尾的我们认为是动态资源
            # # 请求头
            # response_header = "HTTP/1.1 200 OK\r\n"
            # response_header += "\r\n"
            # 调用请求函数
            environ = dict()
            environ["PATH_INFO"] = request_path
            response_body = self.application(
                environ, self.set_response_header)
            # 整合数据
            response_header = "HTTP/1.1 %s\r\n" % self.status
            for temp in self.headers:
                response_header += "%s:%s\r\n" % (temp[0], temp[1])
            response_header += '\r\n'
            # 组合数据发送
            response_data = response_header + response_body
            client_socket.send(response_data.encode('utf8'))
        client_socket.close()

    def set_response_header(self, status, headers):
        self.status = status
        self.headers = [("server", "mini_fram-1.0")]
        self.headers += headers

    def run_forever(self):
        while True:
            client_socket, client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                client_socket])
            pro.start()
            client_socket.close()
        # self.server_socket.close()


def main():
    # print(len(sys.argv))
    # if len(sys.argv) == 3:
    #     try:
    #         port = int(sys.argv[1])
    #         mini_fram_app_name = sys.argv[2]
    #     except Exception as identifier:
    #         print("请输入一个端口为整数")
    #         return
    # else:
    #     print("程序输入参数有误,按照下面的格式")
    #     print("python xxx.py 6666 mini_fram_wsgi:application")
    #     return
    #
    # mini_fram_names = re.match("([^:]+):(.+)", mini_fram_app_name)
    # if mini_fram_names:
    #     mini_fram_name = mini_fram_names.group(1)
    #     mini_app_name = mini_fram_names.group(2)
    # else:
    #     print("程序输入参数有误,按照下面的格式")
    #     print("python xxx.py 8asd8 mini_fram_wsgi:application")
    #     return
    # 打开配置文件,通过eval生成一个字典
    with open("web_server.conf", 'r')as f:
        config_dict = eval(f.read())
    # 获取框架的名字
    mini_fram_name = config_dict["web_fram_name"]
    # 获取函数的名字
    mini_app_name = config_dict["web_app_name"]
    # 获取端口名字
    port = int(config_dict["port"])
    # 去这个文件夹中搜索我们的框架。py文件
    sys.path.append(config_dict["dynamic_path"])
    # 返回一个模块
    frame = __import__(mini_fram_name)
    # 得到模块的application方法
    app = getattr(frame, mini_app_name)
    print(app)
    wsgi_server = WSGIServer(port, app, config_dict["static_path"])
    wsgi_server.run_forever()


if __name__ == "__main__":
    main()

最后加个彩蛋

python 编译为浏览器插件 python制作浏览器_http_06


python 编译为浏览器插件 python制作浏览器_http_07