Skip to content

Commit

Permalink
Replace polling with notifications by web sockets
Browse files Browse the repository at this point in the history
This remove the polling delay and is overall a much nicer solution.
Add `websocket-jetty-server` dependency for web socket support for
the server.
  • Loading branch information
jp7677 committed Nov 15, 2022
1 parent be866a8 commit 333b505
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {

implementation("org.eclipse.jetty:jetty-server:11.0.12")
implementation("org.eclipse.jetty:jetty-servlet:11.0.12")
implementation("org.eclipse.jetty.websocket:websocket-jetty-server:11.0.12")

runtimeOnly("org.slf4j:slf4j-simple:2.0.3")
runtimeOnly("org.jetbrains.kotlin:kotlin-scripting-jsr223:1.7.20")
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/nl/avisi/structurizr/site/generatr/ServeCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package nl.avisi.structurizr.site.generatr

import com.sun.nio.file.SensitivityWatchEventModifier
import jakarta.servlet.ServletContext
import kotlinx.cli.*
import nl.avisi.structurizr.site.generatr.site.copySiteWideAssets
import nl.avisi.structurizr.site.generatr.site.generateDiagrams
Expand All @@ -13,8 +14,12 @@ import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.DefaultServlet
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.websocket.api.WebSocketAdapter
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer
import java.io.File
import java.nio.file.*
import java.time.Duration
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
Expand All @@ -30,6 +35,8 @@ class ServeCommand : Subcommand("serve", "Start a development server") {
ArgType.String, "site-dir", "s", "Directory for the generated site"
).default("build/serve")

private val eventSockets = mutableListOf<EventSocket>()

override fun execute() {
updateSite()
val server = runServer()
Expand Down Expand Up @@ -68,6 +75,8 @@ class ServeCommand : Subcommand("serve", "Start a development server") {
forServing = true
)

sendSiteUpdatedNotifications()
clearInvalidEventSockets()
println("Successfully generated diagrams and site")
}

Expand All @@ -86,13 +95,23 @@ class ServeCommand : Subcommand("serve", "Start a development server") {
ServletContextHandler().apply {
contextPath = "/"
addServlet(createStaticResourceServlet(), "/*")
addWebSocketServlet(this, "/_events")
}

private fun createStaticResourceServlet() =
ServletHolder("default", DefaultServlet()).apply {
setInitParameter("resourceBase", siteDir)
}

private fun addWebSocketServlet(context: ServletContextHandler, pathSpec: String) =
JettyWebSocketServletContainerInitializer
.configure(context) { _: ServletContext?, container: JettyWebSocketServerContainer ->
container.idleTimeout = Duration.ZERO
container.addMapping(pathSpec) { _, _ ->
EventSocket().also { eventSockets.add(it) }
}
}

private fun startWatchService(): WatchService {
val path = File(workspaceFile).absoluteFile.parentFile.toPath()
val watchService = FileSystems.getDefault().newWatchService()
Expand Down Expand Up @@ -153,4 +172,25 @@ class ServeCommand : Subcommand("serve", "Start a development server") {
SensitivityWatchEventModifier.HIGH
)
}

private fun sendSiteUpdatedNotifications() = eventSockets
.filter { it.isValid() }
.forEach { it.sendSiteUpdatedNotification() }

private fun clearInvalidEventSockets() = eventSockets
.toList() // Do a copy first since we might remove elements
.filterNot { it.isValid() }
.forEach { eventSockets.remove(it) }

private class EventSocket : WebSocketAdapter() {
fun sendSiteUpdatedNotification() {
if (session == null)
return

if (session.isOpen)
this.session.remote?.sendString("site-updated")
}

fun isValid() = isConnected
}
}
18 changes: 17 additions & 1 deletion src/main/resources/assets/js/auto-reload.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,20 @@ function reloadIfNeeded() {
});
}

setInterval(reloadIfNeeded, 200);
function connectToWs() {
const ws = new WebSocket("ws://" + location.host + "/_events");
ws.onopen = () => console.log("Connected to socket.");
ws.onclose = (event) => {
console.log('Socket has been closed. Attempting to reconnect ...', event.reason);
setTimeout(() => connectToWs(), 1000);
};
ws.onmessage = (event) => {
if (event.data === "site-updated") {
console.log("Site update detected, detect page content change ...")
reloadIfNeeded();
}
}
}

reloadIfNeeded();
connectToWs();

0 comments on commit 333b505

Please sign in to comment.