Skip to content

Commit

Permalink
Fix WebSocket not working for default web module applications.
Browse files Browse the repository at this point in the history
Signed-off-by: Arjan Tijms <arjan.tijms@omnifish.ee>
  • Loading branch information
arjantijms committed Nov 29, 2023
1 parent f0c6f77 commit 8b2086e
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 2 deletions.
Expand Up @@ -18,6 +18,7 @@

package org.apache.catalina.core;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterRegistration;
import jakarta.servlet.RequestDispatcher;
Expand All @@ -36,6 +37,8 @@
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Map;
Expand Down Expand Up @@ -404,11 +407,88 @@ public FilterRegistration.Dynamic addFilter(String filterName, String className)

@Override
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {

// Add a wrapper to the WebSocket (Tyrus) filter that corrects the URI
// for HTTP upgrade requests when running on the root context.
// Tyrus expects the URI to include the full context path (the application name).
final Filter wrappedFilter;
if ("WebSocket filter".equals(filterName)) {
wrappedFilter = new WebSocketFilterWrapper(filter);
} else {
wrappedFilter = filter;
}

if (IS_SECURITY_ENABLED) {
PrivilegedAction<FilterRegistration.Dynamic> action = () -> context.addFilter(filterName, filter);
PrivilegedAction<FilterRegistration.Dynamic> action = () -> context.addFilter(filterName, wrappedFilter);
return AccessController.doPrivileged(action);
}
return context.addFilter(filterName, filter);

FilterRegistration.Dynamic registration = context.addFilter(filterName, wrappedFilter);

if (registration == null && "WebSocket filter".equals(filterName)) {
// Dummy registration to counter ordering issue between Mojarra
// and Tyrus.
// Should eventually be fixed in those projects.
registration = new FilterRegistration.Dynamic() {

@Override
public void setAsyncSupported(boolean isAsyncSupported) {
}

@Override
public Set<String> setInitParameters(Map<String, String> initParameters) {
return null;
}

@Override
public boolean setInitParameter(String name, String value) {
return false;
}

@Override
public String getName() {
return null;
}

@Override
public Map<String, String> getInitParameters() {
return null;
}

@Override
public String getInitParameter(String name) {
return null;
}

@Override
public String getClassName() {
return null;
}

@Override
public Collection<String> getUrlPatternMappings() {
return null;
}

@Override
public Collection<String> getServletNameMappings() {
return null;
}

@Override
public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {

}

@Override
public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames) {
}
}; {

}
}

return registration;
}


Expand Down
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.core;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import org.apache.catalina.connector.RequestFacade;

/**
* This class is a wrapper for the WebSocket Filter from Tyrus.
*
* <p>
* It corrects the URI to include the context path without "maskDefaultContextMapping" set.
* This is the URI on which Tyrus expects to find a WebSocket. Without setting this, the
* WebSocket will not be found when accessing the application via the default web module
* URL (the context root).
*
* <p>
* E.g. without this correction, for application "foo", "http://localhost:8080/foo" would work, but
* "http://localhost:8080" would not when set to a default web module via a command like:
*
* {@code asadmin set server-config.http-service.virtual-server.server.default-web-module=foo}
*
* @author Arjan Tijms
* @author Ondro Mihalyi
*
*/
public class WebSocketFilterWrapper implements Filter {

private static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";

private final Filter webSocketFilter;

public WebSocketFilterWrapper(Filter webSocketFilter) {
this.webSocketFilter = webSocketFilter;
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
webSocketFilter.init(filterConfig);
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;

if (httpServletRequest.getHeader(SEC_WEBSOCKET_KEY) != null) {
httpServletRequest = new HttpServletRequestWrapper(httpServletRequest) {

@Override
public String getRequestURI() {
RequestFacade wrappedRequest = (RequestFacade) super.getRequest();;
String requestURI = wrappedRequest.getRequestURI();

// Get the contextPath without masking the default context mapping.
String contextPath = wrappedRequest.getContextPath(false);

if (requestURI.equals(contextPath) || requestURI.startsWith(contextPath + "/")) {
return requestURI;
}

return contextPath + requestURI;
}
};
}

webSocketFilter.doFilter(httpServletRequest, response, chain);
}

@Override
public void destroy() {
webSocketFilter.destroy();
}

}

0 comments on commit 8b2086e

Please sign in to comment.