Overview
SimpleWebServer embeds a lightweight HTTP or HTTP/HTTPS web server into your B4J application. It serves static files from a folder on disk and routes any unhandled requests to B4J subroutines, letting you build a web interface without an external server process.
Requests for static files (HTML, CSS, JS, images) are served directly. If a requested path has no
matching file, the library calls your SubName_Get or SubName_Post
subroutine so your code can generate a dynamic response. The Content-Type header is
set automatically based on the response you return.
When using Initialize2, an HTTP server is started alongside the HTTPS server solely
to issue 301 redirects, ensuring all traffic is sent over HTTPS.
What it is
- An embedded HTTP/HTTPS server you start from inside your own B4J app, with no separate web server to install or run.
- A way to put a local web interface on a desktop or server application: admin panels, dashboards, configuration screens and lightweight internal tools.
- A simple bridge between web requests and B4J code: static files are served from a folder, and everything else is handed to your handler subroutines.
- Best suited to a small number of trusted users on a local machine or network, not the public internet.
What it is not
It is deliberately minimal and is not a replacement for a production web framework. In particular, it does not provide:
- High-throughput / high-concurrency serving — suited to a handful of users, not a high-traffic public website. There is no connection tuning, load shedding or back-pressure.
- Single-threaded in debug mode — while release mode uses a thread pool, debug mode routes every request through the main B4J thread, so requests are handled one at a time during development.
- No authentication or authorization — there is no built-in login, sessions, cookies or access control. Anything you need must be implemented in your handler subs.
- No request body parsing — bodies arrive as a raw UTF-8 string. There is no automatic form, query-string or JSON parsing, and no multipart or file-upload handling.
- Text responses only — handler subs return a
String, so dynamically generated binary content (images, PDFs, downloads) is not supported. Binary assets must be served as static files. - No WebSockets or streaming — there is no support for WebSockets, server-sent events, chunked streaming or long-polling. Each request returns a single complete response.
- No HTTP/2 or HTTP/3 — it speaks HTTP/1.1 only.
- No compression or caching — responses are not gzipped, and no
ETag,Cache-Controlor conditional-request handling is applied. - No routing framework — routing is manual via
Select Casein your handler subs. There are no path parameters, wildcards or middleware. - No virtual hosts or reverse proxying — each instance serves one folder on one set of ports.
Features
- Static file serving — serves HTML, CSS, JS, images and other files directly from a folder on disk, with the URL path mapped straight to the file system.
- Dynamic request routing — unhandled GET and POST requests are routed to your
SubName_GetandSubName_Postsubroutines so your code can build responses on the fly. - Automatic
Content-Typedetection — the response type is inferred from the returned string (JSON, HTML or plain text) and from the file extension for static files. - HTTPS support — TLS via a standard Java keystore (JKS), with a companion HTTP server that 301-redirects all traffic to HTTPS.
- Built-in redirects — return
"redirect:/path"from any handler to send a 302 redirect to a path or full URL. - Path traversal protection — requests that try to escape the static files folder are blocked with a 403.
- Automatic index — a request for
/is rewritten to/index.htmlwhen that file exists. - Optional thread pool — request handling is serial by default; call
SetThreadCountto enable concurrent handling via a fixed thread pool (debug mode is always routed to the main B4J thread). - Multiple instances — run several independent server instances side by side in the same application.
- Graceful shutdown —
Stopdrains in-flight requests with a short timeout before closing. - Self-contained — embeds directly into your B4J app with no external web server process to install or manage.
Quick Start
Declare the server as a process global, initialize it in AppStart, then call
Start to begin listening. Define your handler subroutines in the same module.
Main.bas
Sub Process_Globals Private WebServer As SimpleWebServer Private StaticFilesFolder As String End Sub Sub AppStart(Args() As String) StaticFilesFolder = File.Combine(File.DirApp, "www") WebServer.Initialize(8080, StaticFilesFolder, "WebServer") WebServer.Start End Sub Sub WebServer_Get(Path As String, QueryString As String) As String Select Case Path Case "", "/", "/index" Return page_index.Handle(QueryString) Case "/healthcheck" Return "{""status"":""ok""}" End Select Return Null End Sub Sub WebServer_Post(Path As String, Body As String) As String Select Case Path Case "/config" Return page_config.Post(Body) End Select Return Null End Sub
Methods
Initialize HTTP
Creates an HTTP server on the specified port. Static files are served from
StaticFilesFolder. Unhandled requests are routed to the B4J subroutines
SubName_Get and SubName_Post in the calling module. The server does
not begin listening until you call Start.
- HttpPort — Port number to listen on (e.g.
8080). - StaticFilesFolder — Absolute path to the folder containing static files to serve.
- SubName — Prefix for the handler subroutines in the calling module. Case-insensitive.
Example
WebServer.Initialize(8080, File.Combine(File.DirApp, "www"), "WebServer") WebServer.Start
Initialize on an already-initialized server has no effect. Call
Stop first if you need to re-initialize with different settings.
Initialize2 HTTP + HTTPS
Creates both an HTTPS server and an HTTP server. The HTTP server issues a
301 redirect to the HTTPS port for every request, ensuring all traffic is
encrypted. The HTTPS server uses a Java keystore (JKS) file for TLS configuration. Neither
server begins listening until you call Start.
- HttpPort — Port that redirects all requests to HTTPS (e.g.
80). - HttpsPort — Port the HTTPS server listens on (e.g.
443). - StaticFilesFolder — Absolute path to the folder containing static files to serve.
- SubName — Prefix for the handler subroutines in the calling module.
- KeystoreFile — Absolute path to the JKS keystore file containing the TLS certificate.
- KeyStorePassword — Password for the keystore.
Example
WebServer.Initialize2(80, 443, File.Combine(File.DirApp, "www"), "WebServer", "/etc/myapp/keystore.jks", "keystorepassword") WebServer.Start
keytool -genkeypair -keyalg RSA -keysize 2048 -validity 365 -keystore keystore.jks
SetThreadCount
Sets the size of the thread pool used to handle requests. Call this before
Initialize or Initialize2,
as the executor is configured during initialization.
- Count — Number of worker threads. The default of
0means no thread pool is set, so requests are handled serially on the server's dispatch thread. A value greater than0creates a fixed thread pool of that size, allowing requests to be handled concurrently.
Example
WebServer.SetThreadCount(8) WebServer.Initialize(8080, StaticFilesFolder, "WebServer") WebServer.Start
Start Boolean
Starts the server(s) listening for connections. Call this after
Initialize or Initialize2
(and after SetThreadCount, if used). Until
Start is called the server accepts no connections. When initialized with
Initialize2, both the HTTP and HTTPS servers are started.
Returns True if the server started successfully, False if it was
not initialized or an error occurred.
Example
If WebServer.Start = False Then Log("Server failed to start") ExitApplication End If
IsInitialised Boolean
Returns True if the server was initialized successfully (via
Initialize or Initialize2),
False otherwise. Useful for confirming initialization succeeded before calling
Start, or guarding against double-initialization.
Example
If WebServer.IsInitialised = False Then Log("Server failed to initialize") ExitApplication End If
Stop Boolean
Stops the server, waiting up to 2.5 seconds for in-flight requests to complete. If
both HTTP and HTTPS servers are running (from Initialize2), both are stopped.
Returns True on success, False if an error occurred or the
server was not running.
Example
WebServer.Stop
Handler Subroutines
SubName_Get GET
Called when a GET request arrives for a path that has no matching static file on disk.
Define this subroutine in the same module that calls Initialize, replacing
SubName with the value you passed to the Initialize method.
- Path — The request path, e.g.
/config. Leading slash included. - QueryString — The raw query string, e.g.
page=2&sort=asc. May beNullif no query string was sent.
Return a String to send as the response body. Return Null to
send a 404 Not Found. The Content-Type header is set
automatically from the content of your return value (see
Auto-Detection).
Example
Sub WebServer_Get(Path As String, QueryString As String) As String Select Case Path Case "", "/", "/index" Return page_index.Handle(QueryString) Case "/healthcheck" Return "{""status"":""ok""}" Case "/config" Return page_config.Get(QueryString) Case "/restart" Return page_restart.Handle(QueryString) End Select Return Null End Sub
/ is automatically redirected to /index.html
if that file exists in the static files folder, before your handler sub is called.
SubName_Post POST
Called for every POST request, regardless of whether a static file exists at that path. The request body is read as UTF-8 text and passed directly to your subroutine.
- Path — The request path, e.g.
/config. - Body — The raw request body as a UTF-8 string. Commonly JSON or URL-encoded form data.
Return a String to send as the response body, or Null for
a 404 Not Found.
Example
Sub WebServer_Post(Path As String, Body As String) As String Select Case Path Case "/config" Return page_config.Post(Body) Case "/api/data" Return page_api.Post(Body) End Select Return Null End Sub
Response Types
Content-Type Auto-Detection
The Content-Type response header is set automatically based on the first
non-whitespace character of the string your handler returns. You do not need to set it
yourself.
| First character | Content-Type sent | Typical use |
|---|---|---|
{ or [ | application/json | JSON API responses |
< | text/html; charset=utf-8 | HTML pages |
| anything else | text/plain; charset=utf-8 | Plain text |
Examples
' Detected as application/json Return "{""status"":""ok""}" ' Detected as text/html Return "<!DOCTYPE html><html>...</html>" ' Detected as text/plain Return "Server is running"
Redirects
Return a string beginning with redirect: to send a 302 Found
redirect to any path or URL. This works from both GET and POST handlers and is the
standard way to navigate between pages in your app.
Syntax
Return "redirect:/target-path"
Example — conditional redirect based on config state
' page_index.bas Sub Handle(QueryString As String) As String If Main.ConfigGood = False Then Return "redirect:/config" Return "redirect:/agent.html" End Sub
Example — redirect after a successful POST
' page_config.bas Sub Post(Body As String) As String SaveConfig(Body) Return "redirect:/config?saved=1" End Sub
/config), a path with a
query string (/config?saved=1), or a full URL
(https://example.com).
Patterns
Static File Serving
Any file present in StaticFilesFolder is served directly without calling
your handler subs. The URL path maps directly to the file system: a request for
/css/mvp.css serves StaticFilesFolder/css/mvp.css.
A request for / is automatically rewritten to /index.html
if that file exists. Path traversal attempts (e.g. /../etc/passwd) are
blocked and return a 403 Forbidden.
| Extension | Content-Type |
|---|---|
html | text/html |
css | text/css |
js | application/javascript |
json | application/json |
txt | text/plain |
png | image/png |
jpg / jpeg | image/jpeg |
gif | image/gif |
svg | image/svg+xml |
| (other) | application/octet-stream |
Recommended folder layout
www/
index.html
agent.html
css/
mvp.css
js/
chart.js
favicon.ico
Page Module Pattern
For anything beyond trivial responses, the recommended approach is to create a separate B4J code module for each page or endpoint. The main handler subs act purely as a router, delegating to each module.
Main.bas — router only
Sub WebServer_Get(Path As String, QueryString As String) As String Select Case Path Case "", "/", "/index" : Return page_index.Handle(QueryString) Case "/healthcheck" : Return page_healthcheck.Handle(QueryString) Case "/config" : Return page_config.Get(QueryString) Case "/restart" : Return page_restart.Handle(QueryString) End Select Return Null End Sub Sub WebServer_Post(Path As String, Body As String) As String Select Case Path Case "/config" : Return page_config.Post(Body) End Select Return Null End Sub
page_index.bas — redirect based on app state
Sub Handle(QueryString As String) As String If Main.ConfigGood = False Then Return "redirect:/config" Return "redirect:/agent.html" End Sub
page_healthcheck.bas — JSON response
Sub Handle(QueryString As String) As String Return "{""status"":""ok""}" End Sub
Handling Form POSTs
When an HTML form submits with method="POST", the request body arrives
as URL-encoded key=value pairs separated by &. Use the helper below to
parse the body into a Map before processing it.
page_config.bas
Sub Post(Body As String) As String Dim Params As Map = ParseFormBody(Body) SaveConfig(Params) Return Get("") ' re-render the page End Sub Private Sub ParseFormBody(Body As String) As Map Dim Result As Map Result.Initialize If Body.Length = 0 Then Return Result Dim Parts() As String = Regex.Split("&", Body) For Each Part As String In Parts Dim Idx As Int = Part.IndexOf("=") If Idx > 0 Then Dim Key As String = URLDecode(Part.SubString2(0, Idx)) Dim Value As String = URLDecode(Part.SubString(Idx + 1)) Result.Put(Key, Value) End If Next Return Result End Sub Private Sub URLDecode(s As String) As String Try Dim jo As JavaObject jo.InitializeStatic("java.net.URLDecoder") Return jo.RunMethod("decode", Array(s, "UTF-8")) Catch Return s.Replace("+", " ") End Try End Sub
Body
string directly to JSON.Parse instead of using ParseFormBody.
Reference
MIME Types
The following MIME types are built in and applied automatically when serving static files.
| Extension | Content-Type |
|---|---|
html | text/html |
css | text/css |
js | application/javascript |
json | application/json |
txt | text/plain |
png | image/png |
jpg / jpeg | image/jpeg |
gif | image/gif |
svg | image/svg+xml |
| (unrecognized) | application/octet-stream |
Status Codes
The server returns the following HTTP status codes automatically.
| Code | Condition |
|---|---|
200 OK | Static file served, or handler sub returned a non-null string. |
301 Moved Permanently | HTTP request redirected to HTTPS (Initialize2 only). |
302 Found | Handler sub returned "redirect:/path". |
403 Forbidden | Request path attempts to escape the static files folder. |
404 Not Found | No static file found and handler sub returned Null. |
405 Method Not Allowed | Request method other than GET or POST. |
500 Internal Server Error | Unhandled exception inside the request handler. |