All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.websockets.next.runtime.WebSocketConnectorImpl Maven / Gradle / Ivy

There is a newer version: 3.17.2
Show newest version
package io.quarkus.websockets.next.runtime;

import java.lang.reflect.ParameterizedType;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Typed;
import jakarta.enterprise.inject.spi.InjectionPoint;

import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.arc.Arc;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.quarkus.websockets.next.WebSocketClientConnection;
import io.quarkus.websockets.next.WebSocketClientException;
import io.quarkus.websockets.next.WebSocketConnector;
import io.quarkus.websockets.next.WebSocketsClientRuntimeConfig;
import io.quarkus.websockets.next.runtime.WebSocketClientRecorder.ClientEndpoint;
import io.quarkus.websockets.next.runtime.WebSocketClientRecorder.ClientEndpointsContext;
import io.quarkus.websockets.next.runtime.telemetry.WebSocketTelemetryProvider;
import io.smallrye.mutiny.Uni;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.WebSocket;
import io.vertx.core.http.WebSocketClient;
import io.vertx.core.http.WebSocketConnectOptions;
import io.vertx.core.impl.ContextImpl;
import io.vertx.core.impl.VertxImpl;

@Typed(WebSocketConnector.class)
@Dependent
public class WebSocketConnectorImpl extends WebSocketConnectorBase>
        implements WebSocketConnector {

    // derived properties

    private final ClientEndpoint clientEndpoint;

    private final WebSocketTelemetryProvider telemetryProvider;

    WebSocketConnectorImpl(InjectionPoint injectionPoint, Codecs codecs, Vertx vertx, ClientConnectionManager connectionManager,
            ClientEndpointsContext endpointsContext, WebSocketsClientRuntimeConfig config,
            TlsConfigurationRegistry tlsConfigurationRegistry, Instance telemetryProvider) {
        super(vertx, codecs, connectionManager, config, tlsConfigurationRegistry);
        this.clientEndpoint = Objects.requireNonNull(endpointsContext.endpoint(getEndpointClass(injectionPoint)));
        this.telemetryProvider = telemetryProvider.isResolvable() ? telemetryProvider.get() : null;
        setPath(clientEndpoint.path);
    }

    @Override
    public Uni connect() {
        // A new client is created for each connection
        // The client is created when the returned Uni is subscribed
        // The client is closed when the connection is closed
        AtomicReference client = new AtomicReference<>();

        StringBuilder serverEndpoint = new StringBuilder();
        if (baseUri != null) {
            serverEndpoint.append(baseUri.toString());
        } else {
            // Obtain the base URI from the config
            String key = clientEndpoint.clientId + ".base-uri";
            Optional maybeBaseUri = ConfigProvider.getConfig().getOptionalValue(key, String.class);
            if (maybeBaseUri.isEmpty()) {
                throw new WebSocketClientException("Unable to obtain the config value for: " + key);
            }
            serverEndpoint.append(maybeBaseUri.get());
        }
        serverEndpoint.append(replacePathParameters(clientEndpoint.path));

        URI serverEndpointUri;
        try {
            serverEndpointUri = new URI(serverEndpoint.toString());
        } catch (URISyntaxException e) {
            throw new WebSocketClientException(e);
        }

        WebSocketConnectOptions connectOptions = newConnectOptions(serverEndpointUri);
        StringBuilder uri = new StringBuilder();
        if (serverEndpointUri.getPath() != null) {
            uri.append(serverEndpointUri.getRawPath());
        }
        if (serverEndpointUri.getQuery() != null) {
            uri.append("?").append(serverEndpointUri.getQuery());
        }
        connectOptions.setURI(uri.toString());
        for (Entry> e : headers.entrySet()) {
            for (String val : e.getValue()) {
                connectOptions.addHeader(e.getKey(), val);
            }
        }
        subprotocols.forEach(connectOptions::addSubProtocol);

        var telemetrySupport = telemetryProvider == null ? null
                : telemetryProvider.createClientTelemetrySupport(clientEndpoint.path);
        Uni websocket = Uni.createFrom(). emitter(e -> {
            // Create a new event loop context for each client, otherwise the current context is used
            // We want to avoid a situation where if multiple clients/connections are created in a row,
            // the same event loop is used and so writing/receiving messages is de-facto serialized
            // Get rid of this workaround once https://github.com/eclipse-vertx/vert.x/issues/5366 is resolved
            ContextImpl context = ((VertxImpl) vertx).createEventLoopContext();
            context.dispatch(new Handler() {
                @Override
                public void handle(Void event) {
                    WebSocketClient c = vertx.createWebSocketClient(populateClientOptions());
                    client.setPlain(c);
                    if (telemetrySupport != null && telemetrySupport.interceptConnection()) {
                        telemetrySupport.connectionOpened();
                    }
                    c.connect(connectOptions, new Handler>() {
                        @Override
                        public void handle(AsyncResult r) {
                            if (r.succeeded()) {
                                e.complete(r.result());
                            } else {
                                if (telemetrySupport != null && telemetrySupport.interceptConnection()) {
                                    telemetrySupport.connectionOpeningFailed(r.cause());
                                }
                                e.fail(r.cause());
                            }
                        }
                    });
                }
            });
        });
        return websocket.map(ws -> {
            TrafficLogger trafficLogger = TrafficLogger.forClient(config);
            WebSocketClientConnectionImpl connection = new WebSocketClientConnectionImpl(clientEndpoint.clientId, ws,
                    codecs,
                    pathParams,
                    serverEndpointUri, headers, trafficLogger);
            if (trafficLogger != null) {
                trafficLogger.connectionOpened(connection);
            }
            connectionManager.add(clientEndpoint.generatedEndpointClass, connection);

            Endpoints.initialize(vertx, Arc.container(), codecs, connection, ws,
                    clientEndpoint.generatedEndpointClass, config.autoPingInterval(), SecuritySupport.NOOP,
                    config.unhandledFailureStrategy(), trafficLogger,
                    () -> {
                        connectionManager.remove(clientEndpoint.generatedEndpointClass, connection);
                        client.get().close();
                    }, true, true, telemetrySupport);

            return connection;
        });
    }

    String getEndpointClass(InjectionPoint injectionPoint) {
        // The type is validated during build - if it does not represent a client endpoint the build fails
        // WebSocketConnectorImpl -> org.acme.Foo
        ParameterizedType parameterizedType = (ParameterizedType) injectionPoint.getType();
        return parameterizedType.getActualTypeArguments()[0].getTypeName();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy