Language

Persistent state

HTTP is a stateless protocol so your script doesn't retain state by default. But what if you want to remember something between requests? You could configure an external database, but that's kind of a pain for small scripts. Fortunately, hookscript allows each script to store a small amount of data.

Each language has its own way of using state, but here's a script in Go Perl Prolog Python that outputs the number of times that it has been executed.

package main

import "fmt"

func Hook(n *int) {
    *n++
    fmt.Print(*n)
}
use Hookscript;

$state //= 0;
$state++;
print $state;
:- use_module(library(hookscript)).

hook(N0, N) :-
    ignore(N0=0),  % N0 unbound when no state has been stored
    succ(N0,N),
    write(N).
import hookscript

state = hookscript.state

if state == None:
    state = 0
state += 1
print(state, end='')

hookscript.state = state

In that example, the state value is a single integer, but it could have been almost anything. The following example implements a primitive key-value store. The state is a key-value map. Sending a POST request sets the value associated with a key. A GET request reads the value.

package main

import (
    "fmt"
    "net/http"
)

type State *map[string]string

func Hook(r *http.Request, state State) {
    value := ""
    register := r.FormValue("register")
    if *state == nil {
        *state = make(map[string]string)
    }
    registers := *state

    switch r.Method {
    case "GET":
        var ok bool
        value, ok = registers[register]
        if !ok {
            value = fmt.Sprintf("unknown register: %s", register)
        }
    case "POST":
        value = r.FormValue("value")
        if value == "" {
            value = "default value"
        }
        if register == "death" {
            panic("I am dead")
        }
        registers[register] = value
    }

    fmt.Print(value)
}
use Hookscript;
use experimental qw( switch );

my $value;
my $register = $req->param('register') // '';
given ( $req->method ) {
    when ('POST') {
        $value = $req->param('value') // 'default value';
        die "I am dead" if $register eq 'death';
        $state->{$register} = $value;
    }
    default {
        $value = $state->{$register} // "unknown register: $register";
    }
}
print $value;
:- use_module(library(hookscript)).

:- dynamic state:register/2.

hook :-
    req:param(register,'',Register),
    req:method(Method),
    hook(Method,Register,Value),
    write(Value).

hook(post,Register,Value) :-
    req:param(value,'default value',Value),
    ( Register = death -> throw("I am dead"); true ),
    state:retractall(register(Register,_)),
    state:assert(register(Register,Value)).
hook(get,Register,Value) :-
    ( state:register(Register,Value)
    ; format(string(Value),'unknown register: ~w', [Register])
    ).
import hookscript
from hookscript import request

state = hookscript.state or {}

register = request.values.get('register', '')
if request.method == 'POST':
    value = request.values.get('value','default value')
    if register == 'death':
        raise Exception('I am dead')
    state[register] = value
else:
    value = state.get(register,'unknown register: %s' % register)

print(value, end='')
hookscript.state = state

For instructional purposes, this app forbids writing to the death key. If your script exits with a non-zero exit code, you can inspect your script's request log to see what went wrong. Anything sent to stderr is visible there (along with the entire HTTP request and response). This can be very helpful when debugging.

Remember to check the language specific documentation to see exactly how state is supported in Go Perl Prolog Python .

Next