Static File Serving
Overview
HttpServer has built-in static file serving. When WebRoot is configured, it automatically handles static resource requests without writing controllers.
Version Change Notice
Starting from vbman 1.0.0.419, WebRoot has been separated from the Start() method into a chained function. The old syntax Server.Start 8080, "C:\WebRoot" is no longer supported. Please use Server.WebRoot("C:\WebRoot").Start 8080 instead.
Quick Configuration
Private Sub Form_Load()
Set Server = New cHttpServer
' Configure static file root directory
Server.WebRoot("C:\WebRoot").Start 8080
Debug.Print "Static file service: http://localhost:8080/"
End SubDirectory Structure Example
C:\WebRoot\
├── index.html # Home page
├── favicon.ico # Site icon
├── css\
│ ├── style.css
│ └── theme.css
├── js\
│ ├── app.js
│ └── utils.js
├── images\
│ ├── logo.png
│ └── banner.jpg
└── upload\
└── avatar.pngRequest Mapping
| Request URL | Maps to File |
|---|---|
/ | C:\WebRoot\index.html |
/css/style.css | C:\WebRoot\css\style.css |
/js/app.js | C:\WebRoot\js\app.js |
/images/logo.png | C:\WebRoot\images\logo.png |
Supported MIME Types
' System automatically identifies Content-Type for the following file types
' Text types
text/html -> .html, .htm
text/css -> .css
text/javascript -> .js
text/plain -> .txt
' Image types
image/png -> .png
image/jpeg -> .jpg, .jpeg
gif/image -> .gif
image/svg+xml -> .svg
image/x-icon -> .ico
' Application types
application/json -> .json
application/xml -> .xml
application/pdf -> .pdf
' Font types
font/woff2 -> .woff2
font/woff -> .woffDefault Document Mechanism
When the request path corresponds to a directory (e.g., accessing root path /), HttpServer automatically searches for default documents in priority order:
| Priority | Default Document |
|---|---|
| 1 | index.html |
| 2 | index.htm |
| 3 | default.html |
| 4 | default.htm |
Processing Flow
Request "/" or "/subdir/"
│
▼
Check if WebRoot + path is a directory
│
├─> Is directory → Iterate default document list
│ ├─> index.html exists? → Return file ✅
│ ├─> index.htm exists? → Return file ✅
│ ├─> default.html exists? → Return file ✅
│ ├─> default.htm exists? → Return file ✅
│ └─> None exist → Return 403 "Directory listing not allowed" 🔒
│
└─> Not directory → Return 404Adding Custom Default Documents
Private Sub Form_Load()
Set Server = New cHttpServer
' Add custom default document (e.g., home.html)
Server.AddDefaultDocument "home.html"
' Duplicate additions are automatically ignored
Server.WebRoot("C:\WebRoot").Start 8080
End SubAfter adding, the default document search order becomes: index.html → index.htm → default.html → default.htm → home.html
Directory Redirect
When the request path corresponds to a physical directory but doesn't end with /, HttpServer automatically 302 redirects to the path with /:
Request /subdir (corresponds to physical directory)
→ 302 redirect to /subdir/
→ Then search for default documents like /subdir/index.htmlNote: Root path
/doesn't need redirect, directly enters default document search.
Priority Relationship with Routes
If a manual route is registered for root path /, the route executes first and won't enter default document search:
' Route priority: root path handled by controller
Call Server.Router.Add("/", "Home@Index")
' Now accessing "/" → Calls Home.Index, doesn't search for index.htmlPriority Explanation
Static files take priority over route matching:
Request /index.html
│
├──> Check if C:\WebRoot\index.html exists
│ ├─> Exists -> Return static file
│ └─> Does not exist -> Go to route matching
│
└──> Match route /index.htmlMixed Mode (Static Files + API)
Private Sub Form_Load()
Set Server = New cHttpServer
' ========== Configure Routes ==========
' API controllers
Call Server.Router.Reg("Api", New cApiController)
Call Server.Router.Add("/api/users", "Api@Users", OnlyGet)
Call Server.Router.Add("/api/data", "Api@Data", OnlyGet)
' Business controllers
Call Server.Router.Reg("User", New cUserController)
Call Server.Router.Add("/user/login", "User@Login", OnlyPost)
' ========== Start Service ==========
' WebRoot for static files, API requests go to routing
Server.WebRoot("C:\WebRoot").Start 8080
Debug.Print "Service started successfully"
Debug.Print " Frontend: http://localhost:8080/"
Debug.Print " API: http://localhost:8080/api/users"
End SubSingle Page Application (SPA) Support
For React/Vue/Angular SPAs, configure all routes to return index.html:
' cSpaController.cls
Public Sub Index(ctx As cHttpServerContext)
' Return index.html for frontend routing
ctx.Response.File "/index.html"
End Sub
' Register routes
Private Sub Form_Load()
Set Server = New cHttpServer
' API routes
Call Server.Router.Reg("Api", New cApiController)
Call Server.Router.Add("/api/*", "Api@Handle")
' SPA routes: All non-API requests return index.html
Call Server.Router.Reg("Spa", New cSpaController)
Call Server.Router.Add("/*", "Spa@Index")
Server.WebRoot("C:\WebRoot").Start 8080
End SubFile Upload Directory
' Upload files to static directory
Public Sub Upload(ctx As cHttpServerContext)
' Save uploaded file
Dim savePath As String
savePath = ctx.Server.WebRoot & "\upload\" & filename
Call SaveUploadFile(ctx.Request.RawBodyBin, savePath)
' Return accessible URL
Dim result As New Dictionary
result("url") = "/upload/" & filename
ctx.Response.Json result
End SubCache Control
Built-in Lazy Cache + ETag + 304
HttpServer has a three-layer cache mechanism that automatically senses file updates, no manual intervention needed:
Request /css/style.css
│
▼
1. Calculate ETag (file modification time + size, only FSO metadata, no content read)
│
├─> Browser sends If-None-Match and matches → 304 Not Modified ✅
│ (Zero transfer, browser uses local cache)
│
├─> Cache hit and ETag unchanged → Memory output (skip disk I/O) ✅
│
├─> Cache hit but ETag changed → Re-read + update cache 🔄
│
└─> No cache → Disk read + lazy cache (only add if size ≤ limit) 📥Lazy Cache Rules:
- No files are cached by default
- On first access, if file size ≤
MaxCacheFileSize, it's automatically added to memory cache - Files exceeding size limit (e.g., videos, large images) are always read from disk, but still enjoy ETag/304 negotiated cache
- When files are updated on disk, ETag auto-changes and old cache auto-invalidates and re-reads
Cache Configuration
Private Sub Form_Load()
Set Server = New cHttpServer
' Cache configuration (can be modified before Start or at runtime)
Server.MaxCacheFileSize = 1048576 ' Single file cache limit: 1MB (default)
Server.CacheTTLSeconds = 300 ' Cache TTL: 5 minutes (default)
' Set to 0 to disable content cache (keep ETag/304 negotiated cache only)
Server.MaxCacheFileSize = 0
Server.WebRoot("C:\WebRoot").Start 8080
End Sub| Configuration | Default | Description |
|---|---|---|
MaxCacheFileSize | 1048576 (1MB) | Single file cache limit in bytes, 0=disable content cache |
CacheTTLSeconds | 300 (5min) | Cache TTL in seconds, check ETag after expiry to decide refresh, 0=never expire |
Manual Cache Refresh
After deploying new files, you can manually refresh directory structure cache and content cache:
' Refresh directory structure cache (call after adding/deleting files)
' Does not affect content cache, content cache senses file updates via ETag
Server.RefreshCache
' Clear all file content cache (force re-read from disk)
Server.ClearFileCacheBest Practice: In most scenarios, only
RefreshCacheis needed, content cache senses file updates via ETag automatically. UseClearFileCacheonly when you need to force release memory.
Custom Middleware Cache Headers
' cCacheMiddleware.cls
Public Sub Entry(ctx As cHttpServerContext)
' Add browser cache headers for static files
If IsStaticFile(ctx.Request.PathInfo) Then
' Cache for 1 hour
ctx.Response.Header("Cache-Control") = "public, max-age=3600"
End If
End Sub
Private Function IsStaticFile(path As String) As Boolean
Dim ext As String
ext = LCase(Mid(path, InStrRev(path, ".")))
IsStaticFile = (ext = ".css" Or ext = ".js" Or ext = ".png" Or _
ext = ".jpg" Or ext = ".gif" Or ext = ".ico")
End FunctionLast Updated: 2026-06-22