Pequod / PiROS manual ↑ Manual home
04 · Web pages with tintin

Dynamic pages with Pequod + tintin

tintin is the PiROS HTTP server. When it's asked for a .psh file it runs the script and returns its output as the page. That turns Pequod into a template language for a real, if tiny, web stack.

How it fits together

A request for /page.psh makes tintin run psh on that file and stream the result back to the browser:

browser  ──GET /page.psh──▸  tintin  ──exec──▸  psh page.psh  ──▸  HTML  ──▸  browser

tintin runs the script as its own service user and hands it the request context (method, query, body), which Pequod exposes as the $_GET, $_POST, $_SERVER and $_REQUEST superglobals.

Template mode

Any file containing a <? tag is a template: text outside the tags is sent verbatim, code inside runs. Use <?= expr ?> to echo a value inline.

default/www/hello.psh
<!doctype html>
<html><body>
  <h1><?= "Welcome to PiROS" ?></h1>
  <? $now = "today"; ?>
  <p>Served <?= $now ?> by tintin.</p>
</body></html>
Loops around markup. Because HTML outside the tags is literal, you can wrap markup in a loop by opening and closing tags around it: <? foreach (…) { ?><li>…</li><? } ?>.

A data-driven list

The everyday pattern: loop an array, emit a row per item.

default/www/status.psh
<!doctype html>
<html><body><h1>Services</h1>
<? $svc = ["sshd" => "running", "tintin" => "running", "nemo" => "stopped"];
   $up = 0; ?>
<ul>
<? foreach ($svc as $name => $state) {
       if ($state == "running") { $up++; } ?>
  <li><?= htmlspecialchars($name) ?><?= $state ?></li>
<? } ?>
</ul>
<p><?= $up ?> of <?= count($svc) ?> up.</p>
</body></html>

Reading the request: $_GET and $_POST

tintin populates the superglobals from the request. They're associative arrays; index by field name. When there's no request context (running from the shell) they're simply empty, so you can index them safely without guards.

SuperglobalHolds
$_GET["field"]Query-string parameters (?field=value).
$_POST["field"]Submitted form fields (POST body).
$_REQUEST["field"]GET overlaid with POST (POST wins).
$_SERVER["REQUEST_METHOD"]"GET" or "POST".

A form that handles itself

Show the form on GET; process the submission on POST. Always run user values through htmlspecialchars() before echoing them into the page.

default/www/greet.psh
<!doctype html>
<html><body>
<? if ($_SERVER["REQUEST_METHOD"] == "POST") { ?>
  <p>Hello, <?= htmlspecialchars($_POST["name"]) ?>!</p>
<? } else { ?>
  <form method="post">
    <input name="name" placeholder="your name">
    <button>Say hi</button>
  </form>
<? } ?>
</body></html>
Always escape output. Echoing a raw $_GET/$_POST value into HTML is an XSS hole. htmlspecialchars() turns <script> into harmless text. Make it a habit for every user-supplied value.

Config off the web root

Keep settings and secrets out of the served directory. Put them in a file elsewhere and include it — its define()d constants become available, but the file itself is never served.

config/app.psh (outside default/www)
define("SITE_NAME", "PiROS demo");
define("DB", "shop");
default/www/home.psh
<? include "config/app.psh"; ?>
<h1><?= SITE_NAME ?></h1>

Serving the page

Drop the .psh file under tintin's web root and start the service (root):

root@pros> admin service start tintin
root@pros> ps                       # tintin shows as a ring-3 process

Then request it from a browser or curl. A page that reads a database is one small step further — that's the SQL chapter.

Caps to know. A script file is read into 4096 bytes; tintin captures up to 8 KiB of page output. Compose large pages from included fragments, and keep generated output within the capture limit.
← Shell scripting SQL via Queequeg →