http for python

—-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
X-Swift-SaveTime: Sat, 06 Apr 2019 08:49:57 GMT
X-Swift-CacheTime: 0
Timing-Allow-Origin: *
EagleId: 2a51041615545405973986157e
—– 空行 —-
\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进行分割

http编程:
http编程大体与网络编程一致,下面是网络编程步骤:
tcp客户端:
创建客户端套接字对象
和服务端套接字建立连接
发送数据
接收数据
关闭客户端套接字
socket() -> connect() -> send() -> recv () ->close()
tcp服务器:
创建服务端端套接字对象
绑定端口号
设置监听
等待接受客户端的连接请求
接收数据
发送数据
关闭套接字
socket()->bind()->listen()->accept()->recv()-send()->close()
由于程序员在编程时基本只用考虑服务器端的编程,所以这里以服务器端为主。
步骤对应的代码为:
创建服务端端套接字对象:
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
一般创建套接字后为了让程序退出端口后立即释放会加上复用端口号
复用端口号:
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
绑定端口号:
tcp_server_socket.bind((“”, 9000))
设置监听:
tcp_server_socket.listen(128)
等待接受客户端的连接请求:
new_socket, ip_port = self.tcp_server_socket.accept()
#这里的new_socket为接受到的新套接字,ip_port为其请求的ip以及端口
接收数据:
recv_client_data = new_socket.recv(4096)
发送数据:
new_socket.send(response_data)
关闭套接字:
new_socket.close()
http编程时其发送数据时应发送为http响应报文,其步骤与socket编程一致(因http本身基于socket)

实例代码:
import socket
import threading

import framework


class Lei():
    def __init__(self):
        # 创建tcp服务端套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口号复用, 程序退出端口立即释放
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 绑定端口号
        tcp_server_socket.bind(("", 9000))
        # 设置监听
        tcp_server_socket.listen(128)
        self.tcp_server_socket = tcp_server_socket

    @staticmethod
    def thead_http(new_socket):
        recv_client_data = new_socket.recv(4096)
        # 对二进制数据进行解码
        if len(recv_client_data) == 0:
            print("关闭浏览器了")
            new_socket.close()
            return
        recv_client_content = recv_client_data.decode("utf-8")
        #print(recv_client_content)
        qiege = recv_client_content.split(" ",maxsplit=5)
        uri = qiege[1]
        if uri == "/":
            uri = "/index.html"
        if uri.endswith(".html"):
             env = {
                 "uri" : uri,
                 "host" : host,
             }
             status,response_header,response_body=framework.handle_request(env)
             print("返还给客户端的请求报文",status,response_header,response_body)
             response_line = "HTTP/1.1 %s\r\n" %status
             headers = ""
             for header in response_header:
                 headers += "%s: %s\r\n" % header
             response_data = (response_line + headers + "\r\n" + response_body).encode("utf-8")
             new_socket.send(response_data)
             new_socket.close()
        else:
            try:
                with open("static" + uri, "rb") as file:
                    # 读取文件数据
                    file_data = file.read()
            except Exception as e:
                with open("static/error.html","rb") as file:
                    # 读取文件数据
                    file_data = file.read()
                # 响应行
                response_line = "HTTP/1.1 404 not found\r\n"
                # 响应头
                response_header = "Server: PWS1.0\r\n"
                # 响应体
                response_body = file_data
                # 拼接响应报文
                response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
                # 发送数据
                new_socket.send(response_data)
            else:
                # 响应行
                response_line = "HTTP/1.1 200 OK\r\n"
                # 响应头
                response_header = "Server: PWS1.0\r\n"
                # 响应体
                response_body = file_data
                # 拼接响应报文
                response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
                # 发送数据
                new_socket.send(response_data)
                # 关闭服务与客户端的套接字
            finally:
                new_socket.close()

    def start(self):
            while True:
                # 等待接受客户端的连接请求
                new_socket, ip_port = self.tcp_server_socket.accept()
                # 代码执行到此,说明连接建立成功
                threading_http = threading.Thread(target=self.thead_http, args=(new_socket,))
                threading_http.start()
def main():
    duixiang=Lei()
    duixiang.start()

if __name__ == '__main__':
    main()


该代码步骤:
    1.首先定义了两个类 Lei(),和main(),将Lei定义成对象,调用其start方法。if name等于main魔法方法则执行main()表示其需要是主文件才会执行。
    2.首先定义了对象方法,将创建tcp服务端套接字,绑定端口号,设置监听都直接定义在init魔法方法中,则在对象执行时,就会直接监听到端口,并把socket定义成了self.socket,以便于对象中其他类可以直接调用套接字。
    3.此时因为main中执行了start方法,所以应该是从lei()中的start开始执行,首先执行类对象属性(init),然后此时创建套接字连接请求,并将其赋给new_socket,这里用while true死循环是因为可以(一直接受请求),多线程实现接收多个请求(同时)。此时调用多线程与thead_http上(也就是真正处理客户端请求的类),将接受到的数据参数传递到该类中,并启动该线程。
    4.thead_http:建立连接后recv接受请求,首先判断其长度是否为0,如为0直接关闭套接字。decode将其转码,然后切割(split)拿到请求的uri,如果是/则重写为/index.html,然后当期为.html结尾(endswith取其拓展名)。此处html请求这里成为动态请求,因这里有index.html和center.html两个文件,所以会涉及两种请求,并且数据取自数据库。还需要建立一个framework.py的文件来写入其处理方法,并在这里调用,具体后面解释。
    5.这里用了if else,并在里面套用了try,except,else,finally。如果不是html文件,则进入else,并执行try模块,这里则以其uri作为路径读取文件内容,如果有该文件则以http报文形式进行返回(如果有该文件进入else)。如果没有该文件则进入except,读取了error.html并封装为http响应报文,返回其错误信息。finally最后关闭这个套接字(执行完也相当于关闭这个线程)
    6.最后是html动态请求,这里是framework.py
    import pymysql

    def index():
        #返回的状态码以及请求头请求体
        status = "200 ok"
        response_header = [("server", "PWS/1.1")]
        with open("template/index.html", "rb") as file:
            file_data = file.read()
        conn = pymysql.connect(host="10.10.10.85",
                               port=3306,
                               user="root",
                               password="123456",
                               database="python",
                               charset="utf8")
        cursor = conn.cursor()
        sql = "select * from info;"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        conn.close()
        data = ""
        for row in result:
            data += """<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="000007"></td>
                      </tr>""" %row
        response_body = file_data.decode().replace("{%content%}", data)
        return status, response_header, response_body
    def center():
        #返回的状态码以及请求头请求体
        status = "200 ok"
        response_header = [("server", "PWS/1.1")]
        with open("template/center.html", "rb") as file:
            file_data = file.read()
        conn = pymysql.connect(host="10.10.10.85",
                               port=3306,
                               user="root",
                               password="123456",
                               database="python",
                               charset="utf8")
        cursor = conn.cursor()
        sql = "select a.code,a.short,a.chg,a.turnover,a.price,a.highs,b.note_info from info as a inner join focus as b on a.id = b.info_id;"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        conn.close()
        data = ""
        for row in result:
            data += """<tr>
                      <td>%s</td>
                      <td>%s</td>
                      <td>%s</td>
                      <td>%s</td>
                      <td>%s</td>
                      <td>%s</td>
                      <td>%s</td>
                      </tr>""" %row
        response_body = file_data.decode().replace("{%content%}", data)
        return status, response_header, response_body
    def notfound():
        status = "404 notfound"
        response_header = [("server","PWS/1.1")]
        response_body = "not found"
        return  status,response_header,response_body
    def handle_request(env):
        uri = env["uri"]
        print ("动态资源请求uri:", uri )
        for path,func in route_list:
             if uri == path:
                 result = func()
                 return  result

       # if uri == "/index.html":
         #   #如果是index.html则返回index()
         #   result = index()
         #   return result
        else:
            result = notfound()
            return result
    route_list = [
         ("/index.html", index),
         ("/center.html", center),
    ]


​ 这里其实总结为三种处理方式,一种是没找到的,则返回notfound,一种是index.html,还有一种是center.html,所以这里一共4个类,index请求处理方式为一个类,center为一个类,notfound处理方式为一个类,最后handle_request是对这三种方式进行分发的方法类。
​ 首先取到uri(这个uri首先是通过split切割得到然后再动态请求类中定义为字典并传递到framework.py中的)
​ 然后if方法分析uri,这里if后面将route_list分隔为两个变量,route_list为集合,里面有两个元组,其数据有两个,第一个为uri,第二个则是其处理的类。则在这里赋给path和func,当uri变量等于path则执行器方法func。如果不等于,则else执行notefound,则三个类书写完毕。
​ 需要注意的是center和index都使用了pymsql模块,在其本身html文件中有一处是”content”,这里则通过调用mysql查询将其替换为查询语句。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!