Python implements a simple WebSocket server that is compatible with both the old and new versions of the Socket protocol

  • 2020-04-02 13:40:51
  • OfStack

Recently in doing a project need to use the WebSocket technology, introduced in HTML 5, thought it should be easy to fix, who knows the real developed later find there is a lot of trouble, even though we are a development or design team before end, and as a second-hand program apes and will not be on for a long time, but in order to have the same demand friend walk less detours, I decided to stick realization method in this place.

The basic concepts of WebSocket are well explained on wikipedia and a handful of them can be found on the web.

The problem there is a premise, first is to use Python for this Server, if there is no limit to the language of the specific, recommend the preferred Node. Js a third-party libraries: Socket. IO, is very nice, 10 minutes without an injection take medicine don't fix the WebSocket Server, and to write the back-end with js, believe that had a lot of literature and art developers to taste.

But if you choose to use Python, almost all of Google's search results cannot be used, the problem is, the most terrible of the WebSocket protocol itself is a draft, so different browsers support the protocol version is different, is the old version of Safari 5.1 support agreement Hybi - 02, Chrome and Firefox 8.0 support 15 Hybi - 10 is a new version of agreement, the old version of the agreement and the new version protocol handshake method in establishing communication and data transmission format requirements are different, As a result, most web implementations only work with the Safari browser, and Safari and C & F browsers cannot communicate with each other.

The first step to explain is the handshake between the new and older versions of the WebSocket protocol. Let's first look at the structure of the handshake data sent by three different browsers:

Chrome:


GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:1337
Sec-WebSocket-Origin: http://127.0.0.1:8000
Sec-WebSocket-Key: erWJbDVAlYnHvHNulgrW8Q==
Sec-WebSocket-Version: 8
Cookie: csrftoken=xxxxxx; sessionid=xxxxx

Firefox:
GET / HTTP/1.1
Host: 127.0.0.1:1337
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:8.0) Gecko/20100101 Firefox/8.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 8
Sec-WebSocket-Origin: http://127.0.0.1:8000
Sec-WebSocket-Key: 1t3F81iAxNIZE2TxqWv+8A==
Cookie: xxx
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Safari:
GET / HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 127.0.0.1:1337
Origin: http://127.0.0.1:8000
Cookie: sessionid=xxxx; calView=day; dayCurrentDate=1314288000000
Sec-WebSocket-Key1: cV`p1* 42#7  ^9}_ 647  08{
Sec-WebSocket-Key2: O8 415 8x37R A8   4
;"######

 

As you can see, Chrome and Firefox implement the new protocol, so they only transmit an sec-websocket-key header for the server to generate the handshake Token, but there are two keys in the data following the old version of Safari: sec-websocket-key1 and sec-websocket-key2, so the server needs to make a judgment when generating the handshake Token. Starting with Safari, which USES the old version of the protocol, the Token generation algorithm is as follows:

Take all the numeric characters in sec-websocket-key1 to form a value, which is 1427964708 here, then divide by the number of Spaces in Key1, which seems to be 6 Spaces here, to get a value, keep the integer bit of the value, to get the value N1; Do the same for sec-websocket-key2, and you get the second integer, N2. Join N1 and N2 in a sequence of big-endian characters, and then connect with another Key3 to get the original sequence ser_key. So what is Key3? You can see that in the handshake request that Safari sends over at the end, there's this weird 8-byte string ";" ##### ", this is Key3. Going back to ser_key, do md5 on the original sequence to calculate a 16-byte digest, which is the token required by the old version of the protocol, and then send the token back to the Client at the end of the handshake message to complete the handshake.

The new version of the protocol generation Token method is relatively simple: first sec-websock-key and a string of fixed UUID "258eafa5-e914-47da-95ca-c5ab0dc85b11" to do splicture, and then the spliced-string SHA1 encryption, after the digest, do base64 encoding, you can get Token.

In addition, it needs to be noted that the new version and the old version of the handshake protocol back to the Client data structure is different, in the attachment of the server source code written very clearly, you can see.
Completing the handshake is only half the functionality of the WebSocket Server, which is now guaranteed to be able to link to both versions of the browser, but if you try to send a message from Chrome to Safari, it won't work. The reason for this is that the two versions of the protocol have a different Data Framing structure, meaning that the Client sends and receives different Data structures after the handshake has established the connection.

The first step is to get the raw data sent by clients under different versions of the protocol. The old version of the protocol was simpler, essentially adding a '\x00 'in front of the original data and a '\xFF' at the end, so if the Safari Client sends a string 'test', the WebSocket Server actually receives 'x00test\xFF', so you just need to strip off the first and last two characters.

The more troublesome part is the data of the new version of the protocol. According to the interpretation of the new draft, the datagram sent by Chrome and Firefox consists of the following parts: first, a fixed byte (10000001 or 10000002), which can be ignored. The trouble is that the second byte, it is assumed that the second byte is 1011, 1100, the first byte of the first must be 1, said this is a "masked", the remaining seven 0/1 to compute a numerical, here is the rest of the 011 1100, for example, calculated is 60, this value needs to be done as follows:

If the value is between 0000 0000 and 0111 1101 (0 ~ 125), then the value represents the actual length of the data. If this value is exactly equal to 0111 1110 (126), then the next 2 bytes represent the true data length; If this value is exactly equal to 0111 1111 (127), then the next eight bytes represent the length of the data.

Representing data with this judgment, to know the length of the byte at the end of which a, for example, we have examples of 60, the value between 0 ~ 125, so the second byte represents the length of the original data itself (60 bytes), starting from the third byte, so we can catch out 4 bytes, a string of bytes is called "masks" (mask)? , the data after the mask, is the actual data... Brother. Brother, because this data is the actual data after a bit operation according to the mask, the original data is obtained by xor operation of each bit x of the brother data and the ith %4 bit of the mask, where I is the index of x in the brother data. It's hard to see why, but take a look at this code snippet:


def send_data(raw_str):
    back_str = []

    back_str.append('x81')
    data_length = len(raw_str)

    if data_length < 125:
        back_str.append(chr(data_length))
    else:
        back_str.append(chr(126))
        back_str.append(chr(data_length >> 8))
        back_str.append(chr(data_length & 0xFF))

    back_str = "".join(back_str) + raw_str

The resulting back_str can then be sent to Chrome or Firefox using the new protocol.

At this point, the simple WebSocket Server is complete, capable of both old and new versions of the protocol compatible Socket connection, and between the different versions of the data transfer. The source of the Server please click here to download, need to note that the twisted framework is used to run TCP service, the code is not good, just for your reference.


Related articles: