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:

  1. Serializes the content data as JSON
  2. Passes it to the script via stdin
  3. Wraps your code in a loop that iterates over each record
  4. 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

Next Steps