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:
- Reads HOCON configuration and environment variables
- Initializes the database connection pool (HikariCP)
- Loads and evaluates the Content DSL
- Registers HTTP routes, WebSocket endpoints, and static resources
- Starts the Prometheus metrics agent (if enabled)
Routing Architecture
ReadingBat uses Ktor's type-safe routing with @Resource annotations for content
navigation:
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 |
Anonymous browser session tracking |
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 |
GeoInfosTable |
IP geolocation data |
Database Commands
# Reset database (clean + migrate)
make dbreset
# Run migrations only
make dbmigrate
# or
./gradlew flywayMigrate
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 session counts
- Request timing summaries
All metrics are labeled with the agent launch ID for multi-instance deployments.
Running the Server
The server reads its configuration from application.conf specified via the
-config= JVM argument.