HttpWebSocketInboundEndpointHandler.java
/*
* Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.synapse.transport.netty.api;
import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.transport.TransportListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.transport.netty.BridgeConstants;
import org.apache.synapse.transport.netty.api.config.HttpWebSocketInboundEndpointConfiguration;
import org.apache.synapse.transport.netty.api.config.SSLConfiguration;
import org.apache.synapse.transport.netty.listener.Axis2HttpSSLTransportListener;
import org.apache.synapse.transport.netty.listener.Axis2HttpTransportListener;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Objects;
/**
* API class to access Pass-Through (HTTP) and WebSocket Core Inbound Endpoint management classes.
*/
public class HttpWebSocketInboundEndpointHandler {
private static final Log LOG = LogFactory.getLog(HttpWebSocketInboundEndpointHandler.class);
private static HashMap<Integer, TransportListener> transportListenerMap = new HashMap<>();
/**
* Default value for verification timeout to validate whether port is closed successfully.
*/
private static final int DEFAULT_PORT_CLOSE_VERIFY_TIMEOUT = 10;
/**
* Start Endpoint Listener.
*
* @param configurationContext configuration context
* @param httpWebSocketConfig configuration that represents http and websocket inbound endpoint
* @return whether the Endpoint started successfully or not
*/
public static boolean startListener(ConfigurationContext configurationContext,
HttpWebSocketInboundEndpointConfiguration httpWebSocketConfig) {
Axis2HttpTransportListener transportListener = new Axis2HttpTransportListener();
return startServer(transportListener, BridgeConstants.TRANSPORT_HTTPWS, httpWebSocketConfig,
configurationContext);
}
/**
* Start SSL Endpoint Listener.
*
* @param configurationContext configuration context
* @param httpWebSocketConfig configuration that represents http and websocket inbound endpoint
* @return whether the Endpoint started successfully or not
*/
public static boolean startSSLListener(ConfigurationContext configurationContext,
HttpWebSocketInboundEndpointConfiguration httpWebSocketConfig) {
Axis2HttpTransportListener transportListener = new Axis2HttpSSLTransportListener();
return startServer(transportListener, BridgeConstants.TRANSPORT_HTTPSWSS, httpWebSocketConfig,
configurationContext);
}
private static boolean startServer(Axis2HttpTransportListener transportListener, String transportName,
HttpWebSocketInboundEndpointConfiguration httpWebSocketConfig,
ConfigurationContext configurationContext) {
TransportInDescription transportInDescription;
try {
// Generate the TransportInDescription and add it to the ConfigurationContext
transportInDescription = createTransportInDescription(transportName, transportListener,
httpWebSocketConfig);
} catch (AxisFault e) {
logStartupError(e, "Error occurred while generating TransportInDescription. Hence, ",
httpWebSocketConfig, transportName);
return false;
}
try {
// Initialize the Axis2HttpTransportListener and set MessagingHandlers
transportListener.init(configurationContext, transportInDescription);
transportListener.setMessagingHandlers(httpWebSocketConfig.getInboundEndpointHandlers());
} catch (AxisFault e) {
logStartupError(e, "Error occurred while initializing the " + transportName
+ " transport listener. Hence, ", httpWebSocketConfig, transportName);
return false;
}
try {
transportListener.start();
// If the transport it started successfully, store it in the transportListenerMap against the port. This
// map will be used when stopping the listener.
transportListenerMap.put(httpWebSocketConfig.getPort(), transportListener);
} catch (AxisFault e) {
logStartupError(e, "Error occurred while generating TransportInDescription. Hence, ",
httpWebSocketConfig, transportName);
return false;
}
return true;
}
private static void logStartupError(AxisFault e, String msg,
HttpWebSocketInboundEndpointConfiguration httpWebSocketConfig,
String transportName) {
LOG.error(msg + " Could not start the " + transportName + " transport listener for endpoint : "
+ httpWebSocketConfig.getEndpointName() + " on port " + httpWebSocketConfig.getPort(), e);
}
private static TransportInDescription createTransportInDescription(
String transportName, TransportListener transportListener,
HttpWebSocketInboundEndpointConfiguration httpWebSocketConfig)
throws AxisFault {
TransportInDescription transportInDescription = new TransportInDescription(transportName);
transportInDescription.setReceiver(transportListener);
// populate parameters
addParameter(transportInDescription, BridgeConstants.PORT_PARAM, httpWebSocketConfig.getPort());
addParameter(transportInDescription, BridgeConstants.HOSTNAME_PARAM, httpWebSocketConfig.getHostname());
addParameter(transportInDescription, BridgeConstants.HTTP_PROTOCOL_VERSIONS_PARAM,
httpWebSocketConfig.getHttpProtocolVersion());
SSLConfiguration sslConfiguration = httpWebSocketConfig.getSslConfiguration();
// SSLConfiguration will be null for non-secured transport. Hence, need to do the null check here.
if (Objects.nonNull(sslConfiguration)) {
populateSSLParameters(sslConfiguration, transportInDescription);
}
return transportInDescription;
}
private static void populateSSLParameters(SSLConfiguration sslConfig, TransportInDescription transportIn)
throws AxisFault {
addParameter(transportIn, BridgeConstants.KEY_STORE, sslConfig.getKeyStoreElement());
addParameter(transportIn, BridgeConstants.TRUST_STORE, sslConfig.getTrustStoreElement());
addParameter(transportIn, BridgeConstants.SSL_VERIFY_CLIENT, sslConfig.getClientAuthElement());
addParameter(transportIn, BridgeConstants.HTTPS_PROTOCOL, sslConfig.getHttpsProtocolElement());
addParameter(transportIn, BridgeConstants.SSL_PROTOCOL, sslConfig.getSslProtocolElement());
addParameter(transportIn, BridgeConstants.CLIENT_REVOCATION, sslConfig.getRevocationVerifierElement());
addParameter(transportIn, BridgeConstants.PREFERRED_CIPHERS, sslConfig.getPreferredCiphersElement());
addParameter(transportIn, BridgeConstants.SSL_SESSION_TIMEOUT, sslConfig.getSessionTimeoutElement());
addParameter(transportIn, BridgeConstants.SSL_HANDSHAKE_TIMEOUT, sslConfig.getHandshakeTimeoutElement());
}
private static void addParameter(TransportInDescription transportInDescription, String name,
OMElement parameterElement)
throws AxisFault {
Parameter parameter = new Parameter();
parameter.setName(name);
parameter.setParameterElement(parameterElement);
transportInDescription.addParameter(parameter);
}
private static void addParameter(TransportInDescription transportInDescription, String name, Object value)
throws AxisFault {
Parameter parameter = new Parameter();
parameter.setName(name);
parameter.setValue(value);
transportInDescription.addParameter(parameter);
}
/**
* Close ListeningEndpoint running on the given port.
*
* @param port Port of ListeningEndpoint to be closed
* @return IS successfully closed
*/
public static boolean closeEndpoint(int port) {
LOG.info("Closing Endpoint Listener for port " + port);
TransportListener listener = transportListenerMap.get(port);
if (Objects.nonNull(listener)) {
try {
listener.stop();
} catch (AxisFault e) {
LOG.error("Cannot close Endpoint relevant to port " + port, e);
return false;
} finally {
int portCloseVerifyTimeout = DEFAULT_PORT_CLOSE_VERIFY_TIMEOUT;
if (isPortCloseSuccess(port, portCloseVerifyTimeout)) {
LOG.info("Successfully closed Endpoint Listener for port " + port);
} else {
LOG.warn("Port close verify timeout " + portCloseVerifyTimeout + "s exceeded. "
+ "Endpoint Listener for port " + port + " still bound to the ListenerEndpoint.");
}
}
}
return true;
}
private static boolean isPortCloseSuccess(int port, int portCloseVerifyTimeout) {
boolean portCloseSuccess = false;
for (int i = 0; i < portCloseVerifyTimeout; i++) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Verify port [" + port + "] close status. Attempt: " + i);
}
ServerSocket ss = new ServerSocket(port);
ss.close();
ss = null;
portCloseSuccess = true;
break;
} catch (IOException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("The port " + port + " is not closed yet, verify again after waiting 1s", e);
}
try {
Thread.sleep(1000L);
} catch (InterruptedException ex) {
//ignore
}
}
}
return portCloseSuccess;
}
}