mirror of
https://github.com/corda/corda.git
synced 2025-02-10 12:51:37 +00:00
Moved JsonSupport to new webserver module. Fixed a few compile errors.
Fixed compile issues caused by webserver being split to a separate project. WebServer now starts and stops correctly as a separate module.
This commit is contained in:
parent
bc9f86905c
commit
8414c97a61
@ -113,6 +113,7 @@ dependencies {
|
|||||||
compile project(':node')
|
compile project(':node')
|
||||||
compile "com.google.guava:guava:$guava_version"
|
compile "com.google.guava:guava:$guava_version"
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
}
|
}
|
||||||
|
|
||||||
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
|
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
|
||||||
@ -178,7 +179,7 @@ bintrayConfig {
|
|||||||
projectUrl = 'https://github.com/corda/corda'
|
projectUrl = 'https://github.com/corda/corda'
|
||||||
gpgSign = true
|
gpgSign = true
|
||||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||||
publications = ['client', 'core', 'corda', 'finance', 'node', 'node-schemas', 'test-utils']
|
publications = ['client', 'core', 'corda', 'corda-webserver', 'finance', 'node', 'node-schemas', 'test-utils']
|
||||||
license {
|
license {
|
||||||
name = 'Apache-2.0'
|
name = 'Apache-2.0'
|
||||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||||
|
@ -118,7 +118,7 @@ interface CordaRPCOps : RPCOps {
|
|||||||
*/
|
*/
|
||||||
fun uploadAttachment(jar: InputStream): SecureHash
|
fun uploadAttachment(jar: InputStream): SecureHash
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
// TODO: Remove this from the interface
|
||||||
@Deprecated("This service will be removed in a future milestone")
|
@Deprecated("This service will be removed in a future milestone")
|
||||||
fun uploadFile(dataType: String, name: String?, file: InputStream): String
|
fun uploadFile(dataType: String, name: String?, file: InputStream): String
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
}
|
}
|
||||||
|
|
||||||
mainClassName = "net.corda.docs.ClientRpcTutorialKt"
|
mainClassName = "net.corda.docs.ClientRpcTutorialKt"
|
||||||
|
@ -10,6 +10,7 @@ import java.nio.file.Files
|
|||||||
*/
|
*/
|
||||||
class Node {
|
class Node {
|
||||||
static final String JAR_NAME = 'corda.jar'
|
static final String JAR_NAME = 'corda.jar'
|
||||||
|
static final String WAR_NAME = 'corda-webserver.war'
|
||||||
static final String DEFAULT_HOST = 'localhost'
|
static final String DEFAULT_HOST = 'localhost'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,7 +129,8 @@ class Node {
|
|||||||
void build(File rootDir) {
|
void build(File rootDir) {
|
||||||
nodeDir = new File(rootDir, name.replaceAll("\\s",""))
|
nodeDir = new File(rootDir, name.replaceAll("\\s",""))
|
||||||
configureRpcUsers()
|
configureRpcUsers()
|
||||||
installCordaJAR()
|
installCordaJar()
|
||||||
|
installWebserverWar()
|
||||||
installBuiltPlugin()
|
installBuiltPlugin()
|
||||||
installCordapps()
|
installCordapps()
|
||||||
installDependencies()
|
installDependencies()
|
||||||
@ -154,7 +156,7 @@ class Node {
|
|||||||
/**
|
/**
|
||||||
* Installs the corda fat JAR to the node directory.
|
* Installs the corda fat JAR to the node directory.
|
||||||
*/
|
*/
|
||||||
private void installCordaJAR() {
|
private void installCordaJar() {
|
||||||
def cordaJar = verifyAndGetCordaJar()
|
def cordaJar = verifyAndGetCordaJar()
|
||||||
project.copy {
|
project.copy {
|
||||||
from cordaJar
|
from cordaJar
|
||||||
@ -163,6 +165,18 @@ class Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the corda webserver WAR to the node directory
|
||||||
|
*/
|
||||||
|
private void installWebserverWar() {
|
||||||
|
def webWar = verifyAndGetWebserverWar()
|
||||||
|
project.copy {
|
||||||
|
from webWar
|
||||||
|
into nodeDir
|
||||||
|
rename webWar.name, WAR_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs this project's cordapp to this directory.
|
* Installs this project's cordapp to this directory.
|
||||||
*/
|
*/
|
||||||
@ -242,6 +256,24 @@ class Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the corda WAR amongst the dependencies
|
||||||
|
*
|
||||||
|
* @return A file representing the Corda webserver WAR
|
||||||
|
*/
|
||||||
|
private File verifyAndGetWebserverWar() {
|
||||||
|
def maybeWar = project.configurations.runtime.filter {
|
||||||
|
it.toString().contains("corda-webserver-${project.corda_version}.war")
|
||||||
|
}
|
||||||
|
if (maybeWar.size() == 0) {
|
||||||
|
throw new RuntimeException("No Corda Webserver WAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_version}.war\"")
|
||||||
|
} else {
|
||||||
|
def war = maybeWar.getSingleFile()
|
||||||
|
assert(war.isFile())
|
||||||
|
return war
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of cordapps based on what dependent cordapps were specified.
|
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||||
*
|
*
|
||||||
|
@ -9,4 +9,8 @@ class ProjectPublishExtension {
|
|||||||
* True when we do not want to publish default Java components
|
* True when we do not want to publish default Java components
|
||||||
*/
|
*/
|
||||||
Boolean disableDefaultJar = false
|
Boolean disableDefaultJar = false
|
||||||
|
/**
|
||||||
|
* True if publishing a WAR instead of a JAR. Forces disableDefaultJAR to "true" when true
|
||||||
|
*/
|
||||||
|
Boolean publishWar = false
|
||||||
}
|
}
|
@ -60,8 +60,10 @@ class PublishTasks implements Plugin<Project> {
|
|||||||
void configureMavenPublish(BintrayConfigExtension bintrayConfig) {
|
void configureMavenPublish(BintrayConfigExtension bintrayConfig) {
|
||||||
project.apply([plugin: 'maven-publish'])
|
project.apply([plugin: 'maven-publish'])
|
||||||
project.publishing.publications.create(publishName, MavenPublication) {
|
project.publishing.publications.create(publishName, MavenPublication) {
|
||||||
if(!publishConfig.disableDefaultJar) {
|
if(!publishConfig.disableDefaultJar && !publishConfig.publishWar) {
|
||||||
from project.components.java
|
from project.components.java
|
||||||
|
} else if(publishConfig.publishWar) {
|
||||||
|
from project.components.web
|
||||||
}
|
}
|
||||||
groupId project.group
|
groupId project.group
|
||||||
artifactId publishName
|
artifactId publishName
|
||||||
|
@ -88,30 +88,12 @@ dependencies {
|
|||||||
exclude group: "asm"
|
exclude group: "asm"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force commons logging to version 1.2 to override Artemis, which pulls in 1.1.3 (ARTEMIS-424)
|
// For adding serialisation of file upload streams to RPC
|
||||||
compile "commons-logging:commons-logging:1.2"
|
// TODO: Remove this dependency and the code that requires it
|
||||||
|
|
||||||
// Web stuff: for HTTP[S] servlets
|
|
||||||
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
|
||||||
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
|
|
||||||
compile "javax.servlet:javax.servlet-api:3.1.0"
|
|
||||||
compile "org.jolokia:jolokia-agent-war:$jolokia_version"
|
|
||||||
compile "commons-fileupload:commons-fileupload:1.3.2"
|
compile "commons-fileupload:commons-fileupload:1.3.2"
|
||||||
|
|
||||||
// Jersey for JAX-RS implementation for use in Jetty
|
// Force commons logging to version 1.2 to override Artemis, which pulls in 1.1.3 (ARTEMIS-424)
|
||||||
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
compile "commons-logging:commons-logging:1.2"
|
||||||
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
|
|
||||||
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
|
|
||||||
// NOTE there is a Jackson version clash between jersey-media-json-jackson (v2.5.4) and jackson-module-kotlin (v.2.5.5)
|
|
||||||
// Have not found an Issue in the issue tracker for Jersey for this issue
|
|
||||||
compile ("org.glassfish.jersey.media:jersey-media-json-jackson:${jersey_version}") {
|
|
||||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
|
|
||||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
|
|
||||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
|
|
||||||
}
|
|
||||||
compile ("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}") {
|
|
||||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
|
|
||||||
}
|
|
||||||
compile "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}"
|
compile "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}"
|
||||||
|
|
||||||
// Coda Hale's Metrics: for monitoring of key statistics
|
// Coda Hale's Metrics: for monitoring of key statistics
|
||||||
|
@ -26,14 +26,6 @@ class DriverTests {
|
|||||||
// Check that the port is bound
|
// Check that the port is bound
|
||||||
addressMustNotBeBound(executorService, hostAndPort)
|
addressMustNotBeBound(executorService, hostAndPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun webserverMustBeUp(webserverAddr: HostAndPort) {
|
|
||||||
addressMustBeBound(executorService, webserverAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun webserverMustBeDown(webserverAddr: HostAndPort) {
|
|
||||||
addressMustNotBeBound(executorService, webserverAddr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -69,15 +61,4 @@ class DriverTests {
|
|||||||
}
|
}
|
||||||
nodeMustBeDown(nodeInfo.nodeInfo)
|
nodeMustBeDown(nodeInfo.nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `starting a node and independent web server works`() {
|
|
||||||
val addr = driver {
|
|
||||||
val node = startNode("test").getOrThrow()
|
|
||||||
val webserverAddr = startWebserver(node).getOrThrow()
|
|
||||||
webserverMustBeUp(webserverAddr)
|
|
||||||
webserverAddr
|
|
||||||
}
|
|
||||||
webserverMustBeDown(addr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ class ArgsParser {
|
|||||||
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
|
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
|
||||||
.defaultsTo(Level.INFO)
|
.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 logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
|
||||||
private val isWebserverArg = optionParser.accepts("webserver")
|
|
||||||
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
|
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
|
||||||
private val helpArg = optionParser.accepts("help").forHelp()
|
private val helpArg = optionParser.accepts("help").forHelp()
|
||||||
|
|
||||||
@ -42,9 +41,8 @@ class ArgsParser {
|
|||||||
val help = optionSet.has(helpArg)
|
val help = optionSet.has(helpArg)
|
||||||
val loggingLevel = optionSet.valueOf(loggerLevel)
|
val loggingLevel = optionSet.valueOf(loggerLevel)
|
||||||
val logToConsole = optionSet.has(logToConsoleArg)
|
val logToConsole = optionSet.has(logToConsoleArg)
|
||||||
val isWebserver = optionSet.has(isWebserverArg)
|
|
||||||
val isRegistration = optionSet.has(isRegistrationArg)
|
val isRegistration = optionSet.has(isRegistrationArg)
|
||||||
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isWebserver, isRegistration)
|
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isRegistration)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
||||||
@ -55,7 +53,6 @@ data class CmdLineOptions(val baseDirectory: Path,
|
|||||||
val help: Boolean,
|
val help: Boolean,
|
||||||
val loggingLevel: Level,
|
val loggingLevel: Level,
|
||||||
val logToConsole: Boolean,
|
val logToConsole: Boolean,
|
||||||
val isWebserver: Boolean,
|
|
||||||
val isRegistration: Boolean) {
|
val isRegistration: Boolean) {
|
||||||
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
||||||
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
||||||
|
@ -10,7 +10,6 @@ import net.corda.node.services.config.FullNodeConfiguration
|
|||||||
import net.corda.node.utilities.ANSIProgressObserver
|
import net.corda.node.utilities.ANSIProgressObserver
|
||||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||||
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
||||||
import net.corda.node.webserver.WebServer
|
|
||||||
import org.fusesource.jansi.Ansi
|
import org.fusesource.jansi.Ansi
|
||||||
import org.fusesource.jansi.AnsiConsole
|
import org.fusesource.jansi.AnsiConsole
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -61,8 +60,7 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
drawBanner()
|
drawBanner()
|
||||||
|
|
||||||
val logDir = if (cmdlineOptions.isWebserver) "logs/web" else "logs"
|
System.setProperty("log-path", (cmdlineOptions.baseDirectory / "logs").toString())
|
||||||
System.setProperty("log-path", (cmdlineOptions.baseDirectory / logDir).toString())
|
|
||||||
|
|
||||||
val log = LoggerFactory.getLogger("Main")
|
val log = LoggerFactory.getLogger("Main")
|
||||||
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
||||||
@ -94,17 +92,11 @@ fun main(args: Array<String>) {
|
|||||||
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
||||||
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
||||||
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
||||||
if (cmdlineOptions.isWebserver) {
|
|
||||||
log.info("Starting as webserver on ${conf.webAddress}")
|
|
||||||
} else {
|
|
||||||
log.info("Starting as node on ${conf.artemisAddress}")
|
log.info("Starting as node on ${conf.artemisAddress}")
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cmdlineOptions.baseDirectory.createDirectories()
|
cmdlineOptions.baseDirectory.createDirectories()
|
||||||
|
|
||||||
// TODO: Webserver should be split and start from inside a WAR container
|
|
||||||
if (!cmdlineOptions.isWebserver) {
|
|
||||||
val node = conf.createNode()
|
val node = conf.createNode()
|
||||||
node.start()
|
node.start()
|
||||||
printPluginsAndServices(node)
|
printPluginsAndServices(node)
|
||||||
@ -120,13 +112,6 @@ fun main(args: Array<String>) {
|
|||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
node.run()
|
node.run()
|
||||||
} else {
|
|
||||||
val server = WebServer(conf)
|
|
||||||
server.start()
|
|
||||||
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
|
||||||
printBasicNodeInfo("Webserver started up in $elapsed sec")
|
|
||||||
server.run()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Exception during node startup", e)
|
log.error("Exception during node startup", e)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
|
@ -420,7 +420,7 @@ open class DriverDSL(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryWebserver(configuration: FullNodeConfiguration): HostAndPort? {
|
private fun queryWebserver(configuration: FullNodeConfiguration, process: Process): HostAndPort? {
|
||||||
val protocol = if (configuration.useHTTPS) {
|
val protocol = if (configuration.useHTTPS) {
|
||||||
"https://"
|
"https://"
|
||||||
} else {
|
} else {
|
||||||
@ -429,7 +429,7 @@ open class DriverDSL(
|
|||||||
val url = URL(protocol + configuration.webAddress.toString() + "/api/status")
|
val url = URL(protocol + configuration.webAddress.toString() + "/api/status")
|
||||||
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
||||||
|
|
||||||
while (true) try {
|
while (process.isAlive) try {
|
||||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||||
if (response.isSuccessful && (response.body().string() == "started")) {
|
if (response.isSuccessful && (response.body().string() == "started")) {
|
||||||
return configuration.webAddress
|
return configuration.webAddress
|
||||||
@ -437,14 +437,17 @@ open class DriverDSL(
|
|||||||
} catch(e: ConnectException) {
|
} catch(e: ConnectException) {
|
||||||
log.debug("Retrying webserver info at ${configuration.webAddress}")
|
log.debug("Retrying webserver info at ${configuration.webAddress}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.error("Webserver at ${configuration.webAddress} has died")
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
|
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
|
val process = DriverDSL.startWebserver(executorService, handle.configuration, debugPort)
|
||||||
return future {
|
registerProcess(process)
|
||||||
registerProcess(DriverDSL.startWebserver(executorService, handle.configuration, debugPort))
|
return process.map {
|
||||||
queryWebserver(handle.configuration)!!
|
queryWebserver(handle.configuration, it)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +545,7 @@ open class DriverDSL(
|
|||||||
executorService: ScheduledExecutorService,
|
executorService: ScheduledExecutorService,
|
||||||
nodeConf: FullNodeConfiguration,
|
nodeConf: FullNodeConfiguration,
|
||||||
debugPort: Int?): ListenableFuture<Process> {
|
debugPort: Int?): ListenableFuture<Process> {
|
||||||
val className = "net.corda.node.Corda" // cannot directly get class for this, so just use string
|
val className = "net.corda.webserver.WebServer" // cannot directly get class for this, so just use string
|
||||||
val separator = System.getProperty("file.separator")
|
val separator = System.getProperty("file.separator")
|
||||||
val classpath = System.getProperty("java.class.path")
|
val classpath = System.getProperty("java.class.path")
|
||||||
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
||||||
@ -556,8 +559,7 @@ open class DriverDSL(
|
|||||||
listOf("-Dname=node-${nodeConf.artemisAddress}-webserver") + debugPortArg +
|
listOf("-Dname=node-${nodeConf.artemisAddress}-webserver") + debugPortArg +
|
||||||
listOf(
|
listOf(
|
||||||
"-cp", classpath, className,
|
"-cp", classpath, className,
|
||||||
"--base-directory", nodeConf.baseDirectory.toString(),
|
"--base-directory", nodeConf.baseDirectory.toString())
|
||||||
"--webserver")
|
|
||||||
val builder = ProcessBuilder(javaArgs)
|
val builder = ProcessBuilder(javaArgs)
|
||||||
builder.redirectError(Paths.get("error.$className.log").toFile())
|
builder.redirectError(Paths.get("error.$className.log").toFile())
|
||||||
builder.inheritIO()
|
builder.inheritIO()
|
||||||
|
@ -33,7 +33,6 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
import javax.servlet.*
|
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -288,18 +287,6 @@ class Node(override val configuration: FullNodeConfiguration,
|
|||||||
f.setLength(0)
|
f.setLength(0)
|
||||||
f.write(ourProcessID.toByteArray())
|
f.write(ourProcessID.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Servlet filter to wrap API requests with a database transaction.
|
|
||||||
private class DatabaseTransactionFilter(val database: Database) : Filter {
|
|
||||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
|
||||||
databaseTransaction(database) {
|
|
||||||
chain.doFilter(request, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun init(filterConfig: FilterConfig?) {}
|
|
||||||
override fun destroy() {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigurationException(message: String) : Exception(message)
|
class ConfigurationException(message: String) : Exception(message)
|
||||||
|
@ -5,7 +5,6 @@ import net.corda.core.div
|
|||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.slf4j.event.Level
|
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
class ArgsParserTest {
|
class ArgsParserTest {
|
||||||
@ -20,7 +19,6 @@ class ArgsParserTest {
|
|||||||
help = false,
|
help = false,
|
||||||
logToConsole = false,
|
logToConsole = false,
|
||||||
loggingLevel = Level.INFO,
|
loggingLevel = Level.INFO,
|
||||||
isWebserver = false,
|
|
||||||
isRegistration = false))
|
isRegistration = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,12 +67,6 @@ class ArgsParserTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `webserver`() {
|
|
||||||
val cmdLineOptions = parser.parse("--webserver")
|
|
||||||
assertThat(cmdLineOptions.isWebserver).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `both base-directory and config-file`() {
|
fun `both base-directory and config-file`() {
|
||||||
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
||||||
|
@ -1,15 +1,92 @@
|
|||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'war'
|
||||||
apply plugin: 'net.corda.plugins.publish-utils'
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
|
||||||
description 'Corda node modules'
|
description 'Corda webserver module'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://dl.bintray.com/kotlin/exposed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
integrationTestCompile.extendsFrom testCompile
|
||||||
|
integrationTestRuntime.extendsFrom testRuntime
|
||||||
|
runtimeArtifacts
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
integrationTest {
|
||||||
|
kotlin {
|
||||||
|
compileClasspath += main.output + test.output
|
||||||
|
runtimeClasspath += main.output + test.output
|
||||||
|
srcDir file('src/integration-test/kotlin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
resources {
|
||||||
|
srcDir "../config/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
resources {
|
||||||
|
srcDir "../config/dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':node')
|
compile project(':core')
|
||||||
|
compile project(':node') // TODO: Break this dependency
|
||||||
|
|
||||||
|
// Web stuff: for HTTP[S] servlets
|
||||||
|
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
||||||
|
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
|
||||||
|
compile "javax.servlet:javax.servlet-api:3.1.0"
|
||||||
|
compile "org.jolokia:jolokia-agent-war:$jolokia_version"
|
||||||
|
compile "commons-fileupload:commons-fileupload:1.3.2"
|
||||||
|
|
||||||
|
// 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}"
|
||||||
|
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
|
||||||
|
// NOTE there is a Jackson version clash between jersey-media-json-jackson (v2.5.4) and jackson-module-kotlin (v.2.5.5)
|
||||||
|
// Have not found an Issue in the issue tracker for Jersey for this issue
|
||||||
|
compile ("org.glassfish.jersey.media:jersey-media-json-jackson:${jersey_version}") {
|
||||||
|
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
|
||||||
|
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
|
||||||
|
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
|
||||||
|
}
|
||||||
|
|
||||||
|
compile ("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}") {
|
||||||
|
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
|
||||||
|
}
|
||||||
|
|
||||||
|
testCompile "junit:junit:$junit_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
task integrationTest(type: Test) {
|
||||||
|
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||||
|
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name = 'corda-webserver'
|
||||||
|
publishWar = true
|
||||||
|
}
|
||||||
|
|
||||||
|
war {
|
||||||
|
baseName = 'corda-webserver'
|
||||||
|
manifest {
|
||||||
|
attributes('Corda-Version': corda_version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
runtimeArtifacts war
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package net.corda.webserver
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.node.driver.addressMustBeBound
|
||||||
|
import net.corda.node.driver.addressMustNotBeBound
|
||||||
|
import net.corda.node.driver.driver
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class DriverTests {
|
||||||
|
companion object {
|
||||||
|
val executorService = Executors.newScheduledThreadPool(2)
|
||||||
|
|
||||||
|
fun webserverMustBeUp(webserverAddr: HostAndPort) {
|
||||||
|
addressMustBeBound(executorService, webserverAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun webserverMustBeDown(webserverAddr: HostAndPort) {
|
||||||
|
addressMustNotBeBound(executorService, webserverAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `starting a node and independent web server works`() {
|
||||||
|
val addr = driver {
|
||||||
|
val node = startNode("test").getOrThrow()
|
||||||
|
val webserverAddr = startWebserver(node).getOrThrow()
|
||||||
|
webserverMustBeUp(webserverAddr)
|
||||||
|
webserverAddr
|
||||||
|
}
|
||||||
|
webserverMustBeDown(addr)
|
||||||
|
}
|
||||||
|
}
|
@ -1,181 +1,71 @@
|
|||||||
package net.corda.node.webserver
|
@file:JvmName("WebServer")
|
||||||
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
package net.corda.webserver
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
|
||||||
import net.corda.core.utilities.loggerFor
|
import com.typesafe.config.ConfigException
|
||||||
import net.corda.node.printBasicNodeInfo
|
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.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingComponent
|
import net.corda.webserver.internal.NodeWebServer
|
||||||
import net.corda.node.services.messaging.CordaRPCClient
|
import org.slf4j.LoggerFactory
|
||||||
import net.corda.node.webserver.internal.APIServerImpl
|
import java.lang.management.ManagementFactory
|
||||||
import net.corda.node.webserver.servlets.AttachmentDownloadServlet
|
import java.net.InetAddress
|
||||||
import net.corda.node.webserver.servlets.DataUploadServlet
|
import kotlin.system.exitProcess
|
||||||
import net.corda.node.webserver.servlets.ObjectMapperConfig
|
|
||||||
import net.corda.node.webserver.servlets.ResponseFilter
|
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
|
||||||
import org.eclipse.jetty.server.*
|
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet
|
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder
|
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory
|
|
||||||
import org.eclipse.jetty.webapp.WebAppContext
|
|
||||||
import org.glassfish.jersey.server.ResourceConfig
|
|
||||||
import org.glassfish.jersey.server.ServerProperties
|
|
||||||
import org.glassfish.jersey.servlet.ServletContainer
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// TODO: Split into a separate module under client that packages into WAR formats.
|
fun main(args: Array<String>) {
|
||||||
class WebServer(val config: FullNodeConfiguration) {
|
val startTime = System.currentTimeMillis()
|
||||||
private companion object {
|
val argsParser = ArgsParser()
|
||||||
val log = loggerFor<WebServer>()
|
|
||||||
val retryDelay = 1000L // Milliseconds
|
val cmdlineOptions = try {
|
||||||
|
argsParser.parse(*args)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
println("Unknown command line arguments: ${ex.message}")
|
||||||
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val address = config.webAddress
|
// Maybe render command line help.
|
||||||
private lateinit var server: Server
|
if (cmdlineOptions.help) {
|
||||||
|
argsParser.printHelp(System.out)
|
||||||
fun start() {
|
exitProcess(0)
|
||||||
printBasicNodeInfo("Starting as webserver: ${config.webAddress}")
|
|
||||||
server = initWebServer(retryConnectLocalRpc())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun run() {
|
// Set up logging.
|
||||||
while (server.isRunning) {
|
if (cmdlineOptions.logToConsole) {
|
||||||
Thread.sleep(100) // TODO: Redesign
|
// This property is referenced from the XML config file.
|
||||||
}
|
System.setProperty("consoleLogLevel", "info")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initWebServer(localRpc: CordaRPCOps): Server {
|
System.setProperty("log-path", (cmdlineOptions.baseDirectory / "web/logs").toString())
|
||||||
// Note that the web server handlers will all run concurrently, and not on the node thread.
|
val log = LoggerFactory.getLogger("Main")
|
||||||
val handlerCollection = HandlerCollection()
|
println("Logs can be found in ${System.getProperty("log-path")}")
|
||||||
|
|
||||||
// TODO: Move back into the node itself.
|
val conf = try {
|
||||||
// Export JMX monitoring statistics and data over REST/JSON.
|
FullNodeConfiguration(cmdlineOptions.baseDirectory, cmdlineOptions.loadConfig())
|
||||||
if (config.exportJMXto.split(',').contains("http")) {
|
} catch (e: ConfigException) {
|
||||||
val classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator"))
|
println("Unable to load the configuration file: ${e.rootCause.message}")
|
||||||
val warpath = classpath.firstOrNull { it.contains("jolokia-agent-war-2") && it.endsWith(".war") }
|
exitProcess(2)
|
||||||
if (warpath != null) {
|
|
||||||
handlerCollection.addHandler(WebAppContext().apply {
|
|
||||||
// Find the jolokia WAR file on the classpath.
|
|
||||||
contextPath = "/monitoring/json"
|
|
||||||
setInitParameter("mimeType", "application/json")
|
|
||||||
war = warpath
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
log.warn("Unable to locate Jolokia WAR on classpath")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API, data upload and download to services (attachments, rates oracles etc)
|
log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
|
||||||
handlerCollection.addHandler(buildServletContextHandler(localRpc))
|
val info = ManagementFactory.getRuntimeMXBean()
|
||||||
|
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
||||||
|
log.info("Application Args: ${args.joinToString(" ")}")
|
||||||
|
log.info("bootclasspath: ${info.bootClassPath}")
|
||||||
|
log.info("classpath: ${info.classPath}")
|
||||||
|
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
||||||
|
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
||||||
|
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
||||||
|
log.info("Starting as webserver on ${conf.webAddress}")
|
||||||
|
|
||||||
val server = Server()
|
|
||||||
|
|
||||||
val connector = if (config.useHTTPS) {
|
|
||||||
val httpsConfiguration = HttpConfiguration()
|
|
||||||
httpsConfiguration.outputBufferSize = 32768
|
|
||||||
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
|
|
||||||
val sslContextFactory = SslContextFactory()
|
|
||||||
sslContextFactory.keyStorePath = config.keyStoreFile.toString()
|
|
||||||
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
|
|
||||||
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
|
|
||||||
sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())
|
|
||||||
sslContextFactory.setTrustStorePassword(config.trustStorePassword)
|
|
||||||
sslContextFactory.setExcludeProtocols("SSL.*", "TLSv1", "TLSv1.1")
|
|
||||||
sslContextFactory.setIncludeProtocols("TLSv1.2")
|
|
||||||
sslContextFactory.setExcludeCipherSuites(".*NULL.*", ".*RC4.*", ".*MD5.*", ".*DES.*", ".*DSS.*")
|
|
||||||
sslContextFactory.setIncludeCipherSuites(".*AES.*GCM.*")
|
|
||||||
val sslConnector = ServerConnector(server, SslConnectionFactory(sslContextFactory, "http/1.1"), HttpConnectionFactory(httpsConfiguration))
|
|
||||||
sslConnector.port = address.port
|
|
||||||
sslConnector
|
|
||||||
} else {
|
|
||||||
val httpConfiguration = HttpConfiguration()
|
|
||||||
httpConfiguration.outputBufferSize = 32768
|
|
||||||
val httpConnector = ServerConnector(server, HttpConnectionFactory(httpConfiguration))
|
|
||||||
httpConnector.port = address.port
|
|
||||||
httpConnector
|
|
||||||
}
|
|
||||||
server.connectors = arrayOf<Connector>(connector)
|
|
||||||
|
|
||||||
server.handler = handlerCollection
|
|
||||||
server.start()
|
|
||||||
log.info("Started webserver on address $address")
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildServletContextHandler(localRpc: CordaRPCOps): ServletContextHandler {
|
|
||||||
return ServletContextHandler().apply {
|
|
||||||
contextPath = "/"
|
|
||||||
setAttribute("rpc", localRpc)
|
|
||||||
addServlet(DataUploadServlet::class.java, "/upload/*")
|
|
||||||
addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
|
|
||||||
|
|
||||||
val resourceConfig = ResourceConfig()
|
|
||||||
resourceConfig.register(ObjectMapperConfig(localRpc))
|
|
||||||
resourceConfig.register(ResponseFilter())
|
|
||||||
resourceConfig.register(APIServerImpl(localRpc))
|
|
||||||
|
|
||||||
val webAPIsOnClasspath = pluginRegistries.flatMap { x -> x.webApis }
|
|
||||||
for (webapi in webAPIsOnClasspath) {
|
|
||||||
log.info("Add plugin web API from attachment $webapi")
|
|
||||||
val customAPI = try {
|
|
||||||
webapi.apply(localRpc)
|
|
||||||
} catch (ex: InvocationTargetException) {
|
|
||||||
log.error("Constructor $webapi threw an error: ", ex.targetException)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resourceConfig.register(customAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
val staticDirMaps = pluginRegistries.map { x -> x.staticServeDirs }
|
|
||||||
val staticDirs = staticDirMaps.flatMap { it.keys }.zip(staticDirMaps.flatMap { it.values })
|
|
||||||
staticDirs.forEach {
|
|
||||||
val staticDir = ServletHolder(DefaultServlet::class.java)
|
|
||||||
staticDir.setInitParameter("resourceBase", it.second)
|
|
||||||
staticDir.setInitParameter("dirAllowed", "true")
|
|
||||||
staticDir.setInitParameter("pathInfoOnly", "true")
|
|
||||||
addServlet(staticDir, "/web/${it.first}/*")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX
|
|
||||||
resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api",
|
|
||||||
ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED to "true"))
|
|
||||||
|
|
||||||
val container = ServletContainer(resourceConfig)
|
|
||||||
val jerseyServlet = ServletHolder(container)
|
|
||||||
addServlet(jerseyServlet, "/api/*")
|
|
||||||
jerseyServlet.initOrder = 0 // Initialise at server start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retryConnectLocalRpc(): CordaRPCOps {
|
|
||||||
while (true) {
|
|
||||||
try {
|
try {
|
||||||
return connectLocalRpcAsNodeUser()
|
val server = NodeWebServer(conf)
|
||||||
} catch (e: ActiveMQNotConnectedException) {
|
server.start()
|
||||||
log.debug("Could not connect to ${config.artemisAddress} due to exception: ", e)
|
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
||||||
Thread.sleep(retryDelay)
|
println("Webserver started up in $elapsed sec")
|
||||||
// This error will happen if the server has yet to create the keystore
|
server.run()
|
||||||
// Keep the fully qualified package name due to collisions with the Kotlin stdlib
|
} catch (e: Exception) {
|
||||||
// exception of the same name
|
log.error("Exception during node startup", e)
|
||||||
} catch (e: java.nio.file.NoSuchFileException) {
|
exitProcess(1)
|
||||||
log.debug("Tried to open a file that doesn't yet exist, retrying", e)
|
|
||||||
Thread.sleep(retryDelay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
|
|
||||||
log.info("Connecting to node at ${config.artemisAddress} as node user")
|
|
||||||
val client = CordaRPCClient(config.artemisAddress, config)
|
|
||||||
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
|
|
||||||
return client.proxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fetch CordaPluginRegistry classes registered in META-INF/services/net.corda.core.node.CordaPluginRegistry files that exist in the classpath */
|
|
||||||
val pluginRegistries: List<CordaPluginRegistry> by lazy {
|
|
||||||
ServiceLoader.load(CordaPluginRegistry::class.java).toList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.webserver.api
|
package net.corda.webserver.api
|
||||||
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.webserver.api
|
package net.corda.webserver.api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extremely rudimentary query language which should most likely be replaced with a product.
|
* Extremely rudimentary query language which should most likely be replaced with a product.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.node.webserver.internal
|
package net.corda.webserver.internal
|
||||||
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.node.webserver.api.*
|
import net.corda.webserver.api.*
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import javax.ws.rs.core.Response
|
import javax.ws.rs.core.Response
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
package net.corda.webserver.internal
|
||||||
|
|
||||||
|
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.node.services.messaging.ArtemisMessagingComponent
|
||||||
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
|
import net.corda.webserver.servlets.AttachmentDownloadServlet
|
||||||
|
import net.corda.webserver.servlets.DataUploadServlet
|
||||||
|
import net.corda.webserver.servlets.ObjectMapperConfig
|
||||||
|
import net.corda.webserver.servlets.ResponseFilter
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
|
import org.eclipse.jetty.server.*
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||||
|
import org.eclipse.jetty.webapp.WebAppContext
|
||||||
|
import org.glassfish.jersey.server.ResourceConfig
|
||||||
|
import org.glassfish.jersey.server.ServerProperties
|
||||||
|
import org.glassfish.jersey.servlet.ServletContainer
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class NodeWebServer(val config: FullNodeConfiguration) {
|
||||||
|
private companion object {
|
||||||
|
val log = loggerFor<NodeWebServer>()
|
||||||
|
val retryDelay = 1000L // Milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
val address = config.webAddress
|
||||||
|
private lateinit var server: Server
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
printBasicNodeInfo("Starting as webserver: ${config.webAddress}")
|
||||||
|
server = initWebServer(retryConnectLocalRpc())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run() {
|
||||||
|
while (server.isRunning) {
|
||||||
|
Thread.sleep(100) // TODO: Redesign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initWebServer(localRpc: CordaRPCOps): Server {
|
||||||
|
// Note that the web server handlers will all run concurrently, and not on the node thread.
|
||||||
|
val handlerCollection = HandlerCollection()
|
||||||
|
|
||||||
|
// Export JMX monitoring statistics and data over REST/JSON.
|
||||||
|
if (config.exportJMXto.split(',').contains("http")) {
|
||||||
|
val classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator"))
|
||||||
|
val warpath = classpath.firstOrNull { it.contains("jolokia-agent-war-2") && it.endsWith(".war") }
|
||||||
|
if (warpath != null) {
|
||||||
|
handlerCollection.addHandler(WebAppContext().apply {
|
||||||
|
// Find the jolokia WAR file on the classpath.
|
||||||
|
contextPath = "/monitoring/json"
|
||||||
|
setInitParameter("mimeType", "application/json")
|
||||||
|
war = warpath
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.warn("Unable to locate Jolokia WAR on classpath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API, data upload and download to services (attachments, rates oracles etc)
|
||||||
|
handlerCollection.addHandler(buildServletContextHandler(localRpc))
|
||||||
|
|
||||||
|
val server = Server()
|
||||||
|
|
||||||
|
val connector = if (config.useHTTPS) {
|
||||||
|
val httpsConfiguration = HttpConfiguration()
|
||||||
|
httpsConfiguration.outputBufferSize = 32768
|
||||||
|
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
|
||||||
|
val sslContextFactory = SslContextFactory()
|
||||||
|
sslContextFactory.keyStorePath = config.keyStoreFile.toString()
|
||||||
|
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
|
||||||
|
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
|
||||||
|
sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())
|
||||||
|
sslContextFactory.setTrustStorePassword(config.trustStorePassword)
|
||||||
|
sslContextFactory.setExcludeProtocols("SSL.*", "TLSv1", "TLSv1.1")
|
||||||
|
sslContextFactory.setIncludeProtocols("TLSv1.2")
|
||||||
|
sslContextFactory.setExcludeCipherSuites(".*NULL.*", ".*RC4.*", ".*MD5.*", ".*DES.*", ".*DSS.*")
|
||||||
|
sslContextFactory.setIncludeCipherSuites(".*AES.*GCM.*")
|
||||||
|
val sslConnector = ServerConnector(server, SslConnectionFactory(sslContextFactory, "http/1.1"), HttpConnectionFactory(httpsConfiguration))
|
||||||
|
sslConnector.port = address.port
|
||||||
|
sslConnector
|
||||||
|
} else {
|
||||||
|
val httpConfiguration = HttpConfiguration()
|
||||||
|
httpConfiguration.outputBufferSize = 32768
|
||||||
|
val httpConnector = ServerConnector(server, HttpConnectionFactory(httpConfiguration))
|
||||||
|
log.info("Starting webserver on address $address")
|
||||||
|
httpConnector.port = address.port
|
||||||
|
httpConnector
|
||||||
|
}
|
||||||
|
server.connectors = arrayOf<Connector>(connector)
|
||||||
|
|
||||||
|
server.handler = handlerCollection
|
||||||
|
//runOnStop += Runnable { server.stop() }
|
||||||
|
server.start()
|
||||||
|
log.info("Server started")
|
||||||
|
log.info("Embedded web server is listening on", "http://${InetAddress.getLocalHost().hostAddress}:${connector.port}/")
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildServletContextHandler(localRpc: CordaRPCOps): ServletContextHandler {
|
||||||
|
return ServletContextHandler().apply {
|
||||||
|
contextPath = "/"
|
||||||
|
setAttribute("rpc", localRpc)
|
||||||
|
addServlet(DataUploadServlet::class.java, "/upload/*")
|
||||||
|
addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
|
||||||
|
|
||||||
|
val resourceConfig = ResourceConfig()
|
||||||
|
resourceConfig.register(ObjectMapperConfig(localRpc))
|
||||||
|
resourceConfig.register(ResponseFilter())
|
||||||
|
resourceConfig.register(APIServerImpl(localRpc))
|
||||||
|
|
||||||
|
val webAPIsOnClasspath = pluginRegistries.flatMap { x -> x.webApis }
|
||||||
|
for (webapi in webAPIsOnClasspath) {
|
||||||
|
log.info("Add plugin web API from attachment $webapi")
|
||||||
|
val customAPI = try {
|
||||||
|
webapi.apply(localRpc)
|
||||||
|
} catch (ex: InvocationTargetException) {
|
||||||
|
log.error("Constructor $webapi threw an error: ", ex.targetException)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resourceConfig.register(customAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
val staticDirMaps = pluginRegistries.map { x -> x.staticServeDirs }
|
||||||
|
val staticDirs = staticDirMaps.flatMap { it.keys }.zip(staticDirMaps.flatMap { it.values })
|
||||||
|
staticDirs.forEach {
|
||||||
|
val staticDir = ServletHolder(DefaultServlet::class.java)
|
||||||
|
staticDir.setInitParameter("resourceBase", it.second)
|
||||||
|
staticDir.setInitParameter("dirAllowed", "true")
|
||||||
|
staticDir.setInitParameter("pathInfoOnly", "true")
|
||||||
|
addServlet(staticDir, "/web/${it.first}/*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX
|
||||||
|
resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api",
|
||||||
|
ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED to "true"))
|
||||||
|
|
||||||
|
val container = ServletContainer(resourceConfig)
|
||||||
|
val jerseyServlet = ServletHolder(container)
|
||||||
|
addServlet(jerseyServlet, "/api/*")
|
||||||
|
jerseyServlet.initOrder = 0 // Initialise at server start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun retryConnectLocalRpc(): CordaRPCOps {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return connectLocalRpcAsNodeUser()
|
||||||
|
} catch (e: ActiveMQNotConnectedException) {
|
||||||
|
log.debug("Could not connect to ${config.artemisAddress} due to exception: ", e)
|
||||||
|
Thread.sleep(retryDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
|
||||||
|
log.info("Connecting to node at ${config.artemisAddress} as node user")
|
||||||
|
val client = CordaRPCClient(config.artemisAddress, config)
|
||||||
|
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
|
||||||
|
return client.proxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch CordaPluginRegistry classes registered in META-INF/services/net.corda.core.node.CordaPluginRegistry files that exist in the classpath */
|
||||||
|
val pluginRegistries: List<CordaPluginRegistry> by lazy {
|
||||||
|
ServiceLoader.load(CordaPluginRegistry::class.java).toList()
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.webserver.servlets
|
package net.corda.webserver.servlets
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.node.services.StorageService
|
import net.corda.core.node.services.StorageService
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package net.corda.node.webserver.servlets
|
package net.corda.webserver.servlets
|
||||||
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.internal.Node
|
|
||||||
import net.corda.node.services.api.AcceptsFileUpload
|
|
||||||
import org.apache.commons.fileupload.servlet.ServletFileUpload
|
import org.apache.commons.fileupload.servlet.ServletFileUpload
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.servlet.http.HttpServlet
|
import javax.servlet.http.HttpServlet
|
||||||
@ -11,7 +9,7 @@ import javax.servlet.http.HttpServletRequest
|
|||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts binary streams, finds the right [AcceptsFileUpload] implementor and hands the stream off to it.
|
* Uploads to the node via the [CordaRPCOps] uploadFile interface.
|
||||||
*/
|
*/
|
||||||
class DataUploadServlet: HttpServlet() {
|
class DataUploadServlet: HttpServlet() {
|
||||||
private val log = loggerFor<DataUploadServlet>()
|
private val log = loggerFor<DataUploadServlet>()
|
||||||
@ -41,6 +39,7 @@ class DataUploadServlet: HttpServlet() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val dataType = req.pathInfo.substring(1).substringBefore('/')
|
val dataType = req.pathInfo.substring(1).substringBefore('/')
|
||||||
|
@Suppress("DEPRECATION") // TODO: Replace the use of uploadFile
|
||||||
messages += rpc.uploadFile(dataType, item.name, item.openStream())
|
messages += rpc.uploadFile(dataType, item.name, item.openStream())
|
||||||
log.info("${item.name} successfully accepted: ${messages.last()}")
|
log.info("${item.name} successfully accepted: ${messages.last()}")
|
||||||
} catch(e: RuntimeException) {
|
} catch(e: RuntimeException) {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package net.corda.node.webserver.servlets
|
package net.corda.webserver.servlets
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.webserver.utilities.JsonSupport
|
||||||
import net.corda.node.utilities.JsonSupport
|
|
||||||
import javax.ws.rs.ext.ContextResolver
|
import javax.ws.rs.ext.ContextResolver
|
||||||
import javax.ws.rs.ext.Provider
|
import javax.ws.rs.ext.Provider
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.webserver.servlets
|
package net.corda.webserver.servlets
|
||||||
|
|
||||||
import javax.ws.rs.container.ContainerRequestContext
|
import javax.ws.rs.container.ContainerRequestContext
|
||||||
import javax.ws.rs.container.ContainerResponseContext
|
import javax.ws.rs.container.ContainerResponseContext
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.utilities
|
package net.corda.webserver.utilities
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
import com.fasterxml.jackson.core.JsonParseException
|
import com.fasterxml.jackson.core.JsonParseException
|
@ -1,11 +1,10 @@
|
|||||||
package net.corda.node
|
package net.corda.webserver
|
||||||
|
|
||||||
import com.pholser.junit.quickcheck.From
|
import com.pholser.junit.quickcheck.From
|
||||||
import com.pholser.junit.quickcheck.Property
|
import com.pholser.junit.quickcheck.Property
|
||||||
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
|
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
|
||||||
import net.corda.core.testing.PublicKeyGenerator
|
import net.corda.core.testing.PublicKeyGenerator
|
||||||
import net.corda.node.utilities.JsonSupport
|
import net.corda.webserver.utilities.JsonSupport
|
||||||
import net.corda.testing.node.MockIdentityService
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
@ -1 +1 @@
|
|||||||
gradlePluginsVersion=0.8.2
|
gradlePluginsVersion=0.8.3
|
||||||
|
@ -46,6 +46,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':test-utils')
|
compile project(':test-utils')
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':client')
|
compile project(':client')
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
|
@ -49,8 +49,10 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':finance')
|
compile project(':finance')
|
||||||
|
compile project(':node:webserver')
|
||||||
compile project(':test-utils')
|
compile project(':test-utils')
|
||||||
|
|
||||||
// Javax is required for webapis
|
// Javax is required for webapis
|
||||||
|
@ -24,6 +24,7 @@ import net.corda.testing.node.InMemoryMessagingNetwork
|
|||||||
import net.corda.testing.node.MockIdentityService
|
import net.corda.testing.node.MockIdentityService
|
||||||
import net.i2p.crypto.eddsa.KeyPairGenerator
|
import net.i2p.crypto.eddsa.KeyPairGenerator
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import net.corda.webserver.utilities.JsonSupport
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ import java.util.*
|
|||||||
* A simulation in which banks execute interest rate swaps with each other, including the fixing events.
|
* A simulation in which banks execute interest rate swaps with each other, including the fixing events.
|
||||||
*/
|
*/
|
||||||
class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(networkSendManuallyPumped, runAsync, latencyInjector) {
|
class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(networkSendManuallyPumped, runAsync, latencyInjector) {
|
||||||
val om = net.corda.node.utilities.JsonSupport.createInMemoryMapper(MockIdentityService(network.identities))
|
val om = JsonSupport.createInMemoryMapper(MockIdentityService(network.identities))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
currentDateAndTime = LocalDate.of(2016, 3, 8).atStartOfDay()
|
currentDateAndTime = LocalDate.of(2016, 3, 8).atStartOfDay()
|
||||||
|
@ -22,6 +22,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':finance')
|
compile project(':finance')
|
||||||
testCompile project(':test-utils')
|
testCompile project(':test-utils')
|
||||||
|
@ -46,6 +46,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':client')
|
compile project(':client')
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
|
@ -54,6 +54,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
compile project(':finance')
|
compile project(':finance')
|
||||||
|
@ -46,6 +46,7 @@ dependencies {
|
|||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':finance')
|
compile project(':finance')
|
||||||
compile project(':test-utils')
|
compile project(':test-utils')
|
||||||
|
@ -26,6 +26,7 @@ dependencies {
|
|||||||
compile project(':finance')
|
compile project(':finance')
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
|
compile project(':node:webserver')
|
||||||
|
|
||||||
// Log4J: logging framework (with SLF4J bindings)
|
// Log4J: logging framework (with SLF4J bindings)
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||||
|
@ -3,7 +3,7 @@ package net.corda.testing.http
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.utilities.JsonSupport
|
import net.corda.webserver.utilities.JsonSupport
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user