mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Moved the webserver code into the main node module. Driver can now start webserver for nodes.
This commit is contained in:
parent
ecfb762143
commit
5f4d4c1da3
@ -1,5 +1,6 @@
|
||||
package net.corda.node.driver
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
@ -25,6 +26,14 @@ class DriverTests {
|
||||
// Check that the port is bound
|
||||
addressMustNotBeBound(executorService, hostAndPort)
|
||||
}
|
||||
|
||||
fun webserverMustBeUp(webserverAddr: HostAndPort) {
|
||||
addressMustBeBound(executorService, webserverAddr)
|
||||
}
|
||||
|
||||
fun webserverMustBeDown(webserverAddr: HostAndPort) {
|
||||
addressMustNotBeBound(executorService, webserverAddr)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -60,4 +69,15 @@ class DriverTests {
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
package net.corda.node.driver
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
@ -12,7 +10,6 @@ import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
@ -22,21 +19,21 @@ import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.config.NodeSSLConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingComponent
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.node.services.messaging.CordaRPCClient
|
||||
import net.corda.node.services.messaging.NodeMessagingClient
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.utilities.JsonSupport
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import org.slf4j.Logger
|
||||
import java.io.File
|
||||
import java.net.*
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset.UTC
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
@ -88,6 +85,13 @@ interface DriverDSLExposedInterface {
|
||||
type: ServiceType = RaftValidatingNotaryService.type,
|
||||
rpcUsers: List<User> = emptyList()): Future<Pair<Party, List<NodeHandle>>>
|
||||
|
||||
/**
|
||||
* Starts a web server for a node
|
||||
*
|
||||
* @param handle The handle for the node that this webserver connects to via RPC.
|
||||
*/
|
||||
fun startWebserver(handle: NodeHandle): Future<HostAndPort>
|
||||
|
||||
fun waitForAllNodesToFinish()
|
||||
}
|
||||
|
||||
@ -322,16 +326,20 @@ open class DriverDSL(
|
||||
executorService.shutdown()
|
||||
}
|
||||
|
||||
private fun queryNodeInfo(webAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
|
||||
try {
|
||||
val client = CordaRPCClient(webAddress, sslConfig)
|
||||
private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
|
||||
var retries = 0
|
||||
while (retries < 5) try {
|
||||
val client = CordaRPCClient(nodeAddress, sslConfig)
|
||||
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
|
||||
val rpcOps = client.proxy()
|
||||
return rpcOps.nodeIdentity()
|
||||
val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS))
|
||||
return rpcOps.nodeIdentity()
|
||||
} catch(e: Exception) {
|
||||
log.error("Could not query node info at $webAddress due to an exception.", e)
|
||||
return null
|
||||
log.error("Retrying query node info at $nodeAddress")
|
||||
retries++
|
||||
}
|
||||
|
||||
log.error("Could not query node info after $retries retries")
|
||||
return null
|
||||
}
|
||||
|
||||
override fun startNode(providedName: String?, advertisedServices: Set<ServiceInfo>,
|
||||
@ -408,6 +416,15 @@ open class DriverDSL(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startWebserver(handle: NodeHandle): Future<HostAndPort> {
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
|
||||
return future {
|
||||
registerProcess(DriverDSL.startWebserver(executorService, handle.configuration, debugPort))
|
||||
handle.configuration.webAddress
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
startNetworkMapService()
|
||||
}
|
||||
@ -480,11 +497,37 @@ open class DriverDSL(
|
||||
builder.inheritIO()
|
||||
builder.directory(nodeConf.baseDirectory.toFile())
|
||||
val process = builder.start()
|
||||
return Futures.allAsList(
|
||||
addressMustBeBound(executorService, nodeConf.artemisAddress),
|
||||
// TODO There is a race condition here. Even though the messaging address is bound it may be the case that
|
||||
// the handlers for the advertised services are not yet registered. Needs rethinking.
|
||||
).map { process }
|
||||
// TODO There is a race condition here. Even though the messaging address is bound it may be the case that
|
||||
// the handlers for the advertised services are not yet registered. Needs rethinking.
|
||||
return addressMustBeBound(executorService, nodeConf.artemisAddress).map { process }
|
||||
}
|
||||
|
||||
private fun startWebserver(
|
||||
executorService: ScheduledExecutorService,
|
||||
nodeConf: FullNodeConfiguration,
|
||||
debugPort: Int?): ListenableFuture<Process> {
|
||||
val className = "net.corda.node.webserver.MainKt" // cannot directly get class for this, so just use string
|
||||
val separator = System.getProperty("file.separator")
|
||||
val classpath = System.getProperty("java.class.path")
|
||||
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
||||
|
||||
val debugPortArg = if (debugPort != null)
|
||||
listOf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
|
||||
else
|
||||
emptyList()
|
||||
|
||||
val javaArgs = listOf(path) +
|
||||
listOf("-Dname=node-${nodeConf.artemisAddress}-webserver") + debugPortArg +
|
||||
listOf(
|
||||
"-cp", classpath, className,
|
||||
"--base-directory", nodeConf.baseDirectory.toString(),
|
||||
"--web-address", nodeConf.webAddress.toString())
|
||||
val builder = ProcessBuilder(javaArgs)
|
||||
builder.redirectError(Paths.get("error.$className.log").toFile())
|
||||
builder.inheritIO()
|
||||
builder.directory(nodeConf.baseDirectory.toFile())
|
||||
val process = builder.start()
|
||||
return addressMustBeBound(executorService, nodeConf.webAddress).map { process }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,12 @@ import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.div
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.success
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.printBasicNodeInfo
|
||||
import net.corda.node.serialization.NodeClock
|
||||
@ -20,39 +19,19 @@ import net.corda.node.services.RPCUserService
|
||||
import net.corda.node.services.RPCUserServiceImpl
|
||||
import net.corda.node.services.api.MessagingServiceInternal
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.node.services.messaging.ArtemisMessagingComponent.NetworkMapAddress
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.node.services.messaging.CordaRPCClient
|
||||
import net.corda.node.services.messaging.NodeMessagingClient
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.services.transactions.RaftUniquenessProvider
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.servlets.AttachmentDownloadServlet
|
||||
import net.corda.node.servlets.Config
|
||||
import net.corda.node.servlets.DataUploadServlet
|
||||
import net.corda.node.servlets.ResponseFilter
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import org.eclipse.jetty.server.*
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.DefaultServlet
|
||||
import org.eclipse.jetty.servlet.FilterHolder
|
||||
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 org.jetbrains.exposed.sql.Database
|
||||
import java.io.RandomAccessFile
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.InetAddress
|
||||
import java.nio.channels.FileLock
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
import javax.management.ObjectName
|
||||
import javax.servlet.*
|
||||
import kotlin.concurrent.thread
|
||||
@ -293,6 +272,7 @@ class Node(override val configuration: FullNodeConfiguration,
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun init(filterConfig: FilterConfig?) {}
|
||||
override fun destroy() {}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.typesafe.config.Config
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun Config.getHostAndPort(name: String): HostAndPort = HostAndPort.fromString(getString(name))
|
||||
fun Config.getPath(name: String): Path = Paths.get(getString(name))
|
44
node/src/main/kotlin/net/corda/node/webserver/Main.kt
Normal file
44
node/src/main/kotlin/net/corda/node/webserver/Main.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package net.corda.node.webserver
|
||||
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// TODO: Print basic webserver info
|
||||
val parser = OptionParser()
|
||||
val baseDirectoryArg = parser.accepts("base-directory", "The directory to put all files under").withRequiredArg()
|
||||
val webAddressArg = parser.accepts("web-address", "The web address for this server to bind").withOptionalArg()
|
||||
val logToConsoleArg = parser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
|
||||
val helpArg = parser.accepts("help").forHelp()
|
||||
val cmdlineOptions = try {
|
||||
parser.parse(*args)
|
||||
} catch (ex: Exception) {
|
||||
println("Unknown command line arguments: ${ex.message}")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
// Maybe render command line help.
|
||||
if (cmdlineOptions.has(helpArg)) {
|
||||
parser.printHelpOn(System.out)
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
// Set up logging.
|
||||
if (cmdlineOptions.has(logToConsoleArg)) {
|
||||
// This property is referenced from the XML config file.
|
||||
System.setProperty("consoleLogLevel", "info")
|
||||
}
|
||||
|
||||
val baseDirectoryPath = Paths.get(cmdlineOptions.valueOf(baseDirectoryArg))
|
||||
val config = ConfigHelper.loadConfig(baseDirectoryPath)
|
||||
|
||||
println("Starting server")
|
||||
val nodeConf = FullNodeConfiguration(baseDirectoryPath, config)
|
||||
val server = WebServer(nodeConf).start()
|
||||
println("Exiting")
|
||||
}
|
@ -8,7 +8,7 @@ fun main(args: Array<String>) {
|
||||
System.setProperty("consoleLogLevel", "info")
|
||||
driver {
|
||||
val node = startNode().get()
|
||||
val server = WebServer(FullNodeConfiguration(node.config)).start()
|
||||
val server = WebServer(node.configuration).start()
|
||||
waitForAllNodesToFinish()
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
||||
description 'Corda Webserver'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'http://oss.sonatype.org/content/repositories/snapshots'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://dl.bintray.com/kotlin/exposed'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':node')
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
// 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:2.0.0-M1"
|
||||
compile "commons-fileupload:commons-fileupload:1.3.2"
|
||||
|
||||
// Log4J: logging framework (with SLF4J bindings)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
|
||||
compile "org.apache.logging.log4j:log4j-web:${log4j_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}"
|
||||
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}"
|
||||
}
|
||||
|
||||
task run(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.node.webserver.MainKt'
|
||||
}
|
@ -7,6 +7,7 @@ import net.corda.core.getOrThrow
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.node.utilities.getHostAndPort
|
||||
import org.junit.Test
|
||||
|
||||
class BankOfCordaHttpAPITest {
|
||||
|
@ -14,6 +14,7 @@ import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.testing.BOC_PARTY_REF
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.node.utilities.getHostAndPort
|
||||
import net.corda.testing.sequence
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
@ -8,6 +8,7 @@ import net.corda.node.driver.driver
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.node.utilities.getHostAndPort
|
||||
import org.junit.Test
|
||||
|
||||
class TraderDemoTest {
|
||||
|
@ -6,7 +6,6 @@ include 'finance:isolated'
|
||||
include 'core'
|
||||
include 'node'
|
||||
include 'node:capsule'
|
||||
include 'node:webserver'
|
||||
include 'client'
|
||||
include 'experimental'
|
||||
include 'experimental:sandbox'
|
||||
|
@ -4,6 +4,7 @@ package net.corda.testing
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -155,3 +156,5 @@ data class TestNodeConfiguration(
|
||||
override val emailAddress: String = "",
|
||||
override val exportJMXto: String = "",
|
||||
override val devMode: Boolean = true) : NodeConfiguration
|
||||
|
||||
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))
|
||||
|
Loading…
Reference in New Issue
Block a user