Published on Jun 5 2023 in Tomcat Java

This article discusses the usage of websockets in a Tomcat server environment, particularly in the context of proxying requests through Apache webserver. It explores the challenges faced when using the AJP connector of Tomcat, which does not support the websockets protocol, and presents a solution using the mod_proxy_wstunnel module in Apache. The article also covers the configuration required for using websockets over HTTPS (wss) and provides step-by-step instructions for implementing the necessary changes in the Apache configuration. Additionally, alternative approaches for proxying HTTPS to wss are discussed.

Websockets are a communication protocol that enables real-time, full-duplex communication between a client and a server. Unlike traditional HTTP, which follows a request-response model, websockets allow for bidirectional, continuous communication where both the client and server can initiate data transfer. This makes websockets ideal for applications that require instant updates or real-time collaboration.

To resolve issues related to proxying HTTPS to wss, we suggest two possible solutions. The first solution involves using the respective HTTP and HTTPS ports of Tomcat or Glassfish in the wss:// or ws:// URLs, thereby bypassing Apache proxying altogether. The second solution proposes using the mod_proxy_wstunnel module in Apache frontend to accept websocket requests. The article provides the necessary rewrite rules and configuration changes to implement this approach, covering both the HTTP and HTTPS versions.

When working on localhost, accessing Tomcat is straightforward, as it is usually available on its default HTTP port 8080 (or HTTPS port 8443). Websockets work seamlessly in this scenario without any issues. However, when deploying an application in a production environment on a shared host, it is common to use a custom port assigned to Tomcat. In such cases, you may want to omit the port number from the URL, which requires the use of proxying.

The challenge arises because the AJP connector in Tomcat does not support the websockets protocol. To overcome this limitation, you need to utilize another proxy module. In the case of the Apache webserver, the recommended module to use is mod_proxy_wstunnel. This module enables the forwarding of ws:// or wss:// requests to Tomcat’s HTTP or HTTPS connectors, respectively, allowing websockets to function properly.

Let’s consider an example using websockets code provided with Tomcat located at /examples/websocket/echo.xhtml. If you are working on a shared host using proxying to connect to Tomcat with the AJP connector, attempting to establish a websocket connection using the ws://.../examples/websocket/echoProgrammatic URL will result in a ‘Info: WebSocket connection closed, Code: 1006’ error. However, if you use Tomcat’s HTTP port in the URL and connect to ws://...:HTTPPORT/examples/websocket/echoProgrammatic, you will see the message ‘Info: WebSocket connection opened,’ and the message echoing functionality will work as expected.

In cases where you want to use HTTPS (and subsequently wss), it is essential to ensure that a valid certificate is used both on Tomcat’s HTTPS port and the Apache frontend HTTPS port. This step is necessary, especially if you wish to remove the custom port from the URL. By installing the SSL certificate and key in Tomcat’s keystore and enabling the appropriate connector, such as org.apache.coyote.http11.Http11NioProtocol for Java KeyStore (JKS) certificates or org.apache.coyote.http11.Http11AprProtocol for PEM certificates with APR/native library (e.g. with CATALINA_OPTS=-Djava.library.path=/opt/native/...), you can establish secure wss connections.

If you have the same certificate/key installed for your domain in the Apache frontend and you access the portless and proxied URL like https://.../examples/websocket/echo.xhtml, you will get the ‘Info: WebSocket connection closed, Code: 1006’ again. This is because the request has not been proxied via the ws tunnel and generic HTTP connector does not understand websockets.

The first solution is to modify your wss:// or ws:// URLs to use the respective HTTP and HTTPS ports of Tomcat or Glassfish, effectively bypassing Apache proxying. This straightforward solution ensures that the websockets communication is established directly with Tomcat that understands websockets.

Alternatively, you can use the mod_proxy_wstunnel module in the Apache frontend to accept websocket requests. By adding three lines of code before your existing proxy setup, you can configure Apache to handle websocket connections correctly. The provided code snippet includes the necessary rewrite rules and conditions to identify websocket requests and proxy them to the appropriate ws:// endpoint. The existing proxy setup can continue using AJP, HTTP or HTTPS connectors.

RewriteEngine on
RewriteCond %{HTTP:Upgrade} "(?i)websocket"
RewriteCond %{HTTP:Connection} "(?i)upgrade"
RewriteRule ^(.*)$ ws://127.0.0.1:HTTPPORT/$1 [P]

The existing proxy setup can use AJP, HTTP or HTTPS. For example:

ProxyPass /examples ajp://127.0.0.1:AJPPORT/examples retry=5
ProxyPassReverse /examples ajp://127.0.0.1:AJPPORT/examples

This way websocket requests to /examples/ will be proxied to ws:// endpoint while other requests will go to typical ajp://, http:// or https:// endpoints (for https:// you will need to replace 127.0.0.1 with a valid and SSL-secured domain).

To enable proxying from https:// to wss://, you need to make a few changes to the code snippet mentioned earlier. These changes include enabling the SSLProxyEngine, modifying the rewrite rule to use the real domain where the SSL certificate is installed, and specifying the HTTPS port of Tomcat.

SSLProxyEngine on
RewriteEngine on
RewriteCond %{HTTP:Upgrade} "(?i)websocket"
RewriteCond %{HTTP:Connection} "(?i)upgrade"
RewriteRule ^/examples/(.*)$ wss://REAL.SSLED.DOMAIN:HTTPSPORT/examples/$1 [P]

Alternative approach for the last line: RewriteRule .* wss://REAL.SSLED.DOMAIN:HTTPSPORT%{REQUEST_URI} [P]

These custom proxy settings can be made for you on request by support staff or you may be able to set them up using a Java Control Panel provided by your host.

It is important to note that rewrite rules are processed before proxy rules, so the order may be different (proxy rules before rewrite rules) and still valid. Additionally, ensure that Rewrite Engine on and ProxyPreserveHost On directives are globally enabled for the configuration to work correctly. In Apache 2.4, ProxyRequests is set to Off by default, which is the recommended setting for security purposes.

By following these proposed solutions, you can overcome the challenges associated with using websockets in a Tomcat or Glassfish server environment with proxying. Whether you choose to directly connect to Tomcat’s ports or utilize the mod_proxy_wstunnel module in Apache, you can establish and maintain websocket connections seamlessly.

In conclusion, websockets provide a powerful mechanism for real-time communication between clients and servers. However, when using Tomcat with proxying, particularly with the AJP connector, additional considerations and configuration adjustments are required to ensure proper handling of websocket connections. By understanding the limitations of the AJP connector and leveraging the mod_proxy_wstunnel module in Apache, you can successfully enable websockets and facilitate smooth communication within your application.