Xamboo is the orchestration layer for APIs, microservices, and CMS modules into one cohesive platform for modern distributed architectures.
It orchestrates and manages:
- APIs
- Microservices
- Web applications
- Web pages
- Full CMS systems
- Administration backends
All as modular, dynamically compiled components working together under a unified engine.
Built in Go (1.24+), Xamboo is designed for:
- Large-scale content distribution
- High-performance REST and Graph APIs
- Modular service architectures
- Multi-host and multi-site environments
Xamboo is:
- Plugin-based
- Engine-driven
- Modular-first
- CMS-capable
- Multi-host ready
- Highly scalable
- MIT licensed
| # Architecture |
| Incoming Request ↓ Listener (IP:Port) ↓ Host (Domain resolution) ↓ Components (Middleware chain) ↓ CMS Router ↓ Engine ↓ Page / Plugin ↓ Response |
- Multi-site & virtual host support
- Automatic plugin compilation (
go build --buildmode=plugin) - Runtime page compilation
- Hot configuration reload
- Middleware-based architecture
- Directory-based page resolution
- Version & language support
- Meta-language injection
- Template engine
- Language engine
- Library engine (Go plugins)
- 500+ requests/sec production observed
- 3000+ requests/sec lab tested
- TLS 1.2 / TLS 1.3
- Gzip / Deflate compression
- Code minification
- Basic authentication
- SQL injection heuristic protection
- IP blacklist support
- CORS origin control
This chapter provides a complete, production-oriented guide to installing and running a fully functional Xamboo server environment.
Xamboo is built around a modular runtime architecture that dynamically compiles:
- Engines
- Components
- Applications
- CMS pages
- XModules
Because Xamboo uses Go plugins (--buildmode=plugin), installation must
be performed on a Unix/Linux environment.
- Linux (recommended)
- Unix-based systems
- ❌ Windows is NOT supported (Go plugin limitation)
- Go 1.24+ (Go 1.26+ recommended)
- Git
- GNU Make (optional but recommended)
- OpenSSL (if using HTTPS)
Verify Go installation:
go versionA typical Xamboo installation may include:
/home/sites/server
│
├── xamboo-env (core runtime)
├── master (optional administration UI)
├── admin (optional XModules administration)
└── your-sites (your CMS projects)
Only xamboo-env is mandatory.
master and admin are optional but highly recommended for development
and administration.
mkdir -p /home/sites/server
cd /home/sites/server
git initgit pull https://github.com/webability-go/xamboo-env.gitmkdir master
cd master
git init
git pull https://github.com/webability-go/xamboo-master.git
cd ..mkdir admin
cd admin
git init
git pull https://github.com/webability-go/xamboo-admin.git
cd ..Edit:
mainconfig.jsonlisteners.jsonhosts.json
Example listener:
{
"name": "server-http",
"ip": "0.0.0.0",
"port": "80",
"protocol": "http",
"readtimeout": 120,
"writetimeout": 120,
"headersize": 65536
}Example host:
{
"name": "mysite",
"listeners": ["server-http"],
"hostnames": ["example.com", "www.example.com"]
}Ensure:
- DNS points to the server IP
- Firewall allows the configured ports
In mainconfig.json:
"include": [
"master/config/hosts.json",
"admin/config/hosts.json"
]go get -u⚠ Do NOT run:
go mod tidyIf accidentally executed, restore modules:
go get master
go get admin
go get github.com/webability-go/wajaf
go get github.com/webability-go/xdominion
go get github.com/webability-go/xdommask
go get github.com/webability-go/xmodulesand follow the sys logs entries to know if you need to restore more modules for recompilation of the .so libraries
./start.shOr:
go run xamboo.go --config=mainconfig.jsongo build xamboo.goXamboo dynamically compiles plugins at runtime when needed.
Example systemd service:
[Unit]
Description=Xamboo Server
After=network.target
[Service]
WorkingDirectory=/home/sites/server
ExecStart=/home/sites/server/xamboo --config=mainconfig.json
Restart=always
[Install]
WantedBy=multi-user.target
There is a systemd directory into the main xamboo project, with example systemd file.
- Install Go
- Clone xamboo-env
- (Optional) Install master/admin
- Configure JSON files
- Run
go get -u - Start server
Xamboo handles dynamic compilation automatically.
Xamboo is entirely driven by a JSON-based configuration system.
When starting the server, you must provide a configuration file:
xamboo --config=./mainconfig.jsonYou may use absolute paths, but relative paths are strongly recommended for portability.
All paths are resolved relative to the directory where Xamboo is launched.
Xamboo’s configuration system is:
- Modular
- Composable
- Merge-based
- Multi-file capable
- Environment-friendly
You can split configuration across multiple files and include them dynamically.
{
"pluginprefix": "prefix-",
"log": {},
"include": [],
"listeners": [],
"hosts": [],
"components": [],
"engines": []
}Each section is optional within an individual file — but must exist at least once across the full merged configuration.
Main configuration:
{
"log": {},
"include": [
"site1/config.json",
"site2/config.json"
],
"components": [],
"engines": []
}Site configuration example:
{
"comments": "site1/config.json",
"listeners": [ <LISTENER1>, <LISTENER2> ],
"hosts": [ <HOST1> ]
}{
"comments": "site2/config.json",
"listeners": [ <LISTENER3> ],
"hosts": [ <HOST2> ]
}Xamboo concatenates sections of the same type when merging. the result configuration would be (as interpreted by Xamboo):
{
"log": {},
"listeners": [ <LISTENER1>, <LISTENER2>, <LISTENER3> ],
"hosts": [ <HOST1>, <HOST2> ],
"components": [],
"engines": []
}"pluginprefix": "instanceA-"The plugin prefix is appended to every compiled .so plugin.
This prevents naming collisions when running multiple Xamboo instances on the same filesystem.
{
"log": {
"enabled": true,
"sys": "file:./logs/xamboo-sys.log",
"pages": "file:./logs/pages.log",
"pagesformat": "%requestid% %clientip% %method% %protocol% %code% %request% %duration%",
"errors": "file:./logs/errors.log",
"stats": "discard"
}
}The log section can be defined at three levels within the configuration:
- At the root level of the main configuration file (global log settings)
- Inside each listener
- Inside each host
Each level supports different log parameters.
At the root level:
- Only
"sys"and"errors"logs are used. - The
"enabled"parameter is ignored.
At the listener level:
- Only the
"sys"log is used. - The
"enabled"parameter is ignored.
Listener logs typically capture low-level server events such as connection handling and protocol activity.
At the host level, the following parameters are supported:
"enabled"(true/false)"sys""pages""pagesformat""errors""stats"
Any other entries are ignored.
Host-level logging is the most complete and configurable logging layer.
The "sys" log records normal operational events related to the
component.
This includes:
- HTTP server messages
- TLS handshakes
- Startup and shutdown events
- Internal system notifications
The "errors" log captures all runtime errors that occur within the
system, including:
- Application errors
- Component errors
- Panics (including recovered panics)
- Unexpected runtime failures
Note: A 404 response is not considered a system error and is
logged in the "pages" log instead.
The "pages" log records every request handled by the host, including:
- Page hits
- Static file requests
- Response codes
- Performance metrics
This log is typically used for access logging and traffic analysis.
The "stats" entry allows you to trigger a function call for every
processed request.
Instead of writing to a file, it can invoke a custom function from a loaded plugin, passing the full request context.
This allows you to:
- Send metrics to external systems
- Integrate with monitoring platforms
- Store statistics in databases
- Implement custom analytics pipelines
The function receives the full request context and can log or process data as needed.
- file:path
- stdout:
- stderr:
- discard
- call::
Custom stat callback example:
import "github.com/webability-go/xamboo/components/host"
func Log(hw *host.HostWriter) {
// custom log logic
}The listeners section defines the network entry points of your Xamboo server.
A listener is responsible for:
- Binding to a specific IP address
- Listening on a specific port
- Handling a specific protocol (HTTP or HTTPS)
- Managing connection timeouts
- Handling low-level logging
Each listener runs as a dedicated server instance that accepts incoming connections and forwards valid requests to the host resolution system.
"listeners": [
{
"name": "NAME",
"ip": "IP",
"port": "PORT",
"protocol": "PROTOCOL",
"readtimeout": TIMEOUT,
"writetimeout": TIMEOUT,
"headersize": SIZE,
"log": {
"sys": "SYSLOG"
}
}
]You may define multiple listeners in the array.
A unique identifier for the listener.
- Must be a string
- Used internally and referenced by hosts
- Example:
"server-http"
The IP address to bind.
- If set to an empty string
"", Xamboo listens on all available network interfaces - Can be a specific local or public IP
- Example:
"0.0.0.0"→ all interfaces"127.0.0.1"→ localhost only"10.10.10.10"→ specific interface
The TCP port to listen on.
- Must be a string
- Common values:
"80"→ HTTP"443"→ HTTPS"8080"→ alternative HTTP
Ensure the port is open in your firewall configuration.
The communication protocol.
Currently supported:
"http""https"
When using "https", TLS certificates must be configured at the Host
level.
Maximum duration (in seconds) allowed to read the full request.
- Integer between
0and65535 - Recommended value:
120seconds
Prevents slow client attacks and hanging connections.
Maximum duration (in seconds) allowed to write the response.
- Integer between
0and65535 - Recommended value:
120seconds
Prevents stalled responses from blocking the server.
Maximum allowed size (in bytes) for HTTP headers.
- Integer between
4096and65535 - Recommended value:
65536
Protects against oversized header attacks.
Defines where system-level listener logs are written.
The sys log captures:
- TLS handshake events
- Connection open/close
- Server startup/shutdown
- Low-level protocol events
Example:
"log": {
"sys": "file:./logs/listener-http-sys.log"
}Log targets may be:
file:pathstdout:stderr:discard
Below is a production-ready example with both HTTP and HTTPS listeners:
"listeners": [
{
"name": "server-http",
"ip": "10.10.10.10",
"port": "80",
"protocol": "http",
"readtimeout": 120,
"writetimeout": 120,
"headersize": 65536,
"log": {
"sys": "file:./logs/listener-http-sys.log"
}
},
{
"name": "server-https",
"ip": "10.10.10.10",
"port": "443",
"protocol": "https",
"readtimeout": 120,
"writetimeout": 120,
"headersize": 65536,
"log": {
"sys": "file:./logs/listener-https-sys.log"
}
}
]Listeners do not serve content directly.
Instead:
- The listener accepts the incoming request.
- The request is matched against configured hosts.
- The host determines:
- Domain validity
- Enabled components
- CMS routing
- Engine execution
A single listener can serve multiple hosts.
A single host can respond on multiple listeners.
This design allows:
- HTTP → HTTPS redirection
- Multi-domain hosting
- Multi-port environments
- Reverse proxy setups
- Always define both HTTP and HTTPS listeners.
- Use HTTPS in production.
- Keep timeouts at reasonable values (120s recommended).
- Set
headersizehigh enough for modern headers but not excessive. - Log listener
sysoutput separately for easier debugging. - Avoid binding to public IPs unnecessarily; prefer
0.0.0.0behind a reverse proxy.
A Host represents a virtual site that responds to requests received through one or more listeners.
In practical terms, a Host is the equivalent of a website bound to one or more domain names. It defines how requests are processed once they are accepted by a listener.
Any host can:
- Listen on one or multiple configured listeners
- Respond to one or multiple domain names
- Activate built-in or custom components
- Load external plugins
- Execute CMS pages and engines
Hosts are the application-level orchestration layer of Xamboo.
"hosts": [
{
"name": "developers",
"listeners": ["http", "https"],
"hostnames": [
"developers.webability.info",
"www.webability.info",
"webability.info",
"webability.org"
],
"cert": "./example/ssl/cert.pem",
"key": "./example/ssl/privkey.pem",
"plugins": [
{
"Name": "app",
"Library": "./example/app/app.so"
}
],
"COMPONENT-NAME": { COMPONENT-CONFIG }
}
]Multiple hosts may be defined in the array.
A free string used as the internal identifier of the host.
- Does not need to match a domain
- Used for logging and internal reference
- Must be unique within the configuration
Defines which previously declared listeners this host responds to.
"listeners": ["server-http", "server-https"]Rules:
- Listener names must exist in the
listenerssection - A host may respond to multiple listeners
- Multiple hosts may share the same listener
Defines the domain names that this host will respond to.
"hostnames": [
"example.com",
"www.example.com"
]Important notes:
- The incoming HTTP
Hostheader must match one of these entries - DNS must point these domains to your server
- If a request matches the listener but not a hostname, the host will not handle it
These parameters are required only if the host responds on an HTTPS listener.
"cert": "./ssl/cert.pem",
"key": "./ssl/privkey.pem"Requirements:
- The certificate must include all declared hostnames
- Paths should be relative to the server root
- Only used for HTTPS listeners
Hosts may load external compiled Go plugins (.so files).
"plugins": [
{
"Name": "app",
"Library": "./example/app/app.so"
}
]Plugins can provide:
- Applications
- Custom engines
- Custom components
- Business logic
- XModules integration
Plugins must be compiled using:
go build --buildmode=pluginA Host may activate and configure components.
Components act as middleware layers that process requests before and after CMS execution.
Example:
"log": {
"enabled": true,
"pages": "file:./logs/developers.log"
}Each component can be enabled or disabled per host.
The available built-in components are:
- log — Logs system activity and request statistics
- stat — Collects runtime and request statistics
- redirect — Normalizes domain, scheme, and port
- auth — Provides HTTP Basic Authentication
- prot — Protects against SQL injection
- compress — Compresses responses (gzip/deflate)
- minify — Minifies HTML, CSS, JS, JSON, XML, SVG
- origin — Handles CORS headers
- fileserver — Serves static files
- cms — Executes the Xamboo CMS system and engines
- browser — CMS sub-module for device detection
- error — Handles and serves 404 errors
The components are detailed lower in this document.
Once a request matches a host:
- TLS is negotiated (if HTTPS)
- Middleware components execute in configured order
- CMS resolves the requested page (if it is a page)
- The corresponding engine runs
- The response flows back through the middleware chain
- The final output is sent to the client
Components are middleware modules executed in a chain for every request handled by a Host. They are responsible for cross‑cutting concerns such as logging, statistics, canonical redirection, authentication, security checks, compression/minification, CORS, static files, CMS routing, and final error handling.
A component may:
- Inspect or modify the request
- Block or redirect execution
- Transform the response
- Log activity
- Enforce security policies
- Serve static content
- Execute the CMS
A component can be:
- Built-in: shipped with Xamboo and referenced with
"source": "built-in" - External: compiled as a Go plugin (
.so) and referenced with"source": "extern"
Xamboo executes components in the exact order defined in the global components array.
Components are declared globally in the root configuration:
{
"components": [
{ "name": "log", "source": "built-in" },
{ "name": "stat", "source": "built-in" },
{ "name": "redirect", "source": "built-in" },
{ "name": "auth", "source": "built-in" },
{ "name": "prot", "source": "built-in" },
{ "name": "compress", "source": "built-in" },
{ "name": "minify", "source": "built-in" },
{ "name": "origin", "source": "built-in" },
{ "name": "fileserver","source": "built-in" },
{ "name": "cms", "source": "built-in" },
{ "name": "error", "source": "built-in" },
{ "name": "myhandler", "source": "extern", "library": "./example/components/myhandler/myhandler.so" }
]
}Built-in component
{ "name": "compress", "source": "built-in" }External component
{ "name": "mycomponent", "source": "extern", "library": "./path/to/your/mycomponent.so" }External components must be compiled as Go plugins:
go build --buildmode=pluginYou can override a built-in component by providing an external component with the same operational purpose. Some common reasons may be:
- Implement authentication backed by a database or identity provider (replace
auth) - Add a stricter WAF/security gate (insert before
redirector beforecms) - Push logs to centralized systems (replace/extend
logand/orstatscallback) - Better heuristic for system protection adding rate limits (replace
prot)
The built-in components are configured per host (inside the hosts entries), even though they are loaded globally in the components array.
Each component typically supports:
enabled:true/false(when applicable)- A component-specific configuration block
Below are the official built-in components in recommended execution order.
The log component is the primary request and system logging facility at the host level. It can write to files/streams and can optionally call a custom function after each request to export metrics.
It provides up to four log channels:
sys: internal messages for the host (startup/shutdown, internal notices)errors: runtime errors, panics, recovered panics, and failurespages: access log of requests (pages + static files)stats: optional output or callback executed for each request (advanced/custom metrics)
"hosts": [
{
"...": "...",
"log": {
"enabled": true,
"pages": "file:./example/logs/developers.log",
"pagesformat": "%requestid% %clientip% %method% %protocol% %code% %request% %duration% %bytesout% %bytestocompress% %bytestominify%",
"errors": "file:./example/logs/developers-error.log",
"sys": "file:./example/logs/developers-sys.log",
"stats": "discard"
}
}
]-
enabled(bool)
Enables/disables the host logging system. -
pages(string)
Destination for request access logs. -
pagesformat(string)
Format template for each request log line. Supported tokens include:%bytesout%— bytes sent to the client (headers excluded)%bytestocompress%— size before compression%bytestominify%— size before minification%clientip%,%clientport%%code%— HTTP status code%duration%— processing time from request receipt to response ready%hostid%%listenerid%,%listenerip%,%listenerport%%protocol%— HTTP/HTTPS/WS/WSS%method%— GET/POST/PUT/HEAD/OPTIONS/DELETE/...%request%— full request line%starttime%,%endtime%%requestid%— unique internal request identifier
If a token has an empty value, it is replaced with
-. -
errors(string)
Destination for errors. Note: a404is not a system error and is typically logged inpages. -
sys(string)
Destination for host system messages. -
stats(string)
Statistics destination. Can be:file:<path>/stdout:/stderr:/discardcall:<plugin>:<function>to invoke a function in a loaded plugin on each request.
pagesis written for every completed request (page hit and static hit).errorsreceives internal failures, panics (including recovered panics), and unexpected runtime issues.statscan be used to send metrics anywhere (DB, external monitoring, message queue) via a callback.
import "github.com/webability-go/xamboo/components/host"
func Log(hw *host.HostWriter) {
// hw contains RequestStat and Context
// send metrics to your system
}- Use separate files per host (
./logs/<host>-pages.log,./logs/<host>-errors.log). - Keep
pagesformatconsistent across environments for easier parsing. - Use
statscallback for Prometheus/Influx/ELK forwarding rather than mixing logic into request handlers.
The stat component builds and stores request statistics used by the log component and by runtime monitoring tools (including the Master environment).
It collects and maintains:
- Global counters (lifetime requests served)
- Per-minute request rates
- Request breakdown by type/method
- Host and listener runtime measurements
This component is mandatory and cannot be disabled.
- Runs early in the chain to collect accurate timings and counters.
- Provides the data used by
%duration%,%bytesout%, and other access log tokens.
- Keep
statimmediately afterlog(default order). - Do not attempt to disable it; rely on
log.enabled=falseif you want to reduce logging overhead, or do not need logging.
The redirect component ensures URL normalization by enforcing a single canonical scheme, domain, and port for a host.
In environments where a host may respond to:
- Multiple domain names (e.g.,
example.com,www.example.com) - Multiple subdomains
- Different protocols (
httpandhttps) - Different ports
the redirect component guarantees that all traffic is consolidated to one official URL.
This is essential for:
- SEO consistency (avoiding duplicate content)
- Security enforcement (forcing HTTPS)
- Domain normalization
- Port standardization
- Clean URL architecture
When enabled, the component compares the incoming request’s scheme, host, and port with the configured canonical values. If they do not match, the client is automatically redirected to the correct URL using an HTTP 301 (Moved Permanently) status code.
"redirect": {
"enabled": true,
"scheme": "https",
"host": "developers.webability.info:83"
}enabled(bool) — Enables/disables canonical redirection.scheme(string) — Canonical scheme (httporhttps).host(string) — Canonical hostname, optionally with port (domainordomain:port).
If a request arrives as:
http://webability.info/
and the configuration specifies:
scheme: https
host: developers.webability.info:83
the component returns a 301 redirect to:
https://developers.webability.info:83/
The redirect happens before CMS execution or any page rendering.
- Enforce HTTPS in production.
- Choose one canonical domain (with or without
www) and redirect all variants. - Keep
redirectearly in the chain to avoid wasted processing on non-canonical requests.
The auth component enforces HTTP Basic Authentication at the host level. It is commonly used to protect:
- Administration interfaces (master/admin)
- Staging environments
- Private APIs or internal tools
"auth": {
"enabled": true,
"realm": "Xamboo Env test (xamboo/xamboo)",
"user": "xamboo",
"pass": "xamboo",
"users": [
{ "enabled": true,
"user": "name",
"pass": "password"
},
{ "enabled": false,
"user": "name2",
"pass": "password2"
}
]
}enabled(bool) — Enables/disables Basic Auth protection.realm(string) — Realm text displayed by the browser login prompt.user(string) — Expected username (master user).pass(string) — Expected password (master user).users(array of user) — Expected list of users, can be disabled or enabled.
Each user is:
enabled(bool) — Enables/disables this user.user(string) — Expected username.pass(string) — Expected password.
- If credentials are missing or incorrect, the component returns 401 Unauthorized and triggers the browser login dialog.
- If correct, the request continues to the next component, creating the headers needed to the authentication of user.
- Use only for simple protection or internal environments.
- For production-grade authentication (DB users, OAuth/JWT, SSO), create a custom auth component and replace this one.
- Never store production credentials in a public repository; use environment-specific config files.
The prot component implements a basic heuristic protection against SQL injection attempts. It scans incoming variables (GET/POST/PUT) and scores suspicious SQL keyword patterns. If the score exceeds a configured threshold, the request is blocked.
This is a baseline protection layer. It can be replaced with a more advanced/custom security component depending on your needs, for instance adding rate limits, IP protections, etc.
"prot": {
"enabled": true,
"sql": true,
"ignore": ["var1", "var2"],
"threshold": 3
}enabled(bool) — Enables/disables the protection layer.sql(bool) — Enables SQL injection heuristics scanning.ignore(array of strings) — Variable names to exclude from scanning.threshold(int) — Score needed to trigger protection.
Guidance for threshold:
1— very sensitive (high false-positive risk)3— balanced (recommended default)5— less sensitive (harder to trigger)
- If an injection attempt is detected, the component returns 500 and logs details to the host error log.
- If not triggered, the request proceeds normally.
- Keep it enabled on public forms and API endpoints unless you have a stronger WAF.
- Tune
ignorefor fields that may legitimately contain SQL-like strings (advanced search boxes, query languages, graphQL). - Consider replacing with a stronger component for enterprise security requirements.
The compress component compresses responses (gzip or deflate) when the client supports it and the response matches configured MIME types and file patterns.
Compression reduces bandwidth usage and improves load times for text-based resources.
"compress": {
"enabled": true,
"mimes": [
"text/html",
"text/css",
"application/javascript"
],
"files": [
"*.ico",
"*.css",
"*.js",
"*.html"
]
}enabled(bool) — Enables/disables compression.mimes(array of strings) — Allowed MIME types to compress.files(array of strings) — Filename patterns (wildcards*and?) allowed for compression (primarily for fileserver outputs).
Compression happens only if all conditions are met:
- Client requests compression (
Accept-Encoding: gzip, deflate). - The response MIME matches one of
mimes. - If serving a static file, filename matches
filespatterns.
- Enable for HTML/CSS/JS/JSON/XML/SVG.
- Avoid compressing already-compressed formats (jpg, png, mp4, zip).
- Use with
minifyfor best results (minify first, then compress).
The minify component reduces the size of text-based responses by removing unnecessary whitespace and optimizing formatting. It is most effective for HTML, CSS, JavaScript, JSON, SVG, and XML.
"minify": {
"enabled": true,
"html": true,
"css": true,
"js": true,
"json": true,
"svg": true,
"xml": true
}enabled(bool) — Enables/disables minification.html,css,js,json,svg,xml(bool) — Enable/disable minification per output type.
- Minification is applied based on detected MIME type of the response.
- If a type is disabled (e.g.,
"js": false), responses of that type are passed through unchanged.
- Keep enabled for production.
- Disable in development if you need human-readable output for debugging.
- Use together with
compressfor maximal reduction (minify → compress).
The origin component manages CORS behavior for cross-origin API calls. It detects OPTIONS and HEAD preflight requests and responds with the appropriate headers based on configured rules.
Use this component when your host serves:
- REST APIs
- Cross-domain browser calls
- Microservices consumed by web frontends on a different domain
"origin": {
"enabled": true,
"maindomains": ["webability.info"],
"default": "https://developers.webability.info",
"methods": ["GET", "POST", "OPTIONS", "HEAD"],
"headers": [
"Accept", "Content-Type", "Content-Length", "Accept-Encoding",
"X-CSRF-Token", "Authorization", "Origin", "X-Requested-With", "Method"
],
"credentials": true
}enabled(bool) — Enables/disables CORS handling.maindomains(array of strings) — Allowed main domains (used as a rule base).default(string) — Default allowed origin if none matches.methods(array of strings) — Allowed HTTP methods for CORS.headers(array of strings) — Allowed request headers.credentials(bool) — Whether to allow credentials (Access-Control-Allow-Credentials).
- For preflight requests, responds with the computed CORS headers.
- For normal requests, adds CORS headers when appropriate.
- Does not replace application authentication; it only defines browser cross-origin permissions.
- Keep the allowed origins strict—avoid
*in production for sensitive APIs. - Explicitly include only the methods and headers you use.
- If you use cookies/Authorization headers, set
credentials: trueand ensure origins are explicit.
The fileserver component serves static files from a configured directory.
It can run in two modes:
- takeover = true: the host becomes a pure static server.
- takeover = false: static files are served when they exist, otherwise the request falls through to CMS (ideal for websites with assets + pages).
"fileserver": {
"enabled": true,
"takeover": false,
"static": "./example/repository/public/static"
}enabled(bool) — Enables/disables static file serving.takeover(bool) — Full takeover mode.static(string) — Directory path containing static files.
-
If
takeoveris true:- Files are served if present.
- If the file is missing, fileserver triggers 404.
- No CMS fallback occurs.
-
If
takeoveris false:- Files are served if present.
- If missing, request continues to the next component (commonly
cms).
- Use
takeover=falsefor standard CMS sites (assets + pages). - Keep static directories separate per host for clean deployments.
- Put fileserver before CMS (default order) so assets do not consume CMS routing time.
The cms component activates the Xamboo Content Management System. When enabled, the CMS resolves URLs to API, microservices, websockets, pages, loads templates and languages, and executes the configured engines.
This component is the core of Xamboo’s page routing and rendering system.
"cms": {
"enabled": true,
"config": [
"./example/application/config/example.conf"
],
"engines": {
"simple": true,
"library": true,
"template": true,
"language": true,
"wajafapp": true,
"box": true
},
"browser": {
"enabled": true,
"useragent": {
"enabled": true,
"comments": "context.Version becomes: computer, phone, tablet, tv, console, wearable, base"
}
}
}enabled(bool) — Enables/disables the CMS handler.config(array of strings) — List of XConfig files (key=valuepairs) used by CMS and by your code.engines(object) — Enables/disables engines available for pages calculation on this host.browser(object) — Browser/device detection sub-module.
Set unused engines to false to reduce feature surface and prevent unintended execution types.
browser.enabled(bool) — Enables device detection.browser.useragent.enabled(bool) — Enables User-Agent based classification.
Known versions (device classes) include:
pc(or computer),mobile,tablet,tv,console,wearable,base
The
baseversion is used when device type is unknown.
Why an extra file? Because CMS and application code often need a shared parameter store. The XConfig file is a simple key=value configuration used by engines and your business logic.
Example:
# Where the pages are located (relative preferred)
pagesdir=./example/application/pages/
# Home page and error pages
mainpage=home
errorpage=errors/page
errorblock=errors/block
# Default page version and language
version=base
language=en
# Whether pages accept URL path parameters by default
acceptpathparameters=yes- When CMS is enabled and matches a request to a page, it generates the response using templates, language resources, and engines.
- When CMS takes over, it typically becomes the final handler (no further components after CMS will run for successful page routing), except response modifiers already wrapping it in the chain (such as compress/minify).
- Keep
version=baseunless you intentionally manage multiple page versions. - Be cautious with
acceptpathparameters:- Avoid enabling it on the home page to prevent accidental routing collisions (icons/files mapping to
/).
- Avoid enabling it on the home page to prevent accidental routing collisions (icons/files mapping to
- Disable unused engines for a tighter security and maintenance profile.
- Keep your
pagesdirrelative for portability across environments.
The error component is the final fallback handler that returns a 404 Not Found when no previous component has produced a response.
It is intentionally minimal. If you want branded error pages, JSON API errors, or structured error responses, you can replace it with a custom component.
"error": {
"enabled": true
}enabled(bool) — Enables/disables the default 404 handler.
- If enabled and reached, it returns a 404 response.
- Often placed at the end of the chain to ensure every request has a deterministic outcome.
- Keep it enabled unless CMS or another handler fully guarantees 404 responses.
- Replace it for APIs to return JSON error envelopes rather than plain text.
To build your own component, you must export a public variable named Component that implements the xamboo/components/assets.Component interface:
type Component interface {
Start()
NeedHandler() bool
Handler(handler http.HandlerFunc) http.HandlerFunc
}package mycomponent
import (
"net/http"
)
var Component = &MyComponent{}
type MyComponent struct{}
func (mc *MyComponent) Start() {
// called once when the component is loaded for the first time
}
func (mc *MyComponent) NeedHandler() bool {
return true
}
func (mc *MyComponent) Handler(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "404 Not Found", http.StatusNotFound)
}
}Start()is called once when the component is first loaded (similar to initialization).NeedHandler()must returntruefor the component to participate.Handler()wraps the next handler in the chain.
If your component supports enabled=false at the host level, your Handler() should pass through by calling next(w, r) when disabled.
A typical safe order is:
log/stat(observe everything)redirect(canonicalize early)auth/prot(security gates early)compress/minify(response optimizers wrap downstream handlers - performance optimization)origin(CORS for APIs interoperability)fileserver(serve static assets fast)cms(full CMS routing and execution)error(fallback)
This order ensures requests are normalized and protected before expensive routing, and responses are optimized consistently. External components allow you to extend or replace any behavior while preserving the same middleware execution model.
The Engines system is the execution core of the Xamboo CMS.
While Components manage HTTP-level behavior (middleware, security, logging, compression, etc.), Engines define how a page is interpreted and executed.
The CMS manual comes in next chapter.
An engine determines:
- How a page file is processed
- Whether it requires compilation
- Whether it needs templates or language resources
- Whether it runs as pure content or as compiled Go code
- How it interacts with context and identity
Engines are the page-type execution layer of Xamboo.
Engines are declared globally in the root configuration:
{
"engines":
[
{ "name": "redirect", "source": "built-in" },
{ "name": "simple", "source": "built-in" },
{ "name": "library", "source": "built-in" },
{ "name": "template", "source": "built-in" },
{ "name": "language", "source": "built-in" },
{ "name": "wajafapp", "source": "built-in" }
]
}There are 6 built-in engines, but you can register as many custom engines as needed.
Each engine represents a page type handler.
The host can define a new set of engines or restrict some engines with its own list of engines, if the array of engines are specified into the configuration of the host. If there is no definition of engines, the global engines will be used.
{ "name": "simple", "source": "built-in" }{ "name": "myengine", "source": "extern", "library": "./path/to/your/myengine.so" }External engines must be compiled as Go plugins:
go build --buildmode=pluginYou may develop a new engine when:
- You need a new page type
- You want a custom template language
- You want a new compilation logic
- You need to replace an existing built-in engine
- You want to integrate another rendering framework
The redirect engine allows a page to redirect to another page with a specific HTTP status code.
Unlike the redirect component (which normalizes host/domain), this engine redirects at the page level.
Use cases:
- Legacy page migration
- URL restructuring
- SEO page forwarding
Best practice:
- Use 301 for permanent redirects
- Use 302 for temporary redirects
The simple engine processes standard page files (HTML, JS, CSS) with parameter injection and business rule evaluation.
Best practice:
- Use for lightweight dynamic pages
- Move heavy logic to the library engine
The library engine compiles Go code pages as plugins and executes them dynamically.
Features:
- Auto recompilation when code changes
- Full Go power
- High performance
Best practice:
- Use for business logic
- Keep logic separate from templates
The template engine serves XCore v2 XTemplate pages and is mainly used for layouts.
Best practice:
- Keep templates presentation-only
The language engine manages XCore v2 XLanguage resources for multilingual support.
Best practice:
- Avoid hard-coded strings
- Keep translations separated
The WajafApp engine integrates with:
github.com/webability-go/wajaf
It is designed to build administration interfaces and SPAs.
Best practice:
- Use only for admin systems
To build your own engine, you must export:
- Engine
- EngineInstance
Interfaces:
type Engine interface {
NeedInstance() bool
GetInstance(Hostname string, PagesDir string, P string, i identity.Identity) EngineInstance
Run(ctx *context.Context, e interface{}) interface{}
}
type EngineInstance interface {
NeedLanguage() bool
NeedTemplate() bool
Run(ctx *context.Context, template *xcore.XTemplate, language *xcore.XLanguage, e interface{}) interface{}
}Execution Flow:
- Xamboo detects page type.
- Calls NeedInstance().
- If false → Run().
- If true → GetInstance() then Instance.Run() with template/language loaded if needed.
Engines define how CMS pages are executed.
They enable:
- Static rendering
- Go dynamic execution
- Templates
- Languages
- Admin SPA integration
- Use relative paths
- Use pluginprefix for multiple instances
- Keep middleware ordered correctly
- Separate site-specific configs
- Monitor logs during startup
Xamboo’s configuration system is layered, extensible, and designed for scalable multi-host environments.
Xamboo’s CMS is built on a very simple and powerful principle: the directory structure on disk defines the URL space.
Each folder under pagesdir represents a route, and therefore a page (or a family of URLs) that the CMS can resolve and execute.
Example:
- If your pages repository contains a folder
./blog/, then you can request:
https://mysite.com/blog
Xamboo is not a classic “file server” CMS. It does not treat URLs as files. Instead, it treats URLs as pages, described by folders + definition files + engine execution.
- The CMS root directory is defined by the
pagesdirparameter (in the CMS XConfig file). - The home page is always a subdirectory defined by
mainpage. Any files placed directly at the CMS root are ignored. - You must create working error handlers as CMS pages:
errorpage(main error page)errorblock(reusable error block)
These must exist as valid CMS pages and be resolvable by the CMS, otherwise error handling will be incomplete or inconsistent.
A CMS page represents either:
-
A single URL, for example:
https://www.mysite.com/login -
A group of URLs sharing the same root, for example:
https://www.mysite.com/bloghttps://www.mysite.com/blog/channel1https://www.mysite.com/blog/article1- ...
In the second case, the root /blog corresponds to one page, and the remainder of the path is treated as route parameters (path parameters). The page code can use those parameters to decide what content to return.
Every CMS page ultimately produces an output (HTML, JS, CSS, JSON, images, video, etc.). However, there are different ways to build that output. That is why Xamboo uses Engines.
Each page has a type that selects the engine that will build it. Examples include:
- redirect (page-level redirect)
- simple (meta-language + code files)
- template (XCore v2 XTemplate)
- language (XCore v2 XLanguage)
- library (Go plugin page)
- wajafapp (admin SPA/JSON engine)
Built-in engines are documented in the Engines section. External engines may be added and referenced by their configured name.
Pages may be served in multiple combinations of:
- Version (device/layout variant):
pc,mobile,tablet,base, etc. - Language:
en,es,fr, etc.
Each combination is a page instance.
If you have 5 versions and 10 languages, that is 50 potential combinations. Maintaining 50 fully duplicated pages is not realistic.
Xamboo encourages:
- A limited number of templates (often per version)
- A limited number of language tables (per language)
- Engines that combine them at runtime
Instead of maintaining 50 separate page implementations, you typically maintain:
- 5 templates
- 10 language files
And let the engine produce any of the 50 possible outputs.
Xamboo first normalizes the requested URI path:
- Keeps the leading
/ - Removes the trailing
/(if any) - Splits into segments
- Applies compatibility rules:
- Converts routes to lowercase (to avoid issues on case-insensitive OSes)
- Applies allowed-character constraints
A URL like:
/my-route/my-file.html
is not treated as a file unless there is an actual folder named my-file.html inside the my-route folder.
If you want to serve real files, they must exist in the static repository (served by the fileserver component), otherwise a 404 is returned.
Xamboo routes are designed for SEO and clean indexing. Routes should use only:
- Letters A–Z / a–z (including accented letters and ñ if your filesystem supports them)
- Numbers 0–9
- Hyphen
- - Underscore
_
Avoid using punctuation or special characters inside routes.
Capitalization is converted to lowercase for portability.
Examples:
/My-PagE/My-PatH→ repository folder:my-page/my-path/ My-page / My-route→ repository folder with spaces (not recommended)
The CMS searches for a published page by walking backwards from the full path:
a. Locate the folder path exactly as it comes from the URI (protected against "..")
a.1 If the folder does not exist → go to b
a.2 If the folder exists:
- look for the .page file inside
- if missing → go to b
- if present, verify status=published
- if not published → go to b
- if published → execute and return the page
b. Not found:
- remove the last path segment and return to (a)
- if no segments remain:
- if home page accepts path parameters → serve home page
- otherwise → error (404)
In short: the first published page found while trimming from the end is executed. Any remaining segments become path parameters if the page allows them.
The page-level parameter acceptpathparameters confirms whether extra segments may be accepted (and passed into the page), or whether a 404 should be returned.
Every page folder must contain a .page file named after the folder.
Examples:
./home/home.page
./blog/channel/channel.page
The .page file is an XConfig file (key=value format).
# engine name used to build this page
type=simple
# visibility / role of this page
status=publishedhidden(default if missing): never visible, not callable externally, not usable internallypublished: visible from outside (main URL-invoked pages)template: template page, not visible from outsideblock: building block used by other pagesfolder: purely structural folder, no page execution
Page behavior and additional parameters depend on the page type and status.
If you add an external engine, you will reference it using the engine name declared in the global Xamboo configuration ("engines": [...]).
For all page types except redirect, the page requires at least one .instance file.
Instance files contain parameters that may differ by version and language.
The default language and version are set in the CMS configuration file.
If the system cannot resolve a language/version, it falls back to those defaults.
By default:
- Language is not automatically detected (you set it in code, or with a component)
- Version may be set from device type if the CMS browser sub-module is enabled
[page-name].[version].[language].instance
[page-name].[version].instance
[page-name].instance
The [version].[language] pair is called the instance identity.
Xamboo searches for the first existing file in this order:
[current-version].[current-language][current-version].[default-language][current-version][default-version].[current-language][default-version].[default-language]<none>→[page-name].instance
Example: current fr, default en, current version mobile, default base:
mypage.mobile.fr.instance
mypage.mobile.en.instance
mypage.mobile.instance
mypage.base.fr.instance
mypage.base.en.instance
mypage.instance
✅ Strong recommendation: always provide mypage.instance, even if empty.
Instance files can store:
- cache hints
- template injection values
- runtime configuration for Go pages
- feature flags
A redirect page needs no instance. It requires two additional .page parameters:
type=redirect
status=published
redirecturl=/other-url
redirectcode=301redirecturlmay be relative, absolute, or point to another domainredirectcodeis typically301(permanent) or302(temporary)
✅ Best practices:
- redirect pages should be
published - avoid redirect chains
The simple page is the core CMS page type: output code (HTML/JS/CSS/JSON) plus meta-language keywords for dynamic injection.
Required files:
.page- one or more
.instance - one or more
.code
Common .page parameters:
template=<page>(wrap into template page)acceptpathparameters=yes|no
.code files follow the same identity resolution logic as .instance files.
The meta-language is documented in CMS and Meta Language.
A template page is based on .template files containing XCore v2 XTemplate data.
- should not be
published - used as layout/dispatch by other pages
- may have multiple
.templatevariants by identity - may have
.instancefor context parameters
A language page is based on .language files containing XCore v2 XLanguage data.
- should not be
published - used by other pages to inject translations
- may have multiple
.languagevariants by identity - may have
.instancefor context parameters
A library page is a single .go file compiled into a plugin. It must contain a Run function.
Optional companion files:
.templatevariants.languagevariants.instancevariants
Common .page parameters:
template=<page>acceptpathparameters=truewhen handling multiple sub-URLs
Similar to library pages, but designed for administration code using Wajaf.
.goplugin- must contain
Runand optional event functions - typically returns JSON structures
Common .page parameters:
template=<page>acceptpathparameters=true(commonly mandatory)
The meta-language is a set of keywords written inside .code files. The engine replaces those keywords with values or execution results.
Comments are removed during compilation:
%-- This is a comment. It will not appear in the final code. --%
%--
This entire block will never be compiled or visible:
[[BOX,/box:
Anything here
BOX]]
--%
##id## inserts a localized string from the .language file matching the client language (with fallback).
Example usage:
<div style="background-color: blue;">
##welcome##<br />
<span onclick="alert('##hello##');" class="button">##clickme##!</span>
</div>Default fallback language file (mypage.language):
<?xml version="1.0" encoding="UTF-8"?>
<language id="mypage" lang="en">
<entry id="welcome">Welcome to</entry>
<entry id="clickme">Click me</entry>
</language>French variant (mypage.base.fr.language):
<?xml version="1.0" encoding="UTF-8"?>
<language id="mypage" lang="fr">
<entry id="welcome">Bienvenue</entry>
<entry id="clickme">Clique sur moi</entry>
</language>Best practices:
- Keep keys consistent across languages
- Always provide the fallback
mypage.language
Reads all path parameters and writes them as a URL query string.
Requires acceptpathparameters=yes in .page.
Extracts the n-th (1-indexed) path parameter.
Requires acceptpathparameters=yes.
Extracts a variable from:
- query string
?name=value - POST/PUT body variables
Extracts a local parameter passed when calling a page using [[CALL,...]].
Reads a system parameter from the CMS .conf (XConfig) file.
Reads a parameter from the .page of the main URL-invoked page.
Reads a parameter from the .page of the page currently being built (template/block/subpage).
Reads a parameter from the .instance of the main URL-invoked page.
Reads a parameter from the .instance of the page currently being built.
Reads a session parameter (available in context/session for execution).
Includes a JavaScript file in headers only once.
Includes a CSS file in headers only once.
Calls a page, template, or block with optional parameters.
Called page can read them via [[PARAM,...]].
Includes a template and encapsulates the inner content. A box is a local mini-template applied to a snippet.
A Library Page (type=library) is one of the most powerful page types in Xamboo.
Unlike a simple page (meta-language + .code files), a library page is written in Go and compiled as a plugin.
It allows you to implement full business logic, dynamic routing, conditional rendering, and even internal CMS dispatching.
Library pages are ideal for:
- Complex business logic
- API endpoints
- Dynamic content generation
- Proxy behavior
- Conditional page delegation
- Integration with external services
- Advanced routing inside a single CMS page
A minimal library page requires:
/home
home.page
home.instance
home.go
type=library
status=published
acceptpathparameters=true
template=main/template # optionaltype=library→ tells Xamboo to use the library engine.status=published→ page accessible via URL.acceptpathparameters=true→ allow extra URI segments.template=...→ optional template wrapping the result.
At minimum:
home.instance
Even if empty, it must exist.
Instance files may contain:
- configuration flags
- cache hints
- feature switches
- injected parameters
Always provide at least one fallback instance file.
Below is a minimal library page implementation:
package main
import (
"github.com/webability-go/xcore/v2"
"github.com/webability-go/xamboo/cms"
"github.com/webability-go/xamboo/cms/context"
)
func Run(ctx *context.Context, template *xcore.XTemplate, language *xcore.XLanguage, e interface{}) interface{} {
// Read the original URL path
original := ctx.Request.URL.Path
// Conditional delegation, example to call another page from this one by code
if original == "/home" {
return e.(*cms.CMS).Run("home/home", true, nil, "", "", "")
}
// Integrate parameters to inject into the templates (including the languages entries)
params := &xcore.XDataset{
"Param1": "´value of parameter1",
"#": language,
}
// Default behavior: render template
return template.Execute(nil)
}The required exported function signature is:
func Run(ctx *context.Context, template *xcore.XTemplate, language *xcore.XLanguage, e interface{}) interface{}Contains:
- HTTP request
- HTTP response writer
- Session parameters
- Instance parameters
- Page parameters
- Version
- Language
- Return code
This is the main object you use to access runtime information.
The resolved template (if configured in .page).
You can:
- Inject variables
- Execute template
- Ignore it entirely and return custom output
The resolved language table for this page.
Use it to fetch translations manually if needed.
The internal CMS engine reference.
In most cases:
e.(*cms.CMS)This allows you to:
- Run other pages
- Trigger internal routing
- Call blocks/templates programmatically
original := ctx.Request.URL.Path
Reads the full requested path.
if original == "/home" {
return e.(*cms.CMS).Run("home/home", true, nil, "", "", "")
}
If the exact path is /home, it delegates execution to another CMS page.
This demonstrates that:
- A library page can act as a router
- A library page can call another page internally
- You can build conditional routing logic
return template.Execute(nil)
If no special condition is met:
- Execute the associated template
- Return the rendered output
A library page may return:
- string (HTML/JSON/etc.)
- []byte
- structured data
- error (supported in newer versions)
- anything supported by the engine
If returning an error, the CMS will trigger error handling.
You can route internally:
return e.(*cms.CMS).Run("blog/article", true, nil, "", "", "")ctx.Writer.Header().Set("Content-Type", "application/json")
return `{"status":"ok"}`If acceptpathparameters=true:
params := ctx.URLParamsYou can parse dynamic routes like:
/home/123/details
- Always include a fallback
.instance - Keep heavy logic in Go, not templates
- Avoid deep recursion (respect maxrecursion)
- Prefer internal CMS delegation instead of duplicating code
- Keep template logic presentation-only
- Return errors instead of silently failing
Library pages are compiled as Go plugins.
Xamboo automatically:
- Detects changes in
.go - Recompiles when needed
- Loads the plugin
- Caches it
Make sure:
- The package is
package main - The function
Runis exported - Dependencies are correct
- No global state causes race conditions
To create a working library page:
- Create folder
/home - Add
home.page - Add
home.instance - Add
home.go - Ensure
type=library - Restart or reload configuration
A Library Page:
- Is a Go plugin .so
- Executes dynamic business logic
- Can delegate to other CMS pages
- Can serve HTML, JSON, or any content
- Integrates fully with templates and language tables
- Is the most powerful CMS page type in Xamboo
Use it when your page needs real backend intelligence, not just meta-language substitution.
package main
import (
"net/http"
"github.com/webability-go/xamboo/cms/context"
"github.com/webability-go/xcore/v2"
"github.com/webability-go/xmodules/tools"
)
const ERROR_METHODNOTSUPPORTED = "Method not supported"
// Run function is MANDATORY and is the point of call from the xamboo
//
// The enginecontext contains all what you need to link with the system
func Run(ctx *context.Context, template *xcore.XTemplate, xlanguage *xcore.XLanguage, e interface{}) interface{} {
switch ctx.Request.Method {
case "GET":
return rGet(ctx, template, xlanguage, e)
case "POST", "PUT":
return rPost(ctx, template, xlanguage, e)
case "DELETE":
return rDelete(ctx, template, xlanguage, e)
case "OPTIONS": // Ensure pre flight will answer correct headers
return ""
default:
}
// 501 not implemented
http.Error(ctx.Writer, ERROR_METHODNOTSUPPORTED, http.StatusNotImplemented)
return ERROR_METHODNOTSUPPORTED
}
func rGet(ctx *context.Context, template *xcore.XTemplate, xlanguage *xcore.XLanguage, e interface{}) interface{} {
// Code business logic
m["status"] = "ok"
return tools.JSONEncode(m, true)
}
func rPost(ctx *context.Context, template *xcore.XTemplate, xlanguage *xcore.XLanguage, e interface{}) interface{} {
// Code business logic
m["status"] = "ok"
return tools.JSONEncode(m, true)
}
func rDelete(ctx *context.Context, template *xcore.XTemplate, xlanguage *xcore.XLanguage, e interface{}) interface{} {
// Code business logic
m["status"] = "ok"
return tools.JSONEncode(m, true)
}package main
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/webability-go/xamboo/cms/context"
"github.com/webability-go/xamboo/components/host"
"github.com/webability-go/xamboo/components/stat"
xcore "github.com/webability-go/xcore/v2"
)
type listenerStream struct {
Id int
Upgrader websocket.Upgrader
Stream *websocket.Conn
RequestStat *stat.RequestStat
fulldata bool
}
var counter = 1
/*
This function is MANDATORY and is the point of call from the xamboo
The enginecontext contains all what you need to link with the system
*/
func Run(ctx *context.Context, template *xcore.XTemplate, language *xcore.XLanguage, e interface{}) interface{} {
fmt.Println("Entering listener")
// Note: the upgrader will hijack the writer, so we are responsible to actualize the stats
hw := ctx.Writer.(host.HostWriter)
par := hw.GetParams()
irs, _ := par.Get("requeststat")
rs := irs.(*stat.RequestStat)
ls := listenerStream{
Id: counter,
Upgrader: websocket.Upgrader{},
RequestStat: rs,
fulldata: true,
}
counter++
stream, err := ls.Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
fmt.Println(err)
return "ERROR UPGRADING STREAM: " + fmt.Sprint(err)
}
ls.Stream = stream
ls.RequestStat.UpdateProtocol("WSS")
fmt.Println("LISTENER START: ", ls.Id)
defer stream.Close()
cdone := make(chan bool)
go Read(ls, cdone)
go Write(ls, cdone)
<-cdone
<-cdone
fmt.Println("LISTENER CLOSED: ", ls.Id)
return "END STREAM CLOSED"
}
func Read(ls listenerStream, done chan bool) {
for {
_, message, err := ls.Stream.ReadMessage()
if err != nil {
fmt.Println("END STREAM IN READ: " + fmt.Sprint(err))
break
}
fmt.Println("MESSAGE: " + fmt.Sprint(message))
if strings.Contains(string(message), "F") {
ls.fulldata = true
}
// if the client asks for "data", we send it a resume
// err = stream.WriteMessage(websocket.TextMessage, []byte(statmsg))
}
done <- true
}
func Write(ls listenerStream, done chan bool) {
last := time.Now()
for {
// if no changes, do not send anything
// or send a pingpong
// search for all the data > last
newTime := time.Now()
last = newTime
data := make(map[string]interface{})
data["timestamp"] = last.Unix()
data["ping"] = "ping"
datajson, _ := json.Marshal(data)
ls.RequestStat.UpdateStat(0, len(datajson))
err := ls.Stream.WriteMessage(websocket.TextMessage, []byte(datajson))
if err != nil {
fmt.Println("END STREAM IN WRITE: " + fmt.Sprint(err))
break
}
time.Sleep(1 * time.Second)
}
done <- true
}
- Always include a fallback instance:
mypage.instance. - Keep
version=baseunless you actively manage multiple versions. - Enable browser/useragent only if you need device-specific versions.
- Avoid enabling
acceptpathparameterson home unless you know exactly why. - Keep static assets in
fileserver, not in CMS routes. - Do not put heavy business logic in
.code—uselibrarypages for complex logic.
In your CMS XConfig (.conf) file:
pagesdir=...mainpage=...errorpage=...errorblock=...version=baselanguage=en|es|...acceptpathparameters=yes|no(as a routing strategy)
In your pages repository:
mainpage/folder with valid.page,.instance, and code files.errorpage/anderrorblock/folders implemented as real CMS pages.
Xamboo CMS is a folder-driven CMS:
- Routes → folders
- Page definition →
.page - Variants →
.instance - Content →
.code,.template,.language,.go - Execution strategy → engine
type
This architecture makes Xamboo highly modular and scalable, well suited for multi-language, multi-device sites and strong business-rule integration.
Applications must export:
var Application assets.ApplicationResponsibilities:
- Load XModules
- Manage contexts
- Handle datasources
The xmodules are under another project into the webability-go github. Structured business modules:
- User management
- CRM
- ERP
- Ecommerce
- Administration
- 500 req/sec production on 8 CPU server with 16 GB Memory with a simple postgres database
- 3000 req/sec lab on same server
- Automatic plugin recompilation < 3 seconds with waiting queue
- TLS 1.2 / 1.3 supported
MIT License