Server-Side Scripts
bserver can execute server-side scripts in Python, JavaScript (Node.js), or PHP to dynamically generate HTML content. This is used for rendering that requires logic beyond what static YAML can express.
How It Works
Scripts are defined in format definitions using the script: field:
^my-renderer:
script: python
code: |
print(f'<p>{record["key"]}: {record["value"]}</p>')
When bserver encounters content with this format, it:
- Serializes the content data as JSON
- Passes it to the script via stdin
- Wraps your code in a loop that iterates over each record
- Captures stdout as the rendered HTML
Script Languages
Python
^renderer:
script: python
code: |
name = record.get('key', '')
value = record.get('value', '')
print(f'<div class="item">{name}: {value}</div>')
Available variable: record (a Python dict)
bserver looks for python3 first, then python.
JavaScript (Node.js)
^renderer:
script: javascript
code: |
console.log(`<div class="item">${record.key}: ${record.value}</div>`);
Available variable: record (a JavaScript object)
Aliases: javascript, js, node
PHP
^renderer:
script: php
code: |
echo "<div class='item'>{$record['key']}: {$record['value']}</div>\n";
Available variable: $record (a PHP associative array)
Data Format
Map Content
When the content is a map (ordered key-value pairs), each entry becomes a
record with key and value fields:
navlinks:
"/": Home
"/about": About
"/contact": Contact
The script receives via stdin:
[
{"key": "/", "value": "Home"},
{"key": "/about", "value": "About"},
{"key": "/contact", "value": "Contact"}
]
List Content
When content is a list of maps, each map becomes a record directly:
products:
- name: Widget
price: "$9.99"
sku: WDG-001
- name: Gadget
price: "$19.99"
sku: GDG-001
The script receives:
[
{"name": "Widget", "price": "$9.99", "sku": "WDG-001"},
{"name": "Gadget", "price": "$19.99", "sku": "GDG-001"}
]
Null/Empty Content
Scripts can run even without content data. If the format references a name
that has no definition, the script receives null (wrapped as [null]).
This is useful for scripts that generate content entirely from environment
variables or external sources.
External Script Files
Instead of inline code, you can reference an external file:
^renderer:
script: php
file: scripts/render.php
The file path is relative to the document root. For PHP files, <?php and
?> tags are automatically stripped since bserver provides the execution
wrapper.
Environment Variables
Scripts have access to CGI-like environment variables, making them behave similarly to scripts running under Apache or nginx:
Always Available
| Variable | Description |
|---|---|
REQUEST_URI |
URL path for the current request (e.g., /about) |
DOCUMENT_ROOT |
Filesystem path to the document root |
REDIRECT_STATUS |
Always 200 |
SCRIPT_NAME |
Same as REQUEST_URI |
PHP_SELF |
Same as REQUEST_URI (for PHP compatibility) |
Available with HTTP Requests
| Variable | Description |
|---|---|
REMOTE_ADDR |
Client IP address |
SERVER_NAME |
Server hostname |
SERVER_ADDR |
Server IP address |
SERVER_PORT |
Server port (80 or 443) |
HTTP_HOST |
HTTP Host header |
QUERY_STRING |
URL query string |
REQUEST_METHOD |
HTTP method (GET, POST, etc.) |
SERVER_PROTOCOL |
Protocol version (e.g., HTTP/1.1) |
GATEWAY_INTERFACE |
Always CGI/1.1 |
SERVER_SOFTWARE |
Always bserver |
SCRIPT_FILENAME |
Path to script file (if using file:) |
CONTENT_TYPE |
Request Content-Type header |
CONTENT_LENGTH |
Request Content-Length |
All HTTP request headers are also available as HTTP_* variables (e.g.,
HTTP_USER_AGENT, HTTP_ACCEPT).
Real-World Example: Active Navigation
The built-in navbar uses Python scripting to highlight the current page:
^navlinks:
script: python
code: |
import os, html as _html
page = os.environ.get('REQUEST_URI', '/')
link = record.get('key', '')
text = record.get('value', '')
active = ' active bg-primary bg-opacity-10' if link == page else ''
print(f'<li class="nav-item">'
f'<a class="nav-link{active}" '
f'href="{_html.escape(link)}">'
f'{text}</a></li>')
This reads the current page URL from REQUEST_URI and adds Bootstrap's
active class to the matching navigation link.
Script Wrapper Details
bserver wraps your code in a language-specific boilerplate:
Python Wrapper
import json, sys
_data = json.loads(sys.stdin.read())
if not isinstance(_data, list): _data = [_data]
for record in _data:
# your code here (indented 4 spaces)
JavaScript Wrapper
const _data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const _records = Array.isArray(_data) ? _data : [_data];
for (const record of _records) {
// your code here
}
PHP Wrapper
$_data = json_decode(file_get_contents('php://stdin'), true);
if (!is_array($_data)) $_data = [$_data];
foreach ($_data as $record) {
// your code here
}
Execution Details
- Working directory: Set to the document root, so relative file paths in your scripts resolve from there.
- Timeout: Scripts have a 30-second execution timeout. If exceeded, the process is killed and an HTML comment is inserted.
- Error handling: Script errors (non-zero exit) produce an HTML comment with the error message and stderr output.
- Output: Everything written to stdout becomes part of the page HTML. Stderr is captured separately for error reporting.
Next Steps
- Built-in Components - See the navbar's script in context
- Advanced Features - Virtual hosting, custom tags, and more