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

net.openhft.chronicle.wire.channel.ChronicleGatewayMain Maven / Gradle / Ivy

/*
 * Copyright 2016-2022 chronicle.software
 *
 *       https://chronicle.software
 *
 * 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 net.openhft.chronicle.wire.channel;

import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.io.ClosedIORuntimeException;
import net.openhft.chronicle.core.io.InvalidMarshallableException;
import net.openhft.chronicle.threads.NamedThreadFactory;
import net.openhft.chronicle.threads.PauserMode;
import net.openhft.chronicle.wire.Comment;
import net.openhft.chronicle.wire.Marshallable;
import net.openhft.chronicle.wire.channel.impl.*;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * The {@code ChronicleGatewayMain} class represents the primary access point for the Chronicle Gateway.
 * This gateway is pivotal in processing incoming connections and effectively handling requests based on a predetermined protocol.
 * 

* By extending {@link ChronicleContext} and implementing {@link Closeable} and {@link Runnable}, instances of this class * have the ability to manage their lifecycle autonomously and can operate in individual threads. Configuration of the Gateway * can be done utilizing system properties. */ @SuppressWarnings("deprecation") public class ChronicleGatewayMain extends ChronicleContext implements Closeable, Runnable { // Default port for the gateway public static final int PORT = Integer.getInteger("port", 1248); // Determines the pausing strategy for the gateway private static final PauserMode PAUSER_MODE = PauserMode.valueOf( System.getProperty("pauserMode", PauserMode.balanced.name())); // Flag to determine if affinity should be used private static final boolean USE_AFFINITY = Jvm.getBoolean("useAffinity"); // Server socket channel to handle network communications transient ServerSocketChannel ssc; // Thread for executing the gateway transient Thread thread; // Determines the pausing strategy for buffered channels @Comment("PauserMode to use in buffered channels") PauserMode pauserMode = PauserMode.balanced; // Buffering state for the handler @Comment("Default buffering configuration if not set by the Handler") private boolean buffered = false; // Service to manage thread execution private ExecutorService service; /** * Constructs a new ChronicleGatewayMain instance with a specific URL. * The gateway will use the default system context and a new socket registry. * * @param url the URL for the Gateway * @throws InvalidMarshallableException if there's an issue while creating the gateway */ @SuppressWarnings("this-escape") public ChronicleGatewayMain(String url) throws InvalidMarshallableException { this(url, new SocketRegistry(), SystemContext.INSTANCE); addCloseable(socketRegistry()); } /** * Constructs a new ChronicleGatewayMain instance with a specific URL, * socket registry, and system context. * * @param url the URL for the Gateway * @param socketRegistry the SocketRegistry used by the gateway for managing socket connections * @param systemContext the SystemContext that defines system-level configuration for the gateway * @throws InvalidMarshallableException if there's an issue while creating the gateway */ @SuppressWarnings("this-escape") public ChronicleGatewayMain(String url, SocketRegistry socketRegistry, SystemContext systemContext) throws InvalidMarshallableException { super(url, socketRegistry); this.systemContext(systemContext); } /** * The main method acts as the entry point to start the ChronicleGatewayMain. * It creates a new instance of the class and runs it. * The URL for the gateway can be optionally specified as a command-line argument. * * @param args Command line arguments. If the first argument is provided, it will be used as the URL for the gateway. * @throws IOException if there is an I/O issue while starting the gateway * @throws InvalidMarshallableException if there's an issue while creating the gateway */ public static void main(String... args) throws IOException, InvalidMarshallableException { main(ChronicleGatewayMain.class, ChronicleGatewayMain::new, args.length == 0 ? "" : args[0]).run(); } /** * Generates an instance of the {@code ChronicleGatewayMain} class based on the supplied configuration. * * @param Represents the subclass type of {@code ChronicleGatewayMain}. * @param mainClass The main class type for creating an instance. * @param supplier Provides an instance of the main class based on the configuration string. * @param config The configuration string used for creating the main instance. * @return An instance of the {@code ChronicleGatewayMain} class, created and configured based on the provided arguments. * @throws IOException If there's a problem reading the configuration. */ protected static ChronicleGatewayMain main(Class mainClass, Function supplier, String config) throws IOException { ChronicleGatewayMain main; if (config.isEmpty()) { // Default configuration setup for the gateway ChronicleGatewayMain chronicleGatewayMain = supplier.apply("tcp://localhost:" + PORT) .pauserMode(PAUSER_MODE) .buffered(Jvm.getBoolean("buffered")); chronicleGatewayMain.useAffinity(USE_AFFINITY); chronicleGatewayMain.pauserMode = PAUSER_MODE; main = chronicleGatewayMain; } else { // Load the gateway configuration from the specified file main = Marshallable.fromFile(mainClass, config); } return main; } /** * Sets the PauserMode to control the balance between CPU usage and minimising latency * * @param pauserMode The pauser mode to be set. * @return The current instance of {@code ChronicleGatewayMain} for chained calls. */ public ChronicleGatewayMain pauserMode(PauserMode pauserMode) { this.pauserMode = pauserMode; return this; } /** * Checks if the gateway is operating in buffered mode. * * @return {@code true} if in buffered mode, otherwise {@code false}. */ public boolean buffered() { return buffered; } /** * Sets whether new connections will be buffered if the client doesn't specify whether buffering should be used. * * @param buffered Set to {@code true} if buffering should be enabled. * @return The current instance of {@code ChronicleGatewayMain} for chained calls. */ public ChronicleGatewayMain buffered(boolean buffered) { this.buffered = buffered; return this; } /** * Starts the gateway, binding the server socket channel and starting the acceptor thread if not already running. * * @throws IOException If there's a hiccup during the gateway's startup. */ public synchronized ChronicleGatewayMain start() throws IOException { if (isClosed()) throw new IllegalStateException("Closed"); bindSSC(); if (thread == null) { // Initializing the acceptor thread thread = new Thread(this::run, "acceptor"); thread.setDaemon(true); thread.start(); } return this; } /** * Secures the server socket channel if it hasn't been initialized yet. * * @throws IOException If there's an issue accessing or creating the socket channel. */ private void bindSSC() throws IOException { if (ssc == null) { ssc = socketRegistry().acquireServerSocketChannel(url()); } } /** * Main execution loop for the gateway. Accepts incoming connections and handles requests. */ @Override public void run() { // Jvm.startup().on(getClass(), "Starting " + this); service = Executors.newCachedThreadPool(new NamedThreadFactory("connections")); Throwable thrown = null; try { bindSSC(); ChronicleChannelCfg channelCfg = new ChronicleChannelCfg<>().addHostnamePort(null, url().getPort()).pauserMode(pauserMode).buffered(buffered); while (!isClosed()) { final SocketChannel sc = ssc.accept(); sc.socket().setTcpNoDelay(true); final TCPChronicleChannel channel = new TCPChronicleChannel(systemContext(), channelCfg, sc, this::replaceInHeader, this::replaceOutHeader); channel.closeCallback(closeCallback()); service.submit(() -> handle(channel)); } } catch (Throwable e) { thrown = e; } finally { Thread.yield(); boolean closing = isClosing() || socketRegistry().isClosing(); close(); if (thrown != null && !closing) Jvm.error().on(getClass(), thrown); } } /** * Allows replacing of the inbound channel header. * By default, it retains the given header without making any changes. * * @param channelHeader the inbound channel header * @return the possibly replaced channel header */ protected ChannelHeader replaceInHeader(ChannelHeader channelHeader) { return channelHeader; } /** * Allows replacing of the outbound channel header. * If the outbound channel header is an instance of a ChannelHandler, * it replaces it with the response header from the handler. * * @param channelHeader the outbound channel header * @return the possibly replaced channel header */ protected ChannelHeader replaceOutHeader(ChannelHeader channelHeader) { if (channelHeader instanceof ChannelHandler) { ChannelHandler handler = (ChannelHandler) channelHeader; return handler.responseHeader(this); } return channelHeader; } /** * Waits for the service to terminate, giving it 1 second before proceeding. * If interrupted, it logs a warning and re-interrupts the current thread. */ private void waitForService() { try { service.shutdownNow(); service.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { // Logging the interruption and setting the interrupt flag back Jvm.warn().on(getClass(), e); Thread.currentThread().interrupt(); } } @Override protected void performClose() { super.performClose(); Closeable.closeQuietly(ssc); if (service != null) waitForService(); } /** * Manages an incoming connection. This involves decoding the request and dispatching it * to the correct handler based on the read channel header. The method ensures that proper * error handling is in place for various exceptions, including HTTP GET detection and * invalid protocol scenarios. * * @param channel The incoming TCP channel to be managed. */ void handle(TCPChronicleChannel channel) { // Indicate whether the channel should be closed when done handling boolean close = true; ChronicleChannel channel2 = null; try { // Retrieve the inbound channel header final ChannelHeader channelHeader = channel.headerInToUse(); // Validate the channel header and retrieve the handler ChannelHandler bh = validateHandler(channelHeader); if (bh == null) return; // Determine whether buffering is enabled boolean buffered = this.buffered; if (bh.buffered() != null) buffered = bh.buffered(); Jvm.debug().on(ChronicleGatewayMain.class, "Server got " + bh); // Retrieve the outbound channel header final ChannelHeader headerOut = channel.headerOut(); // If the outbound channel header is a redirect, print a message and return if (headerOut instanceof RedirectHeader) { System.out.println("Server redirected " + headerOut); return; } // Instantiate the secondary channel based on whether buffering is enabled channel2 = buffered ? new BufferedChronicleChannel(channel, pauserMode.get()) : channel; Jvm.debug().on(ChronicleGatewayMain.class, "Running " + channel2); // Run the channel handler bh.run(this, channel2); // Determine whether to close the channel when done close = bh.closeWhenRunEnds(); } catch (HTTPDetectedException e) { // Handle the exception case where an HTTP GET request is detected Jvm.warn().on(getClass(), "HTTP GET Detected", e); } catch (InvalidProtocolException e) { // Handle the exception case where an invalid protocol is detected Jvm.warn().on(getClass(), "Invalid Protocol", e); } catch (Throwable t) { // Pause for a moment before checking if the resource is closing Jvm.pause(1); // Log any other exceptions if not closing if (!isClosing() && !channel.isClosing()) if (t instanceof ClosedIORuntimeException) Jvm.warn().on(getClass(), t.toString()); else Jvm.error().on(getClass(), t); } finally { // Close the channels if the close flag is set if (close) Closeable.closeQuietly(channel2, channel); } } /** * Validates that the incoming request contains a valid handler. * * @param marshallable the incoming request * @return a ChannelHandler instance if the request is valid; otherwise null */ @Nullable protected ChannelHandler validateHandler(Marshallable marshallable) { if (!(marshallable instanceof ChannelHandler)) { return new ErrorReplyHandler().errorMsg("The header must be a ChannelHandler"); } return (ChannelHandler) marshallable; } /** * Returns the port number on which the gateway is listening. * * @return the port number */ public int port() { return ssc.socket().getLocalPort(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy