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

Issue 5416: Transfer configuration to connection providers. #5682

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,216 +16,62 @@

package io.helidon.microprofile.tyrus;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import io.helidon.common.Weight;
import io.helidon.common.Weighted;
import io.helidon.common.buffers.BufferData;
import io.helidon.common.buffers.DataWriter;
import io.helidon.common.http.DirectHandler;
import io.helidon.common.http.Http;
import io.helidon.common.http.HttpPrologue;
import io.helidon.common.http.RequestException;
import io.helidon.common.http.WritableHeaders;
import io.helidon.common.uri.UriQuery;
import io.helidon.nima.webserver.ConnectionContext;
import io.helidon.nima.webserver.spi.ServerConnection;
import io.helidon.nima.webserver.http1.Http1Upgrader;
import io.helidon.nima.websocket.webserver.WsUpgradeProvider;

import jakarta.enterprise.inject.spi.CDI;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.Extension;
import jakarta.websocket.server.ServerEndpointConfig;
import org.glassfish.tyrus.core.RequestContext;
import org.glassfish.tyrus.core.TyrusUpgradeResponse;
import org.glassfish.tyrus.core.TyrusWebSocketEngine;
import org.glassfish.tyrus.server.TyrusServerContainer;
import org.glassfish.tyrus.spi.WebSocketEngine;

/**
* Tyrus connection upgrade provider.
* {@link java.util.ServiceLoader} provider implementation for upgrade from HTTP/1.1 to Tyrus connection.
*/
@Weight(Weighted.DEFAULT_WEIGHT + 100) // higher than base class
public class TyrusUpgradeProvider extends WsUpgradeProvider {
private static final Logger LOGGER = Logger.getLogger(TyrusUpgradeProvider.class.getName());

private String path;
private String queryString;
private final TyrusRouting tyrusRouting;
private final WebSocketEngine engine;

/**
* @deprecated This constructor is only to be used by {@link java.util.ServiceLoader}.
* @deprecated This constructor is only to be used by {@link java.util.ServiceLoader}, use {@link #builder()}
*/
@Deprecated()
public TyrusUpgradeProvider() {
TyrusCdiExtension extension = CDI.current().select(TyrusCdiExtension.class).get();
Objects.requireNonNull(extension);
this.tyrusRouting = extension.tyrusRouting();
TyrusServerContainer tyrusServerContainer = initializeTyrus();
this.engine = tyrusServerContainer.getWebSocketEngine();
this(new HashSet<>());
}

@Override
public ServerConnection upgrade(ConnectionContext ctx, HttpPrologue prologue, WritableHeaders<?> headers) {
// Check required header
String wsKey;
if (headers.contains(WS_KEY)) {
wsKey = headers.get(WS_KEY).value();
} else {
// this header is required
return null;
}

// Verify protocol version
String version;
if (headers.contains(WS_VERSION)) {
version = headers.get(WS_VERSION).value();
} else {
version = SUPPORTED_VERSION;
}
if (!SUPPORTED_VERSION.equals(version)) {
throw RequestException.builder()
.type(DirectHandler.EventType.BAD_REQUEST)
.message("Unsupported WebSocket Version")
.header(SUPPORTED_VERSION_HEADER)
.build();
}

// Initialize path and queryString
path = prologue.uriPath().path();
int k = path.indexOf('?');
if (k > 0) {
this.path = path.substring(0, k);
this.queryString = path.substring(k + 1);
} else {
this.queryString = "";
}

// Check if this a Tyrus route exists
TyrusRoute route = ctx.router()
.routing(TyrusRouting.class, tyrusRouting)
.findRoute(prologue);
if (route == null) {
return null;
}

// Validate origin
if (!anyOrigin()) {
if (headers.contains(Http.Header.ORIGIN)) {
String origin = headers.get(Http.Header.ORIGIN).value();
if (!origins().contains(origin)) {
throw RequestException.builder()
.message("Invalid Origin")
.type(DirectHandler.EventType.FORBIDDEN)
.build();
}
}
}

// Protocol handshake with Tyrus
WebSocketEngine.UpgradeInfo upgradeInfo = protocolHandshake(headers);

// todo support subprotocols (must be provided by route)
// Sec-WebSocket-Protocol: sub-protocol (list provided in PROTOCOL header, separated by comma space
DataWriter dataWriter = ctx.dataWriter();
String switchingProtocols = SWITCHING_PROTOCOL_PREFIX + hash(ctx, wsKey) + SWITCHING_PROTOCOLS_SUFFIX;
dataWriter.write(BufferData.create(switchingProtocols.getBytes(StandardCharsets.US_ASCII)));

if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Upgraded to websocket version " + version);
}
return new TyrusConnection(ctx, upgradeInfo);
TyrusUpgradeProvider(Set<String> origins) {
super(origins);
}

TyrusServerContainer initializeTyrus() {
Set<Class<?>> allEndpointClasses = tyrusRouting.routes()
.stream()
.map(TyrusRoute::endpointClass)
.collect(Collectors.toSet());

TyrusServerContainer tyrusServerContainer = new TyrusServerContainer(allEndpointClasses) {
private final WebSocketEngine engine =
TyrusWebSocketEngine.builder(this).build();

@Override
public void register(Class<?> endpointClass) {
throw new UnsupportedOperationException("Cannot register endpoint class");
}

@Override
public void register(ServerEndpointConfig serverEndpointConfig) {
throw new UnsupportedOperationException("Cannot register ServerEndpointConfig");
}

@Override
public Set<Extension> getInstalledExtensions() {
return tyrusRouting.extensions();
}

@Override
public WebSocketEngine getWebSocketEngine() {
return engine;
}
};
@Override
public Http1Upgrader create() {
return new TyrusUpgrader(Set.copyOf(origins()));
}

// Register classes with context path "/"
WebSocketEngine engine = tyrusServerContainer.getWebSocketEngine();
tyrusRouting.routes().forEach(route -> {
try {
if (route.serverEndpointConfig() != null) {
LOGGER.log(Level.FINE, () -> "Registering ws endpoint "
+ route.path()
+ route.serverEndpointConfig().getPath());
engine.register(route.serverEndpointConfig(), route.path());
} else {
LOGGER.log(Level.FINE, () -> "Registering annotated ws endpoint " + route.path());
engine.register(route.endpointClass(), route.path());
}
} catch (DeploymentException e) {
throw new RuntimeException(e);
}
});
// jUnit test accessor for origins set (package private only)
protected Set<String> origins() {
return super.origins();
}

return tyrusServerContainer;
/**
* New builder.
*
* @return builder
*/
public static Builder tyrusBuilder() {
return new Builder();
}

WebSocketEngine.UpgradeInfo protocolHandshake(WritableHeaders<?> headers) {
LOGGER.log(Level.FINE, "Initiating WebSocket handshake with Tyrus...");
/**
* Fluent API builder for {@link TyrusUpgradeProvider}.
*/
public static final class Builder
extends WsUpgradeProvider.AbstractBuilder<TyrusUpgradeProvider.Builder, TyrusUpgradeProvider> {

// Create Tyrus request context, copy request headers and query params
Map<String, String[]> paramsMap = new HashMap<>();
UriQuery uriQuery = UriQuery.create(queryString);
for (String name : uriQuery.names()) {
paramsMap.put(name, uriQuery.all(name).toArray(new String[0]));
private Builder() {
}
RequestContext requestContext = RequestContext.Builder.create()
.requestURI(URI.create(path)) // excludes context path
.queryString(queryString)
.parameterMap(paramsMap)
.build();
headers.forEach(e -> requestContext.getHeaders().put(e.name(), List.of(e.values())));

// Use Tyrus to process a WebSocket upgrade request
final TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse();
final WebSocketEngine.UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse);
@Override
public TyrusUpgradeProvider build() {
return new TyrusUpgradeProvider(origins());
}

// Map Tyrus response headers back to Nima
upgradeResponse.getHeaders()
.forEach((key, value) -> headers.add(
Http.Header.create(
Http.Header.createName(key, key.toLowerCase(Locale.ROOT)),
value)));
return upgradeInfo;
}

}
Loading