Webserver no longer depends on node. Added a configuration parser for the webserver. Size of resulting JAR now 16MB smaller.

This commit is contained in:
Clinton Alexander 2017-03-30 14:27:18 +01:00
parent 3e1b4aaef4
commit 561b2e50a6
5 changed files with 139 additions and 10 deletions

View File

@ -32,7 +32,7 @@ sourceSets {
dependencies {
compile project(':core')
compile project(':node') // TODO: Break this dependency
compile project(':client:rpc')
compile project(':client:jackson')
// Web stuff: for HTTP[S] servlets
@ -42,6 +42,9 @@ dependencies {
compile "org.jolokia:jolokia-agent-war:$jolokia_version"
compile "commons-fileupload:commons-fileupload:$fileupload_version"
// JOpt: for command line flags.
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
// Jersey for JAX-RS implementation for use in Jetty
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"

View File

@ -0,0 +1,95 @@
package net.corda.webserver
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions
import joptsimple.OptionParser
import joptsimple.util.EnumConverter
import net.corda.core.div
import net.corda.core.utilities.loggerFor
import org.slf4j.event.Level
import java.io.PrintStream
import java.nio.file.Path
import java.nio.file.Paths
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
class ArgsParser {
private val optionParser = OptionParser()
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
private val baseDirectoryArg = optionParser
.accepts("base-directory", "The node working directory where all the files are kept")
.withRequiredArg()
.defaultsTo(".")
private val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.defaultsTo("node.conf")
private val loggerLevel = optionParser
.accepts("logging-level", "Enable logging at this level and higher")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
.defaultsTo(Level.INFO)
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.")
private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.")
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
private val helpArg = optionParser.accepts("help").forHelp()
fun parse(vararg args: String): CmdLineOptions {
val optionSet = optionParser.parse(*args)
require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) {
"${baseDirectoryArg.options()[0]} and ${configFileArg.options()[0]} cannot be specified together"
}
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
val configFile = baseDirectory / optionSet.valueOf(configFileArg)
val help = optionSet.has(helpArg)
val loggingLevel = optionSet.valueOf(loggerLevel)
val logToConsole = optionSet.has(logToConsoleArg)
val isRegistration = optionSet.has(isRegistrationArg)
val isVersion = optionSet.has(isVersionArg)
val noLocalShell = optionSet.has(noLocalShellArg)
val sshdServer = optionSet.has(sshdServerArg)
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isRegistration, isVersion, noLocalShell, sshdServer)
}
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
}
data class CmdLineOptions(val baseDirectory: Path,
val configFile: Path,
val help: Boolean,
val loggingLevel: Level,
val logToConsole: Boolean,
val isRegistration: Boolean,
val isVersion: Boolean,
val noLocalShell: Boolean,
val sshdServer: Boolean) {
private val log = loggerFor<CmdLineOptions>()
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
return loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
}
private fun loadConfig(baseDirectory: Path,
configFile: Path = baseDirectory / "node.conf",
allowMissingConfig: Boolean = false,
configOverrides: Map<String, Any?> = emptyMap()): Config {
val parseOptions = ConfigParseOptions.defaults()
val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false))
val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig))
val overrideConfig = ConfigFactory.parseMap(configOverrides + mapOf(
// Add substitution values here
"basedir" to baseDirectory.toString())
)
val finalConfig = overrideConfig
.withFallback(appConfig)
.withFallback(defaultConfig)
.resolve()
log.info("Config:\n${finalConfig.root().render(ConfigRenderOptions.defaults())}")
return finalConfig
}
}

View File

@ -5,8 +5,6 @@ package net.corda.webserver
import com.typesafe.config.ConfigException
import net.corda.core.div
import net.corda.core.rootCause
import net.corda.node.ArgsParser
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.webserver.internal.NodeWebServer
import org.slf4j.LoggerFactory
import java.lang.management.ManagementFactory
@ -41,13 +39,13 @@ fun main(args: Array<String>) {
println("Logs can be found in ${System.getProperty("log-path")}")
val conf = try {
FullNodeConfiguration(cmdlineOptions.baseDirectory, cmdlineOptions.loadConfig())
WebServerConfig(cmdlineOptions.baseDirectory, cmdlineOptions.loadConfig())
} catch (e: ConfigException) {
println("Unable to load the configuration file: ${e.rootCause.message}")
exitProcess(2)
}
log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
log.info("Main class: ${WebServerConfig::class.java.protectionDomain.codeSource.location.toURI().path}")
val info = ManagementFactory.getRuntimeMXBean()
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
log.info("Application Args: ${args.joinToString(" ")}")
@ -68,4 +66,4 @@ fun main(args: Array<String>) {
log.error("Exception during node startup", e)
exitProcess(1)
}
}
}

View File

@ -0,0 +1,34 @@
package net.corda.webserver
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import net.corda.core.div
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.getListOrElse
import net.corda.nodeapi.config.getValue
import java.nio.file.Path
/**
* [baseDirectory] is not retrieved from the config file but rather from a command line argument.
*/
class WebServerConfig(val baseDirectory: Path, val config: Config) : SSLConfiguration {
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
override val keyStorePassword: String by config
override val trustStorePassword: String by config
val myLegalName: String by config
val exportJMXto: String get() = "http"
val rpcUsers: List<User> = config
.getListOrElse<Config>("rpcUsers") { emptyList() }
.map {
val username = it.getString("user")
require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" }
val password = it.getString("password")
val permissions = it.getListOrElse<String>("permissions") { emptyList() }.toSet()
User(username, password, permissions)
}
val useHTTPS: Boolean by config
val p2pAddress: HostAndPort by config
val rpcAddress: HostAndPort? by config
val webAddress: HostAndPort by config
}

View File

@ -4,9 +4,8 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.utilities.loggerFor
import net.corda.node.printBasicNodeInfo
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.webserver.WebServerConfig
import net.corda.webserver.servlets.AttachmentDownloadServlet
import net.corda.webserver.servlets.DataUploadServlet
import net.corda.webserver.servlets.ObjectMapperConfig
@ -25,7 +24,7 @@ import org.glassfish.jersey.servlet.ServletContainer
import java.lang.reflect.InvocationTargetException
import java.util.*
class NodeWebServer(val config: FullNodeConfiguration) {
class NodeWebServer(val config: WebServerConfig) {
private companion object {
val log = loggerFor<NodeWebServer>()
val retryDelay = 1000L // Milliseconds
@ -35,7 +34,7 @@ class NodeWebServer(val config: FullNodeConfiguration) {
private lateinit var server: Server
fun start() {
printBasicNodeInfo("Starting as webserver: ${config.webAddress}")
println("Starting as webserver: ${config.webAddress}")
server = initWebServer(retryConnectLocalRpc())
}