mirror of
https://github.com/corda/corda.git
synced 2024-12-21 13:57:54 +00:00
Added webserver to runnodes.
This commit is contained in:
parent
13551a6b23
commit
3482452c8b
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
-------
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
~~~~~~~
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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++
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user