Implemented a CORS proxy server to circumvent CORS restriction in WebApp
This commit is contained in:
parent
51c09dab7b
commit
523e538ecb
23
README.md
23
README.md
|
@ -75,10 +75,23 @@ fints4k uses slf4j as logging facade.
|
||||||
|
|
||||||
So you can use any logger that supports slf4j, like Logback and log4j, to configure and get fints4k's log output.
|
So you can use any logger that supports slf4j, like Logback and log4j, to configure and get fints4k's log output.
|
||||||
|
|
||||||
|
|
||||||
|
## Sample applications
|
||||||
|
|
||||||
|
### WebApp
|
||||||
|
|
||||||
|
Directly requesting bank servers is forbidden in browsers due to CORS.
|
||||||
|
|
||||||
|
In order to use fints4k directly in browser you need a CORS proxy like the one from CorsProxy
|
||||||
|
[Application.kt](SampleApplications/CorsProxy/src/main/kotlin/net/codinux/web/cors/Application.kt) or https://github.com/Rob--W/cors-anywhere.
|
||||||
|
|
||||||
|
Set CORS proxy's URL in WebApp [main.kt](SampleApplications/WebApp/src/main/kotlin/main.kt).
|
||||||
|
|
||||||
|
Start sample WebApp then with
|
||||||
|
```shell
|
||||||
|
./gradlew WebApp:run --continuous
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
fints4k is dual licensed as [AGPL](LICENSE.md) / commercial software.
|
Not free for commercial applications. More details to follow or [contact](mailto:sales@codinux.net) us.
|
||||||
|
|
||||||
AGPL is a free open source software license.
|
|
||||||
|
|
||||||
If you want to use it in closed source applications [contact](mailto:sales@dankito.net) us.
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib"
|
||||||
|
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:4.9.3"
|
||||||
|
|
||||||
|
implementation "org.slf4j:slf4j-simple:1.7.32"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package net.codinux.web.cors
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
CorsProxyServer().start()
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package net.codinux.web.cors
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange
|
||||||
|
import com.sun.net.httpserver.HttpServer
|
||||||
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
|
||||||
|
class CorsProxyServer {
|
||||||
|
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(CorsProxyServer::class.java)
|
||||||
|
|
||||||
|
|
||||||
|
fun start(port: Int = 8082) {
|
||||||
|
val server = HttpServer.create(InetSocketAddress("0.0.0.0", port), 0)
|
||||||
|
server.createContext("/") { httpExchange -> handleRequest(httpExchange) }
|
||||||
|
server.start()
|
||||||
|
|
||||||
|
log.info("Started CORS proxy at port $port")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun handleRequest(exchange: HttpExchange) {
|
||||||
|
try {
|
||||||
|
setCorsHeaders(exchange)
|
||||||
|
|
||||||
|
// Pre-flight request. Reply successfully:
|
||||||
|
if (exchange.requestMethod == "OPTIONS") {
|
||||||
|
exchange.sendResponseHeaders(200, -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyCall(exchange)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Could not proxy call to ${exchange.requestURI}", e)
|
||||||
|
|
||||||
|
exchange.sendResponseHeaders(500, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun proxyCall(exchange: HttpExchange) {
|
||||||
|
log.info("Proxying call to ${exchange.requestURI}")
|
||||||
|
|
||||||
|
var url = exchange.requestURI.toString()
|
||||||
|
if (url.startsWith("/")) {
|
||||||
|
url = url.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter out host header as otherwise OkHttp is trying to connect to this header
|
||||||
|
val headers = exchange.requestHeaders.mapNotNull { if (it.key.lowercase() == "host") null else it.key to it.value.first() }.toMap()
|
||||||
|
|
||||||
|
// OkHttp throws an exception if for a GET request a request body gets set
|
||||||
|
val requestBody = if ("GET" == exchange.requestMethod.uppercase()) null else exchange.requestBody.readAllBytes()?.toRequestBody()
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.method(exchange.requestMethod, requestBody)
|
||||||
|
.headers(headers.toHeaders())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).execute().use { response ->
|
||||||
|
response.headers.forEach { header -> exchange.responseHeaders.add(header.first, header.second) }
|
||||||
|
|
||||||
|
exchange.sendResponseHeaders(response.code, response.body?.contentLength() ?: -1)
|
||||||
|
|
||||||
|
exchange.responseBody.buffered().use { responseBodyStream -> // we need to close exchange.responseBody otherwise response doesn't get send
|
||||||
|
response.body?.byteStream()?.buffered()?.copyTo(responseBodyStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCorsHeaders(exchange: HttpExchange) {
|
||||||
|
exchange.responseHeaders.add("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
exchange.responseHeaders.add("Access-Control-Allow-Credentials", "true")
|
||||||
|
|
||||||
|
exchange.responseHeaders.add("Access-Control-Allow-Methods", exchange.requestHeaders["access-control-request-method"]?.first() ?: "OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE")
|
||||||
|
|
||||||
|
exchange.responseHeaders.add("Access-Control-Allow-Headers", exchange.requestHeaders["access-control-request-headers"]?.first() ?: "*")
|
||||||
|
|
||||||
|
// exchange.responseHeaders.add("access-control-expose-headers", exchange.requestHeaders.map { it.key }.joinToString(",")) // TODO: needed?
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ fun main() {
|
||||||
render(document.getElementById("root")!!) {
|
render(document.getElementById("root")!!) {
|
||||||
child(AccountTransactionsView::class) {
|
child(AccountTransactionsView::class) {
|
||||||
attrs {
|
attrs {
|
||||||
// to circumvent CORS we have to use a CORS proxy like https://github.com/Rob--W/cors-anywhere. Set CORS proxy's URL here
|
// to circumvent CORS we have to use a CORS proxy like the SampleApplications.CorsProxy Application.kt or
|
||||||
|
// https://github.com/Rob--W/cors-anywhere. Set CORS proxy's URL here
|
||||||
client = FinTsClientDeprecated(SimpleFinTsClientCallback(), ProxyingWebClient("http://localhost:8082/", KtorWebClient()))
|
client = FinTsClientDeprecated(SimpleFinTsClientCallback(), ProxyingWebClient("http://localhost:8082/", KtorWebClient()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,7 @@ include "WebApp"
|
||||||
findProject(":WebApp")?.projectDir = file("SampleApplications/WebApp")
|
findProject(":WebApp")?.projectDir = file("SampleApplications/WebApp")
|
||||||
findProject(":WebApp")?.name = "WebApp"
|
findProject(":WebApp")?.name = "WebApp"
|
||||||
|
|
||||||
|
include "CorsProxy"
|
||||||
|
findProject(":CorsProxy")?.projectDir = file("SampleApplications/CorsProxy")
|
||||||
|
findProject(":CorsProxy")?.name = "CorsProxy"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue