Can HTTP/2 + SSE Completely Replace WebSocket?

Hello everyone, it’s great to see you again. I am Advancing Frontend Technology, and I will guide everyone to focus on cutting-edge frontend topics and delve into the underlying technologies, so we can all improve together. Please feel free to follow, like, bookmark, and share!

1. What is SSE (Server-Sent Events)

Server-Sent Events (SSE) is a standardized protocol that allows web servers to push data to clients without using alternative mechanisms such as ping, long polling, or WebSocket.

Can HTTP/2 + SSE Completely Replace WebSocket?

SSE is a technology published in HTML5 that allows servers to initiate data transmission to browser clients. Once the initial connection is established, the event stream remains open until the client closes it. This technology is sent over traditional HTTP and has various features lacking in WebSocket, such as automatic reconnection, event IDs, and the ability to send arbitrary events.

📢 SSE itself does not provide an automatic reconnection mechanism; its so-called automatic reconnection feature refers to the process where the browser automatically handles disconnections from the server and attempts to reconnect. Of course, developers can also manually reconnect using `eventSource.retry = 5000`.

SSE utilizes the server to declare to the client that it will send streaming information continuously. At this point, the client does not close the connection and will wait for new data streams from the server, similar to video streaming. SSE uses this mechanism to push information to the browser using streaming data. It is based on the HTTP protocol, and currently, all browsers support it except for IE/Edge.

Content-Type: text/event-stream
// Must be UTF-8 encoded text; the stream is essentially a download
Cache-Control: no-cache
Connection: keep-alive

Using SSE can significantly save bandwidth and battery life on portable devices and can work with existing infrastructure since it runs directly over HTTP without needing to upgrade connections like WebSockets.

2. How SSE Leverages HTTP/2 to Address WebSocket’s Shortcomings

Since SSE is based on HTTP, it naturally adapts to HTTP/2, allowing SSE to combine the strengths of both: HTTP/2 can form an efficient transport layer based on multiplexed streams, while SSE provides an API for applications to perform push notifications.

📢 A stream is an independent sequence of frames on a bidirectional HTTP/2 connection between the client and server, with the most important feature being that a single HTTP/2 connection can contain multiple concurrently opened streams, with frames interleaved from multiple streams.

Can HTTP/2 + SSE Completely Replace WebSocket?

If the application uses HTTP/1 for transmission, the Network tab may show the following output:

Can HTTP/2 + SSE Completely Replace WebSocket?

The browser will open multiple HTTP/1.x connections in parallel to speed up page loading, and different browsers have different limits on the number of concurrent connections to the same domain, generally supporting around six different connections.

To overcome this limitation, techniques similar to domain sharding are used to distribute resources across multiple domains. These techniques (which can be considered intrusive) include JavaScript and CSS files, images, and resource inlining, which ironically work against the HTTP/2 world. This may be the most significant impact when migrating to HTTP/2, which is to eliminate optimizations made over many years.

When using HTTP/2, the Network tab will show the browser using a single multiplexed connection, resulting in faster loading times.

Can HTTP/2 + SSE Completely Replace WebSocket?

Since SSE is based on HTTP, this means that when using HTTP/2, multiple SSE streams (server-to-client pushes) can be interleaved on a single TCP connection along with multiple client requests (client-to-server).

const http2 = require('http2');
const fs = require('fs');
// Create HTTP/2 server
const server = http2.createSecureServer({
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem')
});
server.on('stream', (stream, headers) => {
    const path = headers[':path'];
    const method = headers[':method'];
    if (path === '/events' && method === 'GET') {
        // Set response headers
        stream.respond({
            'content-type': 'text/event-stream',
            ':status': 200
        });
        // Send a welcome message
        stream.write(`data: Welcome! Current time is ${new Date().toISOString()}

`);
        // Send a message every 5 seconds
        const intervalId = setInterval(() => {
            stream.write(`data: Current time is ${new Date().toISOString()}

`);
        }, 5000);
        // Cleanup
        stream.on('close', () => {
            clearInterval(intervalId);
        });
    } elseif (path === '/send-data' && method === 'POST') {
        // Client sends a message to the server via fetch('https://localhost:3000/send-data')
        let body = '';
        stream.on('data', chunk => {
            body += chunk.toString();
        });
        stream.on('end', () => {
            console.log('Received data:', body);
            stream.respond({':status': 200});
            stream.end(JSON.stringify({ status: 'success', data: body}));
        });
    } else {
        stream.respond({':status': 404});
        stream.end();
    }
});
// Listen on port
server.listen(3000, () => {
    console.log('Server running at https://localhost:3000/');
});
// Below is an example of the browser connecting to HTTP/2
const eventSource = new EventSource('https://localhost:3000/events');
// Listen for message events
eventSource.onmessage = function(event) {
    const messageDiv = document.getElementById('messages');
    const newMessage = document.createElement('div');
    newMessage.textContent = `New message: ${event.data}`;
    messageDiv.appendChild(newMessage);
};
// Listen for connection open events
eventSource.onopen = function(event) {
    console.log('Connection opened');
};
// Listen for error events
eventSource.onerror = function(event) {
    if (eventSource.readyState === EventSource.CLOSED) {
        console.log('Connection closed');
    } else {
        console.log('Error occurred, attempting to reconnect...');
    }
};
// Send HTTP/2 request
document.getElementById('sendButton').addEventListener('click', () => {
    fetch('https://localhost:3000/send-data', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({message: 'Hello, server!'})
    })
    .then(response => response.json())
    .then(data => {
        console.log('Response:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
});

For example, in the above HTTP/2 code snippet:

  • Independent requests and responses: Since HTTP/2 supports multiplexing, each request and response is an independent stream, allowing the client to receive SSE messages through one stream while sending POST requests through another stream.
  • Bidirectional communication: HTTP/2 supports bidirectional communication, allowing the server and client to send and receive data at any time. This means the client can send data to the server through another stream while receiving SSE messages.

With HTTP/2 and SSE, a pure HTTP bidirectional connection can be used, along with a simple API that allows the application code to register multiple server pushes.

The lack of bidirectional capability has always been the main shortcoming of SSE compared to WebSocket. With HTTP/2, this shortcoming is addressed, making it possible to skip WebSocket and continue using HTTP-based mechanisms.

3. Can HTTP/2 + SSE Completely Replace WebSocket?

The answer is No. WebSocket has already been widely adopted, and in certain specific application scenarios, its underlying design is dedicated to bidirectional capabilities, which will show advantages in lower overhead. For instance, if there is a need to exchange a large volume of messages between both ends, where the amount of messages flowing in both directions is roughly similar (for example, in large multiplayer online games that require all players to stay in sync), WebSocket may be a better choice.

However, in scenarios such as displaying real-time market news, market data, or chat applications, relying on HTTP/2 + SSE will provide an efficient bidirectional communication channel while retaining many advantages of staying within the HTTP world:

  • WebSocket, being an upgrade from an HTTP connection to a completely different protocol unrelated to HTTP, makes it difficult to ensure compatibility with existing web infrastructure.
  • Scalability and security: Web infrastructure components (firewalls, intrusion detection, load balancing) are already established, maintained, and configured based on HTTP, creating a more friendly environment for large/critical applications in terms of resilience, security, and scalability.

Therefore, the conclusion on whether HTTP/2 + SSE can completely replace WebSocket is:

  • HTTP/2 is not a complete replacement for HTTP.
  • Intrusive techniques such as domain sharding, resource inlining, and image sprites are counterproductive in the HTTP/2 world.
  • HTTP/2 is not a substitute for push technologies like WebSocket or SSE; HTTP/2 push can only be handled by the browser, not the application.
  • Combining HTTP/2 and SSE provides efficient HTTP-based bidirectional communication.

WebSocket technology may continue to be used, but the combination of SSE and its EventSource API with the capabilities of HTTP/2 can achieve similar results in most scenarios, and it will be simpler.

References

https://zhuanlan.zhihu.com/p/37365892

https://blog.csdn.net/cnweike/article/details/116056371

https://www.infoq.com/articles/websocket-and-http2-coexist/

https://stackoverflow.com/questions/48344634/why-do-we-need-sse-when-we-have-http2-bidirectional-streaming

Leave a Comment