Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 58 additions & 18 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -333,34 +333,74 @@
<version>1.9.0</version>
</dependency>

<!-- dropwizard deps -->
<!-- ktor deps -->
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-core-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-netty-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<!-- dropwizard deps -->
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-assets</artifactId>
<version>${dropwizard.version}</version>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-sessions-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<!-- dropwizard deps -->

<!-- OAuth library -->
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-views-freemarker</artifactId>
<version>${dropwizard.version}</version>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-auth-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-freemarker-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-status-pages-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-call-logging-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-content-negotiation-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-jackson-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-partial-content-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>

<!-- Servlet API for compatibility -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- dropwizard deps -->
<!-- ktor deps -->

<!-- OAuth library -->
<dependency>
Expand Down Expand Up @@ -500,7 +540,7 @@
<properties>
<activation.version>1.1.1</activation.version>
<critter.version>4.4.4</critter.version>
<dropwizard.version>2.1.5</dropwizard.version>
<ktor.version>3.0.3</ktor.version>
<guice.version>5.1.0</guice.version>
<jackson.version>2.21.0</jackson.version>
<jacoco.version>0.8.5</jacoco.version>
Expand Down
158 changes: 88 additions & 70 deletions src/main/kotlin/javabot/web/JavabotApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,112 +4,130 @@ import com.google.inject.Guice
import com.google.inject.Inject
import com.google.inject.Injector
import com.google.inject.Singleton
import io.dropwizard.Application
import io.dropwizard.assets.AssetsBundle
import io.dropwizard.setup.Bootstrap
import io.dropwizard.setup.Environment
import io.dropwizard.views.ViewBundle
import freemarker.cache.ClassTemplateLoader
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.freemarker.*
import io.ktor.server.http.content.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import java.io.File
import java.nio.file.Files
import java.util.EnumSet
import javabot.Javabot
import javabot.JavabotConfig
import javabot.JavabotModule
import javabot.dao.ApiDao
import javabot.web.resources.AdminResource
import javabot.web.resources.BotResource
import javabot.web.resources.PublicOAuthResource
import javax.servlet.DispatcherType
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.eclipse.jetty.server.session.SessionHandler
import org.slf4j.LoggerFactory

@Singleton
class JavabotApplication @Inject constructor(var injector: Injector) :
Application<JavabotConfiguration>() {
class JavabotApplication @Inject constructor(var injector: Injector) {
var running = false
lateinit var server: EmbeddedServer<*, *>

companion object {
private val LOG = LoggerFactory.getLogger(JavabotApplication::class.java)

@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
Guice.createInjector(JavabotModule())
.getInstance(JavabotApplication::class.java)
.run(*arrayOf("server", "javabot.yml"))
val application =
Guice.createInjector(JavabotModule()).getInstance(JavabotApplication::class.java)
application.run()
}
}

override fun initialize(bootstrap: Bootstrap<JavabotConfiguration>) {
bootstrap.addBundle(ViewBundle())
bootstrap.addBundle(AssetsBundle("/assets", "/assets", null, "assets"))
bootstrap.addBundle(
AssetsBundle("/META-INF/resources/webjars", "/webjars", null, "webjars")
)
}

override fun run(configuration: JavabotConfiguration, environment: Environment) {
environment.applicationContext.isSessionsEnabled = true
environment.applicationContext.sessionHandler = SessionHandler()
fun run() {
val configuration = JavabotConfiguration()

val bot = injector.getInstance(Javabot::class.java)
bot.setUpThreads()

val oauth = injector.getInstance(PublicOAuthResource::class.java)
oauth.configuration = configuration
environment.jersey().register(oauth)
server =
embeddedServer(Netty, port = 8080, host = "0.0.0.0") { configureServer(configuration) }

environment.jersey().register(injector.getInstance(BotResource::class.java))
environment.jersey().register(injector.getInstance(AdminResource::class.java))
environment.jersey().register(RuntimeExceptionMapper(configuration))
running = true
server.start(wait = true)
}

private fun Application.configureServer(configuration: JavabotConfiguration) {
// Install FreeMarker for templating
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "/")
}

environment
.servlets()
.addFilter("javadoc", injector.getInstance(JavadocFilter::class.java))
.addMappingForUrlPatterns(
EnumSet.allOf(DispatcherType::class.java),
false,
"/javadoc/*",
)
// Install sessions
install(Sessions) {
cookie<UserSession>(JavabotConfiguration.SESSION_TOKEN_NAME) {
cookie.path = "/"
cookie.maxAgeInSeconds = 86400 * 30
}
}

environment.healthChecks().register("javabot", JavabotHealthCheck())
// Install status pages for error handling
install(StatusPages) {
exception<Throwable> { call, cause ->
LOG.error("Request failed", cause)
call.respond(
HttpStatusCode.InternalServerError,
"Internal Server Error: ${cause.message}",
)
}

running = false
}
status(HttpStatusCode.NotFound) { call, status ->
call.respondText("404: Page Not Found", status = status)
}
}

class JavadocFilter @Inject constructor(var apiDao: ApiDao, var config: JavabotConfig) :
Filter {
override fun destroy() {}

override fun doFilter(
request: ServletRequest,
response: ServletResponse,
chain: FilterChain,
) {
request as HttpServletRequest
var filePath = request.requestURI.split("/").drop(2).joinToString("/")
if (!filePath.startsWith("/")) {
filePath = "/" + filePath
// Configure routing
routing {
// Static assets using static() which serves files from resources
static("/assets") {
resources("assets")
}
static("/webjars") {
resources("META-INF/resources/webjars")
}
val path = File("javadoc$filePath").toPath()

if (Files.exists(path)) {
response.outputStream.use { stream ->
Files.copy(path, stream)
stream.flush()
// Javadoc filter
get("/javadoc/{...}") {
val apiDao = injector.getInstance(ApiDao::class.java)
val config = injector.getInstance(JavabotConfig::class.java)

val pathAfterJavadoc = call.request.uri.substringAfter("/javadoc/")
val filePath = "/$pathAfterJavadoc"
val path = File("javadoc$filePath").toPath()

if (Files.exists(path)) {
call.respondFile(path.toFile())
} else {
call.respond(HttpStatusCode.NotFound)
}
} else {
(response as HttpServletResponse).sendError(404)
}
}

override fun init(filterConfig: FilterConfig?) {}
// Health check
get("/health") { call.respondText("OK", contentType = ContentType.Text.Plain) }

// Register OAuth routes
val oauth = injector.getInstance(PublicOAuthResource::class.java)
oauth.configuration = configuration
oauth.configureRoutes(this)

// Register Bot routes
val botResource = injector.getInstance(BotResource::class.java)
botResource.configureRoutes(this)

// Register Admin routes
val adminResource = injector.getInstance(AdminResource::class.java)
adminResource.configureRoutes(this)
}
}
}

data class UserSession(val sessionToken: String)
3 changes: 1 addition & 2 deletions src/main/kotlin/javabot/web/JavabotConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package javabot.web

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import io.dropwizard.Configuration
import java.util.HashMap
import java.util.Properties
import javabot.web.model.OAuthConfig

class JavabotConfiguration : Configuration() {
class JavabotConfiguration {

companion object {
val SESSION_TOKEN_NAME: String = "JavabotSession"
Expand Down
10 changes: 0 additions & 10 deletions src/main/kotlin/javabot/web/JavabotHealthCheck.kt

This file was deleted.

69 changes: 0 additions & 69 deletions src/main/kotlin/javabot/web/RuntimeExceptionMapper.kt

This file was deleted.

Loading