Report this

What is the reason for this report?

Request for for a Tokio Tsl Socket server code example that can handle a front end wws request

Posted on August 19, 2020

When upgrading my front end react app to https using nginx, the backend socket server stopped working. The original call to the back end was done via W3CWebSocket(ws://x.x.x.x:xxxx) in react.

I am able to connect to my tokio-native-tls socket server but cannot properly complete the handshake. I seem to be missing the HTML part.

My certificates loaded properly on the server. I don’t understand how to incorporate the additional HTML handling. The upgrade response from the front end is never being read in as part of the handshake. It shows up on the unrelated subsequent read. (see below)

Front end result:

socketMiddleware.js:106 WebSocket connection to 'wss://www.xxx.com:5001/' failed: Error during WebSocket handshake: net::ERR_INVALID_HTTP_RESPONSE

Does anyone have example code for a backend TSL Tokio socket server that can respond properly to a wss://www.xxx.com:xxxx/ request from the front end?

Server debug output:

accept connection from 142.196.248.38:7439 tls_stream TlsStream( SslStream { stream: AllowStd { inner: TcpStream { addr: V4( , ), peer: V4( 142.196.248.38:7439, ), fd: 9, }, context: 0x0000000000000000, }, ssl: Ssl { state: "SSL negotiation finished successfully", verify_result: X509VerifyResult { code: 0, error: "ok", }, }, }, )

Here is the rest of the handshake code that is not making it in:

GET / HTTP/1.1 Host: www.xxx.com:5001 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/84.0.4147.125 Safari/537.36 Upgrade: websocket Origin: https://www.xxx.com Sec-WebSocket-Version: 13 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Sec-WebSocket-Key: +dd3mdZN5i1qw71aG9/rbA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Any help would be appreciated!



This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

Hello,

It appears you’re trying to use a WebSocket (ws/wss) connection with a server built using Tokio and native-tls. Tokio is a Rust networking library which allows you to write asynchronous code. It seems you are trying to establish a WebSocket connection over TLS, but are running into issues during the handshake phase.

Unfortunately, setting up a WebSocket server with native TLS support is quite complex with pure tokio and native-tls because of the specific handling required for the WebSocket protocol.

Instead, I would recommend using a higher-level library such as Tungstenite or Tokio-Tungstenite that abstracts some of these complexities. For a secure WebSocket connection, you might want to use Tokio-rustls instead of native-tls for the TLS part.

Here is an example of a Tokio-Tungstenite WebSocket server with Tokio-rustls:

use tokio::net::TcpListener;
use tokio_rustls::rustls::{ServerConfig, NoClientAuth};
use tokio_rustls::TlsAcceptor;
use tokio_tungstenite::accept_async;
use tungstenite::Message;

#[tokio::main]
async fn main() {
    // Load your certificates and private key
    let certs = load_certs("path/to/your/cert.pem");
    let key = load_private_key("path/to/your/key.pem");
    let mut config = ServerConfig::new(NoClientAuth::new());
    config.set_single_cert(certs, key).unwrap();

    let acceptor = TlsAcceptor::from(Arc::new(config));

    let addr = "0.0.0.0:5001";
    let listener = TcpListener::bind(&addr).await.unwrap();

    while let Ok((stream, _)) = listener.accept().await {
        let acceptor = acceptor.clone();
        tokio::spawn(async move {
            let stream = acceptor.accept(stream).await;
            if let Ok(stream) = stream {
                let ws_stream = accept_async(stream).await;
                if let Ok(ws_stream) = ws_stream {
                    // Handle the websocket connection here
                    while let Ok(msg) = ws_stream.next().await {
                        if let Some(Message::Text(text)) = msg {
                            println!("Received: {}", text);
                        }
                    }
                }
            }
        });
    }
}

fn load_certs(path: &str) -> Vec<Certificate> {
    // Load your certs here
}

fn load_private_key(path: &str) -> PrivateKey {
    // Load your private key here
}

This example server listens for incoming connections and accepts them using the provided TLS acceptor. Then it attempts to perform a WebSocket handshake over the secured connection. If successful, it enters a loop where it listens for text messages and logs them.

Please replace load_certs and load_private_key functions with actual certificate and key loading code.

Please note that you should adjust the actual handling of the WebSocket connection to fit your specific needs.

Also, this is a simplified example and error handling should be improved for production use.

In the frontend, you should then be able to connect to your server with wss://www.xxx.com:5001/ if your DNS and certificates are correctly set up.

Alternatively, you could use Nginx as a reverse proxy to handle the TLS termination can greatly simplify your application’s code, since you’ll only need to deal with regular non-secure WebSocket connection in your Rust application. It is also a common practice for production-level applications due to the added benefits like simplified certificate management, load balancing, and static file serving.

Here is a basic configuration you could use for Nginx:

server {
    listen 443 ssl;
    server_name www.example.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    location / {
        proxy_pass http://localhost:5001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
}

This configuration sets up Nginx to listen for HTTPS connections with the specified server name (replace www.example.com with your domain). The ssl_certificate and ssl_certificate_key directives point to your SSL certificate and private key.

In the location block, any requests are forwarded to the WebSocket server running on localhost:5001. The proxy_set_header directives set up some necessary headers for WebSocket to work correctly.

With this configuration, Nginx accepts secure WebSocket (WSS) connections from clients and forwards them as regular WS connections to the Rust application. You can keep your Rust application running on non-secure WebSocket (ws://) and offload the secure handling to Nginx.

On the client side, you should be able to connect to wss://www.example.com/.

Best,

Bobby

Creating a WebSocket server in Rust using Tokio and native-tls (which can handle WebSocket Secure connections - wss://) requires you to handle the initial HTTP upgrade request to WebSocket. This can be a bit tricky, as you’ll need to manually parse the HTTP request and then respond with appropriate WebSocket handshake headers.

Here is a basic example of how you might implement a WebSocket server using Tokio and native-tls, which responds to a wss:// request:

Dependencies

In your Cargo.toml, you would typically include the following dependencies:

[dependencies]
tokio = { version = "1", features = ["full"] }
native-tls = "0.2"
tokio-native-tls = "0.3"
httparse = "1.5"

Example Server Code

This is a simplified example to give you an idea of how to start:

use native_tls::{Identity, TlsAcceptor};
use tokio::net::TcpListener;
use tokio_native_tls::TlsAcceptorExt;
use httparse::Request;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "0.0.0.0:5001";
    let cert = include_bytes!("path/to/identity.pfx");
    let identity = Identity::from_pkcs12(cert, "password")?;
    let tls_acceptor = TlsAcceptor::new(identity)?;
    let listener = TcpListener::bind(addr).await?;
    let acceptor = TlsAcceptorExt::from(tls_acceptor);

    loop {
        let (stream, _) = listener.accept().await?;
        let acceptor = acceptor.clone();
        tokio::spawn(async move {
            if let Ok(mut tls_stream) = acceptor.accept(stream).await {
                // Read the HTTP request here and parse it
                let mut buffer = [0; 1024];
                let _ = tls_stream.read(&mut buffer).await;
                
                let mut headers = [httparse::EMPTY_HEADER; 16];
                let mut req = Request::new(&mut headers);
                if let Ok(_) = req.parse(&buffer) {
                    // Check if it's a WebSocket upgrade request and handle it
                    if req.method == Some("GET") && req.path == Some("/") {
                        // Construct and send the WebSocket handshake response
                        let response = "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: <accept key>\r\n\r\n";
                        let _ = tls_stream.write_all(response.as_bytes()).await;
                        
                        // Further WebSocket communication would happen here
                    }
                }
            }
        });
    }
}

Important Points:

  • Replace "path/to/identity.pfx" and "password" with the correct path to your TLS identity file and the password.
  • The WebSocket handshake response needs to include a Sec-WebSocket-Accept header, which is a base64-encoded SHA-1 hash of the concatenation of the Sec-WebSocket-Key (provided by the client in the request) with the magic WebSocket GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.
  • This example uses httparse for parsing the HTTP request. It’s a basic example and might need to be expanded for full HTTP request parsing.
  • In a production environment, you should handle errors and edge cases properly.

This example demonstrates the basic mechanism for handling an HTTP upgrade request to WebSocket over TLS in Rust using Tokio and native-tls. Depending on your application’s needs, you may need to integrate a more robust WebSocket library or framework.

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.