Help us shape the future of Go! Take the 2026 Go Developer Survey

go2waa

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 11 Imported by: 0

README

go2waa

Go core for the Alaska Xbase++ WAA gateway protocol: hold one complete conversation with a WAA server — the backend behind WAA1GATE.EXE — from any Go code, with or without a web server in between.

The wire framing, the conversation flow and the CGI output handling are a faithful port of Alaska's original C gateway (1998-2003). The surface around them is new: a fully virtual context (WaaCtx) decides where every answer comes from and where the output goes — an HTTP request, a plain Go call, a test, anything.

The pieces

Piece File Role
WaaCtx waa_ctx.go The interface: ten On* methods answering the server's commands
KvEntry waa_ctx.go One form variable with its collected values
NullWaaCtx null_waa_ctx.go Do-nothing context — embed it, override what you need
DirectWaaCtx direct_waa_ctx.go Fill-by-hand context: data in, results collected in memory
WaaClient waa_client.go Connection config; Call(ctx) runs one conversation
Framing protocol.go Message, GetMessage/PutMessage, the Cmd* constants

A direct call, no web server

ctx := go2waa.NewDirectWaaCtx(nil)
ctx.SetValue("WAA_PACKAGE", "echo")
ctx.SetValue("WAA_FORM", "echo")
ctx.AddValue("multi", "one")
ctx.AddValue("multi", "two")

client := &go2waa.WaaClient{Host: "127.0.0.1", Port: 1027}
if err := client.Call(ctx); err != nil {
    log.Fatal(err)
}

// ctx.Header []string  — the CGI header lines
// ctx.Body   []byte    — the response body
// ctx.Files            — files the application sent, name -> buffer
// ctx.Emails           — raw email messages it requested

One WaaClient is safe for any number of concurrent calls — each call opens its own connection, and each live call needs its own context.

How a conversation works

The gateway starts, the server ends: Call connects, sends CONNECT, waits for READY, and then serves the application's commands until DISCONNECT — environment variables, form variables (single or all), document root, script name, output, file transfers, email requests. Every command becomes one method call on your WaaCtx.

The application's output is a CGI block — header lines, an empty line, then the body. The engine does the framing once, for every context: each header line arrives through OnHeaderLine (with its terminator: the int value carries the actual EOL bytes, 0x0D0A for CRLF), the separator through OnEndHeader, and everything after it streams through OnPutData. If the application never emits a separator, all its output is delivered as body when the conversation ends — exactly what the C gateway did.

The transport is anything that reads and writes bytes; the connection is never persistent: one call, one TCP connection, gone when Call returns.

Install

go get github.com/pablo-botella/go2waa

Tests

go test ./...

The suite under test/ uses only the public API: protocol framing over net.Pipe, and full conversations through WaaClient.Call against a scripted mock server on a loopback port.

TestWaaCall goes further: driven by test/test.json (the Go flavor of the Xbase ao_waa_call request file — a server block instead of the in-process application one), it performs one real call against a live WAA server and writes the response to output.txt/output.html. It is opt-in, behind a build tag — a plain go test ./... never runs it:

go test -tags live -run TestWaaCall ./test

It needs the echo package (test/waa/echo.dll) loaded in a running WAA server; WAA_HOST and WAA_PORT override the JSON, WAA_CALL_REQUEST points at another request file. Once invoked, a missing server is a failure, not a skip — on purpose: a test that cannot do its job should say so.

test/waa/ carries that echo fixture: an Xbase++ package (echo.prg, built by build.bat into echo.dll) that dumps every form variable it receives back as an HTML table — multi-values included.

License

MIT — see LICENSE.md.

Documentation

Index

Constants

View Source
const (
	CmdConnect       int32 = 0   // First connection from the gateway
	CmdDisconnect    int32 = 1   // Last command from the server
	CmdReady         int32 = 2   // Server ready
	CmdAck           int32 = 3   // Positive acknowledgement
	CmdNak           int32 = 4   // Negative acknowledgement
	CmdGetEnv        int32 = 100 // Get environment variable
	CmdPutEnv        int32 = 110 // Response to GetEnv
	CmdGetVar        int32 = 200 // Get form variable
	CmdPutVar        int32 = 210 // Response to GetVar
	CmdPut           int32 = 201 // Write HTML output
	CmdGetAllVars    int32 = 300 // Get all variables
	CmdPutAllVars    int32 = 310 // Response to GetAllVars
	CmdOpenFile      int32 = 400 // Open file for writing
	CmdPutFileData   int32 = 401 // Write data to file
	CmdCloseFile     int32 = 402 // Close file
	CmdGetDocRoot    int32 = 500 // Get document root
	CmdGetScriptName int32 = 600 // Get script name
	CmdEmail         int32 = 700 // Send email
)

CGI protocol commands

View Source
const (
	EolLf   = int(linereader.EolLf)
	EolCr   = int(linereader.EolCr)
	EolCrLf = int(linereader.EolCrLf)
)

Line terminators reported to OnHeaderLine and OnEndHeader; the values are linereader's EolType.

Variables

This section is empty.

Functions

func PutMessage

func PutMessage(conn io.Writer, cmd int32, data []byte) error

PutMessage sends a message to the WAA transport Format: <msgLen 4 bytes><cmd 4 bytes><data>

func PutMessageString

func PutMessageString(conn io.Writer, cmd int32, data string) error

PutMessageString sends a message with string data

Types

type DirectWaaCtx

type DirectWaaCtx struct {
	NullWaaCtx

	// Env answers OnGetEnv verbatim (CGI names, e.g. "SERVER_NAME").
	// REQUEST_METHOD defaults to "POST" when not set.
	Env map[string]string

	// Header collects the CGI header lines emitted by the application,
	// one entry per line, without terminators.
	Header []string

	// Body collects the application output after the header separator —
	// or all of it, when the application never emitted a separator.
	Body []byte

	// Files collects the files sent by the application, name -> content.
	Files map[string]*bytes.Buffer

	// Emails collects the email messages requested by the application,
	// raw and in order of arrival.
	Emails [][]byte
	// contains filtered or unexported fields
}

DirectWaaCtx is a WaaCtx you fill by hand: direct data in, output collected in memory to read after the call. It does not perform the call itself — pass it to WaaClient.Call. A DirectWaaCtx belongs to one conversation: do not share a live one between concurrent calls.

func NewDirectWaaCtx

func NewDirectWaaCtx(env map[string]string) *DirectWaaCtx

NewDirectWaaCtx returns a DirectWaaCtx ready to fill. env may be nil (empty environment).

func (*DirectWaaCtx) AddValue

func (c *DirectWaaCtx) AddValue(name, value string)

AddValue adds one more value to a form variable, creating it if it does not exist.

func (*DirectWaaCtx) OnEmail

func (c *DirectWaaCtx) OnEmail(rfc822data []byte) error

func (*DirectWaaCtx) OnGetAllVars

func (c *DirectWaaCtx) OnGetAllVars() []KvEntry

func (*DirectWaaCtx) OnGetEnv

func (c *DirectWaaCtx) OnGetEnv(name string) string

func (*DirectWaaCtx) OnGetVar

func (c *DirectWaaCtx) OnGetVar(name string) ([]string, bool)

func (*DirectWaaCtx) OnHeaderLine

func (c *DirectWaaCtx) OnHeaderLine(line string, lineTerminator int) error

func (*DirectWaaCtx) OnOpenFile

func (c *DirectWaaCtx) OnOpenFile(filename string) (io.WriteCloser, error)

func (*DirectWaaCtx) OnPutData

func (c *DirectWaaCtx) OnPutData(data []byte) error

func (*DirectWaaCtx) SetMethod

func (c *DirectWaaCtx) SetMethod(method string)

SetMethod sets the REQUEST_METHOD environment variable.

func (*DirectWaaCtx) SetValue

func (c *DirectWaaCtx) SetValue(name, value string)

SetValue sets a form variable to a single value, replacing whatever it had.

func (*DirectWaaCtx) SetValues

func (c *DirectWaaCtx) SetValues(name string, values []string)

SetValues sets a form variable to the given values, replacing whatever it had. The variable keeps the name case of its first appearance.

type KvEntry

type KvEntry struct {
	Key    string
	Values []string
}

KvEntry is one form variable with its collected values, in order of first appearance.

type Message

type Message struct {
	Cmd  int32
	Data []byte
}

Message represents a WAA protocol message

func GetMessage

func GetMessage(conn io.Reader) (*Message, error)

GetMessage reads a message from the WAA transport Format: <msgLen 4 bytes><cmd 4 bytes><data>

func (*Message) DataString

func (m *Message) DataString() string

DataString returns the message data as a string

type NullWaaCtx

type NullWaaCtx struct{}

NullWaaCtx is a do-nothing WaaCtx: empty answers, discarded output, files and email rejected (NAK). Embed it to implement only the methods you need.

func (NullWaaCtx) OnEmail

func (NullWaaCtx) OnEmail(rfc822data []byte) error

func (NullWaaCtx) OnEndHeader

func (NullWaaCtx) OnEndHeader(lineTerminator int) error

func (NullWaaCtx) OnGetAllVars

func (NullWaaCtx) OnGetAllVars() []KvEntry

func (NullWaaCtx) OnGetDocRoot

func (NullWaaCtx) OnGetDocRoot() string

func (NullWaaCtx) OnGetEnv

func (NullWaaCtx) OnGetEnv(name string) string

func (NullWaaCtx) OnGetScriptName

func (NullWaaCtx) OnGetScriptName() string

func (NullWaaCtx) OnGetVar

func (NullWaaCtx) OnGetVar(name string) ([]string, bool)

func (NullWaaCtx) OnHeaderLine

func (NullWaaCtx) OnHeaderLine(line string, lineTerminator int) error

func (NullWaaCtx) OnOpenFile

func (NullWaaCtx) OnOpenFile(filename string) (io.WriteCloser, error)

func (NullWaaCtx) OnPutData

func (NullWaaCtx) OnPutData(data []byte) error

type WaaClient

type WaaClient struct {
	Host string // default 127.0.0.1
	Port int    // default 1024

	// Timeouts in seconds
	ConnTimeout  int // default 30
	ReadTimeout  int // default 60
	WriteTimeout int // default 60
}

WaaClient holds how to reach a WAA server: host, port, timeouts. It keeps no live state — each Call opens its own connection — so one WaaClient is safe for any number of concurrent calls, each with its own WaaCtx.

func (*WaaClient) Call

func (w *WaaClient) Call(ctx WaaCtx) error

Call dials the WAA server, runs one complete conversation against ctx and closes the connection. The connection is not persistent: one call, one connection, gone when the call returns.

type WaaCtx

type WaaCtx interface {
	// OnGetEnv answers CmdGetEnv: the value of an environment variable,
	// empty if unknown.
	OnGetEnv(name string) string

	// OnGetVar answers CmdGetVar: the decoded values of a form variable
	// (a form key may repeat), and whether it exists. The protocol
	// answer carries the first value.
	OnGetVar(name string) ([]string, bool)

	// OnGetAllVars answers CmdGetAllVars: all form variables in order of
	// first appearance, each with its collected values (a form key may
	// repeat). The protocol answer carries them URL-encoded.
	OnGetAllVars() []KvEntry

	// OnGetDocRoot answers CmdGetDocRoot.
	OnGetDocRoot() string

	// OnGetScriptName answers CmdGetScriptName.
	OnGetScriptName() string

	// OnHeaderLine receives one CGI header line emitted by the WAA
	// application, without its terminator; lineTerminator is one of the
	// Eol* constants. Returning an error aborts the conversation.
	OnHeaderLine(line string, lineTerminator int) error

	// OnEndHeader signals the empty line that closes the header block,
	// with its terminator. Returning an error aborts the conversation.
	OnEndHeader(lineTerminator int) error

	// OnPutData receives the application output that follows the header
	// block — the body. If the application never emits a header
	// separator, all its output arrives here at the end of the
	// conversation. Returning an error aborts the conversation.
	OnPutData(data []byte) error

	// OnOpenFile returns the destination stream for a file sent by the WAA
	// server. The loop writes the received data to it and closes it when
	// the server closes the file. Error or nil writer = NAK.
	OnOpenFile(filename string) (io.WriteCloser, error)

	// OnEmail sends an email message, passed through untouched as received
	// from the WAA server. Error = NAK.
	OnEmail(rfc822data []byte) error
}

WaaCtx is what one WAA conversation talks to. It is fully virtual: only methods, no data. The protocol loop (Run) translates each server command into a method call, so implementations decide where the data comes from and where the output goes — an HTTP request, a plain Go call, a test, anything.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL