WIP vaadin undertow launcher
commit
a81c5c22bb
|
@ -0,0 +1,121 @@
|
|||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
.idea/**/codeStyles/
|
||||
.idea/**/codeStyleSettings.xml
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/shelf/
|
||||
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
######################
|
||||
# End JetBrains IDEs #
|
||||
######################
|
||||
|
||||
|
||||
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||
.gradle
|
||||
/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
!gradle-wrapper.properties
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
|
||||
|
||||
|
||||
# From https://github.com/github/gitignore/blob/master/Java.gitignore
|
||||
*.class
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
# Specific to this module
|
||||
|
||||
# iml files are generated by intellij/gradle now
|
||||
**/*.iml
|
|
@ -0,0 +1,85 @@
|
|||
- VaadinUndertow - Vaadin support for the Undertow web server
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/VaadinUndertow
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2021
|
||||
JetBrains s.r.o.
|
||||
|
||||
- Vaadin - An open platform for building modern web apps for Java back ends
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/vaadin/
|
||||
Copyright 2021
|
||||
Vaadin Ltd.
|
||||
|
||||
- Undertow - High performance non-blocking webserver
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/undertow-io/undertow
|
||||
Copyright 2021
|
||||
JBoss
|
||||
Red Hat, Inc.
|
||||
Individual contributors as listed in files
|
||||
|
||||
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/classgraph/classgraph
|
||||
Copyright 2021
|
||||
Luke Hutchison
|
||||
|
||||
- Conversant Disruptor - Disruptor is the highest performing intra-thread transfer mechanism available in Java.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/conversant/disruptor
|
||||
Copyright 2021
|
||||
Conversant, Inc
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2021
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2021
|
||||
QOS.ch
|
||||
|
||||
- JUL to SLF4J - Java Util Logging implemented over SLF4J
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2021
|
||||
QOS.ch
|
||||
|
||||
- Logback - Logback is a logging framework for Java applications
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://logback.qos.ch
|
||||
Copyright 2021
|
||||
QOS.ch
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
|
@ -0,0 +1,218 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dorkbox.gradle.kotlin
|
||||
import java.time.Instant
|
||||
|
||||
///////////////////////////////
|
||||
////// PUBLISH TO SONATYPE / MAVEN CENTRAL
|
||||
////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'>
|
||||
////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'>
|
||||
///////////////////////////////
|
||||
|
||||
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "2.9"
|
||||
id("com.dorkbox.Licensing") version "2.9"
|
||||
id("com.dorkbox.VersionUpdate") version "2.3"
|
||||
id("com.dorkbox.GradlePublish") version "1.11"
|
||||
|
||||
kotlin("jvm") version "1.5.0"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
const val description = "Vaadin support for the Undertow web server"
|
||||
const val group = "com.dorkbox"
|
||||
const val name = "VaadinUndertow"
|
||||
const val id = "VaadinUndertow"
|
||||
const val version = "0.1"
|
||||
|
||||
const val vendor = "Dorkbox LLC"
|
||||
const val vendorUrl = "https://dorkbox.com"
|
||||
const val url = "https://git.dorkbox.com/dorkbox/VaadinUndertow"
|
||||
|
||||
val buildDate = Instant.now().toString()
|
||||
|
||||
const val coroutineVer = "1.4.3"
|
||||
const val vaadinVer = "14.1.17"
|
||||
const val undertowVer = "2.2.9.Final"
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
///// assign 'Extras'
|
||||
///////////////////////////////
|
||||
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
||||
GradleUtils.defaults()
|
||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
|
||||
|
||||
|
||||
licensing {
|
||||
license(License.APACHE_2) {
|
||||
description(Extras.description)
|
||||
author(Extras.vendor)
|
||||
url(Extras.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
setSrcDirs(listOf("src"))
|
||||
|
||||
// want to include java+kotlin files for the source. 'setSrcDirs' resets includes...
|
||||
include("**/*.java", "**/*.kt")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
setSrcDirs(listOf("src"))
|
||||
|
||||
// want to include kotlin files for the source. 'setSrcDirs' resets includes...
|
||||
include("**/*.java", "**/*.kt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
|
||||
|
||||
implementation("com.vaadin:vaadin:${Extras.vaadinVer}")
|
||||
|
||||
// we use undertow 2, with kotlin coroutines on top (with 1 actor per session)
|
||||
implementation("io.undertow:undertow-core:${Extras.undertowVer}")
|
||||
implementation("io.undertow:undertow-servlet:${Extras.undertowVer}")
|
||||
implementation("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
|
||||
|
||||
|
||||
// Uber-fast, ultra-lightweight Java classpath and module path scanner
|
||||
implementation("io.github.classgraph:classgraph:4.8.110")
|
||||
|
||||
implementation("com.dorkbox:Updates:1.1")
|
||||
|
||||
implementation("com.conversantmedia:disruptor:1.2.19")
|
||||
|
||||
// awesome logging framework for kotlin.
|
||||
// https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/
|
||||
// https://github.com/MicroUtils/kotlin-logging
|
||||
implementation("io.github.microutils:kotlin-logging:2.0.10")
|
||||
|
||||
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||
implementation("org.slf4j:jul-to-slf4j:1.7.32")
|
||||
|
||||
|
||||
implementation("ch.qos.logback:logback-core:1.2.5")
|
||||
implementation("ch.qos.logback:logback-classic:1.2.5")
|
||||
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
|
||||
tasks.jar.get().apply {
|
||||
manifest {
|
||||
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
|
||||
attributes["Name"] = Extras.name
|
||||
|
||||
attributes["Specification-Title"] = Extras.name
|
||||
attributes["Specification-Version"] = Extras.version
|
||||
attributes["Specification-Vendor"] = Extras.vendor
|
||||
|
||||
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
||||
attributes["Implementation-Version"] = Extras.buildDate
|
||||
attributes["Implementation-Vendor"] = Extras.vendor
|
||||
}
|
||||
}
|
||||
|
||||
publishToSonatype {
|
||||
groupId = Extras.group
|
||||
artifactId = Extras.id
|
||||
version = Extras.version
|
||||
|
||||
name = Extras.name
|
||||
description = Extras.description
|
||||
url = Extras.url
|
||||
|
||||
vendor = Extras.vendor
|
||||
vendorUrl = Extras.vendorUrl
|
||||
|
||||
issueManagement {
|
||||
url = "${Extras.url}/issues"
|
||||
nickname = "Gitea Issues"
|
||||
}
|
||||
|
||||
developer {
|
||||
id = "dorkbox"
|
||||
name = Extras.vendor
|
||||
email = "email@dorkbox.com"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
|
||||
org.gradle.jvmargs=-Dfile.encoding=UTF-8
|
||||
|
||||
#org.gradle.warning.mode=(all,fail,none,summary)
|
||||
org.gradle.warning.mode=all
|
||||
|
||||
#org.gradle.daemon=false
|
||||
# default is 3 hours, this is 1 minute
|
||||
org.gradle.daemon.idletimeout=60000
|
||||
|
||||
#org.gradle.console=(auto,plain,rich,verbose)
|
||||
org.gradle.console=auto
|
||||
|
||||
#org.gradle.logging.level=(quiet,warn,lifecycle,info,debug)
|
||||
org.gradle.logging.level=lifecycle
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
|
@ -0,0 +1,645 @@
|
|||
package dorkbox.vaadin
|
||||
|
||||
import com.vaadin.flow.server.Constants
|
||||
import com.vaadin.flow.server.VaadinServlet
|
||||
import com.vaadin.flow.server.frontend.FrontendUtils
|
||||
import dorkbox.vaadin.undertow.*
|
||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
||||
import elemental.json.JsonObject
|
||||
import elemental.json.impl.JsonUtil
|
||||
import io.github.classgraph.ClassGraph
|
||||
import io.github.classgraph.ScanResult
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.handlers.cache.CacheHandler
|
||||
import io.undertow.server.handlers.cache.DirectBufferCache
|
||||
import io.undertow.server.handlers.resource.CachingResourceManager
|
||||
import io.undertow.server.handlers.resource.FileResourceManager
|
||||
import io.undertow.server.handlers.resource.ResourceManager
|
||||
import io.undertow.servlet.Servlets
|
||||
import io.undertow.servlet.api.ServletContainerInitializerInfo
|
||||
import io.undertow.servlet.api.ServletSessionConfig
|
||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.servlet.ServletContainerInitializer
|
||||
import javax.servlet.SessionTrackingMode
|
||||
import javax.servlet.annotation.HandlesTypes
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Loads, Configures, and Starts a Vaadin 14 application
|
||||
*/
|
||||
class VaadinApplication(runningAsJar: Boolean) {
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
const val version = "0.1"
|
||||
|
||||
init {
|
||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||
dorkbox.updates.Updates.add(VaadinApplication::class.java, "fc74a52b08c8410fabfea67ac5dca566", version)
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
val tempDir: File = File(System.getProperty("java.io.tmpdir", "tmpDir"), "undertow").absoluteFile
|
||||
|
||||
private val onStopList = mutableListOf<Runnable>()
|
||||
|
||||
val devMode: Boolean
|
||||
private val tokenFileName: String
|
||||
|
||||
private lateinit var urlClassLoader: URLClassLoader
|
||||
private lateinit var resourceCollectionManager: ResourceCollectionManager
|
||||
|
||||
private val resources = ArrayList<ResourceManager>()
|
||||
|
||||
private val exactResources = ArrayList<String>()
|
||||
private val prefixResources = ArrayList<String>()
|
||||
|
||||
private lateinit var cacheHandler: HttpHandler
|
||||
private lateinit var servletHttpHandler: HttpHandler
|
||||
|
||||
init {
|
||||
// find the config/stats.json to see what mode (PRODUCTION or DEV) we should run in.
|
||||
// we COULD just check the existence of this file...
|
||||
// HOWEVER if we are testing a different configuration from our IDE, this method will not work...
|
||||
var tokenJson: JsonObject? = null
|
||||
|
||||
val defaultTokenFile = "VAADIN/${FrontendUtils.TOKEN_FILE}"
|
||||
// token location if we are running in production mode
|
||||
val prodToken = this.javaClass.classLoader.getResource("META-INF/resources/$defaultTokenFile")
|
||||
if (prodToken != null) {
|
||||
tokenFileName = if (runningAsJar) {
|
||||
// the token file name MUST always be from disk! This is hard coded, because later we copy out
|
||||
// this file from the jar to the temp location.
|
||||
File(tempDir, defaultTokenFile).absolutePath
|
||||
} else {
|
||||
if (prodToken.path.startsWith("/")) {
|
||||
prodToken.path.substring(1)
|
||||
} else {
|
||||
prodToken.path
|
||||
}
|
||||
}
|
||||
|
||||
tokenJson = JsonUtil.parse(prodToken.readText(Charsets.UTF_8)) as JsonObject?
|
||||
} else {
|
||||
val devTokenFile = File("build").resolve(FrontendUtils.TOKEN_FILE)
|
||||
if (devTokenFile.canRead()) {
|
||||
tokenFileName = devTokenFile.absolutePath
|
||||
tokenJson = JsonUtil.parse(File(tokenFileName).readText(Charsets.UTF_8)) as JsonObject?
|
||||
}
|
||||
else {
|
||||
tokenFileName = ""
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenFileName.isEmpty() || tokenJson == null || !tokenJson.hasKey(Constants.SERVLET_PARAMETER_PRODUCTION_MODE)) {
|
||||
// this is a problem! we must configure the system first via gradle!
|
||||
throw java.lang.RuntimeException("Unable to continue! Error reading token!" +
|
||||
"You must FIRST compile the vaadin resources for DEV or PRODUCTION mode!")
|
||||
}
|
||||
|
||||
devMode = !tokenJson.getBoolean(Constants.SERVLET_PARAMETER_PRODUCTION_MODE)
|
||||
|
||||
if (devMode && runningAsJar) {
|
||||
throw RuntimeException("Invalid run configuration. It is not possible to run DEV MODE from a deployed jar.\n" +
|
||||
"Something is severely wrong!")
|
||||
}
|
||||
|
||||
// we are ALWAYS running in full Vaadin14 mode
|
||||
System.setProperty(Constants.VAADIN_PREFIX + Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, "false")
|
||||
|
||||
if (devMode) {
|
||||
// set the location of our frontend dir + generated dir when in dev mode
|
||||
System.setProperty(FrontendUtils.PARAM_FRONTEND_DIR, tokenJson.getString(Constants.FRONTEND_TOKEN))
|
||||
System.setProperty(FrontendUtils.PARAM_GENERATED_DIR, tokenJson.getString(Constants.GENERATED_TOKEN))
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAnnotated(annotationScanner: ScanResult,
|
||||
kClass: KClass<*>,
|
||||
classSet: MutableSet<Class<*>>) {
|
||||
val javaClass = kClass.java
|
||||
val canonicalName = javaClass.canonicalName
|
||||
|
||||
when {
|
||||
javaClass.isAnnotation -> {
|
||||
val routes = annotationScanner.getClassesWithAnnotation(canonicalName)
|
||||
val loadedClasses = routes.loadClasses()
|
||||
|
||||
classSet.addAll(loadedClasses)
|
||||
}
|
||||
javaClass.isInterface -> {
|
||||
val classesImplementing = annotationScanner.getClassesImplementing(canonicalName)
|
||||
val loadedClasses = classesImplementing.loadClasses()
|
||||
|
||||
classSet.addAll(loadedClasses)
|
||||
}
|
||||
kClass.isAbstract -> { /* do nothing! */ }
|
||||
else -> throw RuntimeException("Annotation scan for type $canonicalName:$javaClass not supported yet")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
fun initializeResources(runningAsJar: Boolean) {
|
||||
val metaInfResources = "META-INF/resources"
|
||||
|
||||
// resource locations are tricky...
|
||||
// when a JAR : META-INF/resources
|
||||
// when on disk: webApp/META-INF/resources
|
||||
|
||||
val locations = mutableSetOf<URL>()
|
||||
val metaInfValLength = metaInfResources.length + 1
|
||||
|
||||
|
||||
// TODO: check if the modules restriction (see following note) is still the case for vaadin 14
|
||||
// NOTE: we cannot use "modules" yet (so no module-info.java file...) otherwise every dependency gets added to the module path,
|
||||
// and since almost NONE of them support modules, this will break us.
|
||||
|
||||
|
||||
// NOTE: we cannot use "modules" yet (so no module-info.java file...) otherwise every dependency gets added to the module path,
|
||||
// and since almost NONE of them support modules, this will break us.
|
||||
// find all of the jars in the module/classpath with resources in the META-INF directory
|
||||
logger.info("Discovering all bundled jar $metaInfResources locations")
|
||||
|
||||
val scanResultJarDependencies = ClassGraph()
|
||||
.filterClasspathElements { it.endsWith(".jar") }
|
||||
.whitelistPaths(metaInfResources)
|
||||
.scan()
|
||||
|
||||
|
||||
val scanResultLocalDependencies = ClassGraph()
|
||||
.filterClasspathElements { !it.endsWith(".jar") }
|
||||
.whitelistPaths(metaInfResources)
|
||||
.scan()
|
||||
|
||||
|
||||
if (runningAsJar) {
|
||||
// collect all the resources available.
|
||||
logger.info("Extracting all jar $metaInfResources files to $tempDir")
|
||||
|
||||
scanResultJarDependencies.allResources.forEach { resource ->
|
||||
val resourcePath = resource.pathRelativeToClasspathElement
|
||||
val relativePath = resourcePath.substring(metaInfValLength)
|
||||
|
||||
logger.trace {
|
||||
"Discovered resource: $relativePath"
|
||||
}
|
||||
|
||||
// we should copy this resource out, since loading resources from jar files is time+memory intensive
|
||||
val outputFile = File(tempDir, relativePath)
|
||||
|
||||
if (!outputFile.exists()) {
|
||||
val parentFile = outputFile.parentFile
|
||||
if (!parentFile.isDirectory && !parentFile.mkdirs()) {
|
||||
logger.error("Unable to create output directory {}", parentFile)
|
||||
} else {
|
||||
resource.open().use { input ->
|
||||
outputFile.outputStream().use { input.copyTo(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locations.add(tempDir.toURI().toURL())
|
||||
|
||||
// so we can use the undertow cache to serve resources, instead of the vaadin servlet (which doesn't cache, and is really slow)
|
||||
urlClassLoader = object : URLClassLoader(locations.toTypedArray(), this.javaClass.classLoader) {
|
||||
override fun getResource(name: String): URL? {
|
||||
if (name.startsWith("META-INF")) {
|
||||
// the problem is that:
|
||||
// request is : META-INF/VAADIN/build/webcomponentsjs/webcomponents-loader.js
|
||||
// resource is: VAADIN/build/webcomponentsjs/webcomponents-loader.js
|
||||
|
||||
val fixedName = name.substring("META-INF".length)
|
||||
return super.getResource(fixedName)
|
||||
}
|
||||
|
||||
return super.getResource(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// when we are running in DISK (aka, not-running-as-a-jar) mode, we are NOT extracting all of the resources to a temp location.
|
||||
// BECAUSE of this, we must create a MAP of the RELATIVE resource name --> ABSOLUTE resource name
|
||||
// This is so our classloader can find the resource without having to manually configure each requests.
|
||||
val resourceRequestMap = TreeMap<String, String>()
|
||||
|
||||
scanResultJarDependencies.allResources.forEach { resource ->
|
||||
val resourcePath = resource.pathRelativeToClasspathElement
|
||||
val relativePath = resourcePath.substring(metaInfValLength)
|
||||
|
||||
logger.trace {
|
||||
"Discovered resource: $relativePath"
|
||||
}
|
||||
|
||||
locations.add(resource.classpathElementURL)
|
||||
|
||||
resourceRequestMap[relativePath] = resourcePath
|
||||
// some-of the resources are loaded with a "META-INF" prefix by the vaadin servlet
|
||||
resourceRequestMap["META-INF/$relativePath"] = resourcePath
|
||||
}
|
||||
|
||||
|
||||
// some static resources from disk are ALSO loaded by the classloader.
|
||||
scanResultLocalDependencies.allResources.forEach { resource ->
|
||||
val resourcePath = resource.pathRelativeToClasspathElement
|
||||
val relativePath = resourcePath.substring(metaInfValLength)
|
||||
|
||||
logger.trace {
|
||||
"Discovered resource: $relativePath"
|
||||
}
|
||||
|
||||
locations.add(resource.classpathElementURL)
|
||||
|
||||
resourceRequestMap[relativePath] = resourcePath
|
||||
// some-of the resources are loaded with a "META-INF" prefix by the vaadin servlet
|
||||
resourceRequestMap["META-INF/$relativePath"] = resourcePath
|
||||
}
|
||||
|
||||
// so we can use the undertow cache to serve resources, instead of the vaadin servlet (which doesn't cache, and is really slow)
|
||||
urlClassLoader = object : URLClassLoader(locations.toTypedArray(), this.javaClass.classLoader) {
|
||||
val trie = DoubleArrayTrie(resourceRequestMap)
|
||||
|
||||
override fun getResource(name: String): URL? {
|
||||
val resourcePath = trie[name]
|
||||
if (resourcePath != null) {
|
||||
return super.getResource(resourcePath)
|
||||
}
|
||||
|
||||
return super.getResource(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect all the resources available from each location
|
||||
val diskResources = ArrayList<FileResourceManager>()
|
||||
val jarResources = ArrayList<JarResourceManager>()
|
||||
val fileResources = ArrayList<FileResourceManager>()
|
||||
|
||||
locations.forEach {
|
||||
val cleanedUrl = java.net.URLDecoder.decode(it.file, "UTF-8")
|
||||
val file = File(cleanedUrl)
|
||||
when {
|
||||
file.isFile && file.extension == "jar" -> {
|
||||
// the location IN THE JAR is actually "META-INF/resources", so we want to make sure of that when
|
||||
// serving the request, that the correct path is used.
|
||||
jarResources.add(JarResourceManager(file, metaInfResources))
|
||||
}
|
||||
file.isDirectory -> {
|
||||
// if this location is where our "META-INF/resources" directory exists, ALSO add that location, because the
|
||||
// vaadin will request resources based on THAT location as well.
|
||||
val metaInfResourcesLocation = File(file, metaInfResources)
|
||||
if (metaInfResourcesLocation.isDirectory) {
|
||||
diskResources.add(FileResourceManager(metaInfResourcesLocation))
|
||||
|
||||
// we will also serve content from ALL child directories
|
||||
metaInfResourcesLocation.listFiles()?.forEach { childFile ->
|
||||
when {
|
||||
childFile.isDirectory -> prefixResources.add("/${childFile.relativeTo(metaInfResourcesLocation)}")
|
||||
else -> exactResources.add("/${childFile.relativeTo(metaInfResourcesLocation)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diskResources.add(FileResourceManager(file))
|
||||
|
||||
// we will also serve content from ALL child directories
|
||||
// (except for the META-INF dir, which we are ALREADY serving content)
|
||||
file.listFiles()?.forEach { childFile ->
|
||||
when {
|
||||
childFile.isDirectory -> {
|
||||
if (childFile.name != "META-INF") {
|
||||
prefixResources.add("/${childFile.relativeTo(file)}")
|
||||
}
|
||||
}
|
||||
else -> exactResources.add("/${childFile.relativeTo(file)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
logger.error("Attempt to collect resource for an undefined location!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we are searching for resources, the following search order is optimized for access speed and request hit order
|
||||
// DISK
|
||||
// files
|
||||
// jars (since they are containers)
|
||||
// flow-client
|
||||
// flow-push
|
||||
// flow-server
|
||||
// then every other jar
|
||||
resources.addAll(diskResources)
|
||||
resources.addAll(fileResources)
|
||||
|
||||
val client = jarResources.firstOrNull { it.name.contains("flow-client") }
|
||||
val push = jarResources.firstOrNull { it.name.contains("flow-push") }
|
||||
val server = jarResources.firstOrNull { it.name.contains("flow-server") }
|
||||
|
||||
if (client != null && push != null && server != null) {
|
||||
// these jars will ALWAYS be available (as of Vaadin 14.2)
|
||||
// if we are running from a fatjar, then the resources will likely be extracted (so this is not necessary)
|
||||
jarResources.remove(client)
|
||||
jarResources.remove(push)
|
||||
jarResources.remove(server)
|
||||
|
||||
resources.add(client)
|
||||
resources.add(push)
|
||||
resources.add(server)
|
||||
}
|
||||
|
||||
resources.addAll(jarResources)
|
||||
|
||||
// NOTE: atmosphere is requesting the full path of 'WEB-INF/classes/'.
|
||||
// What do to? search this with classgraph OR we re-map this to 'out/production/classes/' ??
|
||||
// also accessed is : WEB-INF/lib/
|
||||
|
||||
// TODO: runtime GZ compression of resources!?! only necessary in the JAR run mode (which is what runs on servers)
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
onStopList.forEach {
|
||||
it.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun start(enableCachedHandlers: Boolean, cacheTimeoutSeconds: Int) {
|
||||
resourceCollectionManager = ResourceCollectionManager(resources)
|
||||
|
||||
val conditionalResourceManager =
|
||||
when {
|
||||
enableCachedHandlers -> {
|
||||
val cacheSize = 1024 // size of the cache
|
||||
val maxFileSize = 1024*1024*1024*10L // 10 mb file. The biggest file size we cache
|
||||
val maxFileAge = TimeUnit.HOURS.toMillis(1) // How long an item can stay in the cache in milliseconds
|
||||
val bufferCache = DirectBufferCache(1024, 10, 1024 * 1024 * 200)
|
||||
CachingResourceManager(cacheSize, maxFileSize, bufferCache, resourceCollectionManager, maxFileAge.toInt())
|
||||
}
|
||||
else -> {
|
||||
// sometimes it is really hard to debug when using the cache
|
||||
resourceCollectionManager
|
||||
}
|
||||
}
|
||||
|
||||
onStopList.add(Runnable {
|
||||
conditionalResourceManager.close()
|
||||
})
|
||||
|
||||
val servletBuilder = Servlets.deployment()
|
||||
.setClassLoader(urlClassLoader)
|
||||
.setResourceManager(conditionalResourceManager)
|
||||
.setDisplayName("Vaadin")
|
||||
.setDefaultEncoding("UTF-8")
|
||||
.setSecurityDisabled(true) // security is controlled in memory using vaadin
|
||||
.setContextPath("/") // root context path
|
||||
.setDeploymentName("Vaadin")
|
||||
.addServlets(
|
||||
Servlets.servlet("VaadinServlet", VaadinServlet::class.java)
|
||||
.setLoadOnStartup(1)
|
||||
.setAsyncSupported(true)
|
||||
.setExecutor(null) // we use coroutines!
|
||||
.addInitParam("productionMode", (!devMode).toString()) // this is set via the gradle build
|
||||
|
||||
// have to say where our NPM/dev mode files live.
|
||||
.addInitParam(FrontendUtils.PROJECT_BASEDIR, File("").absolutePath)
|
||||
|
||||
// have to say where our token file lives
|
||||
.addInitParam(FrontendUtils.PARAM_TOKEN_FILE, tokenFileName)
|
||||
|
||||
// where our stats.json file lives. This loads via classloader, not via a file!!
|
||||
.addInitParam(Constants.SERVLET_PARAMETER_STATISTICS_JSON, "VAADIN/config/stats.json")
|
||||
|
||||
.addInitParam("enable-websockets", "true")
|
||||
.addMapping("/*")
|
||||
)
|
||||
.setSessionPersistenceManager(FileSessionPersistence(tempDir))
|
||||
.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, WebSocketDeploymentInfo())
|
||||
|
||||
val sessionCookieName = ServletSessionConfig.DEFAULT_SESSION_ID
|
||||
|
||||
// use the created actor
|
||||
// FIXME: 8 actors and 2 threads concurrency on the actor map?
|
||||
val coroutineHttpWrapper = CoroutineHttpWrapper(sessionCookieName, 8, 2)
|
||||
|
||||
onStopList.add(Runnable {
|
||||
// launch new coroutine in background and continue, since we want to stop our http wrapper in a different coroutine!
|
||||
GlobalScope.launch {
|
||||
coroutineHttpWrapper.stop()
|
||||
}
|
||||
})
|
||||
|
||||
servletBuilder.initialHandlerChainWrappers.add(coroutineHttpWrapper)
|
||||
|
||||
// destroy the actors on session invalidation
|
||||
servletBuilder.addSessionListener(ActorSessionCleanup(coroutineHttpWrapper.actorsPerSession))
|
||||
|
||||
// configure how the servlet behaves
|
||||
val servletSessionConfig = ServletSessionConfig()
|
||||
// servletSessionConfig.isSecure = true // cookies are only possible when via HTTPS
|
||||
servletSessionConfig.sessionTrackingModes = setOf(SessionTrackingMode.COOKIE)
|
||||
servletSessionConfig.name = sessionCookieName
|
||||
servletBuilder.servletSessionConfig = servletSessionConfig
|
||||
|
||||
|
||||
// Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, we must add the classes
|
||||
// Next, scan all these classes so we can call their onStartup() methods correctly
|
||||
val serviceLoader = ServiceLoader.load(ServletContainerInitializer::class.java)
|
||||
|
||||
ClassGraph().enableAnnotationInfo().enableClassInfo().scan().use { annotationScanner ->
|
||||
for (service in serviceLoader) {
|
||||
val classSet = hashSetOf<Class<*>>()
|
||||
val javaClass = service.javaClass
|
||||
val annotation= javaClass.getAnnotation(HandlesTypes::class.java)
|
||||
|
||||
if (annotation != null) {
|
||||
val classes = annotation.value
|
||||
for (aClass in classes) {
|
||||
addAnnotated(annotationScanner, aClass, classSet)
|
||||
}
|
||||
}
|
||||
|
||||
if (classSet.isNotEmpty()) {
|
||||
servletBuilder.addServletContainerInitializer(ServletContainerInitializerInfo(javaClass, classSet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// INITIALIZING AND STARTING THE SERVLET
|
||||
/////////////////////////////////////////////////////////////////
|
||||
val manager = Servlets.defaultContainer().addDeployment(servletBuilder)
|
||||
manager.deploy()
|
||||
servletHttpHandler = manager.start()
|
||||
|
||||
|
||||
// NOTE: look into SessionRestoringHandler to keep session state across re-deploys (this is normally not used in production). this might just be tricks with classloaders to keep sessions around
|
||||
// we also want to save sessions to disk, and be able to read from them if we want See InMemorySessionManager (we would have to write our own)
|
||||
|
||||
/*
|
||||
* look at the following
|
||||
* GracefulShutdownHandler
|
||||
* LearningPushHandler
|
||||
* RedirectHandler
|
||||
* RequestLimitingHandler
|
||||
* SecureCookieHandler
|
||||
*
|
||||
*
|
||||
* to setup ALPN and ssl, it's FASTER to use openssl instead of java
|
||||
* http://wildfly.org/news/2017/10/06/OpenSSL-Support-In-Wildfly/
|
||||
* https://github.com/undertow-io/undertow/blob/master/core/src/main/java/io/undertow/protocols/alpn/OpenSSLAlpnProvider.java
|
||||
*/
|
||||
|
||||
|
||||
if (devMode) {
|
||||
// NOTE: The vaadin flow files only exist AFTER vaadin is initialized, so this block MUST be after 'manager.deploy()'
|
||||
// in dev mode, the local resources are hardcoded to an **INCORRECT** location. They
|
||||
// are hardcoded to "src/main/resources/META-INF/resources/frontend", so we have to
|
||||
// copy them ourselves from the correct location... ( node_modules/@vaadin/flow-frontend/ )
|
||||
val targetDir = File("build", FrontendUtils.NODE_MODULES + FrontendUtils.FLOW_NPM_PACKAGE_NAME).absoluteFile
|
||||
logger.info("Copying local frontend resources to $targetDir")
|
||||
if (!targetDir.exists()) {
|
||||
throw RuntimeException("Startup directories are missing! Unable to continue - please run compileResources for DEV mode!")
|
||||
}
|
||||
|
||||
File("frontend").absoluteFile.copyRecursively(targetDir, true)
|
||||
}
|
||||
|
||||
|
||||
// directly serve our static requests in the IO thread (and not in a worker/coroutine)
|
||||
val staticResourceHandler = DirectResourceHandler(resourceCollectionManager)
|
||||
if (enableCachedHandlers) {
|
||||
staticResourceHandler.setCacheTime(cacheTimeoutSeconds) // tell the browser to cache our static resources (in seconds)
|
||||
}
|
||||
|
||||
cacheHandler = when {
|
||||
enableCachedHandlers -> {
|
||||
val cache = DirectBufferCache(1024, 10, 1024 * 1024 * 200)
|
||||
CacheHandler(cache, staticResourceHandler)
|
||||
}
|
||||
else -> {
|
||||
// sometimes it is really hard to debug when using the cache
|
||||
staticResourceHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleRequest(exchange: HttpServerExchange) {
|
||||
val path = exchange.relativePath
|
||||
|
||||
// serve the following directly via the resource handler, so we can do it directly in the networking IO thread.
|
||||
// Because this is non-blocking, this is also the preferred way to do this for performance.
|
||||
// at the time of writing, this was "/icons", "/images", and in production mode "/VAADIN"
|
||||
exactResources.forEach {
|
||||
if (path == it) {
|
||||
cacheHandler.handleRequest(exchange)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
prefixResources.forEach {
|
||||
if (path.startsWith(it)) {
|
||||
cacheHandler.handleRequest(exchange)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// this is the default, and will use coroutines + servlet to handle the request
|
||||
servletHttpHandler.handleRequest(exchange)
|
||||
}
|
||||
//
|
||||
// fun startServer(logger: Logger) {
|
||||
// // always show this part.
|
||||
// val webLogger = logger as ch.qos.logback.classic.Logger
|
||||
//
|
||||
// // save the logger level, so that on startup we can see more detailed info, if necessary.
|
||||
// val level = webLogger.level
|
||||
// if (logger.isTraceEnabled) {
|
||||
// webLogger.level = Level.TRACE
|
||||
// }
|
||||
// else {
|
||||
// webLogger.level = Level.INFO
|
||||
// }
|
||||
//
|
||||
// val server = serverBuilder.build()
|
||||
// try {
|
||||
// // NOTE: we start this in a NEW THREAD so we can create and use a thread-group for all of the undertow threads created. This allows
|
||||
// // us to keep our main thread group "un-cluttered" when analyzing thread/stack traces.
|
||||
// //
|
||||
// // This is a hacky, but undertow does not support setting the thread group in the builder.
|
||||
//
|
||||
// val exceptionThrown = AtomicReference<Exception>()
|
||||
// val latch = CountDownLatch(1)
|
||||
//
|
||||
// Thread(threadGroup) {
|
||||
// try {
|
||||
// server.start()
|
||||
// webServer = server
|
||||
//
|
||||
// WebServerConfig.logStartup(logger)
|
||||
//
|
||||
// extraStartables.forEach { it ->
|
||||
// it.run()
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// exceptionThrown.set(e)
|
||||
// } finally {
|
||||
// latch.countDown()
|
||||
// }
|
||||
// }.start()
|
||||
//
|
||||
// latch.await()
|
||||
//
|
||||
// val exception = exceptionThrown.get()
|
||||
// if (exception != null) {
|
||||
// throw exception
|
||||
// }
|
||||
// }
|
||||
// finally {
|
||||
// webLogger.level = level
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// fun stopServer(logger: Logger) {
|
||||
// // always show this part.
|
||||
// val webLogger = logger as ch.qos.logback.classic.Logger
|
||||
// val undertowLogger = LoggerFactory.getLogger("org.xnio.nio") as ch.qos.logback.classic.Logger
|
||||
//
|
||||
// // save the logger level, so that on shutdown we can see more detailed info, if necessary.
|
||||
// val level = webLogger.level
|
||||
// val undertowLevel = undertowLogger.level
|
||||
// if (logger.isTraceEnabled) {
|
||||
// webLogger.level = Level.TRACE
|
||||
// undertowLogger.level = Level.TRACE
|
||||
// }
|
||||
// else {
|
||||
// // we REALLY don't care about shutdown errors. we are shutting down!! (atmosphere likes to screw with us!)
|
||||
// webLogger.level = Level.OFF
|
||||
// undertowLogger.level = Level.OFF
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// webServer?.stop()
|
||||
//
|
||||
// extraStoppables.forEach { it ->
|
||||
// it.run()
|
||||
// }
|
||||
// }
|
||||
// finally {
|
||||
// webLogger.level = level
|
||||
// undertowLogger.level = undertowLevel
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import dorkbox.vaadin.util.ClassUtils.createCompositeInterface
|
||||
import dorkbox.vaadin.util.ClassUtils.forName
|
||||
import java.io.*
|
||||
|
||||
/**
|
||||
* Special ObjectInputStream subclass that resolves class names
|
||||
* against a specific ClassLoader.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.5
|
||||
*/
|
||||
class ConfigurableObjectInputStream
|
||||
|
||||
/**
|
||||
* Create a new ConfigurableObjectInputStream for the given InputStream and ClassLoader.
|
||||
*
|
||||
* @param in the InputStream to read from
|
||||
* @param classLoader the ClassLoader to use for loading local classes
|
||||
* @param acceptProxyClasses whether to accept deserialization of proxy classes (may be deactivated as a security measure)
|
||||
*
|
||||
* @see java.io.ObjectInputStream.ObjectInputStream
|
||||
*/
|
||||
@JvmOverloads
|
||||
constructor(`in`: InputStream?, private val classLoader: ClassLoader?, private val acceptProxyClasses: Boolean = true) : ObjectInputStream(`in`) {
|
||||
|
||||
@Throws(IOException::class, ClassNotFoundException::class)
|
||||
override fun resolveClass(classDesc: ObjectStreamClass): Class<*> {
|
||||
return try {
|
||||
if (classLoader != null) {
|
||||
// Use the specified ClassLoader to resolve local classes.
|
||||
forName(classDesc.name, classLoader)
|
||||
} else {
|
||||
// Use the default ClassLoader...
|
||||
super.resolveClass(classDesc)
|
||||
}
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ClassNotFoundException::class)
|
||||
override fun resolveProxyClass(interfaces: Array<String>): Class<*> {
|
||||
if (!acceptProxyClasses) {
|
||||
throw NotSerializableException("Not allowed to accept serialized proxy classes")
|
||||
}
|
||||
return if (classLoader != null) {
|
||||
// Use the specified ClassLoader to resolve local proxy classes.
|
||||
val resolvedInterfaces = arrayOfNulls<Class<*>?>(interfaces.size)
|
||||
for (i in interfaces.indices) {
|
||||
try {
|
||||
resolvedInterfaces[i] = forName(interfaces[i], classLoader)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
createCompositeInterface(resolvedInterfaces, classLoader)
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
throw ClassNotFoundException(null, ex)
|
||||
}
|
||||
} else {
|
||||
// Use ObjectInputStream's default ClassLoader...
|
||||
try {
|
||||
super.resolveProxyClass(interfaces)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright 2020 Dorkbox LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import dorkbox.vaadin.util.logger
|
||||
import io.undertow.server.Connectors
|
||||
import io.undertow.server.HandlerWrapper
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.session.Session
|
||||
import io.undertow.server.session.SessionListener
|
||||
import io.undertow.util.AttachmentKey
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.channels.actor
|
||||
import kotlinx.coroutines.channels.sendBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
val ACTOR = AttachmentKey.create<SendChannel<HttpServerExchange>>(SendChannel::class.java)!!
|
||||
|
||||
/**
|
||||
* This runs just BEFORE the main servlet handler, and lets us run the servlet logic on an actor. Each servlet has it's own actor,
|
||||
* and can queue 8 "servlet" requests at a time
|
||||
*/
|
||||
class CoroutineHttpWrapper(private val sessionCookieName: String, private val capacity: Int, concurrencyFactor: Int) : HandlerWrapper {
|
||||
|
||||
private val logger = logger()
|
||||
var actorsPerSession = ConcurrentHashMap<String, SendChannel<HttpServerExchange>>(8, 0.9f, concurrencyFactor)
|
||||
|
||||
private lateinit var defaultActor: SendChannel<HttpServerExchange>
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
val job = Job()
|
||||
val mutex = Mutex()
|
||||
|
||||
@kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
private fun createActor(handler: HttpHandler) = scope.actor<HttpServerExchange>(context = job, capacity = capacity) {
|
||||
logger.info { "Starting actor $this" }
|
||||
|
||||
for (msg in channel) {
|
||||
Connectors.executeRootHandler(handler, msg)
|
||||
}
|
||||
|
||||
println("stopping actor $this")
|
||||
}
|
||||
|
||||
|
||||
override fun wrap(handler: HttpHandler): HttpHandler {
|
||||
@kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
defaultActor = createActor(handler)
|
||||
|
||||
return HttpHandler { exchange ->
|
||||
exchange.startBlocking()
|
||||
// IO is on a single thread
|
||||
|
||||
// how to check if we are running in the actor?
|
||||
|
||||
if (exchange.isInIoThread) {
|
||||
// check if we've already created the actor for this exchange
|
||||
var actor: SendChannel<HttpServerExchange> = exchange.getAttachment(ACTOR) ?: defaultActor
|
||||
|
||||
if (actor == defaultActor) {
|
||||
actor = getOrCreateActor(handler, exchange)
|
||||
exchange.putAttachment(ACTOR, actor)
|
||||
}
|
||||
|
||||
// suppressed because we DO NOT use threading, we use coroutines, which are semantically different
|
||||
@Suppress("DEPRECATION")
|
||||
exchange.dispatch() // this marks the exchange as having been DISPATCHED
|
||||
actor.sendBlocking(exchange)
|
||||
}
|
||||
else {
|
||||
// if we are not in the IO thread, just process as normal
|
||||
handler.handleRequest(exchange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateActor(handler: HttpHandler, exchange: HttpServerExchange): SendChannel<HttpServerExchange> {
|
||||
// we key off of the session ID for this. If this is changed, then we have to use whatever is different
|
||||
|
||||
@Suppress("MoveVariableDeclarationIntoWhen")
|
||||
val sessionCookie = exchange.getRequestCookie(sessionCookieName)
|
||||
return when (sessionCookie) {
|
||||
null -> defaultActor
|
||||
else -> {
|
||||
// we have a session ID, so use that to create/use an actor
|
||||
var maybeActor = actorsPerSession[sessionCookie.value]
|
||||
if (maybeActor == null) {
|
||||
@kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
maybeActor = createActor(handler)
|
||||
|
||||
|
||||
// pass coroutine info through java?
|
||||
// https://stackoverflow.com/questions/51808992/kotlin-suspend-fun/51811597#51811597
|
||||
|
||||
// NOTE: For vaadin, we also have to create the lock via QUASAR !!!! since vaadin uses explicit locking (on a lock object) VaadinService.lockSession
|
||||
// /*
|
||||
// * No lock found in the session attribute. Ensure only one lock is
|
||||
// * created and used by everybody by doing double checked locking.
|
||||
// * Assumes there is a memory barrier for the attribute (i.e. that
|
||||
// * the CPU flushes its caches and reads the value directly from main
|
||||
// * memory).
|
||||
// */
|
||||
// synchronized (VaadinService.class) {
|
||||
// lock = getSessionLock(wrappedSession);
|
||||
// if (lock == null) {
|
||||
// lock = new ReentrantLock();
|
||||
// setSessionLock(wrappedSession, lock);
|
||||
// }
|
||||
// }
|
||||
|
||||
actorsPerSession[sessionCookie.value] = maybeActor
|
||||
exchange.putAttachment(ACTOR, maybeActor)
|
||||
}
|
||||
|
||||
maybeActor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stop() = coroutineScope {
|
||||
job.cancelAndJoin()
|
||||
actorsPerSession.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroys the actor when the session is destroyed
|
||||
*/
|
||||
class ActorSessionCleanup(private val map: MutableMap<String, SendChannel<HttpServerExchange>>) : SessionListener {
|
||||
override fun sessionDestroyed(session: Session,
|
||||
exchange: HttpServerExchange?,
|
||||
reason: SessionListener.SessionDestroyedReason) {
|
||||
|
||||
// destroy the Actor and remove the session object
|
||||
map.remove(session.id)?.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2014 Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import io.undertow.UndertowLogger
|
||||
import io.undertow.io.IoCallback
|
||||
import io.undertow.predicate.Predicate
|
||||
import io.undertow.predicate.Predicates
|
||||
import io.undertow.server.HandlerWrapper
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.handlers.ResponseCodeHandler
|
||||
import io.undertow.server.handlers.builder.HandlerBuilder
|
||||
import io.undertow.server.handlers.cache.ResponseCache
|
||||
import io.undertow.server.handlers.encoding.ContentEncodedResourceManager
|
||||
import io.undertow.server.handlers.resource.*
|
||||
import io.undertow.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("unused")
|
||||
/**
|
||||
* MODIFIED from ResourceHandler to serve resources directly in the IO thread!
|
||||
* Some small tweaks to make it more friendly for kotlin (ZERO LOGIC CHANGES IN HANDLER!)
|
||||
*
|
||||
*
|
||||
* @param next Handler that is called if no resource is found
|
||||
* @author Stuart Douglas
|
||||
*/
|
||||
class DirectResourceHandler(@Volatile private var resourceManager: ResourceManager?,
|
||||
@Volatile private var resourceSupplier: ResourceSupplier = DefaultResourceSupplier(resourceManager),
|
||||
private val next: HttpHandler = ResponseCodeHandler.HANDLE_404) : HttpHandler {
|
||||
|
||||
private val welcomeFiles = CopyOnWriteArrayList(arrayOf("index.html", "index.htm", "default.html", "default.htm"))
|
||||
/**
|
||||
* If directory listing is enabled.
|
||||
*/
|
||||
@Volatile
|
||||
private var directoryListingEnabled = false
|
||||
|
||||
/**
|
||||
* If the canonical version of paths should be passed into the resource manager.
|
||||
*/
|
||||
/**
|
||||
* If this handler should use canonicalized paths.
|
||||
*
|
||||
* WARNING: If this is not true and [io.undertow.server.handlers.CanonicalPathHandler] is not installed in
|
||||
* the handler chain then is may be possible to perform a directory traversal attack. If you set this to false make
|
||||
* sure you have some kind of check in place to control the path.
|
||||
* @param canonicalizePaths If paths should be canonicalized
|
||||
*/
|
||||
@Volatile
|
||||
var isCanonicalizePaths = true
|
||||
|
||||
/**
|
||||
* The mime mappings that are used to determine the content type.
|
||||
*/
|
||||
@Volatile
|
||||
private var mimeMappings = MimeMappings.DEFAULT
|
||||
@Volatile
|
||||
private var cachable = Predicates.truePredicate()
|
||||
@Volatile
|
||||
private var allowed = Predicates.truePredicate()
|
||||
|
||||
/**
|
||||
* If this is set this will be the maximum time (in seconds) the client will cache the resource.
|
||||
*
|
||||
*
|
||||
* Note: Do not set this for private resources, as it will cause a Cache-Control: public
|
||||
* to be sent.
|
||||
*
|
||||
*
|
||||
* TODO: make this more flexible
|
||||
*
|
||||
*
|
||||
* This will only be used if the [.cachable] predicate returns true
|
||||
*/
|
||||
@Volatile
|
||||
private var cacheTime: Int? = null
|
||||
|
||||
@Volatile
|
||||
private var contentEncodedResourceManager: ContentEncodedResourceManager? = null
|
||||
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun handleRequest(exchange: HttpServerExchange) {
|
||||
if (exchange.requestMethod.equals(Methods.GET) || exchange.requestMethod.equals(Methods.POST)) {
|
||||
serveResource(exchange, true)
|
||||
}
|
||||
else if (exchange.requestMethod.equals(Methods.HEAD)) {
|
||||
serveResource(exchange, false)
|
||||
}
|
||||
else {
|
||||
if (KNOWN_METHODS.contains(exchange.requestMethod)) {
|
||||
exchange.statusCode = StatusCodes.METHOD_NOT_ALLOWED
|
||||
exchange.responseHeaders.add(Headers.ALLOW,
|
||||
arrayOf(Methods.GET_STRING, Methods.HEAD_STRING, Methods.POST_STRING).joinToString(", "))
|
||||
}
|
||||
else {
|
||||
exchange.statusCode = StatusCodes.NOT_IMPLEMENTED
|
||||
}
|
||||
exchange.endExchange()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun serveResource(exchange: HttpServerExchange,
|
||||
sendContent: Boolean) {
|
||||
|
||||
if (DirectoryUtils.sendRequestedBlobs(exchange)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!allowed.resolve(exchange)) {
|
||||
exchange.statusCode = StatusCodes.FORBIDDEN
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
|
||||
val cache = exchange.getAttachment(ResponseCache.ATTACHMENT_KEY)
|
||||
val cachable = this.cachable.resolve(exchange)
|
||||
|
||||
//we set caching headers before we try and serve from the cache
|
||||
if (cachable && cacheTime != null) {
|
||||
exchange.responseHeaders.put(Headers.CACHE_CONTROL, "public, max-age=" + cacheTime!!)
|
||||
val date = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(cacheTime!!.toLong())
|
||||
val dateHeader = DateUtils.toDateString(Date(date))
|
||||
exchange.responseHeaders.put(Headers.EXPIRES, dateHeader)
|
||||
}
|
||||
|
||||
if (cache != null && cachable) {
|
||||
if (cache.tryServeResponse()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var resource: Resource? = null
|
||||
try {
|
||||
if (File.separatorChar == '/' || !exchange.relativePath.contains(File.separator)) {
|
||||
//we don't process resources that contain the seperator character if this is not /
|
||||
//this prevents attacks where people use windows path seperators in file URLS's
|
||||
resource = resourceSupplier.getResource(exchange, canonicalize(exchange.relativePath))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
clearCacheHeaders(exchange)
|
||||
UndertowLogger.REQUEST_IO_LOGGER.ioException(e)
|
||||
exchange.statusCode = StatusCodes.INTERNAL_SERVER_ERROR
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
|
||||
if (resource == null) {
|
||||
clearCacheHeaders(exchange)
|
||||
//usually a 404 handler
|
||||
next.handleRequest(exchange)
|
||||
return
|
||||
}
|
||||
|
||||
if (resource.isDirectory) {
|
||||
val indexResource: Resource?
|
||||
try {
|
||||
indexResource = getIndexFiles(exchange, resourceSupplier, resource.path, welcomeFiles)
|
||||
} catch (e: IOException) {
|
||||
UndertowLogger.REQUEST_IO_LOGGER.ioException(e)
|
||||
exchange.statusCode = StatusCodes.INTERNAL_SERVER_ERROR
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
|
||||
if (indexResource == null) {
|
||||
if (directoryListingEnabled) {
|
||||
DirectoryUtils.renderDirectoryListing(exchange, resource)
|
||||
return
|
||||
}
|
||||
else {
|
||||
exchange.statusCode = StatusCodes.FORBIDDEN
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
}
|
||||
else if (!exchange.requestPath.endsWith("/")) {
|
||||
exchange.statusCode = StatusCodes.FOUND
|
||||
exchange.responseHeaders.put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.relativePath + "/", true))
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
resource = indexResource
|
||||
}
|
||||
else if (exchange.relativePath.endsWith("/")) {
|
||||
//UNDERTOW-432
|
||||
exchange.statusCode = StatusCodes.NOT_FOUND
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
|
||||
val etag = resource.eTag
|
||||
val lastModified = resource.lastModified
|
||||
if (!ETagUtils.handleIfMatch(exchange, etag, false) || !DateUtils.handleIfUnmodifiedSince(exchange, lastModified)) {
|
||||
exchange.statusCode = StatusCodes.PRECONDITION_FAILED
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
if (!ETagUtils.handleIfNoneMatch(exchange, etag, true) || !DateUtils.handleIfModifiedSince(exchange, lastModified)) {
|
||||
exchange.statusCode = StatusCodes.NOT_MODIFIED
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
val contentEncodedResourceManager = this@DirectResourceHandler.contentEncodedResourceManager
|
||||
val contentLength = resource.contentLength
|
||||
|
||||
if (contentLength != null && !exchange.responseHeaders.contains(Headers.TRANSFER_ENCODING)) {
|
||||
exchange.responseContentLength = contentLength
|
||||
}
|
||||
var rangeResponse: ByteRange.RangeResponseResult? = null
|
||||
var start: Long = -1
|
||||
var end: Long = -1
|
||||
if (resource is RangeAwareResource && resource.isRangeSupported && contentLength != null && contentEncodedResourceManager == null) {
|
||||
|
||||
exchange.responseHeaders.put(Headers.ACCEPT_RANGES, "bytes")
|
||||
//TODO: figure out what to do with the content encoded resource manager
|
||||
val range = ByteRange.parse(exchange.requestHeaders.getFirst(Headers.RANGE))
|
||||
if (range != null && range.ranges == 1 && resource.contentLength != null) {
|
||||
rangeResponse = range.getResponseResult(resource.contentLength!!,
|
||||
exchange.requestHeaders.getFirst(Headers.IF_RANGE),
|
||||
resource.lastModified,
|
||||
if (resource.eTag == null) null else resource.eTag.tag)
|
||||
if (rangeResponse != null) {
|
||||
start = rangeResponse.start
|
||||
end = rangeResponse.end
|
||||
exchange.statusCode = rangeResponse.statusCode
|
||||
exchange.responseHeaders.put(Headers.CONTENT_RANGE, rangeResponse.contentRange)
|
||||
val length = rangeResponse.contentLength
|
||||
exchange.responseContentLength = length
|
||||
if (rangeResponse.statusCode == StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//we are going to proceed. Set the appropriate headers
|
||||
|
||||
if (!exchange.responseHeaders.contains(Headers.CONTENT_TYPE)) {
|
||||
val contentType = resource.getContentType(mimeMappings)
|
||||
if (contentType != null) {
|
||||
exchange.responseHeaders.put(Headers.CONTENT_TYPE, contentType)
|
||||
}
|
||||
else {
|
||||
exchange.responseHeaders.put(Headers.CONTENT_TYPE, "application/octet-stream")
|
||||
}
|
||||
}
|
||||
if (lastModified != null) {
|
||||
exchange.responseHeaders.put(Headers.LAST_MODIFIED, resource.lastModifiedString)
|
||||
}
|
||||
if (etag != null) {
|
||||
exchange.responseHeaders.put(Headers.ETAG, etag.toString())
|
||||
}
|
||||
|
||||
if (contentEncodedResourceManager != null) {
|
||||
try {
|
||||
val encoded = contentEncodedResourceManager.getResource(resource, exchange)
|
||||
if (encoded != null) {
|
||||
exchange.responseHeaders.put(Headers.CONTENT_ENCODING, encoded.contentEncoding)
|
||||
exchange.responseHeaders.put(Headers.CONTENT_LENGTH, encoded.resource.contentLength!!)
|
||||
encoded.resource.serve(exchange.responseSender, exchange, IoCallback.END_EXCHANGE)
|
||||
return
|
||||
}
|
||||
|
||||
} catch (e: IOException) {
|
||||
//TODO: should this be fatal
|
||||
UndertowLogger.REQUEST_IO_LOGGER.ioException(e)
|
||||
exchange.statusCode = StatusCodes.INTERNAL_SERVER_ERROR
|
||||
exchange.endExchange()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!sendContent) {
|
||||
exchange.endExchange()
|
||||
}
|
||||
else if (rangeResponse != null) {
|
||||
(resource as RangeAwareResource).serveRange(exchange.responseSender, exchange, start, end, IoCallback.END_EXCHANGE)
|
||||
}
|
||||
else {
|
||||
resource.serve(exchange.responseSender, exchange, IoCallback.END_EXCHANGE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCacheHeaders(exchange: HttpServerExchange) {
|
||||
exchange.responseHeaders.remove(Headers.CACHE_CONTROL)
|
||||
exchange.responseHeaders.remove(Headers.EXPIRES)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun getIndexFiles(exchange: HttpServerExchange,
|
||||
resourceManager: ResourceSupplier?,
|
||||
base: String,
|
||||
possible: List<String>): Resource? {
|
||||
val realBase: String
|
||||
if (base.endsWith("/")) {
|
||||
realBase = base
|
||||
}
|
||||
else {
|
||||
realBase = "$base/"
|
||||
}
|
||||
for (possibility in possible) {
|
||||
val index = resourceManager!!.getResource(exchange, canonicalize(realBase + possibility))
|
||||
if (index != null) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun canonicalize(s: String): String {
|
||||
return if (isCanonicalizePaths) {
|
||||
CanonicalPathUtils.canonicalize(s)
|
||||
}
|
||||
else s
|
||||
}
|
||||
|
||||
fun isDirectoryListingEnabled(): Boolean {
|
||||
return directoryListingEnabled
|
||||
}
|
||||
|
||||
fun setDirectoryListingEnabled(directoryListingEnabled: Boolean): DirectResourceHandler {
|
||||
this.directoryListingEnabled = directoryListingEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun addWelcomeFiles(vararg files: String): DirectResourceHandler {
|
||||
this.welcomeFiles.addAll(Arrays.asList(*files))
|
||||
return this
|
||||
}
|
||||
|
||||
fun setWelcomeFiles(vararg files: String): DirectResourceHandler {
|
||||
this.welcomeFiles.clear()
|
||||
this.welcomeFiles.addAll(Arrays.asList(*files))
|
||||
return this
|
||||
}
|
||||
|
||||
fun getMimeMappings(): MimeMappings {
|
||||
return mimeMappings
|
||||
}
|
||||
|
||||
fun setMimeMappings(mimeMappings: MimeMappings): DirectResourceHandler {
|
||||
this.mimeMappings = mimeMappings
|
||||
return this
|
||||
}
|
||||
|
||||
fun getCachable(): Predicate {
|
||||
return cachable
|
||||
}
|
||||
|
||||
fun setCachable(cachable: Predicate): DirectResourceHandler {
|
||||
this.cachable = cachable
|
||||
return this
|
||||
}
|
||||
|
||||
fun getAllowed(): Predicate {
|
||||
return allowed
|
||||
}
|
||||
|
||||
fun setAllowed(allowed: Predicate): DirectResourceHandler {
|
||||
this.allowed = allowed
|
||||
return this
|
||||
}
|
||||
|
||||
fun getResourceSupplier(): ResourceSupplier? {
|
||||
return resourceSupplier
|
||||
}
|
||||
|
||||
fun setResourceSupplier(resourceSupplier: ResourceSupplier): DirectResourceHandler {
|
||||
this.resourceSupplier = resourceSupplier
|
||||
this.resourceManager = null
|
||||
return this
|
||||
}
|
||||
|
||||
fun getResourceManager(): ResourceManager? {
|
||||
return resourceManager
|
||||
}
|
||||
|
||||
fun setResourceManager(resourceManager: ResourceManager): DirectResourceHandler {
|
||||
this.resourceManager = resourceManager
|
||||
this.resourceSupplier = DefaultResourceSupplier(resourceManager)
|
||||
return this
|
||||
}
|
||||
|
||||
fun getCacheTime(): Int? {
|
||||
return cacheTime
|
||||
}
|
||||
|
||||
fun setCacheTime(cacheTime: Int): DirectResourceHandler {
|
||||
this.cacheTime = cacheTime
|
||||
return this
|
||||
}
|
||||
|
||||
fun getContentEncodedResourceManager(): ContentEncodedResourceManager? {
|
||||
return contentEncodedResourceManager
|
||||
}
|
||||
|
||||
fun setContentEncodedResourceManager(contentEncodedResourceManager: ContentEncodedResourceManager): DirectResourceHandler {
|
||||
this.contentEncodedResourceManager = contentEncodedResourceManager
|
||||
return this
|
||||
}
|
||||
|
||||
class Builder : HandlerBuilder {
|
||||
|
||||
override fun name(): String {
|
||||
return "resource"
|
||||
}
|
||||
|
||||
override fun parameters(): Map<String, Class<*>> {
|
||||
val params = HashMap<String, Class<*>>()
|
||||
params["location"] = String::class.java
|
||||
params["allow-listing"] = Boolean::class.java
|
||||
return params
|
||||
}
|
||||
|
||||
override fun requiredParameters(): Set<String> {
|
||||
return setOf("location")
|
||||
}
|
||||
|
||||
override fun defaultParameter(): String {
|
||||
return "location"
|
||||
}
|
||||
|
||||
override fun build(config: Map<String, Any>): HandlerWrapper {
|
||||
return Wrapper(config["location"] as String, config["allow-listing"] as Boolean)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Wrapper internal constructor(private val location: String,
|
||||
private val allowDirectoryListing: Boolean) : HandlerWrapper {
|
||||
|
||||
override fun wrap(handler: HttpHandler): HttpHandler {
|
||||
val rm = PathResourceManager(Paths.get(location), 1024)
|
||||
val resourceHandler = DirectResourceHandler(rm)
|
||||
resourceHandler.setDirectoryListingEnabled(allowDirectoryListing)
|
||||
return resourceHandler
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Set of methods prescribed by HTTP 1.1. If request method is not one of those, handler will
|
||||
* return NOT_IMPLEMENTED.
|
||||
*/
|
||||
private val KNOWN_METHODS = HashSet<HttpString>()
|
||||
|
||||
init {
|
||||
KNOWN_METHODS.add(Methods.OPTIONS)
|
||||
KNOWN_METHODS.add(Methods.GET)
|
||||
KNOWN_METHODS.add(Methods.HEAD)
|
||||
KNOWN_METHODS.add(Methods.POST)
|
||||
KNOWN_METHODS.add(Methods.PUT)
|
||||
KNOWN_METHODS.add(Methods.DELETE)
|
||||
KNOWN_METHODS.add(Methods.TRACE)
|
||||
KNOWN_METHODS.add(Methods.CONNECT)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import io.undertow.servlet.UndertowServletLogger
|
||||
import io.undertow.servlet.api.SessionPersistenceManager
|
||||
import io.undertow.servlet.api.SessionPersistenceManager.PersistentSession
|
||||
import java.io.*
|
||||
|
||||
/**
|
||||
* [SessionPersistenceManager] that stores session information in a file.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Peter Leibiger
|
||||
* @author Raja Kolli
|
||||
*/
|
||||
class FileSessionPersistence(private val dir: File) : SessionPersistenceManager {
|
||||
override fun persistSessions(deploymentName: String, sessionData: Map<String, PersistentSession>) {
|
||||
try {
|
||||
save(sessionData, getSessionFile(deploymentName))
|
||||
} catch (ex: Exception) {
|
||||
UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(ex)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun save(sessionData: Map<String, PersistentSession>, file: File) {
|
||||
ObjectOutputStream(FileOutputStream(file)).use { stream -> save(sessionData, stream) }
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun save(sessionData: Map<String, PersistentSession>, stream: ObjectOutputStream) {
|
||||
val session: MutableMap<String, Serializable> = LinkedHashMap()
|
||||
sessionData.forEach { (key: String, value: PersistentSession) -> session[key] = SerializablePersistentSession(value) }
|
||||
stream.writeObject(session)
|
||||
}
|
||||
|
||||
override fun loadSessionAttributes(deploymentName: String, classLoader: ClassLoader): Map<String, PersistentSession>? {
|
||||
try {
|
||||
val file = getSessionFile(deploymentName)
|
||||
if (file.exists()) {
|
||||
return load(file, classLoader)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(ex)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ClassNotFoundException::class)
|
||||
private fun load(file: File, classLoader: ClassLoader): Map<String, PersistentSession> {
|
||||
ConfigurableObjectInputStream(FileInputStream(file), classLoader).use { stream -> return load(stream) }
|
||||
}
|
||||
|
||||
@Throws(ClassNotFoundException::class, IOException::class)
|
||||
private fun load(stream: ObjectInputStream): Map<String, PersistentSession> {
|
||||
val session = readSession(stream)
|
||||
val time = System.currentTimeMillis()
|
||||
val result: MutableMap<String, PersistentSession> = LinkedHashMap()
|
||||
session.forEach { (key: String, value: SerializablePersistentSession) ->
|
||||
val entrySession = value.persistentSession
|
||||
if (entrySession.expiration.time > time) {
|
||||
result[key] = entrySession
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(ClassNotFoundException::class, IOException::class)
|
||||
private fun readSession(stream: ObjectInputStream): Map<String, SerializablePersistentSession> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return stream.readObject() as Map<String, SerializablePersistentSession>
|
||||
}
|
||||
|
||||
private fun getSessionFile(deploymentName: String): File {
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
return File(dir, "$deploymentName.session")
|
||||
}
|
||||
|
||||
override fun clear(deploymentName: String) {
|
||||
getSessionFile(deploymentName).delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* Session data in a serializable form.
|
||||
*/
|
||||
internal class SerializablePersistentSession(session: PersistentSession) : Serializable {
|
||||
private val expiration = session.expiration
|
||||
private val sessionData = LinkedHashMap(session.sessionData)
|
||||
|
||||
val persistentSession: PersistentSession
|
||||
get() = PersistentSession(expiration, sessionData)
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import io.undertow.UndertowMessages
|
||||
import io.undertow.server.handlers.resource.Resource
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||
import io.undertow.server.handlers.resource.ResourceManager
|
||||
import io.undertow.server.handlers.resource.URLResource
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* [ResourceManager] for JAR resources.
|
||||
*
|
||||
* @author Ivan Sopov
|
||||
* @author Andy Wilkinson
|
||||
* @author Dorkbox LLC
|
||||
*/
|
||||
internal class JarResourceManager(val jarFile: String, base: String = "") : ResourceManager {
|
||||
|
||||
val name: String by lazy { File(jarFile).nameWithoutExtension }
|
||||
|
||||
constructor(jarFile: File, base: String = "") : this(jarFile.absolutePath, base)
|
||||
|
||||
private val fixedBase = when {
|
||||
base.startsWith("/") -> base
|
||||
base.isNotBlank() -> "/$base"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getResource(path: String): Resource? {
|
||||
val cleanedPath = when {
|
||||
path.startsWith("/") -> path
|
||||
else -> "/$path"
|
||||
}
|
||||
|
||||
val url = URL("jar:file:$jarFile!$fixedBase$cleanedPath")
|
||||
|
||||
val resource = URLResource(url, path)
|
||||
if (path.isNotBlank() && path != "/" && resource.contentLength < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
||||
override fun isResourceChangeListenerSupported(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun registerResourceChangeListener(listener: ResourceChangeListener) {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
|
||||
}
|
||||
|
||||
override fun removeResourceChangeListener(listener: ResourceChangeListener) {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "JarResourceManager($jarFile)"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2020 Dorkbox LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import io.undertow.UndertowMessages
|
||||
import io.undertow.server.handlers.resource.Resource
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||
import io.undertow.server.handlers.resource.ResourceManager
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
|
||||
class ResourceCollectionManager(private val resources: List<ResourceManager>) : ResourceManager {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
private val changeListenerSupported : Boolean by lazy {
|
||||
var supported = true
|
||||
resources.forEach { resourceManager ->
|
||||
if (!resourceManager.isResourceChangeListenerSupported) {
|
||||
supported = false
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
supported
|
||||
}
|
||||
|
||||
override fun getResource(path: String?): Resource? {
|
||||
resources.forEach { it ->
|
||||
val resource = it.getResource(path)
|
||||
if (resource != null) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun isResourceChangeListenerSupported(): Boolean {
|
||||
return changeListenerSupported
|
||||
}
|
||||
|
||||
override fun removeResourceChangeListener(listener: ResourceChangeListener?) {
|
||||
if (changeListenerSupported) {
|
||||
resources.forEach { resourceManager ->
|
||||
resourceManager.removeResourceChangeListener(listener)
|
||||
}
|
||||
} else {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun registerResourceChangeListener(listener: ResourceChangeListener?) {
|
||||
if (changeListenerSupported) {
|
||||
resources.forEach { resourceManager ->
|
||||
resourceManager.registerResourceChangeListener(listener)
|
||||
}
|
||||
} else {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
resources.forEach { it ->
|
||||
try {
|
||||
it.close()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error closing resourceManager", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
|
||||
object ContentSecurityPolicyHandler {
|
||||
private val CSP_HEADER = "Content-Security-Policy"
|
||||
|
||||
enum class ContentSecurityPolicy constructor(val value: String) {
|
||||
/** blocks the use of this type of resource. */
|
||||
NONE("'none'"),
|
||||
|
||||
/** matches the current origin (but not subdomains). */
|
||||
SELF("'self'"),
|
||||
|
||||
/** allows the use of inline JS and CSS. */
|
||||
UNSAFE_INLINE("'unsafe-inline'"),
|
||||
|
||||
/** allows the use of mechanisms like eval(). */
|
||||
UNSAFE_EVAL("'unsafe-eval'")
|
||||
}
|
||||
|
||||
|
||||
// https://scotthelme.co.uk/content-security-policy-an-introduction/#whatcanweprotect
|
||||
class Builder {
|
||||
private val policyMap: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
/** Define loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback) */
|
||||
fun defaultSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["default-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback) */
|
||||
fun defaultSrc(vararg policies: String): Builder {
|
||||
policyMap["default-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which scripts the protected resource can execute */
|
||||
fun scriptSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["script-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which scripts the protected resource can execute */
|
||||
fun scriptSrc(vararg policies: String): Builder {
|
||||
policyMap["script-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load plugins */
|
||||
fun objectSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["object-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load plugins */
|
||||
fun objectSrc(vararg policies: String): Builder {
|
||||
policyMap["object-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which styles (CSS) the user applies to the protected resource */
|
||||
fun styleSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["style-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which styles (CSS) the user applies to the protected resource */
|
||||
fun styleSrc(vararg policies: String): Builder {
|
||||
policyMap["style-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load images */
|
||||
fun imgSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["img-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load images */
|
||||
fun imgSrc(vararg policies: String): Builder {
|
||||
policyMap["img-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load video and audio */
|
||||
fun mediaSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["media-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load video and audio */
|
||||
fun mediaSrc(vararg policies: String): Builder {
|
||||
policyMap["media-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can embed frames */
|
||||
fun frameSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["frame-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can embed frames */
|
||||
fun frameSrc(vararg policies: String): Builder {
|
||||
policyMap["frame-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load fonts */
|
||||
fun fontSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["font-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define from where the protected resource can load fonts */
|
||||
fun fontSrc(vararg policies: String): Builder {
|
||||
policyMap["font-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which URIs the protected resource can load using script interfaces */
|
||||
fun connectSrc(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["connect-src"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which URIs the protected resource can load using script interfaces */
|
||||
fun connectSrc(vararg policies: String): Builder {
|
||||
policyMap["connect-src"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which URIs can be used as the action of HTML form elements */
|
||||
fun formAction(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["form-action"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define which URIs can be used as the action of HTML form elements */
|
||||
fun formAction(vararg policies: String): Builder {
|
||||
policyMap["form-action"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Specifies an HTML sandbox policy that the user agent applies to the protected resource */
|
||||
fun sandbox(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["sandbox"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Specifies an HTML sandbox policy that the user agent applies to the protected resource */
|
||||
fun sandbox(vararg policies: String): Builder {
|
||||
policyMap["sandbox"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define script execution by requiring the presence of the specified nonce on script elements */
|
||||
fun scriptNonce(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["script-nonce"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define script execution by requiring the presence of the specified nonce on script elements */
|
||||
fun scriptNonce(vararg policies: String): Builder {
|
||||
policyMap["script-nonce"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded */
|
||||
fun pluginTypes(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["plugin-types"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/** Define the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded */
|
||||
fun pluginTypes(vararg policies: String): Builder {
|
||||
policyMap["plugin-types"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs a user agent to activate or deactivate any heuristics used to filter or block reflected cross-site scripting attacks,
|
||||
* equivalent to the effects of the non-standard X-XSS-Protection header
|
||||
*/
|
||||
fun reflectedXss(policy: ContentSecurityPolicy): Builder {
|
||||
policyMap["reflected-xss"] = policy.value
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs a user agent to activate or deactivate any heuristics used to filter or block reflected cross-site scripting attacks,
|
||||
* equivalent to the effects of the non-standard X-XSS-Protection header
|
||||
*/
|
||||
fun reflectedXss(vararg policies: String): Builder {
|
||||
policyMap["reflected-xss"] = policies.joinToString(separator = " ")
|
||||
return this
|
||||
}
|
||||
|
||||
/** Specifies a URI to which the user agent sends reports about policy violation */
|
||||
fun reportUri(uri: String): Builder {
|
||||
policyMap["report-uri"] = uri
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(delegate: HttpHandler): HttpHandler {
|
||||
val policy = policyMap.entries.joinToString(separator = "; ") { it.key + " " + it.value }
|
||||
return SetHeaderHandler(delegate, CSP_HEADER, policy)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
|
||||
object ReferrerPolicyHandlers {
|
||||
private val REFERRER_POLICY_STRING = "Referrer-Policy"
|
||||
|
||||
|
||||
// See https://scotthelme.co.uk/a-new-security-header-referrer-policy/
|
||||
enum class ReferrerPolicy constructor(val value: String) {
|
||||
EMPTY(""),
|
||||
NO_REFERRER("no-referrer"),
|
||||
NO_REFERRER_WHEN_DOWNGRADE("no-referrer-when-downgrade"),
|
||||
SAME_ORIGIN("same-origin"),
|
||||
ORIGIN("origin"),
|
||||
STRICT_ORIGIN("strict-origin"
|
||||
),
|
||||
ORIGIN_WHEN_CROSS_ORIGIN("origin-when-cross-origin"),
|
||||
STRICT_ORIGIN_WHEN_CROSS_ORIGIN("strict-origin-when-cross-origin"),
|
||||
UNSAFE_URL("unsafe-url")
|
||||
}
|
||||
|
||||
fun policy(next: HttpHandler, policy: ReferrerPolicy): HttpHandler {
|
||||
return SetHeaderHandler(next, REFERRER_POLICY_STRING, policy.value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
import io.undertow.util.Headers
|
||||
|
||||
/**
|
||||
* HTTP Strict Transport Security (HSTS) is a policy mechanism that allows a web server to enforce the use of TLS in a compliant User Agent (UA), such as a
|
||||
* web browser. HSTS allows for a more effective implementation of TLS by ensuring all communication takes place over a secure transport layer on the
|
||||
* client side.
|
||||
*
|
||||
* Most notably HSTS mitigates variants of man in the middle (MiTM) attacks where TLS can be stripped out of communications with a server, leaving a user
|
||||
* vulnerable to further risk.
|
||||
*/
|
||||
object StrictTransportSecurityHandlers {
|
||||
|
||||
fun hsts(next: HttpHandler, maxAge: Long): HttpHandler {
|
||||
return SetHeaderHandler(next, Headers.STRICT_TRANSPORT_SECURITY_STRING, "max-age=$maxAge")
|
||||
}
|
||||
|
||||
fun hstsIncludeSubdomains(next: HttpHandler, maxAge: Long): HttpHandler {
|
||||
return SetHeaderHandler(next, Headers.STRICT_TRANSPORT_SECURITY_STRING, "max-age=$maxAge; includeSubDomains")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
|
||||
object XContentTypeOptionsHandler {
|
||||
private val X_CONTENT_TYPE_OPTIONS_STRING = "X-Content-Type-Options"
|
||||
|
||||
fun nosniff(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_CONTENT_TYPE_OPTIONS_STRING, "nosniff")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
import io.undertow.util.HttpString
|
||||
import java.util.function.Function
|
||||
|
||||
object XFrameOptionsHandlers {
|
||||
private val X_FRAME_OPTIONS_STRING = "X-Frame-Options"
|
||||
private val X_FRAME_OPTIONS = HttpString(X_FRAME_OPTIONS_STRING)
|
||||
|
||||
fun deny(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_FRAME_OPTIONS_STRING, "DENY")
|
||||
}
|
||||
|
||||
fun sameOrigin(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_FRAME_OPTIONS_STRING, "SAMEORIGIN")
|
||||
}
|
||||
|
||||
fun allowFromOrigin(next: HttpHandler, origin: String): HttpHandler {
|
||||
return SetHeaderHandler(next, X_FRAME_OPTIONS_STRING, "ALLOW-FROM $origin")
|
||||
}
|
||||
|
||||
fun allowFromDynamicOrigin(next: HttpHandler, originExtractor: Function<HttpServerExchange, String>): HttpHandler {
|
||||
// Since this is dynamic skip using the SetHeaderHandler
|
||||
return HttpHandler { exchange ->
|
||||
exchange.responseHeaders.put(X_FRAME_OPTIONS, originExtractor.apply(exchange))
|
||||
next.handleRequest(exchange)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 StubbornJava
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.undertow.security
|
||||
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.SetHeaderHandler
|
||||
|
||||
object XXssProtectionHandlers {
|
||||
private val X_XSS_PROTECTION_STRING = "X-Xss-Protection"
|
||||
|
||||
fun disable(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_XSS_PROTECTION_STRING, "0")
|
||||
}
|
||||
|
||||
fun enable(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_XSS_PROTECTION_STRING, "1")
|
||||
}
|
||||
|
||||
fun enableAndBlock(next: HttpHandler): HttpHandler {
|
||||
return SetHeaderHandler(next, X_XSS_PROTECTION_STRING, "1; mode=block")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import com.helger.commons.lang.ClassLoaderHelper.getDefaultClassLoader
|
||||
import java.io.Closeable
|
||||
import java.io.Externalizable
|
||||
import java.io.Serializable
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Miscellaneous `java.lang.Class` utility methods.
|
||||
* Mainly for internal use within the framework.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Keith Donald
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @since 1.1
|
||||
* @see ReflectionUtils
|
||||
*/
|
||||
object ClassUtils {
|
||||
/** Suffix for array class names: `"[]"`. */
|
||||
const val ARRAY_SUFFIX = "[]"
|
||||
/** Prefix for internal array class names: `"["`. */
|
||||
private const val INTERNAL_ARRAY_PREFIX = "["
|
||||
/** Prefix for internal non-primitive array class names: `"[L"`. */
|
||||
private const val NON_PRIMITIVE_ARRAY_PREFIX = "[L"
|
||||
/** The package separator character: `'.'`. */
|
||||
private const val PACKAGE_SEPARATOR = '.'
|
||||
/** The inner class separator character: `'$'`. */
|
||||
private const val INNER_CLASS_SEPARATOR = '$'
|
||||
/**
|
||||
* Map with primitive wrapper type as key and corresponding primitive
|
||||
* type as value, for example: Integer.class -> int.class.
|
||||
*/
|
||||
private val primitiveWrapperTypeMap: MutableMap<Class<*>, Class<*>?> = IdentityHashMap(8)
|
||||
/**
|
||||
* Map with primitive type as key and corresponding wrapper
|
||||
* type as value, for example: int.class -> Integer.class.
|
||||
*/
|
||||
private val primitiveTypeToWrapperMap: MutableMap<Class<*>, Class<*>> = IdentityHashMap(8)
|
||||
/**
|
||||
* Map with primitive type name as key and corresponding primitive
|
||||
* type as value, for example: "int" -> "int.class".
|
||||
*/
|
||||
private val primitiveTypeNameMap: MutableMap<String, Class<*>> = HashMap(32)
|
||||
/**
|
||||
* Map with common Java language class name as key and corresponding Class as value.
|
||||
* Primarily for efficient deserialization of remote invocations.
|
||||
*/
|
||||
private val commonClassCache: MutableMap<String, Class<*>> = HashMap(64)
|
||||
/**
|
||||
* Common Java language interfaces which are supposed to be ignored
|
||||
* when searching for 'primary' user-level interfaces.
|
||||
*/
|
||||
private var javaLanguageInterfaces: Set<Class<*>>? = null
|
||||
|
||||
/**
|
||||
* Register the given common classes with the ClassUtils cache.
|
||||
*/
|
||||
private fun registerCommonClasses(vararg commonClasses: Class<*>) {
|
||||
for (clazz in commonClasses) {
|
||||
commonClassCache[clazz.name] = clazz
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for `Class.forName()` that also returns Class instances
|
||||
* for primitives (e.g. "int") and array class names (e.g. "String[]").
|
||||
* Furthermore, it is also capable of resolving inner class names in Java source
|
||||
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
|
||||
* @param name the name of the Class
|
||||
* @param classLoader the class loader to use
|
||||
* (may be `null`, which indicates the default class loader)
|
||||
* @return a class instance for the supplied name
|
||||
* @throws ClassNotFoundException if the class was not found
|
||||
* @throws LinkageError if the class file could not be loaded
|
||||
* @see Class.forName
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ClassNotFoundException::class, LinkageError::class)
|
||||
fun forName(name: String, classLoader: ClassLoader?): Class<*> {
|
||||
var clazz = resolvePrimitiveClassName(name)
|
||||
if (clazz == null) {
|
||||
clazz = commonClassCache[name]
|
||||
}
|
||||
if (clazz != null) {
|
||||
return clazz
|
||||
}
|
||||
|
||||
// "java.lang.String[]" style arrays
|
||||
if (name.endsWith(ARRAY_SUFFIX)) {
|
||||
val elementClassName = name.substring(0, name.length - ARRAY_SUFFIX.length)
|
||||
val elementClass = forName(elementClassName, classLoader)
|
||||
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
|
||||
}
|
||||
|
||||
// "[Ljava.lang.String;" style arrays
|
||||
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
|
||||
val elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length, name.length - 1)
|
||||
val elementClass = forName(elementName, classLoader)
|
||||
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
|
||||
}
|
||||
|
||||
// "[[I" or "[[Ljava.lang.String;" style arrays
|
||||
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
|
||||
val elementName = name.substring(INTERNAL_ARRAY_PREFIX.length)
|
||||
val elementClass = forName(elementName, classLoader)
|
||||
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
|
||||
}
|
||||
|
||||
var clToUse = classLoader
|
||||
if (clToUse == null) {
|
||||
clToUse = getDefaultClassLoader()
|
||||
}
|
||||
|
||||
return try {
|
||||
Class.forName(name, false, clToUse)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
val lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR)
|
||||
if (lastDotIndex != -1) {
|
||||
val innerClassName = name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1)
|
||||
try {
|
||||
return Class.forName(innerClassName, false, clToUse)
|
||||
} catch (ex2: ClassNotFoundException) { // Swallow - let original exception get through
|
||||
}
|
||||
}
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the given class name as primitive class, if appropriate,
|
||||
* according to the JVM's naming rules for primitive classes.
|
||||
*
|
||||
* Also supports the JVM's internal class names for primitive arrays.
|
||||
* Does *not* support the "[]" suffix notation for primitive arrays;
|
||||
* this is only supported by [.forName].
|
||||
* @param name the name of the potentially primitive class
|
||||
* @return the primitive class, or `null` if the name does not denote
|
||||
* a primitive class or primitive array class
|
||||
*/
|
||||
fun resolvePrimitiveClassName(name: String?): Class<*>? {
|
||||
var result: Class<*>? = null
|
||||
// Most class names will be quite long, considering that they
|
||||
// SHOULD sit in a package, so a length check is worthwhile.
|
||||
if (name != null && name.length <= 7) { // Could be a primitive - likely.
|
||||
result = primitiveTypeNameMap[name]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a composite interface Class for the given interfaces,
|
||||
* implementing the given interfaces in one single Class.
|
||||
*
|
||||
* This implementation builds a JDK proxy class for the given interfaces.
|
||||
* @param interfaces the interfaces to merge
|
||||
* @param classLoader the ClassLoader to create the composite Class in
|
||||
* @return the merged interface as Class
|
||||
* @throws IllegalArgumentException if the specified interfaces expose
|
||||
* conflicting method signatures (or a similar constraint is violated)
|
||||
* @see java.lang.reflect.Proxy.getProxyClass
|
||||
*/
|
||||
// on JDK 9
|
||||
@JvmStatic
|
||||
fun createCompositeInterface(interfaces: Array<Class<*>?>, classLoader: ClassLoader?): Class<*> {
|
||||
return Proxy.getProxyClass(classLoader, *interfaces)
|
||||
}
|
||||
|
||||
|
||||
|
||||
init {
|
||||
primitiveWrapperTypeMap[Boolean::class.java] = Boolean::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Byte::class.java] = Byte::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Char::class.java] = Char::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Double::class.java] = Double::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Float::class.java] = Float::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Int::class.java] = Int::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Long::class.java] = Long::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Short::class.java] = Short::class.javaPrimitiveType
|
||||
primitiveWrapperTypeMap[Void::class.java] = Void.TYPE
|
||||
|
||||
// Map entry iteration is less expensive to initialize than forEach with lambdas
|
||||
for ((key, value) in primitiveWrapperTypeMap) {
|
||||
primitiveTypeToWrapperMap[value as Class<*>] = key
|
||||
registerCommonClasses(key)
|
||||
}
|
||||
|
||||
|
||||
val primitiveTypes = mutableSetOf<Class<*>>()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
primitiveTypes.addAll(primitiveWrapperTypeMap.values as Collection<Class<*>>)
|
||||
|
||||
Collections.addAll(primitiveTypes, BooleanArray::class.java, ByteArray::class.java, CharArray::class.java,
|
||||
DoubleArray::class.java, FloatArray::class.java, IntArray::class.java, LongArray::class.java, ShortArray::class.java)
|
||||
primitiveTypes.add(Void.TYPE)
|
||||
for (primitiveType in primitiveTypes) {
|
||||
primitiveTypeNameMap[primitiveType.getName()] = primitiveType
|
||||
}
|
||||
registerCommonClasses(Array<Boolean>::class.java, Array<Byte>::class.java, Array<Char>::class.java, Array<Double>::class.java,
|
||||
Array<Float>::class.java, Array<Int>::class.java, Array<Long>::class.java, Array<Short>::class.java)
|
||||
registerCommonClasses(Number::class.java, Array<Number>::class.java, String::class.java, Array<String>::class.java,
|
||||
Class::class.java, emptyArray<Class<*>>().javaClass, Any::class.java, Array<Any>::class.java)
|
||||
|
||||
registerCommonClasses(Throwable::class.java, Exception::class.java, RuntimeException::class.java,
|
||||
Error::class.java, StackTraceElement::class.java, Array<StackTraceElement>::class.java)
|
||||
registerCommonClasses(Enum::class.java, Iterable::class.java, MutableIterator::class.java, Enumeration::class.java,
|
||||
MutableCollection::class.java, MutableList::class.java, MutableSet::class.java, MutableMap::class.java, MutableMap.MutableEntry::class.java, Optional::class.java)
|
||||
|
||||
val javaLanguageInterfaceArray = arrayOf(Serializable::class.java, Externalizable::class.java,
|
||||
Closeable::class.java, AutoCloseable::class.java, Cloneable::class.java, Comparable::class.java)
|
||||
registerCommonClasses(*javaLanguageInterfaceArray)
|
||||
javaLanguageInterfaces = HashSet(Arrays.asList(*javaLanguageInterfaceArray))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,534 @@
|
|||
package dorkbox.vaadin.util
|
||||
|
||||
//import dorkbox.network.Client
|
||||
//import dorkbox.network.Configuration
|
||||
//import dorkbox.network.Server
|
||||
//import dorkbox.network.connection.Connection
|
||||
//import dorkbox.network.connection.EndPoint
|
||||
//import dorkbox.network.connection.Listener
|
||||
//import dorkbox.network.connection.connectionType.ConnectionRule
|
||||
//import dorkbox.network.connection.connectionType.ConnectionType
|
||||
//import dorkbox.network.rmi.RemoteObjectCallback
|
||||
//import dorkbox.network.serialization.Serialization
|
||||
//import io.netty.util.NetUtil
|
||||
//import java.io.IOException
|
||||
//import java.net.Socket
|
||||
//import java.time.LocalDateTime
|
||||
//import java.util.*
|
||||
//import java.util.concurrent.atomic.AtomicBoolean
|
||||
//
|
||||
///**
|
||||
// * Provides
|
||||
// * - command monitor : that will sit and wait for CLI commands to be executed (after the app starts up)
|
||||
// * - remote stop : allows another instance of this app to stop the current instance
|
||||
// *
|
||||
// * @param host null to listen to the ANY interface
|
||||
// * @param service what type of service the SERVER is to run under
|
||||
//*/
|
||||
//abstract class CommandLineApp(private val logger: org.slf4j.Logger, private val host: String?, service: Servers) : ShutdownService {
|
||||
// private val shutdownHook = Thread(Runnable { this.shutdownThread() })
|
||||
//
|
||||
// internal var shutdownInProgress = AtomicBoolean() // Middle of shutting down
|
||||
// internal var isShutdown = AtomicBoolean() // Completed shutting down
|
||||
//
|
||||
// private val shutdownLock = Object()
|
||||
//
|
||||
// /**
|
||||
// * A list of actions that must be run on shutdown
|
||||
// */
|
||||
// protected val shutdownActions: MutableList<Runnable> = Collections.synchronizedList(LinkedList())
|
||||
//
|
||||
// private val actionMap = LinkedHashMap<String, CommandAction>()
|
||||
//
|
||||
// private val tcpPort = service.port
|
||||
// private val appName = service.name
|
||||
//
|
||||
// protected val server: Server<Connection>
|
||||
//
|
||||
// val isRunning: Boolean
|
||||
// get() = server.isRunning
|
||||
//
|
||||
//
|
||||
// init {
|
||||
// // Register handler to shutdown server if system is shutdown
|
||||
// Runtime.getRuntime()
|
||||
// .addShutdownHook(shutdownHook)
|
||||
//
|
||||
// // Remote method invocation for controlling netref via enterprise
|
||||
// val configuration = Configuration()
|
||||
// configuration.tcpPort = tcpPort
|
||||
// configuration.host = host
|
||||
//
|
||||
// configuration.serialization = Serialization.DEFAULT()
|
||||
// configuration.localChannelName = EndPoint.LOCAL_CHANNEL // we also enable in-JVM local channels
|
||||
//
|
||||
// /*
|
||||
// * Based on which CLIENT is connecting to this SERVER, it MUST have an identical serialization configuration! If not, then there will be
|
||||
// * serialization errors for RMI or whatever else.
|
||||
// */
|
||||
//
|
||||
// // implementations in NR Server/Engine (this is always first)
|
||||
// configuration.serialization.registerRmi(ShutdownService::class.java, CommandLineApp::class.java)
|
||||
//
|
||||
// getConfiguration(configuration, Server::class.java)
|
||||
//
|
||||
// val serviceId: Int
|
||||
// try {
|
||||
// server = Server(configuration)
|
||||
//
|
||||
// // we disable remote key validation BECAUSE we can accept connections on localhost AND we have multiple connections on it VIA SSH tunneling.
|
||||
// server.disableRemoteKeyValidation()
|
||||
//
|
||||
// val disabledEncryptionIps = Args.server.disabledEncryptionIps
|
||||
// for (disabledEncryptionIp in disabledEncryptionIps) {
|
||||
// if (disabledEncryptionIp == "127.0.0.1/32") {
|
||||
// // actually use what is detected as the localhost... simply because it's possible that this can be different
|
||||
// server.addConnectionTypeFilter(ConnectionRule(NetUtil.LOCALHOST, 32, ConnectionType.COMPRESS))
|
||||
// }
|
||||
// else {
|
||||
// val ip = disabledEncryptionIp.substringBefore("/")
|
||||
// val cidr = disabledEncryptionIp.substringAfter("/").toInt()
|
||||
// server.addConnectionTypeFilter(ConnectionRule(ip, cidr, ConnectionType.COMPRESS))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// serviceId = server.createGlobalObject(this)
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// throw RuntimeException("Unable to start up server.", e)
|
||||
// }
|
||||
//
|
||||
// ServerService.SERVER_SHUTDOWN.validate(serviceId)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Based on which CLIENT is connecting to this SERVER, it MUST have an identical serialization configuration!
|
||||
// *
|
||||
// * If not, then there will be serialization errors for RMI or whatever else.
|
||||
// *
|
||||
// * @param endpointClass should be either Server.class or Client.class
|
||||
// */
|
||||
// protected open fun getConfiguration(configuration: Configuration, endpointClass: Class<out EndPoint>) {}
|
||||
//
|
||||
//
|
||||
//
|
||||
// fun start() {
|
||||
// // Send email when server starts,
|
||||
// SmtpProcessor.sendNetRefTeamEmail("$appName Started", "$appName started at IP ???? a customers location.")
|
||||
//
|
||||
// commandMonitor()
|
||||
//
|
||||
// // don't block because we do that ourselves
|
||||
// server.bind(false)
|
||||
//
|
||||
// postBindActions()
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// fun block() {
|
||||
// if (!Args.isUnitTest) {
|
||||
// // we don't want to block during unit tests!
|
||||
//
|
||||
// synchronized(shutdownLock) {
|
||||
// try {
|
||||
// shutdownLock.wait()
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// logger.error("Error waiting for shutdown lock", e)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// logger.info("'{}' shutdown finished.", appName)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * actions to occur AFTER the server has bound to the socket
|
||||
// */
|
||||
// protected open fun postBindActions() {}
|
||||
//
|
||||
//
|
||||
// protected fun blockNotify() {
|
||||
// synchronized(shutdownLock) {
|
||||
// shutdownLock.notifyAll()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Only adds a command action if we are NOT a daemon process
|
||||
// */
|
||||
// protected fun addCommandAction(vararg actions: CommandAction) {
|
||||
// if (!Args.isDaemon) {
|
||||
// synchronized(actionMap) {
|
||||
// for (action in actions) {
|
||||
// actionMap[action.command
|
||||
// .toLowerCase(Locale.US)] = action
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Only adds a command action if we are NOT a daemon process
|
||||
// */
|
||||
// protected fun addCommandAction(action: CommandAction) {
|
||||
// if (!Args.isDaemon) {
|
||||
// synchronized(actionMap) {
|
||||
// actionMap.put(action.command
|
||||
// .toLowerCase(Locale.US), action
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun commandMonitor(threadGroup: ThreadGroup = Thread.currentThread().threadGroup) {
|
||||
// // if we start in "daemon mode" in a bash shell, we won't have access to a scanner.
|
||||
// if (Args.isDaemon) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // start an "exit" monitor, so that if the user types exit in the console (in order to properly shutdown the database)
|
||||
// // we can watch for it.
|
||||
// val thread = Thread(threadGroup, {
|
||||
// // give some time for the logs (if available) to settle
|
||||
// try {
|
||||
// Thread.sleep(2000L)
|
||||
// }
|
||||
// catch (ignored: InterruptedException) {
|
||||
// }
|
||||
//
|
||||
// printInfo()
|
||||
//
|
||||
// try {
|
||||
// System.err.println("Please type \"exit\" to safely stop the app")
|
||||
// Scanner(System.`in`).use { scanner ->
|
||||
// // read in a line at a time
|
||||
// scanner.useDelimiter(OS.LINE_SEPARATOR)
|
||||
//
|
||||
// var next = scanner.next()
|
||||
// while (next != null) {
|
||||
// next = next.toLowerCase(Locale.US)
|
||||
// if (next == "exit") {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// // if there is a space, this means that there are parameters.
|
||||
// var parameters: String? = null
|
||||
// val i = next.indexOf(' ')
|
||||
// if (i > 0) {
|
||||
// parameters = next.substring(i + 1)
|
||||
// next = next.substring(0, i)
|
||||
// }
|
||||
//
|
||||
// if (parameters == null) {
|
||||
// parameters = ""
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// synchronized(actionMap) {
|
||||
// val commandAction = actionMap[next]
|
||||
// if (commandAction != null) {
|
||||
// commandAction.action(parameters)
|
||||
// printInfo()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// logger.error("Error executing command '$next'.", e)
|
||||
// }
|
||||
//
|
||||
// next = scanner.next()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// shutdown()
|
||||
// }
|
||||
// catch (ignored: Exception) {
|
||||
// // if we start in "daemon mode" in a bash shell, scanner.next() will crash.
|
||||
// }
|
||||
// }, "Application Exit Monitor")
|
||||
// thread.isDaemon = true
|
||||
// thread.start()
|
||||
// }
|
||||
//
|
||||
// private fun printInfo() {
|
||||
// synchronized(actionMap) {
|
||||
// val actions = StringBuilder(256)
|
||||
// actions.append("System is listening for the following commands: ")
|
||||
// .append(OS.LINE_SEPARATOR)
|
||||
// actions.append('\t')
|
||||
// .append("exit")
|
||||
// .append(OS.LINE_SEPARATOR)
|
||||
//
|
||||
// for ((key, value) in actionMap) {
|
||||
// val comment = value
|
||||
// .comment
|
||||
//
|
||||
// actions.append('\t')
|
||||
// .append(key)
|
||||
//
|
||||
// var length = key.length
|
||||
// while (length++ < 10) {
|
||||
// actions.append(' ')
|
||||
// }
|
||||
//
|
||||
// if (comment != null && comment.isNotEmpty()) {
|
||||
// actions.append('\t')
|
||||
// .append("(")
|
||||
// .append(comment)
|
||||
// .append(")")
|
||||
// }
|
||||
//
|
||||
// actions.append(OS.LINE_SEPARATOR)
|
||||
// }
|
||||
//
|
||||
// System.err.println(actions)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun shutdownThread() {
|
||||
// // only remove our shutdown hook if we are not in the ABORT shutdown process (which is what the shutdown hook does)
|
||||
// val thread = Thread.currentThread()
|
||||
// if (thread !== shutdownHook) {
|
||||
// try {
|
||||
// logger.debug("Removing Shutdown Hook")
|
||||
// Runtime.getRuntime()
|
||||
// .removeShutdownHook(shutdownHook)
|
||||
// }
|
||||
// catch (ignored: Exception) {
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// shutdown()
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Creates a client to send a remote shutdown command. Stays in this method until the remote app is fully shutdown
|
||||
// */
|
||||
// fun sendShutdown() {
|
||||
// var hostForShutdown = host
|
||||
// if (host == null || host == NetworkUtil.EXTERNAL_IPV4) {
|
||||
// // this means we are listening on the any interface, but for shutdown, we have to connect to localhost...
|
||||
// hostForShutdown = NetworkUtil.LOCALHOST
|
||||
// }
|
||||
//
|
||||
// // since we check the socket, if we are NOT connected to a socket, then we're done.
|
||||
// try {
|
||||
// Socket(hostForShutdown, tcpPort).use { sock ->
|
||||
// if (!sock.isConnected) {
|
||||
// // if we can NOT connect to the socket, it means that we are not running, so exit early.
|
||||
//
|
||||
// logger.info("No existing instance to close...")
|
||||
// logger.info("Server has shut down.")
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (ignored: Exception) {
|
||||
// }
|
||||
//
|
||||
// // NOTE: this causes problems in the server!!
|
||||
// // // since we check the socket, if we are NOT connected to a socket, then we're done.
|
||||
// // try (Socket sock = new Socket(serverHost, tcpPort)) {
|
||||
// // if (!sock.isConnected()) {
|
||||
// // // if we can NOT connect to the socket, it means that we are not running, so exit early.
|
||||
// //
|
||||
// // logger.info("No existing instance to close...");
|
||||
// // logger.info("Server has shut down.");
|
||||
// // return;
|
||||
// // }
|
||||
// // } catch (Exception ignored) {
|
||||
// // }
|
||||
//
|
||||
//
|
||||
//
|
||||
// val configuration = Configuration()
|
||||
// configuration.tcpPort = tcpPort
|
||||
// configuration.host = hostForShutdown
|
||||
//
|
||||
//
|
||||
// // this MUST match what the server has!
|
||||
// configuration.serialization = Serialization.DEFAULT()
|
||||
//
|
||||
// /*
|
||||
// * Based on which CLIENT is connecting to this SERVER, it MUST have an identical serialization configuration! If not, then there will be
|
||||
// * serialization errors for RMI or whatever else.
|
||||
// */
|
||||
//
|
||||
// getConfiguration(configuration, Client::class.java)
|
||||
//
|
||||
// val shutdown = Object()
|
||||
// val didShutdown = AtomicBoolean()
|
||||
//
|
||||
//
|
||||
// var client: Client<Connection>? = null
|
||||
// try {
|
||||
// client = Client(configuration)
|
||||
//
|
||||
// // we disable remote key validation BECAUSE we can accept connections on localhost AND we have multiple connections on it VIA SSH tunneling.
|
||||
// client.disableRemoteKeyValidation()
|
||||
//
|
||||
// val callback = RemoteObjectCallback<ShutdownService> { remoteObject ->
|
||||
// // true if we are shutdown down, false if we are in progress. Must be on a new thread
|
||||
// if (remoteObject == null) {
|
||||
// didShutdown.set(true)
|
||||
//
|
||||
// synchronized(shutdown) {
|
||||
// shutdown.notifyAll()
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// try {
|
||||
// remoteObject.shutdown()
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// logger.error("Error during remote shutdown command!", e)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// client.listeners()
|
||||
// .add(Listener.OnConnected<Connection> { connection ->
|
||||
// connection.getRemoteObject(ServerService.SERVER_SHUTDOWN.rmiId, callback)
|
||||
// })
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
//
|
||||
// // always try to connect to the remote netref. Retry max 10 times
|
||||
// var maxCount = 10
|
||||
// while (client != null && !didShutdown.get() && maxCount-- > 0) {
|
||||
// try {
|
||||
// client.connect(5000)
|
||||
//
|
||||
// if (!client.isConnected) {
|
||||
// client.stop()
|
||||
// // this means we got disconnected right away
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// logger.info("Waiting for remote application to shut down...")
|
||||
// synchronized(shutdown) {
|
||||
// shutdown.wait(5000)
|
||||
// }
|
||||
//
|
||||
// if (didShutdown.get()) {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// client.close()
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// client.stop()
|
||||
// // this means that we could not connect, thus the remote app has shutdown
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if (maxCount > 0) {
|
||||
// logger.info("Successfully stopped remote application.")
|
||||
// }
|
||||
// else {
|
||||
// logger.error("Unable to shutdown the server in a reasonable amount of time.")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Request to shutdown.
|
||||
// */
|
||||
// override fun shutdown() {
|
||||
// if (isShutdown.get() || shutdownInProgress.get()) {
|
||||
// // is shut down or in progress of shutting down
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // this must be run on a NEW thread, so it doesn't block anything
|
||||
// val thread = Thread {
|
||||
// if (shutdownInProgress.getAndSet(true)) {
|
||||
// // in progress of shutting down!
|
||||
// return@Thread
|
||||
// }
|
||||
//
|
||||
// // we wait for a little bit here for the network connections to cleanup...
|
||||
// logger.debug("Waiting for shutdown cleanup...")
|
||||
// try {
|
||||
// Thread.sleep(2_000L)
|
||||
// }
|
||||
// catch (ignored: InterruptedException) {
|
||||
// }
|
||||
//
|
||||
// server.stop()
|
||||
// server.waitForShutdown()
|
||||
//
|
||||
//
|
||||
// // rarely, different threads will call shutdown (which causes 2+ shutdown events to occur. We make sure that only ONE thread can
|
||||
// // shutdown at a time.
|
||||
// val cleanedShutdown: MutableList<Runnable>
|
||||
// logger.debug("Clearing Shutdown Actions")
|
||||
// synchronized(shutdownActions) {
|
||||
// // we shutdown items in the reverse order they were added
|
||||
// shutdownActions.reverse()
|
||||
//
|
||||
// cleanedShutdown = shutdownActions.toMutableList()
|
||||
// shutdownActions.clear()
|
||||
// }
|
||||
//
|
||||
// logger.info("Shutting down the server.")
|
||||
//
|
||||
// if (!Args.stopIssued()) {
|
||||
// // Send email when app server stops, but NOT if it's the issuing "stop" server (which also calls stop)
|
||||
// logger.info("Sending stopped Email for '{}' application", appName)
|
||||
// SmtpProcessor.sendNetRefTeamEmail("$appName Stopped", "$appName stopped at a customers location.")
|
||||
// }
|
||||
//
|
||||
// // it's possible to call stop more than once
|
||||
// if (cleanedShutdown.isNotEmpty()) {
|
||||
// val iterator = cleanedShutdown.iterator()
|
||||
// while (iterator.hasNext()) {
|
||||
// val shutdownAction = iterator.next()
|
||||
// iterator.remove()
|
||||
// try {
|
||||
// shutdownAction.run()
|
||||
// }
|
||||
// catch (e: Exception) {
|
||||
// val isIO = IOException::class.java.isAssignableFrom(e.javaClass)
|
||||
// if (!isIO) {
|
||||
// logger.error("Error during shutdown.", e)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// isShutdown.set(true)
|
||||
//
|
||||
// // make sure our main thread exits, only AFTER everything else has shutdown
|
||||
// blockNotify()
|
||||
//
|
||||
// logger.info("$appName app has shut down.")
|
||||
// }
|
||||
// thread.name = "Remote shutdown thread"
|
||||
// thread.isDaemon = true
|
||||
// thread.start()
|
||||
// }
|
||||
//
|
||||
// fun showStartupStats() {
|
||||
// val jvmName = System.getProperty("java.vm.name")
|
||||
// val jvmVersion = System.getProperty("java.version")
|
||||
// val jvmVendor = System.getProperty("java.vm.specification.vendor")
|
||||
// logger.info("Execution JVM: $jvmVendor $jvmName $jvmVersion")
|
||||
// logger.info("Execution arguments: " + Args.commandlineArguments)
|
||||
//
|
||||
// logger.info("Starting {} : {} ", appName, LocalDateTime.now().toString())
|
||||
// }
|
||||
//
|
||||
// abstract fun run()
|
||||
//}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2017 Pronghorn Technology LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
inline fun <reified T> T.logger(): KLogger {
|
||||
if (T::class.isCompanion) {
|
||||
return KotlinLogging.logger(T::class.java.enclosingClass.name)
|
||||
}
|
||||
return KotlinLogging.logger(T::class.java.name)
|
||||
}
|
||||
|
||||
|
||||
fun Exception.stackTraceToString(): String {
|
||||
val exceptionWriter = StringWriter()
|
||||
printStackTrace(PrintWriter(exceptionWriter))
|
||||
return exceptionWriter.toString()
|
||||
}
|
||||
|
||||
inline fun ignoreException(block: () -> Unit) {
|
||||
try {
|
||||
block()
|
||||
} catch (ex: Exception) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun ignoreExceptions(vararg blocks: () -> Unit) {
|
||||
blocks.forEach { block ->
|
||||
try {
|
||||
block()
|
||||
} catch (ex: Exception) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright 2016 zhanhb.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.util;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
*
|
||||
* @author zhanhb
|
||||
*/
|
||||
class ConcurrentWeakIdentityHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
|
||||
|
||||
private final ConcurrentMap<Key<K>, V> map;
|
||||
private final ReferenceQueue<K> queue = new ReferenceQueue<>();
|
||||
private transient Set<Map.Entry<K, V>> es;
|
||||
|
||||
ConcurrentWeakIdentityHashMap(int initialCapacity) {
|
||||
this.map = new ConcurrentHashMap<>(initialCapacity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("CollectionWithoutInitialCapacity")
|
||||
ConcurrentWeakIdentityHashMap() {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V get(Object key) {
|
||||
purgeKeys();
|
||||
return map.get(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V put(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.put(new Key<>(key, queue), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
purgeKeys();
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"NestedAssignment", "element-type-mismatch"})
|
||||
private
|
||||
void purgeKeys() {
|
||||
Reference<? extends K> reference;
|
||||
while ((reference = queue.poll()) != null) {
|
||||
map.remove(reference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NestedAssignment")
|
||||
public
|
||||
Set<Map.Entry<K, V>> entrySet() {
|
||||
Set<Map.Entry<K, V>> entrySet;
|
||||
return ((entrySet = this.es) == null) ? es = new EntrySet() : entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V putIfAbsent(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.putIfAbsent(new Key<>(key, queue), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V remove(Object key) {
|
||||
return map.remove(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean remove(Object key, Object value) {
|
||||
purgeKeys();
|
||||
return map.remove(new Key<>(key, null), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean replace(K key, V oldValue, V newValue) {
|
||||
purgeKeys();
|
||||
return map.replace(new Key<>(key, null), oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V replace(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.replace(new Key<>(key, null), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsKey(Object key) {
|
||||
purgeKeys();
|
||||
return map.containsKey(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("empty-statement")
|
||||
public
|
||||
void clear() {
|
||||
while (queue.poll() != null) {
|
||||
;
|
||||
}
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsValue(Object value) {
|
||||
purgeKeys();
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
private static
|
||||
class Key<T> extends WeakReference<T> {
|
||||
|
||||
private final int hash;
|
||||
|
||||
Key(T t, ReferenceQueue<T> queue) {
|
||||
super(t, queue);
|
||||
hash = System.identityHashCode(Objects.requireNonNull(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(Object obj) {
|
||||
return this == obj || obj instanceof Key && ((Key<?>) obj).get() == get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class Iter implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
private final Iterator<Map.Entry<Key<K>, V>> it;
|
||||
private Map.Entry<K, V> nextValue;
|
||||
|
||||
Iter(Iterator<Map.Entry<Key<K>, V>> it) {
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasNext() {
|
||||
if (nextValue != null) {
|
||||
return true;
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Key<K>, V> entry = it.next();
|
||||
K key = entry.getKey().get();
|
||||
if (key != null) {
|
||||
nextValue = new Entry(key, entry.getValue());
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Map.Entry<K, V> next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Map.Entry<K, V> entry = nextValue;
|
||||
nextValue = null;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void remove() {
|
||||
it.remove();
|
||||
nextValue = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
|
||||
@Override
|
||||
public
|
||||
Iterator<Map.Entry<K, V>> iterator() {
|
||||
return new Iter(map.entrySet().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
return ConcurrentWeakIdentityHashMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void clear() {
|
||||
ConcurrentWeakIdentityHashMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("element-type-mismatch")
|
||||
public
|
||||
boolean contains(Object o) {
|
||||
if (!(o instanceof Map.Entry)) {
|
||||
return false;
|
||||
}
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return ConcurrentWeakIdentityHashMap.this.get(e.getKey()) == e.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean remove(Object o) {
|
||||
if (!(o instanceof Map.Entry)) {
|
||||
return false;
|
||||
}
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return ConcurrentWeakIdentityHashMap.this.remove(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class Entry extends SimpleEntry<K, V> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Entry(K key, V value) {
|
||||
super(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V setValue(V value) {
|
||||
ConcurrentWeakIdentityHashMap.this.put(getKey(), value);
|
||||
return super.setValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(Object obj) {
|
||||
if (obj instanceof Map.Entry) {
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) obj;
|
||||
return getKey() == e.getKey() && getValue() == e.getValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return System.identityHashCode(getKey()) ^ System.identityHashCode(getValue());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package dorkbox.vaadin.util;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public
|
||||
class DummyTrustManager implements X509TrustManager {
|
||||
|
||||
public
|
||||
X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[] {};
|
||||
}
|
||||
|
||||
public
|
||||
void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
public
|
||||
void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2017 Pronghorn Technology LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import mu.KotlinLogging
|
||||
|
||||
private const val MAX_POW2 = 1 shl 30
|
||||
|
||||
inline fun <reified T : Any> validatePowerOfTwoCapacity(caller: T,
|
||||
value: Int): Int {
|
||||
return when {
|
||||
isPowerOfTwo(value) -> {
|
||||
value
|
||||
}
|
||||
else -> {
|
||||
val logger = KotlinLogging.logger(caller.javaClass.name)
|
||||
logger.warn {
|
||||
"${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}."
|
||||
}
|
||||
roundToNextPowerOfTwo(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun roundToNextPowerOfTwo(value: Int): Int {
|
||||
if (value > MAX_POW2) {
|
||||
throw IllegalArgumentException("There is no larger power of 2 int for value:$value since it exceeds 2^31.")
|
||||
}
|
||||
if (value < 0) {
|
||||
throw IllegalArgumentException("Given value:$value. Expecting value >= 0.")
|
||||
}
|
||||
return 1 shl 32 - Integer.numberOfLeadingZeros(value - 1)
|
||||
}
|
||||
|
||||
fun isPowerOfTwo(value: Int): Boolean = value == roundToNextPowerOfTwo(value)
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2020 Dorkbox LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.util
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package dorkbox.vaadin.util
|
||||
|
||||
import ch.qos.logback.classic.Level
|
||||
import io.undertow.Undertow
|
||||
import io.undertow.UndertowOptions
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.handlers.ResponseCodeHandler
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
|
||||
open class WebServer(private val threadGroup: ThreadGroup, protected val logger: Logger) {
|
||||
protected val serverBuilder = Undertow.builder()
|
||||
|
||||
.setSocketOption(org.xnio.Options.REUSE_ADDRESSES, true)
|
||||
.setSocketOption(org.xnio.Options.SSL_ENABLED, true)
|
||||
|
||||
.setServerOption(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, 163840) // 16384 is default
|
||||
.setServerOption(UndertowOptions.SSL_USER_CIPHER_SUITES_ORDER, true)
|
||||
.setServerOption(UndertowOptions.ENABLE_STATISTICS, true)!!
|
||||
|
||||
|
||||
private var webServer: Undertow? = null
|
||||
private val extraStartables = mutableListOf<Runnable>()
|
||||
private val extraStoppables = mutableListOf<Runnable>()
|
||||
|
||||
fun addStartable(startable: () -> Unit) {
|
||||
extraStartables.add( Runnable { startable() })
|
||||
}
|
||||
fun addStoppable(stoppable: () -> Unit) {
|
||||
extraStoppables.add( Runnable { stoppable() })
|
||||
}
|
||||
|
||||
fun setMainHandlerForSingleServerStartup(httpHandler: HttpHandler) {
|
||||
// if (WebServerConfig.httpsEnabled) {
|
||||
// // so we will always upgrade from HTTP -> HTTPS
|
||||
// serverBuilder.addHttpListener(PortInformation.http, NetworkUtil.EXTERNAL_IPV4, createHttpsRedirect())
|
||||
//
|
||||
// // make this always be HTTPS
|
||||
// serverBuilder.addHttpsListener(PortInformation.https, NetworkUtil.EXTERNAL_IPV4, WebServerUtils.createSSLContext
|
||||
// ("keystore", "dorkbox"), WebServerUtils.createHttps(httpHandler))
|
||||
// }
|
||||
// else {
|
||||
// // make sure we serve the HTTP page
|
||||
// serverBuilder.addHttpListener(PortInformation.http, NetworkUtil.EXTERNAL_IPV4, httpHandler)
|
||||
// }
|
||||
|
||||
// ALWAYS have a 404 handler prepared!
|
||||
serverBuilder.setHandler(ResponseCodeHandler.HANDLE_404)
|
||||
}
|
||||
|
||||
fun startServer(logger: Logger) {
|
||||
// always show this part.
|
||||
val webLogger = logger as ch.qos.logback.classic.Logger
|
||||
|
||||
// save the logger level, so that on startup we can see more detailed info, if necessary.
|
||||
val level = webLogger.level
|
||||
if (logger.isTraceEnabled) {
|
||||
webLogger.level = Level.TRACE
|
||||
}
|
||||
else {
|
||||
webLogger.level = Level.INFO
|
||||
}
|
||||
|
||||
val server = serverBuilder.build()
|
||||
try {
|
||||
// NOTE: we start this in a NEW THREAD so we can create and use a thread-group for all of the undertow threads created. This allows
|
||||
// us to keep our main thread group "un-cluttered" when analyzing thread/stack traces.
|
||||
//
|
||||
// This is a hacky, but undertow does not support setting the thread group in the builder.
|
||||
|
||||
val exceptionThrown = AtomicReference<Exception>()
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
Thread(threadGroup) {
|
||||
try {
|
||||
server.start()
|
||||
webServer = server
|
||||
|
||||
// WebServerConfig.logStartup(logger)
|
||||
|
||||
extraStartables.forEach { it ->
|
||||
it.run()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
exceptionThrown.set(e)
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}.start()
|
||||
|
||||
latch.await()
|
||||
|
||||
val exception = exceptionThrown.get()
|
||||
if (exception != null) {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
finally {
|
||||
webLogger.level = level
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun stopServer(logger: Logger) {
|
||||
// always show this part.
|
||||
val webLogger = logger as ch.qos.logback.classic.Logger
|
||||
val undertowLogger = LoggerFactory.getLogger("org.xnio.nio") as ch.qos.logback.classic.Logger
|
||||
|
||||
// save the logger level, so that on shutdown we can see more detailed info, if necessary.
|
||||
val level = webLogger.level
|
||||
val undertowLevel = undertowLogger.level
|
||||
if (logger.isTraceEnabled) {
|
||||
webLogger.level = Level.TRACE
|
||||
undertowLogger.level = Level.TRACE
|
||||
}
|
||||
else {
|
||||
// we REALLY don't care about shutdown errors. we are shutting down!! (atmosphere likes to screw with us!)
|
||||
webLogger.level = Level.OFF
|
||||
undertowLogger.level = Level.OFF
|
||||
}
|
||||
|
||||
try {
|
||||
webServer?.worker?.shutdown()
|
||||
webServer?.stop()
|
||||
|
||||
extraStoppables.forEach { it ->
|
||||
it.run()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
webLogger.level = level
|
||||
undertowLogger.level = undertowLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package dorkbox.vaadin.util
|
||||
|
||||
import io.undertow.Handlers
|
||||
import io.undertow.attribute.ExchangeAttributes
|
||||
import io.undertow.predicate.Predicates
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.handlers.LearningPushHandler
|
||||
import io.undertow.server.session.InMemorySessionManager
|
||||
import io.undertow.server.session.SessionAttachmentHandler
|
||||
import io.undertow.server.session.SessionCookieConfig
|
||||
import io.undertow.util.Headers
|
||||
import io.undertow.util.StatusCodes
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyStore
|
||||
import java.util.concurrent.Executor
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* Starts blocking and runs in a new thread, as necessary
|
||||
*/
|
||||
fun HttpServerExchange.runBlocking(executor: Executor? = null, handler: HttpHandler) {
|
||||
if (!isBlocking) {
|
||||
startBlocking()
|
||||
|
||||
if (isInIoThread) {
|
||||
dispatch(executor, handler) // run a new one
|
||||
} else {
|
||||
handler.handleRequest(this) // process normal
|
||||
}
|
||||
}
|
||||
else {
|
||||
handler.handleRequest(this) // process normal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts blocking and runs in a new thread, as necessary
|
||||
*/
|
||||
fun HttpServerExchange.runBlocking(handler: HttpHandler) {
|
||||
runBlocking(null, handler)
|
||||
}
|
||||
|
||||
object WebServerUtils {
|
||||
private const val NETREF_START_DATE = 1374710400L // July 25, 2013 (when we named netref)
|
||||
|
||||
fun setNoCache(exchange: HttpServerExchange) {
|
||||
// NOTE: IE 9 requires the following HTML headers to prevent caching!
|
||||
// <meta HTTP-EQUIV="Pragma" CONTENT="no-cache">
|
||||
// <meta HTTP-EQUIV="Expires" CONTENT="-1">
|
||||
// The actual HTML that we serve has to be edited to include this.
|
||||
// SEE: https://superuser.com/questions/461285/how-to-disable-caching-in-internet-explorer-9
|
||||
|
||||
val headers = exchange.responseHeaders
|
||||
headers.put(Headers.CACHE_CONTROL, "no-cache, no-store, private, must-revalidate, max-age=0, max-stale=0, post-check=0, pre-check=0")
|
||||
headers.put(Headers.PRAGMA, "no-cache")
|
||||
headers.put(Headers.CONNECTION, "Close")
|
||||
headers.put(Headers.EXPIRES, NETREF_START_DATE) // July 25, 2013 (when we named netref)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createSSLContext(keyStore: String, storePassword: String): SSLContext {
|
||||
val javaKeyStore = loadKeyStore(keyStore, storePassword)
|
||||
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
keyManagerFactory.init(javaKeyStore, storePassword.toCharArray())
|
||||
val keyManagers = keyManagerFactory.keyManagers
|
||||
|
||||
val trustAllCerts = arrayOf<X509TrustManager>(DummyTrustManager())
|
||||
|
||||
val sslContext: SSLContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(keyManagers, trustAllCerts, null)
|
||||
|
||||
// by default, the SSL context here has the unsafe SSL ciphers ALREADY removed
|
||||
return sslContext
|
||||
}
|
||||
|
||||
private fun loadKeyStore(keyStore: String, storePassword: String): KeyStore {
|
||||
val stream = Files.newInputStream(Paths.get(keyStore)) ?: throw IllegalArgumentException("Could not load keystore '$keyStore'")
|
||||
stream.use { inputStream ->
|
||||
val loadedKeystore = KeyStore.getInstance("JKS")
|
||||
loadedKeystore.load(inputStream, storePassword.toCharArray())
|
||||
return loadedKeystore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an HTTPs temporary redirect handler that will send all requests to HTTPS first
|
||||
*/
|
||||
fun createHttpsRedirect(): HttpHandler {
|
||||
return HttpHandler { exchange ->
|
||||
exchange.responseHeaders.add(Headers.LOCATION, "https://" + exchange.hostName + exchange.relativePath)
|
||||
exchange.statusCode = StatusCodes.TEMPORARY_REDIRECT
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTTP/2 handler that will GUARANTEE all requests are upgraded to HTTPS first
|
||||
*/
|
||||
fun createHttps(nextHandler: HttpHandler): HttpHandler {
|
||||
return Handlers.predicate(Predicates.secure(), nextHandler, createHttpsRedirect())
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTTP/2 handler that will just track and serve HTTP requests
|
||||
*/
|
||||
fun createHttp2(nextHandler: HttpHandler): HttpHandler {
|
||||
val transportTrackingHandler = Handlers.header(nextHandler, "x-undertow-transport", ExchangeAttributes.transportProtocol())
|
||||
|
||||
val learningPushHandler = LearningPushHandler(100, -1, transportTrackingHandler)
|
||||
return SessionAttachmentHandler(learningPushHandler, InMemorySessionManager("learnedSessions"), SessionCookieConfig())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the remote IP address either from the NGINX/AWS header, or via the physical remote address.
|
||||
*
|
||||
* The physical remote address is worthless if we are using NGINX/AWS to proxy the web request, as it will be the
|
||||
* NGINX/AWS server IP instead of the actual "real" ip.
|
||||
*/
|
||||
fun getRemoteAddress(exchange: HttpServerExchange): String {
|
||||
// X-Forwarded-For standard (also is set by Nginx and AWS). If we do something different, this will change.
|
||||
return exchange.requestHeaders["X-Forwarded-For"]?.peekFirst() ?: exchange.sourceAddress.address.hostAddress
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* AhoCorasickDoubleArrayTrie Project
|
||||
* https://github.com/hankcs/AhoCorasickDoubleArrayTrie
|
||||
*
|
||||
* Copyright 2008-2018 hankcs <me@hankcs.com>
|
||||
* You may modify and redistribute as long as this attribution remains.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.vaadin.util.ahoCorasick
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* A state has the following functions
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* * success; successfully transferred to another state
|
||||
* * failure; if you cannot jump along the string, jump to a shallow node
|
||||
* * emits; hit a pattern string
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* The root node is slightly different. The root node has no failure function. Its "failure" refers to moving to the next state according to the string path. Other nodes have a failure state.
|
||||
*
|
||||
*
|
||||
* @author Robert Bor
|
||||
*/
|
||||
class State
|
||||
/**
|
||||
* Construct a node with a depth of depth
|
||||
*/
|
||||
@JvmOverloads constructor(
|
||||
/**
|
||||
* The length of the pattern string is also the depth of this state
|
||||
*/
|
||||
/**
|
||||
* Get node depth
|
||||
*/
|
||||
val depth: Int = 0) {
|
||||
|
||||
/**
|
||||
* The fail function, if there is no match, jumps to this state.
|
||||
*/
|
||||
private var failure: State? = null
|
||||
|
||||
/**
|
||||
* Record mode string as long as this state is reachable
|
||||
*/
|
||||
private var emits: MutableSet<Int>? = null
|
||||
/**
|
||||
* The goto table, also known as the transfer function. Move to the next state based on the next character of the string
|
||||
*/
|
||||
private val success = TreeMap<Char, State>()
|
||||
|
||||
/**
|
||||
* Corresponding subscript in double array
|
||||
*/
|
||||
var index: Int = 0
|
||||
|
||||
/**
|
||||
* Get the largest value
|
||||
*/
|
||||
val largestValueId: Int?
|
||||
get() = if (emits == null || emits!!.size == 0) {
|
||||
null
|
||||
}
|
||||
else emits!!.iterator().next()
|
||||
|
||||
/**
|
||||
* Whether it is the termination status
|
||||
*/
|
||||
val isAcceptable: Boolean
|
||||
get() = this.depth > 0 && this.emits != null
|
||||
|
||||
val states: Collection<State>
|
||||
get() = this.success.values
|
||||
|
||||
val transitions: Collection<Char>
|
||||
get() = this.success.keys
|
||||
|
||||
/**
|
||||
* Add a matching pattern string (this state corresponds to this pattern string)
|
||||
*/
|
||||
fun addEmit(keyword: Int) {
|
||||
if (this.emits == null) {
|
||||
this.emits = TreeSet(Collections.reverseOrder())
|
||||
}
|
||||
this.emits!!.add(keyword)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some matching pattern strings
|
||||
*/
|
||||
fun addEmit(emits: Collection<Int>) {
|
||||
for (emit in emits) {
|
||||
addEmit(emit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pattern string represented by this node (we)
|
||||
*/
|
||||
fun emit(): Collection<Int> {
|
||||
return this.emits ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failure status
|
||||
*/
|
||||
fun failure(): State? {
|
||||
return this.failure
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the failure status
|
||||
*/
|
||||
fun setFailure(failState: State,
|
||||
fail: IntArray) {
|
||||
this.failure = failState
|
||||
fail[index] = failState.index
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the next state
|
||||
*
|
||||
* @param character wants to transfer by this character
|
||||
* @param ignoreRootState Whether to ignore the root node, it should be true if the root node calls itself, otherwise it is false
|
||||
*
|
||||
* @return transfer result
|
||||
*/
|
||||
private fun nextState(character: Char,
|
||||
ignoreRootState: Boolean): State? {
|
||||
var nextState: State? = this.success[character]
|
||||
if (!ignoreRootState && nextState == null && this.depth == 0) {
|
||||
nextState = this
|
||||
}
|
||||
return nextState
|
||||
}
|
||||
|
||||
/**
|
||||
* According to the character transfer, the root node transfer failure will return itself (never return null)
|
||||
*/
|
||||
fun nextState(character: Char): State? {
|
||||
return nextState(character, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* According to character transfer, any node transfer failure will return null
|
||||
*/
|
||||
fun nextStateIgnoreRootState(character: Char): State? {
|
||||
return nextState(character, true)
|
||||
}
|
||||
|
||||
fun addState(character: Char): State {
|
||||
var nextState = nextStateIgnoreRootState(character)
|
||||
if (nextState == null) {
|
||||
nextState = State(this.depth + 1)
|
||||
this.success[character] = nextState
|
||||
}
|
||||
return nextState
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val sb = StringBuilder("State{")
|
||||
sb.append("depth=").append(depth)
|
||||
sb.append(", ID=").append(index)
|
||||
sb.append(", emits=").append(emits)
|
||||
sb.append(", success=").append(success.keys)
|
||||
sb.append(", failureID=").append(if (failure == null) "-1" else failure!!.index)
|
||||
sb.append(", failure=").append(failure)
|
||||
sb.append('}')
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get goto table
|
||||
*/
|
||||
fun getSuccess(): Map<Char, State> {
|
||||
return success
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a node with a depth of 0
|
||||
*/
|
Loading…
Reference in New Issue