package module
v0.5.1 Latest Latest

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

Go to latest
Published: Jan 29, 2024 License: LGPL-3.0 Imports: 15 Imported by: 1


Houston is an Express-like Gemini server, written in Go. Primarily because I want to. Not because it’s necessary. There are plenty other Gemini servers that have all important functionality covered, and several are written in Go themselves. I just want to make my own.

That said, this aims to be lightweight and easy to use. This was initially developed to suite my own purposes, for my own capsules, as I wanted a server that I understood. Not somebody else’s.


My initial goal for Houston was to serve static files. It’s moved beyond that already. Here are my goals for the project:

  • Serve static directories
  • Handle requests with user-defined functions
  • Basic logging (a toggle for major events like incoming connections, nothing fancy – to keep it simple).
  • Rate-limiting capabilities, to prevent DOS attacks and spam.
  • Intuitive support for templates.
  • Titan protocol integration.
    • Lagrange is currently the only client I know of that implements this on the user-side, so not super high priority.

Basic Usage Example

Easiest way to learn, for me, is by reading an example. Here you go:

package main

import (


func main() {
    r := houston.BlankRouter()

    // Serve static files from directory `./static` at URL `/`.
    // For example:
    // * Request to `/` yields file `./static/index.gmi`
    // * Request to `/something/example.txt` yields file `./static/something/example.txt`
    r.Sandbox("/", "static")

    // You can handle requests more dynamically and programatically
    // by using `Router.Handle()`, which takes a callback function.
    // The callback is given a `Context` instance, which contains
    // information about the client and the request that can be
    // operated on however you want.
    r.Handle("/input", func(ctx houston.Context) {
        ctx.InputAndDo("Guess a number from 1 to 10", func(s string) {
            asInt, err := strconv.ParseInt(s, 10, 32)
            if err != nil {
                ctx.BadRequest("Please enter an integer!")
            if asInt == 3 {
                ctx.SendString("text/plain", "You got it right!")
            } else {
                ctx.SendString("text/plain", "Sorry, you're incorrect.")

    newServer := houston.NewServer(&r, &houston.ServerConfig{
        // Set the server up with a TLS certificate and key.
        // Self-signed is acceptable and normal in Gemini.
        CertificatePath: "cert/localhost.crt",
        KeyPath: "cert/localhost.key",

        // If you're trying to put your server into production,
        // you'll want to specify a hostname different from
        // `localhost`. Port defaults to 1965.
        Hostname: "",
        Port: 1965,

        // You can enable connection logging with Houston.
        // Toggle the boolean flag, and give it the log file's
        // path, and it will record connections to your capsule.
        EnableLog: true,
        LogFilePath: "houston.log",

        // Houston comes with a rate-limiter (token-bucket algorithm),
        // and can be enabled and configured. By default `MaxRate` and
        // `BucketSize` are set to 2. These are good defaults for most.
        EnableLimiting: true,
        MaxRate: 2,
        BucketSize: 2,

    // With our router's URL endpoints set up, and the server
    // options configured, the server can be started up. Just SIGTERM
    // to stop it (Ctrl+C for most machines/terminals).

More Info

Router structs are used by Server structs to provide functionality for handling request-to-response. Routers can have Route and Sandbox instances. They can be added to a router by doing Router.Handle(url, func (net.Conn) {}) or Router.Sandbox(url, sandboxDirPath).

Route instances connect a URL path to a function that is executed when it’s visited.

Sandbox instances connect a URL path to a local directory that holds static files. For example, if you connect /hello to local dir /hello-static, and /hello-static has a file named index.gmi in it, and someone visits /hello, it will attempt to load the file /hello-static/index.gmi. Or any other file specified from that dir.

Context instances provide the URL of a connection, the actual net.Conn of the connection, some methods for conveniently sending responses, and other features.




This section is empty.


This section is empty.


func GetMimetypeFromPath

func GetMimetypeFromPath(targetPath string) string

func Template added in v0.3.0

func Template(file string, data interface{}) (string, error)


type Context

type Context struct {
	URL        string
	Connection net.Conn

func NewContext

func NewContext(newUrl string, newConn net.Conn) Context

func (*Context) BadRequest

func (ctx *Context) BadRequest(info string)

func (*Context) CGIError

func (ctx *Context) CGIError(info string)

func (*Context) CertNotAuthorized

func (ctx *Context) CertNotAuthorized(info string)

func (*Context) CertNotValid

func (ctx *Context) CertNotValid(info string)

func (*Context) ClientCertRequired

func (ctx *Context) ClientCertRequired(info string)

func (*Context) DelimInputAndDo added in v0.3.1

func (ctx *Context) DelimInputAndDo(prompt string, delim string, handler MultiInputHandler)

Takes a multiple-value query, splits it on the delimiter, and passes the slice of values into the handler.

func (*Context) GetParam added in v0.5.1

func (c *Context) GetParam(key string) (string, error)

func (*Context) GetParams added in v0.5.1

func (c *Context) GetParams() (url.Values, error)

func (*Context) GetQuery

func (c *Context) GetQuery() string

func (*Context) Gone

func (ctx *Context) Gone(info string)

func (*Context) Input

func (ctx *Context) Input(prompt string)

func (*Context) InputAndDo

func (ctx *Context) InputAndDo(prompt string, handler InputHandler)

func (*Context) NotFound

func (ctx *Context) NotFound(info string)

func (*Context) PermFailure

func (ctx *Context) PermFailure(info string)

func (*Context) ProxyError

func (ctx *Context) ProxyError(info string)

func (*Context) ProxyRequestRefused

func (ctx *Context) ProxyRequestRefused(info string)

func (*Context) RedirectPerm

func (ctx *Context) RedirectPerm(url string)

func (*Context) RedirectTemp

func (ctx *Context) RedirectTemp(url string)

func (*Context) SendBytes

func (ctx *Context) SendBytes(mimeType string, content []byte)

Send content data to the client.

func (*Context) SendFile

func (ctx *Context) SendFile(mimeType string, path string) error

func (*Context) SendString

func (ctx *Context) SendString(mimeType string, str string)

func (*Context) SendStringf

func (ctx *Context) SendStringf(mimeType string, str string, values ...interface{})

func (*Context) SendTemplate added in v0.3.0

func (ctx *Context) SendTemplate(mimeType string, path string, data interface{}) error

func (*Context) SensitiveInput

func (ctx *Context) SensitiveInput(prompt string)

func (*Context) SensitiveInputAndDo added in v0.2.1

func (ctx *Context) SensitiveInputAndDo(prompt string, handler InputHandler)

func (*Context) ServerUnavailable

func (ctx *Context) ServerUnavailable(info string)

func (*Context) SlowDown

func (ctx *Context) SlowDown(waitSeconds int)

func (*Context) Success

func (ctx *Context) Success(mimeType string)

func (*Context) TempFail

func (ctx *Context) TempFail(info string)

type InputHandler

type InputHandler func(string)

type LimitedConn added in v0.4.0

type LimitedConn struct {
	IP          string
	Limiter     *rate.Limiter
	Reservation *rate.Reservation

Rate-limited connection. Stores the IP address, and the pointer to the rate-limiter it's associated with.

type MultiInputHandler added in v0.3.1

type MultiInputHandler func([]string)

type Route

type Route struct {
	Path    string
	Handler RouteHandler

type RouteHandler

type RouteHandler func(Context)

type Router

type Router struct {
	Routes       []Route
	Sandboxes    []Sandbox
	ErrorHandler RouteHandler

func BlankRouter

func BlankRouter() Router

func NewRouter

func NewRouter(config RouterOpts) Router

func (*Router) GetRouteHandler

func (r *Router) GetRouteHandler(targetPath string) RouteHandler

Get the handler for a given route. If no route matches a handler, then return the default error handler.

func (*Router) Handle added in v0.3.3

func (r *Router) Handle(targetPath string, handler RouteHandler)

func (*Router) Sandbox added in v0.3.3

func (r *Router) Sandbox(targetPath string, sandboxDirPath string)

type RouterOpts

type RouterOpts struct {
	ErrorHandler RouteHandler

type Sandbox

type Sandbox struct {
	Path      string
	LocalPath string

Sandboxes represent "static" file directories where having code execute upon URL visitation is not necessary.

type Server

type Server struct {
	TLSConfig *tls.Config
	Router    *Router
	Config    ServerConfig

func NewServer

func NewServer(router *Router, config *ServerConfig) Server

func (*Server) Start

func (s *Server) Start()

type ServerConfig added in v0.4.0

type ServerConfig struct {
	// TLS certificate and key file paths.
	CertificatePath string
	KeyPath         string

	// Self-explanatory.
	Hostname string
	Port     uint16

	// Whether connection logging should occur,
	// and where the file should be located.
	EnableLog   bool
	LogFilePath string
	// This can be accessed during the server's lifetime to
	// manually write more info to the config, other than what's
	// built-in to Houston.
	LogFile *os.File

	// Whether the server should apply rate-limiting.
	EnableLimiting bool
	MaxRate        rate.Limit
	BucketSize     int

	// Example: file exists at `/page.gmi`, and user requests `/page`. Should
	// the server append the `.gmi` and return `/page.gmi`? This assumption will
	// happen before handler functions are checked.
	ImplyExtension bool

Jump to

Keyboard shortcuts

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