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-Control or conditional-request handling is applied.
  • No routing framework — routing is manual via Select Case in 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_Get and SubName_Post subroutines so your code can build responses on the fly.
  • Automatic Content-Type detection — 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.html when that file exists.
  • Optional thread pool — request handling is serial by default; call SetThreadCount to 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 shutdownStop drains 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

Initialize HTTP

WebServer.Initialize(HttpPort As Int, StaticFilesFolder As String, SubName As String)

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
Calling Initialize on an already-initialized server has no effect. Call Stop first if you need to re-initialize with different settings.

Initialize2 HTTP + HTTPS

WebServer.Initialize2(HttpPort As Int, HttpsPort As Int, StaticFilesFolder As String, SubName As String, KeystoreFile As String, KeyStorePassword As String)

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
The keystore must be in JKS format. To generate a self-signed keystore for development: keytool -genkeypair -keyalg RSA -keysize 2048 -validity 365 -keystore keystore.jks

SetThreadCount

WebServer.SetThreadCount(Count As Int)

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 0 means no thread pool is set, so requests are handled serially on the server's dispatch thread. A value greater than 0 creates a fixed thread pool of that size, allowing requests to be handled concurrently.

Example

WebServer.SetThreadCount(8)
WebServer.Initialize(8080, StaticFilesFolder, "WebServer")
WebServer.Start
This setting has no effect in debug mode: B4J's debugger requires handler subs to run on the main thread, so in debug mode every request is routed there regardless of the thread count. The thread pool applies only to release (compiled) builds.

Start Boolean

WebServer.Start() As 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

WebServer.IsInitialised() As 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

WebServer.Stop() As 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

SubName_Get GET

Sub SubName_Get(Path As String, QueryString As String) As String

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 be Null if 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
A request for / is automatically redirected to /index.html if that file exists in the static files folder, before your handler sub is called.

SubName_Post POST

Sub SubName_Post(Path As String, Body As String) As String

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

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/jsonJSON API responses
<text/html; charset=utf-8HTML pages
anything elsetext/plain; charset=utf-8Plain 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
The redirect target can be an absolute path (/config), a path with a query string (/config?saved=1), or a full URL (https://example.com).

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.

ExtensionContent-Type
htmltext/html
csstext/css
jsapplication/javascript
jsonapplication/json
txttext/plain
pngimage/png
jpg / jpegimage/jpeg
gifimage/gif
svgimage/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
For JSON POST bodies (e.g. from a fetch API call), pass the Body string directly to JSON.Parse instead of using ParseFormBody.

MIME Types

The following MIME types are built in and applied automatically when serving static files.

ExtensionContent-Type
htmltext/html
csstext/css
jsapplication/javascript
jsonapplication/json
txttext/plain
pngimage/png
jpg / jpegimage/jpeg
gifimage/gif
svgimage/svg+xml
(unrecognized)application/octet-stream

Status Codes

The server returns the following HTTP status codes automatically.

CodeCondition
200 OKStatic file served, or handler sub returned a non-null string.
301 Moved PermanentlyHTTP request redirected to HTTPS (Initialize2 only).
302 FoundHandler sub returned "redirect:/path".
403 ForbiddenRequest path attempts to escape the static files folder.
404 Not FoundNo static file found and handler sub returned Null.
405 Method Not AllowedRequest method other than GET or POST.
500 Internal Server ErrorUnhandled exception inside the request handler.