python静态web服务器面向对象开发三重奏

1. 基础知识

----http get请求报文—

----请求行-----

GET / HTTP/1.1 =>请求方法(方式)请求的资源路径http协议的版本

----请求头----

Host: www.itcast.cn => 服务器的主机ip地址和端口号,提示如果看不到端口号默认是80

Connection: keep-alive => 和服务端程序保存长连接,当客户端和服务端有一段时间(3-5)没有进行通信,那么服务器程序会主动向客户端断开连接

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/73.0.3683.86 Safari/537.36 => 用户代理,客户端程序名称,当后续讲爬虫的时候可以根据是否有User-Agent进行反爬机制

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3 => 告诉服务端程序可以接受的数据类型

Accept-Encoding: gzip, deflate => 告诉服务端程序支持的压缩算法

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 => 告诉服务端程序支持的语言

Cookie: UM_distinctid=169f06ab9d21a-0a52d93224ce3-12376d57-13c680-169f06ab9d571a; accessId=22bdcd10-6250-11e8-917f-9fb8db4dc43c; bad_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=5c8a5a11-5811-11e9-975d-5932d6370ce7; parent_qimo_sid_22bdcd10-6250-11e8-917f-9fb8db4dc43c=69928890-5811-11e9-ac5b-eb2506ccad9a; CNZZDATA4617777=cnzz_eid%3D324231225-1554516145-%26ntime%3D1554521548; href=http%3A%2F%2Fwww.itcast.cn%2F; pageViewNum=5; Hm_lvt_0cb375a2e834821b74efffa6c71ee607=1554516722,1554523431; Hm_lpvt_0cb375a2e834821b74efffa6c71ee607=1554523431; nice_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=fc5f90f1-5820-11e9-b896-dba72e2578c0; openChat22bdcd10-6250-11e8-917f-9fb8db4dc43c=true => 客户端用户身份的标识

-----空行-----

\r\n


-----http get请求的原始报文数据------------

----请求行-----

GET / HTTP/1.1\r\n

----请求头----

Host: www.itcast.cn\r\n

Connection: keep-alive\r\n

Upgrade-Insecure-Requests: 1\r\n

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\r\n

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3\r\n

Accept-Encoding: gzip, deflate\r\n

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n

Cookie: UM_distinctid=169f06ab9d21a-0a52d93224ce3-12376d57-13c680-169f06ab9d571a; accessId=22bdcd10-6250-11e8-917f-9fb8db4dc43c; bad_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=5c8a5a11-5811-11e9-975d-5932d6370ce7; parent_qimo_sid_22bdcd10-6250-11e8-917f-9fb8db4dc43c=69928890-5811-11e9-ac5b-eb2506ccad9a; CNZZDATA4617777=cnzz_eid%3D324231225-1554516145-%26ntime%3D1554521548; href=http%3A%2F%2Fwww.itcast.cn%2F; pageViewNum=5; Hm_lvt_0cb375a2e834821b74efffa6c71ee607=1554516722,1554523431; Hm_lpvt_0cb375a2e834821b74efffa6c71ee607=1554523431; nice_id22bdcd10-6250-11e8-917f-9fb8db4dc43c=fc5f90f1-5820-11e9-b896-dba72e2578c0; openChat22bdcd10-6250-11e8-917f-9fb8db4dc43c=true\r\n

-----空行-----

\r\n

-----http get请求报文的格式----

请求行\r\n

请求头\r\n

空行(\r\n)

提示: 每项信息之间都需要一个\r\n,是要http协议规定

-----http post请求报文的格式----

请求行\r\n

请求头\r\n

空行(\r\n)

请求体

提示: 请求体就是浏览器发送给服务器的数据


----http 响应报文解析----

---- 响应行(状态行) --------

HTTP/1.1 200 OK => http协议版本 状态码 状态描述

---- 响应头 ----------

Server: Tengine => 服务器的名称

Content-Type: text/html; charset=UTF-8 => 服务器发送给浏览器的内容类型及编码格式

Transfer-Encoding: chunked => 服务器发送给客户端程序(浏览器)的数据不确定数据长度, 数据发送结束的接收标识: 0\r\n,Content-Length: 200(字节),服务器发送给客户端程序的数据确定长度。 内容长度这两个选项只能二选一

Connection: keep-alive => 和客户端保持长连接

Date: Sat, 06 Apr 2019 08:49:57 GMT => 服务器的时间

— 以下都是自定义响应头信息,字节定义响应头的名字和响应头的值,

比如: is_login: True

Accept-Ranges: bytes

Ali-Swift-Global-Savetime: 1554540597

Via: cache45.l2nu29-1[3,200-0,M], cache11.l2nu29-1[4,0], kunlun2.cn249[30,200-0,M], kunlun2.cn249[33,0]

X-Cache: MISS TCP_MISS dirn:-2:-2

----- 空行 ----

\r\n

----- 响应体 就是真正意义上给浏览器解析使用的数据----

网页数据

提示: 对于请求头和响应头信息程序员都可以进行自定义,按照客户端和服务器约定好的方式来制定即可。


----http 响应原始报文解析----

---- 响应行(状态行) --------

HTTP/1.1 200 OK\r\n

---- 响应头 ----------

Server: Tengine\r\n

Content-Type: text/html; charset=UTF-8\r\n

Transfer-Encoding: chunked\r\n

Connection: keep-alive\r\n

Date: Sat, 06 Apr 2019 08:49:57 GMT\r\n

— 以下都是自定义响应头信息,字节定义响应头的名字和响应头的值,比如:

is_login: True

Accept-Ranges: bytes\r\n

Ali-Swift-Global-Savetime: 1554540597\r\n

Via: cache45.l2nu29-1[3,200-0,M], cache11.l2nu29-1[4,0], kunlun2.cn249[30,200-0,M], kunlun2.cn249[33,0]\r\n

X-Cache: MISS TCP_MISS dirn:-2:-2\r\n

X-Swift-SaveTime: Sat, 06 Apr 2019 08:49:57 GMT\r\n

X-Swift-CacheTime: 0\r\n

Timing-Allow-Origin: *\r\n

EagleId: 2a51041615545405973986157e\r\n

----- 空行 ----

\r\n

----- 响应体 就是真正意义上给浏览器解析使用的数据----

网页数据


---- http响应报文的格式 -----

响应行\r\n

响应头\r\n

空行\r\n

响应体\r\n

提示: 每项信息之间都要有一个\r\n进行分割


2. 开发步骤

  • HTTP报文底层通过tcp传输报文
  • 对收到的报文解析出所需信息如:请求页面
  • 对报文组装:请求行+头+换行+体(真正所需页面)
  • 一些可能错误处理如:空数据、请求/、错误请求
  • 提供多线程进行多任务处理

3. 固定页面

import socket

if __name__ == '__main__':
    # 创建服务端套接字
    tcp_web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用
    tcp_web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定端口号
    tcp_web_socket.bind(('', 8000))
    #设置监听
    tcp_web_socket.listen(128)

    # 不断循环实现多次访问,但是单线程的
    while True:
        # 接收生成新的套接字
        new_socket, ip_port = tcp_web_socket.accept()
        
        # 接收数据
        recv_data = new_socket.recv(4096)
        # 对二进制数据解码
        recv_content = recv_data.decode('utf-8')
        
        # 对发送的数据组装处理
        # 1. 响应行
        response_line = 'HTTP/1.0 200 OK\r\n'
        # 2. 响应头
        response_header = 'Server: ahang\r\n'
        # 3. 读取响应体
        f = open('static\index.html', 'r')
        response_body = f.read()
        f.close()
        # 4. 组装响应数据为HTTP的response报文格式
        response_content = response_line + response_header + '\r\n' + response_body
        # 5. 对数据编码为二进制,有利于不同格式数据的传输,如图片
        response_data = response_content.encode('utf-8')
        # 6. 发送数据
        new_socket.send(response_data)


4. 指定页面

import socket

if __name__ == '__main__':
    tcp_web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    tcp_web_socket.bind(('', 8000))

    tcp_web_socket.listen(128)

    while True:
        # 至此建立了tcp的连接,可以进行数据的传输
        new_socket, ip_port = tcp_web_socket.accept()

        # 对数据接收处理
        recv_data = new_socket.recv(4096)
        # 情况一:当传来的数据为空时,可能为恶意传输,关闭此次连接
        if len(recv_data) == 0:
            new_socket.close()
        # 对传入的二进制解码
        recv_content = recv_data.decode('utf-8')
        # 对传入的HTTP的request报文分离出想要的信息,请求的网页名称
        recv_list = recv_content.split(' ', 2)
        recv_path = recv_list[1]
        print(recv_path)

        try:
            # 情况二:如果为根路径,重定向到index.html默认页面
            if recv_path == '/':
                recv_path = '/index.html'
            # 对请求页面打开,如果能访问则以读取二进制方式打开,不能访问则不存在请求的页面,放入异常
            f = open('static' + recv_path, 'rb')
        except Exception as result:
            # 情况三:对不存在的页面重定位到错误页面
            print(result)
            response_line = 'HTTP/1.0 404 NotFund\r\n'
            f = open('static/error.html', 'rb')
            response_body = f.read()
            response_header = 'Server: ahang\r\n'
            f.close()
            response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
            new_socket.send(response_data)
        else:
            # 对正常的页面返回
            response_line = 'HTTP/1.0 200 OK\r\n'
            response_header = 'Server: ahang\r\n'
            response_body = f.read()
            f.close()
            response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
            new_socket.send(response_data)
        finally:
            # 最后都要关闭该连接
            new_socket.close()


5. 多任务版

import socket
import threading

# 对功能函数独立,子线程对数据的处理
def web_server(new_socket):
    # 接收数据请求,指定每次接收的大小
    recv_data = new_socket.recv(4096)
    if len(recv_data) == 0:
        new_socket.close()
        return
    recv_content = recv_data.decode('utf-8')
    recv_list = recv_content.split(' ', 2)
    recv_path = recv_list[1]
    print(recv_path)

    try:
        # 默认页面
        if recv_path == '/':
            recv_path = '/index.html'
        f = open('static'+recv_path, 'rb')
    except Exception as result:
        print(result)
        f = open('static/error.html', 'rb')
        response_body = f.read()
        f.close()
        response_line = 'HTTP/1.0 404 ERROR\r\n'
        response_header = 'Server: ahang\r\n'
        send_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
        new_socket.send(send_data)
    else:
        response_body = f.read()
        f.close()
        response_line = 'HTTP/1.0 200 OK\r\n'
        response_header = 'Server: ahang\r\n'
        send_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
        new_socket.send(send_data)
    finally:
        new_socket.close()




if __name__ == '__main__':

    tcp_web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    tcp_web_socket.bind(('', 8000))
    tcp_web_socket.listen(128)

    while True:
        new_socket, ip_port = tcp_web_socket.accept()
        print(f'与客户端{ip_port}建立连接成功!!!')
        
        # 创建子线程且设为守护主线程
        sub_http_thread = threading.Thread(target=web_server, args=(new_socket,), daemon=True)

        sub_http_thread.start()

    # tcp_web_socket.close()

6. 面向对象版静态web服务器且可以通过命令行指定端口

C:\Users\study\web编程>python 05_通过命令行传递参数版web静态服务器.py 9000

import socket
import threading
import sys

# 定义服务器的类
class HttpWebServer(object):

    # 初始化类,定义套接字的属性
    def __init__(self, port):
        self.tcp_web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

        self.tcp_web_socket.bind(('', port))
        self.tcp_web_socket.listen(128)

    @staticmethod
    def web_server(new_socket):
        # 接收数据请求,指定每次接收的大小
        recv_data = new_socket.recv(4096)
        if len(recv_data) == 0:
            new_socket.close()
            return
        recv_content = recv_data.decode('utf-8')
        recv_list = recv_content.split(' ', 2)
        recv_path = recv_list[1]
        print(recv_path)

        try:
            # 默认页面
            if recv_path == '/':
                recv_path = '/index.html'
            f = open('static'+recv_path, 'rb')
        except Exception as result:
            print(result)
            f = open('static/error.html', 'rb')
            response_body = f.read()
            f.close()
            response_line = 'HTTP/1.0 404 ERROR\r\n'
            response_header = 'Server: ahang\r\n'
            send_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
            new_socket.send(send_data)
        else:
            response_body = f.read()
            f.close()
            response_line = 'HTTP/1.0 200 OK\r\n'
            response_header = 'Server: ahang\r\n'
            send_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body
            new_socket.send(send_data)
        finally:
            new_socket.close()

    def start(self):
        while True:
            new_socket, ip_port = self.tcp_web_socket.accept()
            print(f'与客户端{ip_port}建立连接成功!!!')
            # 创建子线程且设为守护主线程
            sub_http_thread = threading.Thread(target=self.web_server, args=(new_socket,), daemon=True)

            sub_http_thread.start()

def main():

    # 从命令行传入参数,形成列表
    params = sys.argv
    if (len(params) != 2) or (not params[1].isdigit()):
        print('请按照指定格式输入:python3 xxxx 8000')
        return
    port = int(params[1])

    httpsever = HttpWebServer(port)
    httpsever.start()

if __name__ == '__main__':
    main()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值