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
/**
* Returns true if node is up and ready
*/
fun ready(): Boolean
/*
* Add note(s) to an existing Vault transaction
*/
@ -107,9 +102,8 @@ interface CordaRPCOps : RPCOps {
*/
fun uploadAttachment(jar: InputStream): SecureHash
/**
* Uploads a file by data type to the node and returns a meaningful string to the user
*/
@Suppress("DEPRECATION")
@Deprecated("This service will be removed in a future milestone")
fun uploadFile(dataType: String, name: String?, file: InputStream): String
/**

View File

@ -218,14 +218,22 @@ interface KeyManagementService {
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 {
/**
* 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.
*/
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). */
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 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.
: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
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``
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
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

View File

@ -34,21 +34,16 @@ of the node internal subsystems.
extensions to be created, or registered at startup. In particular:
a. The ``webApis`` property is a list of JAX-RS annotated REST access
classes. These classes will be constructed by the embedded web server
and must have a single argument constructor taking a ``ServiceHub``
reference. This reference provides access to functions such as querying
for states through the ``VaultService`` interface, or access to the
``NetworkMapCache`` to identify services on remote nodes. The framework will
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.)
classes. These classes will be constructed by the bundled web server
and must have a single argument constructor taking a ``CordaRpcOps``
reference. This will allow it to communicate with the node process
via the RPC interface. These web APIs will not be available if the
bundled web server is not started.
b. The ``staticServeDirs`` property maps static web content to virtual
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
process using the RPC mechanism to control access.)
jars. These static serving directories will not be available if the
bundled web server is not started.
c. The ``requiredFlows`` property is used to declare new protocols in
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
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).
3. Web APIs: You may register your own endpoints under /api/ of the built-in web server.
4. Static web endpoints: You may register your own static serving directories for serving web content.
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 from the web server.
5. Registering your additional classes used in RPC.
Services

View File

@ -1,8 +1,9 @@
Node administration
===================
When a node is running, it exposes an embedded database server, an embedded web server that lets you monitor it,
you can upload and download attachments, access a REST API and so on.
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. A bundled
Jetty web server exposes the same interface over HTTP.
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
``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``,
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.
* ``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
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.
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
~~~~~~~

View File

@ -24,6 +24,16 @@ if which osascript >/dev/null; then
first=false
fi
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
end tell"
osascript -e "$script"
@ -39,6 +49,7 @@ else
if [ -d $dir ]; then
pushd $dir >/dev/null
xterm -T "`basename $dir`" -e 'java -jar JAR_NAME' &
xterm -T "`basename $dir`" -e 'java -jar JAR_NAME --webserver' &
popd >/dev/null
fi
done

View File

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

View File

@ -80,7 +80,11 @@ fun main(args: Array<String>) {
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: ${cmdlineOptions.isWebserver}")
if(cmdlineOptions.isWebserver) {
log.info("Starting as webserver on ${conf.webAddress}")
} else {
log.info("Starting as node on ${conf.artemisAddress}")
}
try {
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.config.ConfigHelper
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.CordaRPCClient
import net.corda.node.services.messaging.NodeMessagingClient
@ -327,7 +327,7 @@ open class DriverDSL(
executorService.shutdown()
}
private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: SSLConfiguration): NodeInfo? {
var retries = 0
while (retries < 5) try {
val client = CordaRPCClient(nodeAddress, sslConfig)
@ -335,7 +335,7 @@ open class DriverDSL(
val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS))
return rpcOps.nodeIdentity()
} catch(e: Exception) {
log.debug("Retrying query node info at $nodeAddress")
log.error("Retrying query node info at $nodeAddress")
retries++
}

View File

@ -222,7 +222,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
customServices.addAll(buildPluginServices(tokenizableServices))
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)
// TODO: uniquenessProvider creation should be inside makeNotaryService(), but notary service initialisation

View File

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

View File

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

View File

@ -12,12 +12,11 @@ class APIServerImpl(val rpcOps: CordaRPCOps) : APIServer {
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 {
return if (rpcOps.ready()) {
Response.ok("started").build()
} else {
Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("not started").build()
}
return Response.ok("started").build()
}
override fun info() = rpcOps.nodeIdentity()