Skip to content

Server

ReadingBat runs on a Ktor web server using the CIO (Coroutine I/O) engine. The server handles HTTP requests, serves HTML pages, manages WebSocket connections, and integrates with PostgreSQL for persistence.

Entry Point

The server is launched via ReadingBatServer.start(), which invokes Ktor's EngineMain. The Application.module() function is the Ktor entry point that initializes all components:

  1. Reads HOCON configuration and environment variables
  2. Initializes the database connection pool (HikariCP)
  3. Loads and evaluates the Content DSL
  4. Registers HTTP routes, WebSocket endpoints, and static resources
  5. Starts the Prometheus metrics agent (if enabled)

Routing Architecture

ReadingBat uses Ktor's type-safe routing with @Resource annotations for content navigation:

/content/{language}/{group}/{challenge}

The nested resource hierarchy:

Resource URL Pattern Example
Language /content/{lang} /content/java
Language.Group /content/{lang}/{group} /content/java/Warmup-1
Language.Group.Challenge /content/{lang}/{group}/{challenge} /content/java/Warmup-1/SleepIn

Route Categories

Category Description
User routes Challenge pages, answer submission, account management
Admin routes Class creation, student enrollment, progress dashboards
SysAdmin routes System configuration, monitoring
OAuth routes GitHub and Google authentication callbacks
WebSocket routes Real-time updates for answers, progress, and dashboards

Page Generation

All HTML pages are generated server-side using Kotlinx.html. Each page has its own file in the com.readingbat.pages package following a companion object function pattern:

// Example pattern (simplified)
object ChallengePage {
  fun challengePage(
    content: ReadingBatContent,
    challenge: Challenge,
    user: User?,
    // ...
  ): String = // returns HTML string
}

Client-side interactivity (answer checking, real-time updates) is handled by JavaScript generated in the pages/js/ package.

WebSocket Endpoints

Real-time features use WebSocket connections:

Endpoint Purpose
Challenge WS Live answer checking and feedback
Challenge Group WS Group-level completion status
Class Summary WS Teacher view of class progress
Student Summary WS Individual student progress
Clock WS Server time synchronization
Logging WS Real-time log streaming

WebSocket connections are validated for proper language, group, class code, and enrollment status before establishing.

Database

ReadingBat uses PostgreSQL with the Exposed ORM. Connection pooling is managed by HikariCP.

Key Tables

Table Purpose
UsersTable User accounts with salted password hashes
BrowserSessionsTable Browser sessions of logged-in users
UserSessionsTable Maps sessions to authenticated users
UserChallengeInfoTable Current answer state per user per challenge
UserAnswerHistoryTable Historical record of all answer attempts
ClassesTable Teacher-created classes
EnrolleesTable Student enrollments in classes
ServerRequestsTable Request logging for logged-in users
GeoInfosTable IP geolocation data

Database Commands

# Run migrations
make dbmigrate
# or
./gradlew flywayMigrate

# Drop all database objects, then re-migrate
make dbclean && make dbmigrate

# Show migration status / validate applied migrations
make dbinfo
make dbvalidate

Migration SQL files are located in src/main/resources/db/migration/.

Script Engines

Challenge code is evaluated using JSR-223 script engine pools:

Pool Engine Default Size
javaScriptPool Java scripting 5
kotlinScriptPool Kotlin scripting 5
pythonScriptPool Python (Jython) 5
kotlinEvaluatorPool Kotlin expression eval 5
pythonEvaluatorPool Python expression eval 5

Pool sizes are configurable via HOCON properties (e.g., readingbat.scripts.javaPoolSize).

Metrics

ReadingBat exports Prometheus metrics including:

  • Content load counts and durations
  • Language page view counts
  • WebSocket connection metrics
  • Cache size gauges
  • Active user counts (logged-in sessions only; anonymous visitors and bots are not recorded)
  • Request timing summaries
  • JVM heap, GC, thread, and class-loading metrics (e.g., jvm_memory_used_bytes)

Metrics use the Prometheus Java client 1.x and are served on :8083/metrics by default. All application metrics are labeled with the agent launch ID for multi-instance deployments.

At startup the server also logs whether the JVM will write a heap dump on OutOfMemoryError, and warns when it will not. Enable dumps with JAVA_TOOL_OPTIONS (see Configuration).

Running the Server

# Via Gradle
./gradlew run

# Via Make
make run

# Continuous build (excludes tests)
./gradlew build --continuous -x test

The server reads its configuration from application.conf specified via the -config= JVM argument.

Local runs (./gradlew run, make run, and make uber) start the JVM with -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=build, so an OOM leaves a heap dump in build/.