小言_互联网的博客

前端补充协议

490人阅读  评论(0)

http协议

所有web框架都是由socket衍生出来的,socket通过tcp进行连接,web框架通过http协议连接。
http协议: 浏览是socket客户端,在浏览器输入服务端ip,只要accept连接成功,就可以收发数据,浏览器send一堆字符串,遵循http协议,格式:请求头,请求体组成的字符串。服务端解析字符串,按照固定的格式解析出来。返回给浏览器数据,也遵循http协议。连接即断开,一次请求一次响应就断开。
所有http就是建立在tcp协议的一个规范,规定发送数据的格式和请求响应后断开连接。也就是无状态(断开后之前的就不认识了),短链接(一次请求响应断开close )。
如:在浏览器发送baidu.com
a. 向百度发送连接请求
b. 百度接受浏览器发送的链接请求,recv状态
c. 浏览器socket.send(‘GET /s?wd=十六进制 Http1.1/r/nAccept-Encoding: gzip, deflate, br\r\nHost: www.baidu.com\r\nUser-Agent: …’)
get请求没有请求体,请求头和请求体分割\r\n\r\n

d. 百度接收到用户发送的数据 data = server.recv(8096),进行业务处理,根据wd搜索的关键字检索本地和json相关的数据,再给用户返回数据。响应头,响应体


响应体反应到浏览器上了
e. 浏览器和服务端端口链接。
再一次搜索以上步骤再会走一遍。

  • http协议常见的请求头,描述发送者的基本信息
    content-type:指定请求体中发送的数据格式
    user-agent:我的设备信息,如:来自于xx设备的评论
    cookies:保存原来服务端给客户端写入的信息。
    accept:告诉服务器客户端能接受的数据格式
    host:当前要访问的主机信息
    referer:防盗链。跳转时访问其他页面把原来的网页携带上。如:访问博客园,浏览器打印了博客园的网站,回车后返回博客园的信息,如访问一篇文章,里面包含图片,再发请求,把图片内容下载后,浏览器才能展示,如51cto,51cto返回图片过程中用户不知道是51cto网站的东西,但是博客园利用了这个,博客园不用存储图片了,51cto免费存图片,而访问图片要消耗流量和钱,没有获利只有付出。于是就返回给博客园不显示图片,返回图片:请访问51cto,把用户导入了,分流了。src发get请求,浏览器吧当前url放到referer中,51cto判断是不是博客园,如果不是博客园就只能发公告或别的图片,如果是就给他看,
  • 请求的方式
    get。put,delete,post,patch,opthons:跨域发ajax复杂请求,做预检,
  • 响应状态码
    200 201 成功
    301 临时重定向 302 永久重定向
    402:客户端错误 url not found 403:客户端csrf token 错误
    500 服务端代码错误

websocket协议

  • 存在意义:服务端主动向客户端推送消息
  • websocket和http协议的区别
    http无状态,无法找到对方主动发消息,只能响应对方的消息,属于单工模式。
    websocket不断开时双方就可以相互发send消息。双工通道。
    如:浏览器弹出广告。
  • websocket原理
    websocket也是使用的tcp,是基于tcp创建的,利用http协议并加以改造。规定了发送数据的格式及是否断开连接(不断开)。
  • 过程:
1. 客户端浏览器向服务端发送websocket连接的请求,按websocket请求通过tcp添加了一个socket连接connect,
2. 服务端接收到连接accept,开始recv,浏览器向服务端发送字符串(遵循http协议的格式,如:GET/s?wd='linda' Http1.1\r\nAccept-Encoding:gzip,\r\nHost:www.xxx.com\r\nSec-WebSocket-Key:askdjfaaskdfjlasdjkf\r\n,)

服务端接收到客户请求,解析随机字符串,对其进行加密,加密方法:‘askdjfaaskdfjlasdjkf ’+ ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’(全球公认:魔法字符串),对他们进行拼接,加密(全球公认算法),得到密文字符串。再将密文字符串返回给浏览器(遵循http格式返回,有响应头,响应体),如:响应头:HTTP/1.1 101 …\r\nSec-WebSocket-Accept:密文\r\n\r\n
3. 浏览器进行校验,内置的功能可以完成校验,浏览器报错:连接失败,没报错:连接成功。

例:
第一次握手环节:
客户端的浏览器发送WebSocket请求

服务端代码

import socket


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.bind(('127.0.0.1', 8002))

sock.listen(5)


conn, address = sock.accept()


data = conn.recv(1024)

print(data)


conn.close()
sock.close()

服务端拿到客户端发送的请求:

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8002\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36\r\nUpgrade: websocket\r\nOrigin: http://localhost:63342\r\nSec-WebSocket-Version: 13\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=m36aBGOEbBwG6ddvNbvbf7SddSE14Qg0rin5vDgsAqP4C34qeufiYC4Nr3Ei43zP\r\nSec-WebSocket-Key: dovhwrbqlcKJqaXGYplq5g==\r\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n'

该请求,遵循了http协议,关键是请求头的随机字符串:Sec-WebSocket-Key: dovhwrbqlcKJqaXGYplq5g==

第二次握手环节:

服务器拿到数据后进行验证及加密,返回给服务端浏览器

import socket
import hashlib
import base64

def get_headers(data):
    '''
        将请求头格式化成字典
    '''
    header_dict = {}
    data = str(data, encoding='utf-8')
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(' ')) == 3:
                header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
        else:
            k, v = header_list[i].split(':', 1)
            header_dict[k] = v.strip()
    return header_dict

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
conn, address = sock.accept()
data = conn.recv(1024)
header_dict = get_headers(data)   # 把浏览器返回的数据转换成字典
print('header_dict: ', header_dict)

'''
header_dict:  {'method': 'GET', 'url': '/', 'protocol': 'HTTP/1.1', 'Host': '127.0.0.1:8002', 'Connection': 'Upgrade', '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/68.0.3440.75 Safari/537.36', 'Upgrade': 'websocket', 'Origin': 'http://localhost:63342', 'Sec-WebSocket-Version': '13', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cookie': 'csrftoken=m36aBGOEbBwG6ddvNbvbf7SddSE14Qg0rin5vDgsAqP4C34qeufiYC4Nr3Ei43zP', 'Sec-WebSocket-Key': 'W3uHDBw7fy/obPfpNsjYCA==', 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits'}
'''
random_str = header_dict['Sec-WebSocket-Key']   # 再字典中拿到随机字符串
print('random_str: ', random_str)   # random_str:  W3uHDBw7fy/obPfpNsjYCA==
random_magic_str = random_str + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'  # 把随机字符串和公认的魔法字符串进行拼接
print('random_magic_str: ',random_magic_str)  # random_magic_str:  W3uHDBw7fy/obPfpNsjYCA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
value = base64.b64encode(hashlib.sha1(random_magic_str.encode('utf-8')).digest())  # 使用指定的算吗进行加密,base加密后为字节
print('value: ', value)  # value:  b'QonkW5IRyI1YroryW9QN8nK1POg='
response_template = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"  # 浏览器response模版
response_str = response_template % (value.decode('utf-8'), header_dict['Host'], header_dict['url'])
print('response_str: ',response_str)  
'''
response_str:  HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection: Upgrade
Sec-WebSocket-Accept: QonkW5IRyI1YroryW9QN8nK1POg=
WebSocket-Location: ws://127.0.0.1:8002/
'''
conn.send(bytes(response_str, encoding='utf-8')) # 把该字符串返回给浏览器,如果验证完成,就正式连接成功了
while True:
    content = conn.recv(8096)
    print('客户端验证成功给我发送的数据:', content)
conn.close()
sock.close()

浏览器再次发送请求,第三次握手成功

没有报错,证明成功建立websocket连接,以上就是三次握手环节,

  • websocket收发数据原理:

连接成功后给浏览器给服务端发送数据,如

服务端:

客户端验证成功给我发送的数据: b'\x81\x86\xe4\xdd\xf0\x00\x00`P\xe5A`'

此消息是发送的’你好‘加密后的bytes

  • 解密过程
    websocket官网关于如何解密图


区别内容和报头的方法:


发送的数据越大,报头的长度越长

通过mask key 和内容进行位运算解密:

客户端发送数据:

服务端拿到加密的数据后解密后得到客户发送的内容:


import socket
import hashlib
import base64


def get_headers(data):
    '''
        将请求头格式化成字典
    '''
    header_dict = {}
    data = str(data, encoding='utf-8')
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(' ')) == 3:
                header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
        else:
            k, v = header_list[i].split(':', 1)
            header_dict[k] = v.strip()
    return header_dict
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
conn, address = sock.accept()
data = conn.recv(1024)
header_dict = get_headers(data)
random_str = header_dict['Sec-WebSocket-Key']
random_magic_str = random_str + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = base64.b64encode(hashlib.sha1(random_magic_str.encode('utf-8')).digest())  # base加密后为字节
response_template = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"
response_str = response_template % (value.decode('utf-8'), header_dict['Host'], header_dict['url'])
conn.send(bytes(response_str, encoding='utf-8')) # 只要把该字符串返回给浏览器,并验证完成,就正式连接成功了


while True:
    content = conn.recv(8096)
    payload_len = content[1] & 127  # 判断第二位字节大小。与127做位运算, 127为01111111,假设content[1]为10010011,做&运算结果为00010011,
    if payload_len == 126:  # 做125,126,127判断
        extend_payload_len = content[2:4]
        mask = content[4:8]  # mask key:4-8
        decoded = content[8:]
    elif payload_len == 127:
        extend_payload_len = content[2:10]
        mask = content[10:14]  # mask key: 10-14
        decoded = content[14:]
    else:
        extend_payload_len = None  # 小于等于125
        mask = content[2:6]  # 2-6为mask key
        decoded = content[6:]
    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]  # mask key 和 内容进行位运算
        bytes_list.append(chunk)
    print(bytes_list)
    body = str(bytes_list, encoding='utf-8')  # 解码后就是用户传的结果
    print('body: ', body)

conn.close()
sock.close()

服务端运行结果

bytearray(b'\xe4\xbd\xa0\xe5\xa5\xbd')
body:  你好

服务端怎样给用户主动返回数据呢?

import socket
import hashlib
import base64
def get_headers(data):
    '''
        将请求头格式化成字典
    '''
    header_dict = {}
    data = str(data, encoding='utf-8')
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(' ')) == 3:
                header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
        else:
            k, v = header_list[i].split(':', 1)
            header_dict[k] = v.strip()
    return header_dict



def send_msg(conn, msg_bytes):
    '''
    WebSocket服务端向客户端发送消息
    :param conn: 客户端连接到服务端的socket对象,
    :param msg_bytes: 向客户端发送的字节
    :return:
    '''
    import struct

    token = b"\x81"
    length = len(msg_bytes)
    if length < 126:
        token += struct.pack("B", length)
    elif length <= 0xFFFF:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)

    msg = token + msg_bytes
    conn.send(msg)
    return True
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
conn, address = sock.accept()
data = conn.recv(1024)
header_dict = get_headers(data)
random_str = header_dict['Sec-WebSocket-Key']
random_magic_str = random_str + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = base64.b64encode(hashlib.sha1(random_magic_str.encode('utf-8')).digest())  # base加密后为字节
response_template = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"
response_str = response_template % (value.decode('utf-8'), header_dict['Host'], header_dict['url'])
conn.send(bytes(response_str, encoding='utf-8')) # 只要把该字符串返回给浏览器,并验证完成,就正式连接成功了

while True:
    content = conn.recv(8096)
    payload_len = content[1] & 127  # 判断第二位字节大小。与127做位运算, 127为01111111,假设content[1]为10010011,做&运算结果为00010011,
    if payload_len == 126:  # 做125,126,127判断
        extend_payload_len = content[2:4]
        mask = content[4:8]  # mask key:4-8
        decoded = content[8:]
    elif payload_len == 127:
        extend_payload_len = content[2:10]
        mask = content[10:14]  # mask key: 10-14
        decoded = content[14:]
    else:
        extend_payload_len = None  # 小于等于125
        mask = content[2:6]  # 2-6为mask key
        decoded = content[6:]
    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]  # mask key 和 内容进行位运算
        bytes_list.append(chunk)
    print(bytes_list)
    body = str(bytes_list, encoding='utf-8')  # 解码后就是用户传的结果
    print('客户端通过websocket给我发送的数据: ', body)
    
    send_msg(conn, b'osfksudjfsdf')


conn.close()
sock.close()

客户端:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var ws = new WebSocket('ws://127.0.0.1:8002');
        ws.onmessage = function (event) {
            console.log(event.data);
        }  // onmessage: 回调函数,服务端主动给客户端回消息,自动触发该函数       
    </script>
</body>
</html>

成功给客户端返回数据

应用场景:
flask:geventwebsocket
django:chanels

除了握手环节的请求和响应的格式用到http协议,收发消息时都没有用。websocket不是基于http,而是利用了http的一个特性。

https协议

应用层:http协议网络数据的传输
局限性:服务端不能主动给客户端推送消息,websocket不是万能的
https:解决数据传输的安全,意思是http不安全,基于http,把默认端口变成443,链接成功后不会立即发数据,而是要秘钥,
数据加密传输,防止数据劫持,爬虫等,
流程:

  1. 客户端向服务端发起链接
  2. 服务端接受链接
  3. 客户端向服务端发起url请求
  4. ,服务端接收后生成非对称秘钥(公钥和私钥,公钥对数据加密,私钥用来解密,也就是公钥加密的数据私钥才能解密)。保留私钥,返回公钥。,服务端接收后生成非对称秘钥(公钥和私钥,公钥对数据加密,私钥用来解密,也就是公钥加密的数据私钥才能解密)。保留私钥,返回公钥。
  5. 客户端拿到公钥后验证公钥的合法性,不合法浏览器报错,终止链接。如:xx不安全。如果合法,认证成功。则生成一个随机字符串的密码:对称密钥(可以进行加密和解密)。先将对称密钥通过公钥进行加密。再发给服务端。
  6. 服务端接收到使用公钥加密后的对称密钥后。使用私钥将随机字符串解密出来,这个字符串是对称密钥。告诉客户端收到这个对称密钥了。
  7. 客户端给服务端发送数据,使用对称密钥对数据进行加密。
  8. 服务端接收消息后,使用对称密钥把数据解密。获取数据。业务处理完成后,回复是用对称密钥加密,
  9. 客户端再用对称密钥解密。
    真正对数据进行加密的是对称密钥,而非对称密钥对对称密钥进行加密
    为什么不用非对称加密进行数据传输:
    对称加密比非对称加密加密速度和解密速度要快的多客户端拿到公钥后验证公钥的正确性
    3456步就是ssl协议,也就是根据对称密钥进行收发数据。
    第4步返回公钥时,容易发生数据劫持,劫持到公钥后,客户端把数据发送到挟持方,劫持方生成随机对称密钥,再发送给服务端。虽然不能解密数据,但是可以伪装数据。于是有了CA数字证书认证机构。服务端把公钥钥给这个机构,让机构给公钥做数字签名,得到一个证书,证书中含有公钥。于是返回给客户端一个含公钥的CA证书。这个证书含有很多信息,如公司名等等。然后客户端对证书进行校验。而浏览器中内置了验证规则。证书已经植入电脑受信任的机构进行验证。
    如:

    合法:

    缺陷:如果浏览器手动导入不信任的证书,可能再一次被劫持。虽然看似安全,其实是不安全的。

转载:https://blog.csdn.net/weixin_42233629/article/details/88322644
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场