IIS As Reverse Proxy To WebSocket Server

In this article we will use IIS as a reverse proxy to a WebSocket Server.

Prerequisites:

Architecture

Client => Reverse Proxy (IIS) => Backend (Node.js)

IIS will act as a reverse proxy to forward requests from “localhost” OR “reverseproxy.com” to “localhost:8080”.

In IIS we will have a site called “ReverseProxy” with bindings for HTTP & HTTPS “localhost” + HTTP & HTTPS “reverseproxy.com”.

For “reverseproxy.com” we will fake an “A” Record in “C:\Windows\System32\drivers\etc\hosts” file.

127.0.0.1 reverseproxy.com

So, if client request “localhost” or “reverseproxy.com” IIS should forward it to “http://localhost:8080" that will shown an “index.html” from where the WebSocket connection will be created.

WebSocket endpoints:

What is WebSocket?

WebSocket is bidirectional, a full-duplex protocol that is used in the same scenario of client-server communication, unlike HTTP it starts from ws:// or wss://. It is a stateful protocol, which means the connection between client and server will keep alive until it is terminated by either party (client or server). After closing the connection by either of the client and server, the connection is terminated from both ends.

I do want to make a clarification regarding “[…] starts from ws:// or wss://”. We need to understand every WebSocket request starts as a HTTP or HTTPS request. If all succeeds, the same HTTP or HTTPS connection will be upgraded to a WebSocket connection.

WebSocket protocol needs at least HTTP/1.1

What is “ws://” and “wss://”

We just said that every WebSocket request starts as an HTTP or HTTPS request.

So:

“ws://” happens over HTTP.

“wss://” happens over HTTPS.

Understanding A WebSocket Request

WebSocket is not a protocol on its own. It is a protocol on top of HTTP protocol.

Actually, if you manually type in the Browser “ws://<whatever>” you will receive “ERR_UNKNOWN_URL_SCHEME”.

When you make a WebSocket request, it first makes a HTTP request with three special headers:

Now, the Server knows this request wants to be upgraded to WebSocket connection. If all goes well, it will do just that, and it will switch protocols (101) and upgrade this same connection to “ws://” of “wss://”.

In JavaScript you create a WebSocket connection with

let socket = new WebSocket(URL);

For example, if you browse to this site: WebSocket (javascript.info) open DevTools and locate the following SS

You can type in the console:

let socket = new WebSocket(“wss://javascript.info/article/websocket/chat/ws”);

As soon as you hit “enter” it will make the connection and then, you can send a message with

socket.send(message);

IIS As Reverse Proxy:

Need prerequisites!

IIS

As the aim of this article is to explain IIS as reverse proxy for WebSocket, I will revise IIS prerequisites here:

IIS — Site

Create a site in IIS called “ReverseProxy”. This site will act as a reverse proxy and forward traffic to “http://localhost:8080”, where our WebSocket server will be.

The site can be pointing to an empty folder, does not matter. Remember, this site will just act as a proxy.

The idea is to forward requests from “reverseproxy.com” or “localhost” to “localhost:8080”. Therefore, we need to add those bindings to our site:

To generate the certificate, you can run the next command in PS:

New-SelfSignedCertificate -DnsName “reverseproxy.com” -CertStoreLocation “cert:\LocalMachine\My”

For localhost you can use the “IIS Express Development Certificate”.

IIS-ARR

Now, go at Server level in IIS > ARR and ensure “Enable proxy” is checked.

This will make the whole URL available for us to rewrite.

Without ARR and “Enable proxy” we can only rewrite the path of a given URL.

Example:

Externally, client will see “http://something/car1/year”, but on the server it will go to “http://something/cars/brands/years/year/2022.html”

After “Enable proxy” we have access to the full URL, so we can completely rewrite it.

IIS — URL Rewrite

I want to create a rule as simple as possible.

We will rewrite every request to “reverseproxy.com” OR “localhost” to “http://localhost:8080”.

Go to URL Rewrite module at Server Level and create the following rule:

I have read multiple discussions that IIS does not handle well the “Sec-WebSocket-Extensions: permessage-deflate”, however I have not had any issues whatsoever. If you face that issue, rewrite that header with an empty value. I will add those articles below.

Code Section

Server.js:

Index.html:

Testing

Request to “https://localhost"

Client => “https://localhost” => ARR redirect to => “http://localhost:8080” => shows => “index.html” => open WebSocket connection to => “ws://localhost:8080”.

In Fiddler we can see the initial request, then it gets tunnel to “localhost:8080” and “index.html” creates a connection to “ws://localhost:8080”.

If we take a closer look at the WebSocket connection, we can clearly see how it was a GET request to “http://localhost:8080” with needed WebSocket headers that it got upgraded to a WebSocket connection.

HTML view:

In IIS Logs we will see a 200.

2022–09–29 16:15:32 ::1 GET / X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=9ed47fd8–70d1–493c-b843-fde938419d71&SERVER-STATUS=200 80 — ::1 HTTP/1.1 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/106.0.0.0+Safari/537.36–200 0 0 62

Should not IIS Log 101? We can clearly see 101 in Fiddler, and we know connection is being established as we are seeing the messages in the html.

This is because IIS never made a WebSocket connection. As far as it is concerned, it got a request, forwarded to “localhost:8080” and that gave back a html file.

Now, this html file will establish a WebSocket connection to “localhost:8080” (Node.js) and will start pinging every 20 seconds and appending/updating those messages into the html file.

The WebSocket conversation is between the html file, so to speak, and the WebSocket server. Messages are only being updated through the same connection into the html file.

Request to “http://reverseproxy".

Client => “http://reverseproxy.com” => ARR redirect to => “http://localhost:8080” => shows => “index.html” => open WebSocket connection to => “wss://reverseproxy.com”.

This “wss://reverseproxy.com” follows the following flow:

(Remember this next request has upgrade header)

“https://reverseproxy.com” => ARR redirect to => “http://localhost:8080” => acknowledge and accept request to switch protocol => WebSocket connection get established.

Fiddler:

Request and response headers of the WebSocket connection:

In FREB we will see two requests. The first one for the html file:

Client => http://reverseproxy.com => redirected to http://localhost:8080 => shows “index.html”

And then, the second request is for “wss://reverseproxy.com” that, by now we know that it will start as an “HTTPS” request.

HTTPS request to “https://reverseproxy.com" with upgrade to header => forwarded to “localhost:8080” => Server accepts it and switch protocol.

In IIS Logs we should see two requests:

2022–09–30 20:53:35 127.0.0.1 GET / X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=ad279eb5–5553–4609–8c40–4f529aee77f7&SERVER-STATUS=200 443–127.0.0.1 HTTP/2 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/105.0.0.0+Safari/537.36+Edg/105.0.1343.53–200 0 0 22

2022–09–30 20:53:41 127.0.0.1 GET / X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=a3614585-e908–40f2–9435-e3c23a2a6e31&SERVER-STATUS=101 443–127.0.0.1 HTTP/1.1 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/105.0.0.0+Safari/537.36+Edg/105.0.1343.53–101 0 64 5808

This time, we are using the same HTTPS connection that we passed through the IIS with upgrade header. As it passed through IIS, we will FREB and logs for 101 status codes.

In Summary:

Resources:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store