Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix session affinity management (stickiness) in wildfly-http-client #108

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static org.jboss.marshalling.ClassNameTransformer.JAVAEE_TO_JAKARTAEE;
import static org.wildfly.httpclient.common.HttpMarshallerFactory.DEFAULT_FACTORY;
import static org.wildfly.httpclient.common.Protocol.VERSION_ONE_PATH;
import static org.wildfly.httpclient.common.Protocol.VERSION_PATH;
import static org.wildfly.httpclient.common.Protocol.VERSION_TWO_PATH;

/**
Expand Down Expand Up @@ -86,7 +87,12 @@ private EENamespaceInteroperability() {}

/**
* Wraps the HTTP server handler into an EE namespace interoperable handler. Such handler implements the
* EE namespace interoperability at the server side before delegating to the wrapped {@code httpHandler}
* EE namespace interoperability at the server side before delegating to the wrapped {@code httpHandler}.
* The resulting handler handles the EE namespace interoperability according to the value of {@link
* #EE_NAMESPACE_INTEROPERABLE_MODE}. It accepts {@code javax} namespace requests at the path prefix {@code
* "/v1"}, while {@code jakarta} namespace requests are received at the path prefix {@code "/v2"}. Both
* requests are forwarded to {@code handler}, but in case of {@code "/v1"} the {@code javax} namespace is
* converted to {@code jakarta}.
*
* @param httpHandler the handler to be wrapped
* @return handler the ee namespace interoperability handler
Expand All @@ -95,13 +101,46 @@ static HttpHandler createInteroperabilityHandler(HttpHandler httpHandler) {
return createProtocolVersionHttpHandler(new EENamespaceInteroperabilityHandler(httpHandler), new JakartaNamespaceHandler(httpHandler));
}

static HttpHandler createProtocolVersionHttpHandler(HttpHandler interoperabilityHandler, HttpHandler latestProtocolHandler) {
/**
* Wraps the multi-versioned HTTP server handlers into an EE namespace interoperable handler. Such handler
* implements the EE namespace interoperability at the server side before delegating to the wrapped HTTP
* handler. The resulting handler handles the EE namespace interoperability according to the value of
* {@link #EE_NAMESPACE_INTEROPERABLE_MODE}. It accepts {@code javax} namespace requests at the path prefix
* {@code "/v1"}, while {@code jakarta} namespace requests are received at the subsequent path prefixes
* {@code "/v2"}, {@code "/v3"}, and so on. Requests to {@code "/v1"} and {@code "/v2"} path prefixes will be
* forwarded to {@code multiVersionedProtocolHandlers[0]}, while {@code "/v3"} will be forwarded to {@code
* multiVersionedProtocolHandlers[1]}. The sequence of paths follows until each multi-versioned handler
* {@code multiVersionedProtocolHandlers[N]} is associated with a prefix path {@code "/v<N+2>"}.
*
* @param multiVersionedProtocolHandlers the multiple handlers to be wrapped.
* @return handler the ee namespace interoperability handler
*/
static HttpHandler createInteroperabilityHandler(HttpHandler... multiVersionedProtocolHandlers) {
assert multiVersionedProtocolHandlers.length > 0;
HttpHandler[] versionedJakartaNamespaceHandlers = new HttpHandler[multiVersionedProtocolHandlers.length];
for (int i = 0; i < multiVersionedProtocolHandlers.length; i++) {
versionedJakartaNamespaceHandlers[i] = new JakartaNamespaceHandler(multiVersionedProtocolHandlers[i]);
}
return createProtocolVersionHttpHandler(new EENamespaceInteroperabilityHandler(multiVersionedProtocolHandlers[0]), versionedJakartaNamespaceHandlers);
}

private static HttpHandler createProtocolVersionHttpHandler(HttpHandler interoperabilityHandler, HttpHandler latestProtocolHandler) {
final PathHandler versionPathHandler = new PathHandler();
versionPathHandler.addPrefixPath(VERSION_ONE_PATH, interoperabilityHandler);
versionPathHandler.addPrefixPath(VERSION_TWO_PATH, latestProtocolHandler);
return versionPathHandler;
}

private static HttpHandler createProtocolVersionHttpHandler(HttpHandler interoperabilityHandler, HttpHandler... versionedProtocolHandlers) {
final PathHandler versionPathHandler = new PathHandler();
versionPathHandler.addPrefixPath(VERSION_ONE_PATH, interoperabilityHandler);
int version = 2;
for (HttpHandler versionedProtocolHandler: versionedProtocolHandlers) {
versionPathHandler.addPrefixPath(VERSION_PATH + version++, versionedProtocolHandler);
}
return versionPathHandler;
}

/**
* Returns the HTTPMarshallerFactoryProvider instance responsible for taking care of marshalling
* and unmarshalling according to the values negotiated by the ee namespace interoperability headers.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.wildfly.httpclient.common;

/*
* Versioning enum for HttpHandler implementations.
*
* TODO: due to the way EENamespaceInteroperability.createInteroperabilityHandler(HttpHandler...) works,
* the original protocol versions JAVAEE_PROTOCOL_VERSION and JAKARTA_PROTOCOL_VERSION need to share
* the same handler instance, so it was not possible to match protocol handler indexes to protocol versions
* in a 1-to-1 manner. In order to avoid a confusing protocol version to handler version mismatch, they share
* the handler installed at index 2.
* TODO: integrate this with the Protocol class in a nice way.
*
* @author Richard Achmatowicz
*/
public enum HandlerVersion {
EARLIEST(2),
VERSION_1(2),
VERSION_2(2),
LATEST(3)
;
private final int version;

HandlerVersion(int version) {
this.version = version;
}

public int getVersion() {
return version;
}

public boolean since(HandlerVersion version) {
return this.version >= version.version;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,6 @@ interface HttpClientMessages extends BasicLogger {
@Message(id = 14, value = "JavaEE to JakartaEE backward compatibility layer have been installed")
void javaeeToJakartaeeBackwardCompatibilityLayerInstalled();

@Message(id = 15, value = "Failed to acquire backend server")
RuntimeException failedToAcquireBackendServer(@Cause Throwable e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,11 @@ private void runPending() {
try {

final SSLContext context = sslContext;
System.out.println("HttpConnectionPool: use UndertowClient to create connection, thread = " + Thread.currentThread().getName());
UndertowClient.getInstance().connect(new ClientCallback<ClientConnection>() {
@Override
public void completed(ClientConnection result) {
System.out.println("HttpConnectionPool: use connection to process handler, thread = " + Thread.currentThread().getName());
result.getCloseSetter().set((ChannelListener<ClientConnection>) connections::remove);
ClientConnectionHolder clientConnectionHolder = createClientConnectionHolder(result, hostPoolAddress.getURI(), context);
clientConnectionHolder.tryAcquire(); //aways suceeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;

import java.util.function.Function;

/**
* Mode configuration for http services.
* <p>
Expand All @@ -30,42 +28,72 @@
*
* @author Flavia Rainone
*/
public enum HttpServiceConfig {
public class HttpServiceConfig {

/**
* Default configuration. Used by both EE namespace interoperable and non-interoperable servers
*/
DEFAULT (EENamespaceInteroperability::createInteroperabilityHandler, EENamespaceInteroperability.getHttpMarshallerFactoryProvider());
private static final HttpServiceConfig INSTANCE = new HttpServiceConfig (EENamespaceInteroperability.getHttpMarshallerFactoryProvider());

/**
* Returns the default configuration.
*
* @return the configuration for http services
*/
public static HttpServiceConfig getInstance() {
return DEFAULT;
return INSTANCE;
}

private final Function<HttpHandler, HttpHandler> handlerWrapper;
private final HttpMarshallerFactoryProvider marshallerFactoryProvider;

HttpServiceConfig(Function<HttpHandler, HttpHandler> handlerWrapper, HttpMarshallerFactoryProvider marshallerFactoryProvider) {
this.handlerWrapper = handlerWrapper;
HttpServiceConfig(HttpMarshallerFactoryProvider marshallerFactoryProvider) {
this.marshallerFactoryProvider = marshallerFactoryProvider;
}

/**
* Wraps the http service handler. Should be applied to all http handlers configured by
* a http service.
* <br>
* The resulting handler is compatible with EE namespace interoperability and accepts
* {@code javax} namespace requests at the path prefix {@code "/v1"}, while {@code jakarta}
* namespace requests are received at the path prefix {@code "/v2"}. Both requests are
* forwarded to {@code handler}, but in case of {@code "/v1"} the {@code javax} namespace
* is converted to {@code jakarta}.
*
* @param handler responsible for handling the HTTP service requests directed to a specific
* URI
* URI. This handler must operate on {@code jakarta} namespace.
* @return the HttpHandler that should be provided to Undertow and associated with the HTTP
* service URI. The resulting handler is a wrapper that will add any necessary actions
* before invoking the inner {@code handler}.
*/
public HttpHandler wrap(HttpHandler handler) {
return handlerWrapper.apply(handler);
return EENamespaceInteroperability.createInteroperabilityHandler(handler);
}

/**
* Wraps a multi-version series of handlers. Each handler represents a version of the same operation
* provided by a HTTP service.
* <br>
* The resulting handler receives {@code javax} namespace requests at the path prefix {@code "/v1"},
* translates them to {@code jakarta namespace} and forwards them to {@code multiVersionedHandlers[0]}.
* The subsequent handlers in the {@code multiVersionedHandlers} array are mapped to path {@code "/v2"},
* {@code "/v3"} and so on.
* <br>
* Use this method when the http service supports more than one version of an HTTP Handler. This will be
* the case as http handlers evolve to incorporate new features and fixes that change the particular
* protocol format used by the HTTP handler for the specific operation it represents.
*
* @param multiVersionedHandlers responsible for handling the HTTP service requests directed to a specific
* URI. The handlers must be in crescent protocol number order, i.e., in the
* sequence corresponding to {@code "/v2"}, {@code "/v3}, {@code "/v4"}. All
* the handlers must be compatible with requests in the Jakarta namespace.
*
* @return the HttpHandler that should be provided to Undertow and associated with the HTTP
* service URI. The resulting handler is a wrapper that will take care of protocol
* versioning to invoke the appropriate handler
*/
public HttpHandler wrap(HttpHandler... multiVersionedHandlers) {
return EENamespaceInteroperability.createInteroperabilityHandler(multiVersionedHandlers);
}

/**
Expand Down
Loading