io.continual.restHttp.HttpService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of continualRestHttp Show documentation
Show all versions of continualRestHttp Show documentation
Continual library for RESTful HTTP services.
/*
* Copyright 2019, Continual.io
*
* 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 io.continual.restHttp;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import java.util.LinkedList;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.http11.Http11NioProtocol;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.continual.builder.Builder.BuildFailure;
import io.continual.http.service.framework.CHttpServlet.SessionLifeCycle;
import io.continual.http.service.framework.inspection.CHttpObserverMgr;
import io.continual.iam.IamService;
import io.continual.metrics.MetricsCatalog;
import io.continual.metrics.MetricsService;
import io.continual.services.Service;
import io.continual.services.ServiceContainer;
import io.continual.util.data.StreamTools;
import io.continual.util.data.json.JsonVisitor;
import io.continual.util.data.json.JsonVisitor.ObjectVisitor;
public class HttpService implements Service
{
private final String kSetting_ServletWorkDir = "workDir";
private final String kDefault_ServletWorkDir = new File ( System.getProperty ( "java.io.tmpdir" )).getAbsolutePath ();
private final String kSetting_Keystore = "keystore";
private final String kSetting_KeystoreFile = "file";
private final String kSetting_KeystoreAlias = "alias";
private final String kDefault_KeystoreAlias = "tomcat";
private final String kSetting_KeystoreAliasScan = "scanForAlias";
private final String kSetting_KeystorePassword = "password";
private final String kDefault_KeystorePassword = "changeme";
private final String kSetting_KeystorePasswordFile = "passwordFile";
private final String kSetting_Port = "port";
public HttpService ( ServiceContainer sc, JSONObject settings ) throws BuildFailure
{
this (
sc,
settings,
settings.optString ( "name", "default" ),
settings.optInt ( "port", 8080 )
);
}
protected HttpService ( ServiceContainer sc, JSONObject rawConfig, String serviceName, int defaultPort ) throws BuildFailure
{
try
{
fServices = sc;
final JSONObject settings = sc.getExprEval ().evaluateJsonObject ( rawConfig );
final String acctsServiceName = settings.optString ( "accountService", null );
if ( acctsServiceName != null )
{
fAccounts = fServices.get ( acctsServiceName, IamService.class );
}
else
{
fAccounts = null;
}
final String metricsServiceName = settings.optString ( "metricsService", null );
if ( metricsServiceName != null )
{
final MetricsService ms = fServices.get ( metricsServiceName, MetricsService.class );
if ( ms == null ) throw new BuildFailure ( "Metrics service specified as " + metricsServiceName + " but the service was not found." );
fMetrics = ms.getCatalog ( "http" );
}
else
{
fMetrics = null;
}
final String inspectionServiceName = settings.optString ( "inspector", null );
if ( inspectionServiceName != null )
{
fInspector = fServices.get ( inspectionServiceName, CHttpObserverMgr.class );
if ( fInspector == null ) throw new BuildFailure ( "Inspector service specified as \"" + inspectionServiceName + "\" but the service was not found." );
}
else
{
fInspector = null;
}
fSettings = settings;
fRunning = false;
fServlets = new LinkedList ();
System.setProperty (
"org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH",
"true" );
fTomcat = new Tomcat ();
final String servletWorkDir = settings.optString ( kSetting_ServletWorkDir, kDefault_ServletWorkDir );
fWorkDir = new File ( servletWorkDir );
if ( !fWorkDir.exists () )
{
fWorkDir.mkdirs ();
}
// the settings can have sub-objects "http" and/or "https". If neither is present, read "port" for the port number.
JSONObject httpConfig = settings.optJSONObject ( "http" );
final JSONObject httpsConfig = settings.optJSONObject ( "https" );
// if we have neither http nor https, stand up http on the default port
if ( httpConfig == null && httpsConfig == null )
{
httpConfig = new JSONObject ()
.put ( "port", settings.optInt ( kSetting_Port, defaultPort ) )
;
}
// HTTP setup...
if ( httpConfig != null )
{
final int port = httpConfig.optInt ( kSetting_Port, defaultPort );
if ( port > 0 )
{
final Connector connector = new Connector ( Http11NioProtocol.class.getName () );
connector.setPort ( port );
transferConnectorAttributes ( connector, httpConfig.optJSONObject ( "tomcat" ) );
fTomcat.getService ().addConnector ( connector );
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] listens for HTTP on " + port + "." );
}
else
{
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] will not listen for HTTP." );
}
}
// HTTPS setup...
if ( httpsConfig != null )
{
final int port = httpsConfig.optInt ( kSetting_Port, defaultPort );
if ( port > 0 )
{
final Connector connector = new Connector ( Http11NioProtocol.class.getName () );
final JSONObject keystoreConfig = httpsConfig.getJSONObject ( kSetting_Keystore );
String keystoreFilename = keystoreConfig.getString ( kSetting_KeystoreFile );
final File keystoreFile = new File ( keystoreFilename );
if ( !keystoreFile.isAbsolute () )
{
// tomcat requires an absolute filename, or it'll use env var CATALINA_HOME. We want a file
// relative to the current working directory.
final Path path = FileSystems.getDefault().getPath(".").toAbsolutePath();
final File file = new File ( path.toFile (), keystoreFilename );
keystoreFilename = file.getAbsolutePath ();
log.info ( "Using absolute path [" + keystoreFilename + "] for keystore." );
}
// the keystore password can be delivered directly in configuration, or loaded from a file
String keystorePassword = kDefault_KeystorePassword;
if ( keystoreConfig.has ( kSetting_KeystorePassword ) )
{
keystorePassword = keystoreConfig.getString ( kSetting_KeystorePassword );
}
else if ( keystoreConfig.has ( kSetting_KeystorePasswordFile ) )
{
final String pwdFileName = keystoreConfig.optString ( kSetting_KeystorePasswordFile, null );
final File pwdFile = new File ( pwdFileName );
try ( FileInputStream fis = new FileInputStream ( pwdFile ) )
{
final byte[] pwdData = StreamTools.readBytes ( fis );
keystorePassword = new String ( pwdData ).trim ();
}
catch ( IOException x )
{
log.warn ( "There was a problem trying to read {}: {}", pwdFileName, x.getMessage () );
}
}
// the keystore alias can be delivered directly in configuration, or determined by a scan of the keystore
String keystoreAlias = kDefault_KeystoreAlias;
if ( keystoreConfig.has ( kSetting_KeystoreAlias ) )
{
keystoreAlias = keystoreConfig.getString ( kSetting_KeystoreAlias );
}
else if ( keystoreConfig.optBoolean ( kSetting_KeystoreAliasScan, false ) )
{
keystoreAlias = scanKeystoreForPrivateKey ( keystoreFilename, keystorePassword );
}
connector.setScheme ( "https" );
connector.setSecure ( true );
connector.setProperty ( "keystoreFile", keystoreFilename );
connector.setProperty ( "keystorePass", keystorePassword );
connector.setProperty ( "keyAlias", keystoreAlias );
connector.setProperty ( "clientAuth", "false" );
connector.setProperty ( "sslProtocol", "TLS" );
connector.setProperty ( "SSLEnabled", "true" );
connector.setPort ( port );
transferConnectorAttributes ( connector, httpConfig.optJSONObject ( "tomcat" ) );
fTomcat.getService ().addConnector ( connector );
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] listens for HTTPS on " + port + "." );
}
else
{
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] will not listen for HTTPS." );
}
}
}
catch ( JSONException x )
{
throw new BuildFailure ( x );
}
}
public HttpService addRouter ( String servletName, HttpRouter s )
{
fServlets.add ( s );
return this;
}
@Override
public void start () throws FailedToStart
{
try
{
if ( fSettings.optBoolean ( "enabled", true ) )
{
final HttpServlet hs = new HttpServlet (
fServices,
fAccounts,
SessionLifeCycle.valueOf ( fSettings.optString ( "lifeCycle", SessionLifeCycle.NO_SESSION.toString () ) ),
fSettings,
fMetrics,
fInspector
);
for ( HttpRouter router : fServlets )
{
hs.addRouter ( router );
}
final String servletName = "httpService";
final Context rootCtx = fTomcat.addContext ( "", fWorkDir.getAbsolutePath () );
Tomcat.addServlet ( rootCtx, servletName, hs );
rootCtx.addServletMappingDecoded ( "/*", servletName );
try
{
fTomcat.start();
fRunning = true;
}
catch ( LifecycleException e )
{
log.warn ( "Couldn't start tomcat." , e );
fRunning = false;
}
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] is listening." );
}
else
{
log.info ( "Service [" + fSettings.optString ( "name", "" ) + "] is disabled." );
}
}
catch ( BuildFailure x )
{
throw new FailedToStart ( x );
}
}
@Override
public void requestFinish ()
{
try
{
fTomcat.stop ();
fRunning = false;
}
catch ( LifecycleException e )
{
log.warn ( "Couldn't stop tomcat.", e );
}
}
@Override
public boolean isRunning ()
{
return fRunning;
}
private final ServiceContainer fServices;
private final IamService,?> fAccounts;
private final MetricsCatalog fMetrics;
private final CHttpObserverMgr fInspector;
private final JSONObject fSettings;
private final Tomcat fTomcat;
private boolean fRunning;
private final LinkedList fServlets;
private final File fWorkDir;
private static final Logger log = LoggerFactory.getLogger ( HttpService.class );
private void transferConnectorAttributes ( Connector connector, JSONObject config )
{
if ( config == null ) return;
JsonVisitor.forEachElement ( config, new ObjectVisitor
© 2015 - 2025 Weber Informatics LLC | Privacy Policy