http/2 is functional
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# webapp
|
# webapp
|
||||||
webapp.env = dev
|
webapp.env = dev
|
||||||
webapp.url = http://localhost:8080
|
webapp.url = https://localhost:8080
|
||||||
|
|
||||||
# store
|
# store
|
||||||
store = file
|
store = file
|
||||||
|
@@ -46,6 +46,21 @@
|
|||||||
<artifactId>jetty-jndi</artifactId>
|
<artifactId>jetty-jndi</artifactId>
|
||||||
<version>${jetty.version}</version>
|
<version>${jetty.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-java-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>http2-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
@@ -127,11 +142,17 @@
|
|||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-failsafe-plugin</artifactId>
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
@@ -1,16 +1,37 @@
|
|||||||
package org.jeudego.pairgoth.application
|
package org.jeudego.pairgoth.application
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory
|
||||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection
|
import org.eclipse.jetty.server.handler.ContextHandlerCollection
|
||||||
|
import org.eclipse.jetty.util.resource.Resource
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||||
import org.eclipse.jetty.webapp.WebAppContext
|
import org.eclipse.jetty.webapp.WebAppContext
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
import java.net.JarURLConnection
|
import java.net.JarURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
||||||
fun main(vararg args: String) {
|
fun main(vararg args: String) {
|
||||||
try {
|
try {
|
||||||
@@ -32,9 +53,9 @@ private fun extractWarFiles() {
|
|||||||
Files.createDirectories(targetPath)
|
Files.createDirectories(targetPath)
|
||||||
|
|
||||||
// extract wars
|
// extract wars
|
||||||
val webappsFolderURL = object{}::class.java.enclosingClass.getResource("/META-INF/webapps") ?: throw Error("webapps not found")
|
val webappsFolderURL = getResource("/META-INF/webapps") ?: throw Error("webapps not found")
|
||||||
val jarConnection = webappsFolderURL.openConnection() as JarURLConnection
|
val jarConnection = webappsFolderURL.openConnection() as JarURLConnection
|
||||||
val jarFile: JarFile = jarConnection.getJarFile()
|
val jarFile: JarFile = jarConnection.jarFile
|
||||||
jarFile.entries().toList().filter { entry ->
|
jarFile.entries().toList().filter { entry ->
|
||||||
entry.name.startsWith(jarConnection.entryName)
|
entry.name.startsWith(jarConnection.entryName)
|
||||||
}.forEach { entry ->
|
}.forEach { entry ->
|
||||||
@@ -46,36 +67,89 @@ private fun extractWarFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val mainClass = object{}::class.java.enclosingClass
|
||||||
|
private val jarPath = mainClass.protectionDomain.codeSource.location.path.let { URLDecoder.decode(it, "UTF-8") }
|
||||||
|
private val serverProps = Properties()
|
||||||
|
private fun getResource(resource: String) = mainClass.getResource(resource)
|
||||||
|
private fun getResourceProperty(key: String) = serverProps.getProperty(key)?.let { property ->
|
||||||
|
val url = property.replace("\$jar", jarPath)
|
||||||
|
if (!Resource.newResource(url).exists()) throw Error("resource not found: $url")
|
||||||
|
URL(url)
|
||||||
|
} ?: throw Error("missing property: $key")
|
||||||
|
|
||||||
private fun launchServer() {
|
private fun launchServer() {
|
||||||
|
|
||||||
// create server
|
|
||||||
val server = Server(8080) // CB TODO port is to be calculated from webapp.url
|
|
||||||
|
|
||||||
// create webapps contexts
|
// create webapps contexts
|
||||||
val apiContext = createContext("api", "/api");
|
val apiContext = createContext("api", "/api")
|
||||||
val viewContext = createContext("view", "/");
|
val viewContext = createContext("view", "/")
|
||||||
|
|
||||||
// handle properties
|
// handle properties
|
||||||
val properties = File("./pairgoth.properties");
|
val defaultProps = getResource("/server.default.properties") ?: throw Error("missing default server properties")
|
||||||
|
defaultProps.openStream().use {
|
||||||
|
serverProps.load(InputStreamReader(it, StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
val properties = File("./pairgoth.properties")
|
||||||
if (properties.exists()) {
|
if (properties.exists()) {
|
||||||
val props = Properties()
|
serverProps.load(FileReader(properties))
|
||||||
props.load(FileReader(properties));
|
serverProps.entries.forEach { entry ->
|
||||||
props.entries.forEach { entry ->
|
|
||||||
val property = entry.key as String
|
val property = entry.key as String
|
||||||
val value = entry.value as String
|
val value = entry.value as String
|
||||||
if (property.startsWith("logger.")) {
|
if (property.startsWith("logger.")) {
|
||||||
// special handling for logger properties
|
// special handling for logger properties
|
||||||
val webappLoggerPropKey = "webapp-slf4j-logger.${property.substring(7)}"
|
val webappLoggerPropKey = "webapp-slf4j-logger.${property.substring(7)}"
|
||||||
apiContext.setInitParameter(webappLoggerPropKey, value);
|
apiContext.setInitParameter(webappLoggerPropKey, value)
|
||||||
viewContext.setInitParameter(webappLoggerPropKey, value);
|
viewContext.setInitParameter(webappLoggerPropKey, value)
|
||||||
|
} else if (property.startsWith("webapp.ssl.")) {
|
||||||
|
// do not propagate ssl properties further
|
||||||
} else {
|
} else {
|
||||||
System.setProperty("pairgoth.$property", value);
|
System.setProperty("pairgoth.$property", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create server
|
||||||
|
val server = Server(8080) // CB TODO port is to be calculated from webapp.url
|
||||||
|
|
||||||
// register webapps
|
// register webapps
|
||||||
server.handler = ContextHandlerCollection(apiContext, viewContext);
|
server.handler = ContextHandlerCollection(apiContext, viewContext)
|
||||||
|
|
||||||
|
// set up http/2
|
||||||
|
val httpConfig = HttpConfiguration().apply {
|
||||||
|
addCustomizer(SecureRequestCustomizer())
|
||||||
|
}
|
||||||
|
val http11 = HttpConnectionFactory(httpConfig)
|
||||||
|
val h2 = HTTP2ServerConnectionFactory(httpConfig)
|
||||||
|
val alpn = ALPNServerConnectionFactory().apply {
|
||||||
|
defaultProtocol = http11.protocol
|
||||||
|
}
|
||||||
|
val cert = getResourceProperty("webapp.ssl.cert").readBytes()
|
||||||
|
val key = getResourceProperty("webapp.ssl.key").readText().let {
|
||||||
|
val encodedKey = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*").matcher(it).replaceFirst("$1")
|
||||||
|
Base64.getDecoder().decode(encodedKey.replace("\n", ""))
|
||||||
|
}
|
||||||
|
val pass = serverProps.getProperty("webapp.ssl.pass") ?: "foobar"
|
||||||
|
|
||||||
|
val keyFactory = KeyFactory.getInstance("RSA")
|
||||||
|
val keySpec = PKCS8EncodedKeySpec(key)
|
||||||
|
val privKey = keyFactory.generatePrivate(keySpec)
|
||||||
|
|
||||||
|
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
val store = KeyStore.getInstance("JKS").apply {
|
||||||
|
load(null)
|
||||||
|
setCertificateEntry("certificate", certificateFactory.generateCertificate(ByteArrayInputStream(cert)) as X509Certificate)
|
||||||
|
setKeyEntry("key", privKey, pass.toCharArray(), arrayOf(certificateFactory.generateCertificate(ByteArrayInputStream(cert))))
|
||||||
|
}
|
||||||
|
val sslContextFactory = SslContextFactory.Server().apply {
|
||||||
|
keyStoreType = "JKS"
|
||||||
|
keyStore = store
|
||||||
|
keyStorePassword = pass
|
||||||
|
// if (pass.isNotEmpty()) keyManagerPassword = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
val tls = SslConnectionFactory(sslContextFactory, alpn.protocol)
|
||||||
|
val connector = ServerConnector(server, tls, alpn, h2, http11)
|
||||||
|
connector.port = 8443
|
||||||
|
server.addConnector(connector)
|
||||||
|
|
||||||
// launch server
|
// launch server
|
||||||
server.start()
|
server.start()
|
||||||
@@ -84,5 +158,5 @@ private fun launchServer() {
|
|||||||
|
|
||||||
private fun createContext(webapp: String, contextPath: String) = WebAppContext().also { context ->
|
private fun createContext(webapp: String, contextPath: String) = WebAppContext().also { context ->
|
||||||
context.war = "$tmp/pairgoth/webapps/$webapp-webapp-$version.war"
|
context.war = "$tmp/pairgoth/webapps/$webapp-webapp-$version.war"
|
||||||
context.contextPath = contextPath;
|
context.contextPath = contextPath
|
||||||
}
|
}
|
||||||
|
3
webserver/src/main/resources/server.default.properties
Normal file
3
webserver/src/main/resources/server.default.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
webapp.ssl.key = jar:file:$jar!/ssl/localhost.key
|
||||||
|
# webapp.ssl.pass = foobar (not supported for now)
|
||||||
|
webapp.ssl.cert = jar:file:$jar!/ssl/localhost.crt
|
25
webserver/src/main/resources/ssl/localhost.crt
Normal file
25
webserver/src/main/resources/ssl/localhost.crt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIELTCCAxWgAwIBAgIUOOB8QYucWNOJYAg1ypawCJpdE9kwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgaUxCzAJBgNVBAYTAkZSMQwwCgYDVQQIDANJZEYxDjAMBgNVBAcMBVBhcmlz
|
||||||
|
MSwwKgYDVQQKDCNGw4PCqWTDg8KpcmF0aW9uIEZyYW7Dg8KnYWlzZSBkZSBHbzER
|
||||||
|
MA8GA1UECwwIcGFpcmdvdGgxEjAQBgNVBAMMCWxvY2FsaG9zdDEjMCEGCSqGSIb3
|
||||||
|
DQEJARYUcGFpcmdvdGhAamV1ZGVnby5vcmcwHhcNMjMwNjA5MTMwMDMyWhcNMzMw
|
||||||
|
NjA2MTMwMDMyWjCBpTELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA0lkRjEOMAwGA1UE
|
||||||
|
BwwFUGFyaXMxLDAqBgNVBAoMI0bDg8KpZMODwqlyYXRpb24gRnJhbsODwqdhaXNl
|
||||||
|
IGRlIEdvMREwDwYDVQQLDAhwYWlyZ290aDESMBAGA1UEAwwJbG9jYWxob3N0MSMw
|
||||||
|
IQYJKoZIhvcNAQkBFhRwYWlyZ290aEBqZXVkZWdvLm9yZzCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAJXPkr0CZf4xneSXFIUUWn1HNlgEVWEbhUbnrzqA
|
||||||
|
DqGN/MkpUI6/viXurNg1yLRPLeRvmlSUhwPc6Sq5HGZqimKFXcU/JNp+H4yiQluo
|
||||||
|
ykOvHTGhkYUtXJK91oxrghqU2kEOfJeoOyCpcBvPzBP0a3iSDBUTgazNiZWKvZ7L
|
||||||
|
MwYDj0sfC0d8zXlKxpsg62G9AIxOJEol5l7BrxtlUdO9xS44s6vrhlXYkdOdt5gV
|
||||||
|
77iQOWWiFJyBuhFhKrtUXP5yVPGVVko7HC6vtaiVRbzdh4g0j2tI4sq88tcTWpXA
|
||||||
|
iIrbmSCCDNkW0XfMap/zgL9Tkqb5kEbdPPuDitZftt9zsMsCAwEAAaNTMFEwHQYD
|
||||||
|
VR0OBBYEFDR7lP45ae2rgeyPiaIUk9AOd6geMB8GA1UdIwQYMBaAFDR7lP45ae2r
|
||||||
|
geyPiaIUk9AOd6geMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
||||||
|
ACp3YHugHxuoktyEB/pSEhkXFAYhaUYty6ESb0hQzF4CK9xY0fdabC40RFjGhGlg
|
||||||
|
STBez30thKbmcYoCUy0VG9lf8qndF0xgjihXzBHmRd1b4PO8AkAeMIbAKGhFeptf
|
||||||
|
MKIdfBjmrYAUA0L6GMU/h5hWPOzf6KACiJ8nHNjgYFsTrHO52QGqSqgy4j1l6TtQ
|
||||||
|
hlM3rxK0gSWzafZW/syxDXeM/Rocq0R48E90WpO9A4E9dg2PQtYT2zk7MMuMMIRg
|
||||||
|
D317F3FSFWI6XIg6EyLVx69rfuKiMqZ4ghTyXKyKcSC+LJQJhKFtOiLAdXaJ3naF
|
||||||
|
I7MPu1wZ/ZGRd4AvjsF/5jc=
|
||||||
|
-----END CERTIFICATE-----
|
28
webserver/src/main/resources/ssl/localhost.key
Normal file
28
webserver/src/main/resources/ssl/localhost.key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVz5K9AmX+MZ3k
|
||||||
|
lxSFFFp9RzZYBFVhG4VG5686gA6hjfzJKVCOv74l7qzYNci0Ty3kb5pUlIcD3Okq
|
||||||
|
uRxmaopihV3FPyTafh+MokJbqMpDrx0xoZGFLVySvdaMa4IalNpBDnyXqDsgqXAb
|
||||||
|
z8wT9Gt4kgwVE4GszYmVir2eyzMGA49LHwtHfM15SsabIOthvQCMTiRKJeZewa8b
|
||||||
|
ZVHTvcUuOLOr64ZV2JHTnbeYFe+4kDllohScgboRYSq7VFz+clTxlVZKOxwur7Wo
|
||||||
|
lUW83YeINI9rSOLKvPLXE1qVwIiK25kgggzZFtF3zGqf84C/U5Km+ZBG3Tz7g4rW
|
||||||
|
X7bfc7DLAgMBAAECggEAAtwIgUOdY5HJA55h1AVCXoNluq9LjrUWcAgJayS1AKdc
|
||||||
|
XKqa0OdBGzrqPwQfQ4to9iydTT92ZEh7/QJgU925ijhpd+1NbGFulP/RvYDQ4fDJ
|
||||||
|
NqssbcWJOCcG8zjOYRL6BWE1aKx1HQAWau03ZiKkeoKNAp9PoQHU8WaWaqaWzrwm
|
||||||
|
XZA2K1yb8jKdEGEqvyvt8bOIwAgerdWwNQPe6oqQKeCH2471YRKtIsAdVRqQwxzB
|
||||||
|
GmKyNcc8AaG4hJAZGu59HLqAwBdLht/v2ZU5QFBeRq4ltzxe9wPlW08dHu1D3l4M
|
||||||
|
rdIRrWn1FFVFjs71fgb+Abs9Fpi/+F3JNEyoxnz8YQKBgQDR5C4VFR4YczCFOXaB
|
||||||
|
JAq7aF2SCLggotRO33O/Nbdl7+qJxVjl80DcbLhlgYQIZC4osUoF7JPfXPJVrYX7
|
||||||
|
plEy/071YHikuLpBcd0FBQpnmDi0BQWbbBMKVwQCH53Lrz4FuI+3jbEozKi7BIvN
|
||||||
|
WMzAt6fIXyPyPMI7eGWiUcUcZQKBgQC2uJmlTGDQOhPbl2ZS0Iya3hyZ0iQXI25L
|
||||||
|
iyrHYvKN7AgQnYYn4eYrJoRh/gh9cWDSEUSB9OLY8ekEVmzmUWQVjgycaYE4seFO
|
||||||
|
B4NWc2L+aALszES9C3SRupMa3jVgDPBI4u/DCWJrg9ttSPMEgwYFk33mFxlAQc53
|
||||||
|
0LBn14pNbwKBgBVYeFtKh4IDDPcvjd66VKEUjxeP7XHcPW08CmByzRD/4kFaoZzZ
|
||||||
|
LUp9gA9KqavUzGD1DsslcTBxGnAeMpcSJgXisxv/UKWn58FKHCkrhxBcCcA9FoHk
|
||||||
|
7tbJXK3+mySg0NTyHSOUtGSq06oZX0Jl+oTK6LRXAKfdB//WUbe9SyeFAoGAE8dL
|
||||||
|
ql7oI+IFgEGVK+WzMphUVDow+eg16it4R/jn9IDWJqZGfU6wkX8r2UecN6fsKREB
|
||||||
|
b2fInl8hL/0C8LNiuAqWRuAMwsxObRnXF6aJ0qwDlQpPbn8s8RFXFxNyh6Ee6WTX
|
||||||
|
Oy9q3eR5/gxlcdmU70mV2TAq5Y+5/7IxRixIpjUCgYEAlMK/pFAnmaUqgl5UUyA5
|
||||||
|
vDGgCoPb3ZBTF8yNndaOq07cDmGPIURwQKiI24iAm2HNyoEP5gVEvZ8jY2DAgqPt
|
||||||
|
zsv6TaG1w112HA+l1tJ8cfQTMm7COwXFjdPJ0lOb8VaNoAzeW+/B7+IsVCa2yjP4
|
||||||
|
LZWxz7cjuLlYfZk3KM06HmA=
|
||||||
|
-----END PRIVATE KEY-----
|
Reference in New Issue
Block a user