搭建简易的WebServer(基于pyhton实现简易Web框架 使用socket套接字)

 1. 使用web底层socket的方式实现简易服务器的搭建,用来理解学习

# 1、导入socket模块
import socket
import re
import gevent
import sys
# 破解,让gevent 识别耗时操作
from gevent import monkey
monkey.patch_all()
import Application


class HttpServer(object):

    # 用来初始化 套接字
    def __init__(self, port):
        
        # 2、创建tcp套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 3、设置地址重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 4、绑定端口
        tcp_server_socket.bind(("", port))
        # 5、设置监听,最大允许客户端连接数128(套接字有主动变为被动)
        tcp_server_socket.listen(128)
        # 套接字,保存实例属性中
        self.tcp_server_socket = tcp_server_socket

    # 启动Web服务器,并且接受客户端连接
    def start(self):
        # 6、等待客户端连接(能够接受多个客户端连接)
        while True:
            # 7、定义函数,实现客户端信息接收和响应
            new_client_socket, ip_port = self.tcp_server_socket.accept()
            print("[新客户端来了]:", ip_port)
            # request_handler(new_client_socket)
            g1 = gevent.spawn(self.request_handler, new_client_socket)
            # 因为接收客户端链接使用的是While True  死循环,主线程不会退出,协程一定会执行完毕
            # g1.join()

    def request_handler(self, new_client_socket):

        # ​ 8、接收浏览器请求,并判断请求是否为空
        recv_data = new_client_socket.recv(1024)
        if not recv_data:
            print("浏览器可能已经关闭!~")
            new_client_socket.close()
            return
        # 把接受的内容解码
        request_text = recv_data.decode()
        # 根据\r\n 拆分字符串,目的:得到第一行
        request_list = request_text.split("\r\n")
        # request_list[0] 就是我们的请求行
        # print(request_list[0])
        ret = re.search(r"\s(.*)\s", request_list[0])
        # 判断报文是否有误
        if not ret:
            print("浏览器请求的报文格式错误!")
            new_client_socket.close()
            return

        # 获取路径
        path_info = ret.group(1)
        # print(path_info)
        # 访问默认的页面
        if path_info == "/":
            path_info = "/index.html"
        # ​ 9、拼接响应报文

        # 判断如果是动态资源 交给框架处理
        if path_info.endswith(".html"):
            # 字典存储 用户的请求信息
            env = {
                "PATH_INFO": path_info
            }

            status, headers, response_body = Application.app(env)

            # 使用框架返回的数据拼接响应报文
            response_line = "HTTP/1.1 %s\r\n" % status

            response_header = ""
            for header in headers:
                response_header += "%s: %s\r\n" % header

            response_data = response_line + response_header + "\r\n" + response_body
            new_client_socket.send(response_data.encode())
            new_client_socket.close()
        # 否则认为是静态资源
        else:
            #  9.2 响应头
            response_header = "Server:PythonWS1.0\r\n"
            #  9.3 空行
            response_blank = "\r\n"
            #  9.4 响应体
            # response_content = "HelloWorld!\r\n"
            # 打开文件,并且读取内容,然后把读取的内容返回给客户端

            try:
                with open("static"+path_info, "rb") as file:
                    # response_content 是二进制类型
                    response_content = file.read()
            except Exception as e:
                #  9.1 相应行
                response_line = "HTTP/1.1 404 Not Found\r\n"
                response_content = "Error ! %s" % str(e)
                # 对返回的内容进行编码
                response_content = response_content.encode()
            else:
                #  9.1 相应行
                response_line = "HTTP/1.1 200 OK\r\n"
            finally:
                # ​ 10、定义变量保存响应报文内容
                response_data = (response_line + response_header + response_blank).encode() + response_content
                # ​ 11、发送响应报文给客户端浏览器
                new_client_socket.send(response_data)

                # ​ 12、关闭此次连接的套接字
                new_client_socket.close()


def main():

    # sys.argv 可以获取终端启动程序的时候的启动参数
    # sys.argv 返回一个列表,列表中依次保存我们输入的参数内容
    # print(sys.argv)
    if len(sys.argv) != 2:
        print("服务器启动失败,参数的格式:python3 HttpServer.py 端口号")
        return

    # 判断端口号,不能是一个字符串,应该是纯数字
    if not sys.argv[1].isdigit():
        print("服务器启动失败,端口号应该是纯数字!")
        return
    # 获取端口号
    port = int(sys.argv[1])

    # 实例化 HttpServer 对象
    httpserver = HttpServer(port)

    # 启动服务器
    httpserver.start()


if __name__ == '__main__':

    main()
 2. 建立子应用app用来接收请求

"""web框架  web应用程序"""
import time


def get_time():
    """当用户请求/gettime.html执行当前方法"""
    return time.ctime()

def index():
    """当用户请求/index.html 执行"""
    # 1 读取模板文件
    with open("template/index.html") as file:
        html_data = file.read()

    # 2 查询数据库
    data_from_mysql = "have fun"

    # 3 使用从数据库中查询出来的数据 替换 模板变量
    html_data = html_data.replace("{%content%}", data_from_mysql)

    return html_data


def center():
    """当用户请求/center.html 执行"""
    # 1 读取模板文件
    with open("template/center.html") as file:
        html_data = file.read()

    # 2 查询数据库
    data_from_mysql = "have fun"

    # 3 使用从数据库中查询出来的数据 替换 模板变量
    html_data = html_data.replace("{%content%}", data_from_mysql)

    return html_data

# 路由列表  django框架添加路由的方式
route_list = [
    ('/gettime.html', get_time),
    ('/center.html', center),
    ('/index.html', index)
]

def app(env):
    # 接收  取出用户的信息
    path_info = env['PATH_INFO']
    print("接收到用户的动态资源请求 %s" % path_info)
    # if path_info == '/gettime.html':
    #     # 状态 响应头 响应体
    #     return '200 OK', [('Server', 'PWS5.0')], get_time()
    # elif path_info == '/index.html':
    #     return '200 OK', [('Server', 'PWS5.0')], index()
    # elif path_info == '/center.html':
    #     return '200 OK', [('Server', 'PWS5.0')], center()
    # 将用户请求路径 和 路由列表中每一个进行比较 如果一致 就执行对应的函数代码
    for path, func in route_list:
        if path_info == path:
            return '200 OK', [('Server', 'PWS5.0')], func()
    else:
        # 状态 响应头 响应体
        return '404 Not Found',[('Server', 'PWS5.0')],"response body from app"

 

posted @ 2018-10-01 15:52  Yeang  阅读(348)  评论(0编辑  收藏  举报