Asked  7 Months ago    Answers:  5   Viewed   31 times
  • How can I send and receive messages on the server side using WebSocket, as per the protocol?

  • Why do I get seemingly random bytes at the server when I send data from the browser to the server? It the data encoded somehow?

  • How does the framing work in both the server ? client and client ? server directions?

 Answers

77

Note: This is some explanation and pseudocode as to how to implement a very trivial server that can handle incoming and outcoming WebSocket messages as per the definitive framing format. It does not include the handshaking process. Furthermore, this answer has been made for educational purposes; it is not a full-featured implementation.

Specification (RFC 6455)


Sending messages

(In other words, server ? browser)

The frames you're sending need to be formatted according to the WebSocket framing format. For sending messages, this format is as follows:

  • one byte which contains the type of data (and some additional info which is out of scope for a trivial server)
  • one byte which contains the length
  • either two or eight bytes if the length does not fit in the second byte (the second byte is then a code saying how many bytes are used for the length)
  • the actual (raw) data

The first byte will be 1000 0001 (or 129) for a text frame.

The second byte has its first bit set to 0 because we're not encoding the data (encoding from server to client is not mandatory).

It is necessary to determine the length of the raw data so as to send the length bytes correctly:

  • if 0 <= length <= 125, you don't need additional bytes
  • if 126 <= length <= 65535, you need two additional bytes and the second byte is 126
  • if length >= 65536, you need eight additional bytes, and the second byte is 127

The length has to be sliced into separate bytes, which means you'll need to bit-shift to the right (with an amount of eight bits), and then only retain the last eight bits by doing AND 1111 1111 (which is 255).

After the length byte(s) comes the raw data.

This leads to the following pseudocode:

bytesFormatted[0] = 129

indexStartRawData = -1 // it doesn't matter what value is
                       // set here - it will be set now:

if bytesRaw.length <= 125
    bytesFormatted[1] = bytesRaw.length

    indexStartRawData = 2

else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
    bytesFormatted[1] = 126
    bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length      ) AND 255

    indexStartRawData = 4

else
    bytesFormatted[1] = 127
    bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
    bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
    bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
    bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
    bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
    bytesFormatted[8] = ( bytesRaw.length >>  8 ) AND 255
    bytesFormatted[9] = ( bytesRaw.length       ) AND 255

    indexStartRawData = 10

// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)


// now send bytesFormatted (e.g. write it to the socket stream)

Receiving messages

(In other words, browser ? server)

The frames you obtain are in the following format:

  • one byte which contains the type of data
  • one byte which contains the length
  • either two or eight additional bytes if the length did not fit in the second byte
  • four bytes which are the masks (= decoding keys)
  • the actual data

The first byte usually does not matter - if you're just sending text you are only using the text type. It will be 1000 0001 (or 129) in that case.

The second byte and the additional two or eight bytes need some parsing, because you need to know how many bytes are used for the length (you need to know where the real data starts). The length itself is usually not necessary since you have the data already.

The first bit of the second byte is always 1 which means the data is masked (= encoded). Messages from the client to the server are always masked. You need to remove that first bit by doing secondByte AND 0111 1111. There are two cases in which the resulting byte does not represent the length because it did not fit in the second byte:

  • a second byte of 0111 1110, or 126, means the following two bytes are used for the length
  • a second byte of 0111 1111, or 127, means the following eight bytes are used for the length

The four mask bytes are used for decoding the actual data that has been sent. The algorithm for decoding is as follows:

decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]

where encodedByte is the original byte in the data, encodedByteIndex is the index (offset) of the byte counting from the first byte of the real data, which has index 0. masks is an array containing of the four mask bytes.

This leads to the following pseudocode for decoding:

secondByte = bytes[1]

length = secondByte AND 127 // may not be the actual length in the two special cases

indexFirstMask = 2          // if not a special case

if length == 126            // if a special case, change indexFirstMask
    indexFirstMask = 4

else if length == 127       // ditto
    indexFirstMask = 10

masks = bytes.slice(indexFirstMask, 4) // four bytes starting from indexFirstMask

indexFirstDataByte = indexFirstMask + 4 // four bytes further

decoded = new array

decoded.length = bytes.length - indexFirstDataByte // length of real data

for i = indexFirstDataByte, j = 0; i < bytes.length; i++, j++
    decoded[j] = bytes[i] XOR masks[j MOD 4]


// now use "decoded" to interpret the received data
Tuesday, June 1, 2021
 
Rocket
answered 7 Months ago
39
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
URL = "http://www.somedomain.com"
objHTTP.Open "POST", URL, False
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
objHTTP.send("")

Alternatively, for greater control over the HTTP request you can use WinHttp.WinHttpRequest.5.1 in place of MSXML2.ServerXMLHTTP.

Tuesday, June 1, 2021
 
ioleo
answered 7 Months ago
85

It's a limitation of Swift that a protocol cannot conform to itself. Thus from and to do not conform to Codable as bizarre as that seems.

You can get around it by using generics which basically means you declare from and to as arbitrary types that conform to Codable. Here's how:

struct Configuration<F: Connection, T: Connection>: Codable {
    var from: F
    var to: T
}


let myFrom = SFTPConnection(path: "foo", user: "me", sshKey: "hgfnjsfdjs")
let myTo = FTPConnection(path: "foo", user: "me", password: "hgfnjsfdjs")
let example = Configuration(from: myFrom, to: myTo)

So F and T are types that conform to Connection. When you instantiate example in the last line, the compiler infers F is SFTPConnection and T is FTPConnection.

Once I added the generic parameters, Configuration was able to synthesise the conformance to Codable without the extension.


To answer Sh_kahn's point about having two generic parameters, I did this to allow from and to to be connections of different types. If you always want the two connections to be of the same type i.e. always two SFTPConnections or two FTPConnections you should declare the Configuration like this:

struct Configuration<C: Connection>: Codable {
    var from: C
    var to: C
}
Saturday, August 7, 2021
 
LOKESH
answered 4 Months ago
46

You must be using JSONObjects to send the Base64 String to the server. If so then the JSONObject will add " " escape characters to the string which are illegal Base64 characters. All you need to do is

String rectifiedString = Base64String.replace("\","");

on the server end before using the acquired string.

Optional

Also on the server side use

import javax.xml.bind.DatatypeConverter;

and

String result = DatatypeConverter.printBase64Binary(bytearrayToDecode);

byte[] result = DatatypeConverter.parseBase64Binary(str);

instead.

It will not only get the job done but is much faster at encoding/decoding (as benchmarked here).

Friday, August 13, 2021
 
Freddie
answered 4 Months ago
23

Now that I know how to do this, I will answer my own question so that we can keep this information (in details) here: (This is based on Mason Chang's answer to this question.)

  1. Stop meteor execution.
  2. Edit /usr/lib/meteor/app/meteor/run.js (or the corresponding run.js installed by meteorite in HOME//.meteorite/meteors/meteor/meteor/[LONG_HEX_CODE]/app/meteor):
    change the line
    [path.join(bundle_path, 'main.js'), '--keepalive']
    to
    ['--debug-brk', path.join(bundle_path, 'main.js'), '--keepalive']
    //--debug-brk makes the new thread break at the first line;
  3. Add debugger statements as breakpoints in your server code;
  4. Run node-inspector & in a server terminal. (google "node-inspector" to install it.)
  5. Run meteor; (this will not have the debugger attached as there's no server thread yet, if you have no client window open.)
  6. Refresh client browser window; (to initiate a server thread that will break at the first line, and be attached to node-inspector.)
  7. Open a browser window at [SERVER:8080], your server code stops at first line (main.js in your [PROJECT_DIR]/.meteor/local/build);
  8. Hit the RUN button on the debugger browser window; depending on where your debugger statements are, you may have to do some triggering actions in client browser window to run to the debugger breakpoints. (Note that if you wait too long to hit the RUN button, your client window may time out, and you have to refresh again.)
  9. Now you can do the usual debugging stuff in server debugger window: step through, watch variables, execute in console, look at the stack, etc.

Edit: For logging on server side, you can use either Meteor._debug() and console.log(), they will show up in the terminal where you run meteor. On client side, these logging statements will output to the console of your browser's dev. tools.

Sunday, September 12, 2021
 
wavyGravy
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share