Skip to content

Commit

Permalink
Merge pull request #6 from VIRTUE-DBIS/ssl
Browse files Browse the repository at this point in the history
SSL Support
  • Loading branch information
sauterl authored Aug 30, 2021
2 parents 231a8bc + 7117281 commit 82085c2
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 24 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ data/
logs/
.idea/
out/
bin/

## keystore
*.pem
*.jks
*.p12


### Kotlin ###
# Compiled class file
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ the [setup guide](https://github.com/VIRTUE-DBIS/virtual-exhibition-presenter/wi
VREM can be built using [Gradle](http://gradle.org/). Building and running it is as simple as:

```
./gradlew clean deploy
cd build/libs
java -jar virtual-exhibition-manager-2.0.0-SNAPSHOT.jar
java -jar virtual-exhibition-manager-2.0.0-SNAPSHOT.jar <command>
./gradlew clean distZip
unzip build/distributions/virtual-exhibition-manager-$
virtual-exhibition-manager-$/bin/virtual-exhibition-manager <command>
```

Make sure you have the correct working directory set so VREM can properly import exhibitions and serve content.
Expand All @@ -42,7 +41,7 @@ Before starting, you should adapt the configurations in your config.json file (s
After doing so, you may serve stored exhibitions by running VREM with the following command:

```
java -jar virtual-exhibition-manager-2.0.0-SNAPSHOT.jar server -c /path/to/your/config.json
virtual-exhibition-manager-$/bin/virtual-exhibition-manager server -c /path/to/your/config.json
```

## Importing an exhibition
Expand Down
13 changes: 11 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def javaCompatibility = 11

// Project settings.
group 'ch.unibas.dmi.dbis'
version '2.0.0-SNAPSHOT'
version '2.1.0'

// Main class.
mainClassName = 'ch.unibas.dmi.dbis.vrem.VREMKt'
Expand All @@ -41,7 +41,7 @@ sourceSets {
}

// Jar properties.
jar {
tasks.named('jar') {
manifest {
attributes 'Version': archiveVersion.get()
attributes 'Main-Class': mainClassName // Same as for the application plugin.
Expand All @@ -56,6 +56,8 @@ jar {
configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}

exclude 'META-INF', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA'
}

// Generate Cineast OpenAPI bindings.
Expand Down Expand Up @@ -117,6 +119,7 @@ idea {

repositories {
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}

dependencies {
Expand All @@ -133,6 +136,12 @@ dependencies {
// Javalin.
implementation group: 'io.javalin', name: 'javalin', version: "$javalinVersion"

//jetty
implementation group: 'org.eclipse.jetty.http2', name: 'http2-server', version: "$jettyVersion"
implementation group: 'org.eclipse.jetty', name: 'jetty-alpn-conscrypt-server', version: "$jettyVersion"
implementation group: 'org.eclipse.jetty.alpn', name: 'alpn-api', version: "$alpnApiVersion"
implementation group: 'org.mortbay.jetty.alpn', name: 'alpn-boot', version: "$alpnBootVersion"

// Fuel.
implementation group: 'com.github.kittinunf.fuel', name: 'fuel', version: "$fuelVersion"
implementation group: 'com.github.kittinunf.fuel', name: 'fuel-kotlinx-serialization', version: "$fuelVersion"
Expand Down
6 changes: 5 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
},
"server": {
"documentRoot": "data",
"port": 4545
"httpPort": 4544,
"httpsPort": 4545,
"enableSsl": true,
"keystorePath": "keystore.jks",
"keystorePassword": "password"
},
"cineast": {
"host": "127.0.0.1",
Expand Down
15 changes: 9 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
javaCompatibility=11
kotlinVersion=1.5.20
kotlinVersion=1.5.30
openApiGenVersion=5.1.1
log4jslf4jVersion=2.14.1
kotlinLoggingVersion=2.0.8
cliktVersion=2.8.0
kotlinxSerializationVersion=1.2.1
kmongoVersion=4.2.7
javalinVersion=3.13.7
kotlinxSerializationVersion=1.2.2
kmongoVersion=4.2.8
javalinVersion=3.13.11
fuelVersion=2.3.1
okhttpVersion=4.9.0
moshiVersion=1.11.0
okhttpVersion=4.9.1
moshiVersion=1.12.0
jettyVersion = 9.4.25.v20191220
alpnApiVersion = 1.1.3.v20160715
alpnBootVersion = 8.1.12.v20180117
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import kotlinx.serialization.Serializable
* Configuration of the REST endpoint.
*
* @property documentRoot The document root path as a string.
* @property port The port of the endpoint.
* @property httpPort The port of the endpoint.
* @property httpPort The port of the https endpoint.
* @property enableSsl if ssl / https should be enabled
* @property keystorePassword password for the keystore
* @property keystorePath path for the keystore
*/
@Serializable
data class WebServerConfig(val documentRoot: String, val port: Short)
data class WebServerConfig(val documentRoot: String, val httpPort: Int, val httpsPort: Int, val enableSsl: Boolean, val keystorePath: String, val keystorePassword: String)
84 changes: 80 additions & 4 deletions src/main/kotlin/ch/unibas/dmi/dbis/vrem/rest/APIEndpoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.*
import io.javalin.http.staticfiles.Location
import io.javalin.plugin.json.FromJsonMapper
import io.javalin.plugin.json.JavalinJson
import io.javalin.plugin.json.ToJsonMapper
Expand All @@ -20,9 +21,15 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import mu.KotlinLogging
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory
import org.eclipse.jetty.server.*
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
import java.io.File
import java.time.Duration
import org.eclipse.jetty.util.thread.QueuedThreadPool


private val logger = KotlinLogging.logger {}

Expand Down Expand Up @@ -78,6 +85,10 @@ class APIEndpoint : CliktCommand(name = "server", help = "Start the REST API end
val endpoint = Javalin.create { conf ->
conf.defaultContentType = "application/json"
conf.enableCorsForAllOrigins()
conf.server { setupHttpServer(config) }
conf.enforceSsl = config.server.enableSsl

conf.addStaticFiles("./data", Location.EXTERNAL)

// Logger.
/*conf.requestLogger { ctx, ms ->
Expand All @@ -98,8 +109,13 @@ class APIEndpoint : CliktCommand(name = "server", help = "Start the REST API end
post(exhibitionHandler::saveExhibition)
}
}
path("/content/get/:path") {
get(contentHandler::serveContent)
path("/content/") {
path("get/:path") {
get(contentHandler::serveContent)
}
path("get/"){
get(contentHandler::serveContentBody)
}
}
path("/exhibits") {
path("list") {
Expand All @@ -114,18 +130,78 @@ class APIEndpoint : CliktCommand(name = "server", help = "Start the REST API end
endpoint.exception(Exception::class.java) { e, ctx ->
logger.error(e) { "Exception occurred, sending 500 and exception name." }
ctx.status(500)
.json(ErrorResponse("Error of type ${e.javaClass.simpleName} occurred. Check server log for additional information."))
.json(ErrorResponse("Error of type ${e.javaClass.simpleName} occurred. Check server log for additional information."))
}
endpoint.after { ctx ->
ctx.header("Access-Control-Allow-Origin", "*")
ctx.header("Access-Control-Allow-Headers", "*")
}
endpoint.start(config.server.port.toInt())
endpoint.start(config.server.httpPort)

println("Started the server.")
println("Ctrl+C to stop the server.")

// TODO CLI to process commands (/quit and the like).
}

private fun setupHttpServer(config: Config): Server {

val threadPool = QueuedThreadPool()
threadPool.name = "server"

val httpConfig = HttpConfiguration().apply {
sendServerVersion = false
sendXPoweredBy = false
if (config.server.enableSsl) {
secureScheme = "https"
securePort = config.server.httpsPort
}
}

/*
* Straight from https://www.eclipse.org/jetty/documentation/jetty-11/programming_guide.php encrypted http/2
*/
if (config.server.enableSsl) {
val httpsConfig = HttpConfiguration(httpConfig).apply {
addCustomizer(SecureRequestCustomizer())
}

val fallback = HttpConnectionFactory(httpsConfig)

val alpn = ALPNServerConnectionFactory().apply {
defaultProtocol = fallback.protocol
}

val sslContextFactory = SslContextFactory.Server().apply {
keyStorePath = config.server.keystorePath
setKeyStorePassword(config.server.keystorePassword)
//cipherComparator = HTTP2Cipher.COMPARATOR
provider = "Conscrypt"
}

val ssl = SslConnectionFactory(sslContextFactory, alpn.protocol)

val http2 = HTTP2ServerConnectionFactory(httpsConfig)

return Server(threadPool).apply {
//HTTP Connector
addConnector(ServerConnector(server, HttpConnectionFactory(httpConfig), HTTP2ServerConnectionFactory(httpConfig)).apply {
port = config.server.httpPort
})
// HTTPS Connector
addConnector(ServerConnector(server, ssl, alpn, http2, fallback).apply {
port = config.server.httpsPort
})
}
} else {
return Server(threadPool).apply {
//HTTP Connector
addConnector(ServerConnector(server, HttpConnectionFactory(httpConfig), HTTP2ServerConnectionFactory(httpConfig)).apply {
port = config.server.httpPort
})

}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class RequestContentHandler(private val docRoot: Path, private val cineastConfig
const val URL_ID_SUFFIX = ".remote"
}

fun serveContentBody(ctx: Context) {
val path = ctx.queryParam("path", "")!!
serve(ctx, path)
}


/**
* Serves the requested content.
*
Expand All @@ -33,6 +39,10 @@ class RequestContentHandler(private val docRoot: Path, private val cineastConfig
*/
fun serveContent(ctx: Context) {
val path = ctx.pathParam(PARAM_KEY_PATH)
serve(ctx, path)
}

private fun serve(ctx: Context, path: String) {

if (path.isBlank()) {
logger.error { "The requested path was blank - did you forget to send the actual content path?" }
Expand All @@ -41,7 +51,7 @@ class RequestContentHandler(private val docRoot: Path, private val cineastConfig
}

if (path.endsWith(URL_ID_SUFFIX)) {
// ID is composed as exhibitionID/imageID.remote.
// ID is composed as exhibitionID/imageID.remote for cineast
val id = path.substring(path.indexOf("/") + 1, path.indexOf(URL_ID_SUFFIX))
var resultBytes: ByteArray? = null

Expand Down Expand Up @@ -75,16 +85,18 @@ class RequestContentHandler(private val docRoot: Path, private val cineastConfig
ctx.status(404)
return
}

ctx.contentType(Files.probeContentType(absolute))
val content = Files.probeContentType(absolute)
ctx.contentType(content)
ctx.header("Content-Type", content);
ctx.header("Transfer-Encoding", "identity")
ctx.header("Access-Control-Allow-Origin", "*")
ctx.header("Access-Control-Allow-Headers", "*")

logger.info { "Serving $absolute." }
logger.info { "Serving $absolute as $content" }

ctx.result(absolute.toFile().inputStream())
}
}


}

0 comments on commit 82085c2

Please sign in to comment.