Added webserver to runnodes.

This commit is contained in:
Clinton Alexander 2017-01-25 13:45:39 +00:00 committed by Clinton Alexander
parent 13551a6b23
commit 3482452c8b
16 changed files with 75 additions and 57 deletions

View File

@ -82,11 +82,6 @@ interface CordaRPCOps : RPCOps {
*/ */
fun nodeIdentity(): NodeInfo fun nodeIdentity(): NodeInfo
/**
* Returns true if node is up and ready
*/
fun ready(): Boolean
/* /*
* Add note(s) to an existing Vault transaction * Add note(s) to an existing Vault transaction
*/ */
@ -107,9 +102,8 @@ interface CordaRPCOps : RPCOps {
*/ */
fun uploadAttachment(jar: InputStream): SecureHash fun uploadAttachment(jar: InputStream): SecureHash
/** @Suppress("DEPRECATION")
* Uploads a file by data type to the node and returns a meaningful string to the user @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
/** /**

View File

@ -218,14 +218,22 @@ interface KeyManagementService {
fun freshKey(): KeyPair fun freshKey(): KeyPair
} }
// TODO: Move and document // TODO: Move to a more appropriate location
/**
* An interface that denotes a service that can accept file uploads.
*/
interface FileUploader { interface FileUploader {
/** /**
* Accepts the data in the given input stream, and returns some sort of useful return message that will be sent * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent
* back to the user in the response. * back to the user in the response.
*/ */
fun upload(file: InputStream): String fun upload(file: InputStream): String
fun accepts(prefix: String): Boolean
/**
* Check if this service accepts this type of upload. For example if you are uploading interest rates this could
* be "my-service-interest-rates". Type here does not refer to file extentions or MIME types.
*/
fun accepts(type: String): Boolean
} }
/** /**
@ -244,7 +252,8 @@ interface StorageService {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */ /** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage val attachments: AttachmentStorage
/** Provides file uploads of arbitrary files to services **/ @Suppress("DEPRECATION")
@Deprecated("This service will be removed in a future milestone")
val uploaders: List<FileUploader> val uploaders: List<FileUploader>
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage

View File

@ -82,13 +82,16 @@ path to the node's base directory.
:messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally. :messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally.
:webAddress: The host and port on which the node is available for web operations. :webAddress: The host and port on which the bundled webserver will listen if it is started.
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one .. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative
Names contained within the development certificates. This is addition to requiring the ``/config/dev/corda_dev_ca.cer`` Names contained within the development certificates. This is addition to requiring the ``/config/dev/corda_dev_ca.cer``
root certificate be installed as a Trusted CA. root certificate be installed as a Trusted CA.
.. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field
is present the web server will start.
:extraAdvertisedServiceIds: A list of ServiceType id strings to be advertised to the NetworkMapService and thus be available :extraAdvertisedServiceIds: A list of ServiceType id strings to be advertised to the NetworkMapService and thus be available
when other nodes query the NetworkMapCache for supporting nodes. This can also include plugin services loaded from .jar when other nodes query the NetworkMapCache for supporting nodes. This can also include plugin services loaded from .jar
files in the plugins folder. Optionally, a custom advertised service name can be provided by appending it to the service files in the plugins folder. Optionally, a custom advertised service name can be provided by appending it to the service

View File

@ -34,21 +34,16 @@ of the node internal subsystems.
extensions to be created, or registered at startup. In particular: extensions to be created, or registered at startup. In particular:
a. The ``webApis`` property is a list of JAX-RS annotated REST access a. The ``webApis`` property is a list of JAX-RS annotated REST access
classes. These classes will be constructed by the embedded web server classes. These classes will be constructed by the bundled web server
and must have a single argument constructor taking a ``ServiceHub`` and must have a single argument constructor taking a ``CordaRpcOps``
reference. This reference provides access to functions such as querying reference. This will allow it to communicate with the node process
for states through the ``VaultService`` interface, or access to the via the RPC interface. These web APIs will not be available if the
``NetworkMapCache`` to identify services on remote nodes. The framework will bundled web server is not started.
provide a database transaction in scope during the lifetime of the web
call, so full access to database data is valid. Unlike
``servicePlugins`` the ``webApis`` cannot register new protocols, or
initiate threads. (N.B. The intent is to move the Web support into a
separate helper process using the RPC mechanism to control access.)
b. The ``staticServeDirs`` property maps static web content to virtual b. The ``staticServeDirs`` property maps static web content to virtual
paths and allows simple web demos to be distributed within the CorDapp paths and allows simple web demos to be distributed within the CorDapp
jars. (N.B. The intent is to move the Web support into a separate helper jars. These static serving directories will not be available if the
process using the RPC mechanism to control access.) bundled web server is not started.
c. The ``requiredFlows`` property is used to declare new protocols in c. The ``requiredFlows`` property is used to declare new protocols in
the plugin jar. Specifically the property must return a map with a key the plugin jar. Specifically the property must return a map with a key

View File

@ -12,10 +12,10 @@ App plugins
To create an app plugin you must you must extend from `CordaPluginRegistry`_. The JavaDoc contains To create an app plugin you must you must extend from `CordaPluginRegistry`_. The JavaDoc contains
specific details of the implementation, but you can extend the server in the following ways: specific details of the implementation, but you can extend the server in the following ways:
1. Required flows: Specify which flows will be whitelisted for use in your web APIs. 1. Required flows: Specify which flows will be whitelisted for use in your RPC calls.
2. Service plugins: Register your services (see below). 2. Service plugins: Register your services (see below).
3. Web APIs: You may register your own endpoints under /api/ of the built-in web server. 3. Web APIs: You may register your own endpoints under /api/ of the bundled web server.
4. Static web endpoints: You may register your own static serving directories for serving web content. 4. Static web endpoints: You may register your own static serving directories for serving web content from the web server.
5. Registering your additional classes used in RPC. 5. Registering your additional classes used in RPC.
Services Services

View File

@ -1,8 +1,9 @@
Node administration Node administration
=================== ===================
When a node is running, it exposes an embedded database server, an embedded web server that lets you monitor it, When a node is running, it exposes an RPC interface that lets you monitor it,
you can upload and download attachments, access a REST API and so on. you can upload and download attachments, access a REST API and so on. A bundled
Jetty web server exposes the same interface over HTTP.
Logging Logging
------- -------

View File

@ -346,3 +346,11 @@ external legacy systems by insertion of unpacked data into existing
tables. To enable these features the contract state must implement the tables. To enable these features the contract state must implement the
``QueryableState`` interface to define the mappings. ``QueryableState`` interface to define the mappings.
Node Web Server
---------------
A web server comes bundled with the node by default, but is not started
automatically. This web server exposes both RPC backed API calls and
static content serving. The web server is not automatically started,
you must explicitly start it in the node driver or define a web port
in your `Cordformation`_ configuration.

View File

@ -45,7 +45,8 @@ The most important fields regarding network configuration are:
* ``artemisAddress``: This specifies a host and port. Note that the address bound will **NOT** be ``my-corda-node``, * ``artemisAddress``: This specifies a host and port. Note that the address bound will **NOT** be ``my-corda-node``,
but rather ``::`` (all addresses on all interfaces). The hostname specified is the hostname *that must be externally but rather ``::`` (all addresses on all interfaces). The hostname specified is the hostname *that must be externally
resolvable by other nodes in the network*. In the above configuration this is the resolvable name of a machine in a vpn. resolvable by other nodes in the network*. In the above configuration this is the resolvable name of a machine in a vpn.
* ``webAddress``: The address the webserver should bind. Note that the port should be distinct from that of ``artemisAddress``. * ``webAddress``: The address the webserver should bind. Note that the port should be distinct from that of ``artemisAddress``
if they are on the same machine.
* ``networkMapService``: Details of the node running the network map service. If it's this node that's running the service * ``networkMapService``: Details of the node running the network map service. If it's this node that's running the service
then this field must not be specified. then this field must not be specified.
@ -58,7 +59,9 @@ You should see a banner, some log lines and eventually ``Node started up and reg
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound. .. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
In terms of process management there is no prescribed method. You may start the jars by hand or perhaps use systemd and friends. In terms of process management there is no prescribed method. You may start the jars by hand or perhaps use systemd and friends. If you have
deployed your nodes via `Cordformation`_ you can run them with the ``runnodes`` scripts in ``build/nodes`` of your project. This is primarily
for development and testing purposes and will start all of the nodes you have created on the same machine
Logging Logging
~~~~~~~ ~~~~~~~

View File

@ -24,6 +24,16 @@ if which osascript >/dev/null; then
first=false first=false
fi fi
done done
for dir in `ls`; do
if [ -d $dir ]; then
cmd="bash -c 'cd $rootdir/$dir; /usr/libexec/java_home -v 1.8 --exec java -jar JAR_NAME --webserver && exit'"
script="$script
tell application \"System Events\" to tell process \"Terminal\" to keystroke \"t\" using command down
delay 0.5
do script \"$cmd\" in window 1"
first=false
fi
done
script="$script script="$script
end tell" end tell"
osascript -e "$script" osascript -e "$script"
@ -39,6 +49,7 @@ else
if [ -d $dir ]; then if [ -d $dir ]; then
pushd $dir >/dev/null pushd $dir >/dev/null
xterm -T "`basename $dir`" -e 'java -jar JAR_NAME' & xterm -T "`basename $dir`" -e 'java -jar JAR_NAME' &
xterm -T "`basename $dir`" -e 'java -jar JAR_NAME --webserver' &
popd >/dev/null popd >/dev/null
fi fi
done done

View File

@ -6,6 +6,7 @@ Pushd %~dp0
FOR /D %%G in (.\*) DO ( FOR /D %%G in (.\*) DO (
Pushd %%G Pushd %%G
start java -jar corda.jar start java -jar corda.jar
start java -jar corda.jar --webserver
Popd Popd
) )

View File

@ -80,7 +80,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}")
log.info("Starting as webserver: ${cmdlineOptions.isWebserver}") if(cmdlineOptions.isWebserver) {
log.info("Starting as webserver on ${conf.webAddress}")
} else {
log.info("Starting as node on ${conf.artemisAddress}")
}
try { try {
cmdlineOptions.baseDirectory.createDirectories() cmdlineOptions.baseDirectory.createDirectories()

View File

@ -17,7 +17,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeSSLConfiguration import net.corda.node.services.config.SSLConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.CordaRPCClient import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.messaging.NodeMessagingClient
@ -327,7 +327,7 @@ open class DriverDSL(
executorService.shutdown() executorService.shutdown()
} }
private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? { private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: SSLConfiguration): NodeInfo? {
var retries = 0 var retries = 0
while (retries < 5) try { while (retries < 5) try {
val client = CordaRPCClient(nodeAddress, sslConfig) val client = CordaRPCClient(nodeAddress, sslConfig)
@ -335,7 +335,7 @@ open class DriverDSL(
val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS)) val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS))
return rpcOps.nodeIdentity() return rpcOps.nodeIdentity()
} catch(e: Exception) { } catch(e: Exception) {
log.debug("Retrying query node info at $nodeAddress") log.error("Retrying query node info at $nodeAddress")
retries++ retries++
} }

View File

@ -222,7 +222,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
customServices.addAll(buildPluginServices(tokenizableServices)) customServices.addAll(buildPluginServices(tokenizableServices))
val uploaders: List<FileUploader> = listOf(storageServices.first.attachments as NodeAttachmentService) + val uploaders: List<FileUploader> = listOf(storageServices.first.attachments as NodeAttachmentService) +
customServices.filter { it is AcceptsFileUpload }.map { it as AcceptsFileUpload } customServices.filterIsInstance(AcceptsFileUpload::class.java)
(storage as StorageServiceImpl).initUploaders(uploaders) (storage as StorageServiceImpl).initUploaders(uploaders)
// TODO: uniquenessProvider creation should be inside makeNotaryService(), but notary service initialisation // TODO: uniquenessProvider creation should be inside makeNotaryService(), but notary service initialisation

View File

@ -78,10 +78,6 @@ class CordaRPCOpsImpl(
return services.myInfo return services.myInfo
} }
override fun ready(): Boolean {
return true
}
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) { override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
return databaseTransaction(database) { return databaseTransaction(database) {
services.vaultService.addNoteToTransaction(txnId, txnNote) services.vaultService.addNoteToTransaction(txnId, txnNote)

View File

@ -30,7 +30,6 @@ import java.util.*
class WebServer(val config: FullNodeConfiguration) { class WebServer(val config: FullNodeConfiguration) {
private companion object { private companion object {
val log = loggerFor<WebServer>() val log = loggerFor<WebServer>()
val maxRetries = 60 // TODO: Make configurable
val retryDelay = 1000L // Milliseconds val retryDelay = 1000L // Milliseconds
} }
@ -39,7 +38,7 @@ class WebServer(val config: FullNodeConfiguration) {
fun start() { fun start() {
printBasicNodeInfo("Starting as webserver: ${config.webAddress}") printBasicNodeInfo("Starting as webserver: ${config.webAddress}")
server = initWebServer(connectLocalRpcWithRetries(maxRetries)) server = initWebServer(retryConnectLocalRpc())
} }
fun run() { fun run() {
@ -78,10 +77,10 @@ class WebServer(val config: FullNodeConfiguration) {
httpsConfiguration.outputBufferSize = 32768 httpsConfiguration.outputBufferSize = 32768
httpsConfiguration.addCustomizer(SecureRequestCustomizer()) httpsConfiguration.addCustomizer(SecureRequestCustomizer())
val sslContextFactory = SslContextFactory() val sslContextFactory = SslContextFactory()
sslContextFactory.keyStorePath = config.keyStorePath.toString() sslContextFactory.keyStorePath = config.keyStoreFile.toString()
sslContextFactory.setKeyStorePassword(config.keyStorePassword) sslContextFactory.setKeyStorePassword(config.keyStorePassword)
sslContextFactory.setKeyManagerPassword(config.keyStorePassword) sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
sslContextFactory.setTrustStorePath(config.trustStorePath.toString()) sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())
sslContextFactory.setTrustStorePassword(config.trustStorePassword) sslContextFactory.setTrustStorePassword(config.trustStorePassword)
sslContextFactory.setExcludeProtocols("SSL.*", "TLSv1", "TLSv1.1") sslContextFactory.setExcludeProtocols("SSL.*", "TLSv1", "TLSv1.1")
sslContextFactory.setIncludeProtocols("TLSv1.2") sslContextFactory.setIncludeProtocols("TLSv1.2")
@ -153,20 +152,15 @@ class WebServer(val config: FullNodeConfiguration) {
} }
} }
private fun connectLocalRpcWithRetries(retries: Int): CordaRPCOps { private fun retryConnectLocalRpc(): CordaRPCOps {
for(i in 0..retries - 1) { while(true) {
try { try {
log.info("Connecting to node at ${config.artemisAddress} as node user") return connectLocalRpcAsNodeUser()
val client = CordaRPCClient(config.artemisAddress, config)
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
return client.proxy()
} catch (e: ActiveMQNotConnectedException) { } catch (e: ActiveMQNotConnectedException) {
log.debug("Could not connect to ${config.artemisAddress} due to exception: ", e) log.debug("Could not connect to ${config.artemisAddress} due to exception: ", e)
Thread.sleep(retryDelay) Thread.sleep(retryDelay)
} }
} }
return connectLocalRpcAsNodeUser()
} }
private fun connectLocalRpcAsNodeUser(): CordaRPCOps { private fun connectLocalRpcAsNodeUser(): CordaRPCOps {

View File

@ -12,12 +12,11 @@ class APIServerImpl(val rpcOps: CordaRPCOps) : APIServer {
return LocalDateTime.ofInstant(rpcOps.currentNodeTime(), ZoneId.of("UTC")) return LocalDateTime.ofInstant(rpcOps.currentNodeTime(), ZoneId.of("UTC"))
} }
/**
* This endpoint is for polling if the webserver is serving. It will always return 200.
*/
override fun status(): Response { override fun status(): Response {
return if (rpcOps.ready()) { return Response.ok("started").build()
Response.ok("started").build()
} else {
Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("not started").build()
}
} }
override fun info() = rpcOps.nodeIdentity() override fun info() = rpcOps.nodeIdentity()