hg

package module
v0.0.0-...-0fd17a8 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: MIT Imports: 29 Imported by: 1

README

go-hg ☿

Package hg provides ☿ Mercury Protocol and Gemini Protocol client and server implementations, for the Go programming-language (golang).

The hg package provides an API in a style similar to the "net/http" package that is part of the Go standard library, including support for "middleware".

Documentation

Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-hg

GoDoc

Mercury Protocol

The ☿ Mercury Protocol is a simple client-server protocol.

The ☿ Mercury Protocol is derived from the Gemini Protocol — basically the Mercury Protocol is the Gemini Protocol without the TLS encryption. In a sense, the ☿ Mercury Protocol is a “naked” form of the Gemini Protocol.

The ☿ Mercury Protocol, through the Gemini Protocol, was inspired by the Gopher Protocol.

Gemini Protocol Server from a ☿ Mercury Protocol Server

► To turn a ☿ Mercury Protocol server into a Gemini Protocol server, launch the ☿ Mercury Protocol server on the address "localhost:1961" (rather than the usual ":1961"), and then put a TLS proxy server in front of it (listening at ":1965") that modifies any "gemini://..." URI in the Gemini Protocol request into a "mercury://..." URI before sending it to the ☿ Mercury Protocol server.

(Or modify your handlers to accept both "mercury://..." and "gemini://..." URIs.)

Example ☿ Mercury Protocol Server

A very simple ☿ Mercury Protocol server might look like this:

package main

import (
	"github.com/reiver/go-hg"

	"context"
	"fmt"
	"os"
)

func main() {

	const address = ":1961"

	var handler hg.Handler = hg.HandlerFunc(serveMercury)

	err := hg.ListenAndServe(address, handler)

	if nil != err {
		fmt.Fprintln(os.Stderr, "problem with ☿ Mercury Protocol server:", err)
		os.Exit(1)
		return
	}
}

func serveMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	w.WriteHeader(ctx, hg.StatusSuccess, "text/plain")
	fmt.Fprintln(w.Writer(ctx), "Hello world!")
}

In this example, the ☿ Mercury Protocol server just outputs a file with the contents “Hello world!”.

If you wanted to write your own ☿ Mercury Protocol server based on this code, then you would change what is inside the serveMercury() function.

Example ☿ Mercury Protocol Client

A very simple ☿ Mercury Protocol client might look like this:

package main

import (
	"github.com/reiver/go-hg"

	"context"
	"fmt"
	"io"
	"os"
)

func main() {

	const address =       "example.com:1961"
	const uri = "mercury://example.com/apple/banana/cherry.txt"

	var request hg.Request
	err := request.Parse(uri)

	if nil != err {
		fmt.Fprintln(os.Stderr, "problem parsing URI:", err)
		os.Exit(1)
		return
	}

	responsereader, err := hg.DialAndCall(context.Background(), address, request)
	if nil != err {
		fmt.Fprintln(os.Stderr, "problem with request:", err)
		os.Exit(1)
		return
	}
	defer responsereader.Close()

	var ctx context.Context = context.Background()
	io.Copy(os.Stdout, responsereader.Reader(ctx))
}

In this code, the download file is just outputted to STDOUT. You could modify this code to do whatever you want.

Note that we can do more sophisticated things by inspecting the error that was returned. To deal with redirects, etc.

So, we could do that with code like the following:

package main

import (
	"github.com/reiver/go-hg"

	"context"
	"fmt"
	"io"
	"os"
)

func main() {

	const address =       "example.com:1961"
	const uri = "mercury://example.com/apple/banana/cherry.gmni"

	var request hg.Request
	err := request.Parse(uri)

	if nil != err {
		fmt.Fprintf(os.Stderr, "problem parsing URI %q: %s\n", uri, err)
		os.Exit(1)
		return
	}

	ctx := context.Background()
	responsereader, err := hg.DialAndCall(ctx, address, request)
	if nil != err {
		fmt.Fprintf(os.Stderr, "problem dialing and connecting to %q: %s", uri, err)
		os.Exit(1)
		return
	}
	defer responsereader.Close()

	_, err = io.Copy(os.Stdout, responsereader.Reader(context.Background()))
	if nil != err {
		fmt.Fprintf(os.Stderr, "problem outputing response body for %q to STDOUT: %s", uri, err)
		os.Exit(1)
		return
	}
}

Hypermedia, Hypertext

The ☿ Mercury Protocol and the Gemini Protocol are often used with a (specific) hypermedia & hypertext file data format known as gemtext.

(The name “gemtext” is short for “gemini text”.)

Gemtext is a formatted text file data format similar to markdown, and inspired by the line typing convention in Gopher.

Here is an example gemtext file:

# Joe Blow's Capsule

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nibh cras pulvinar mattis nunc sed blandit libero volutpat. Tellus mauris a diam maecenas. Quis enim lobortis scelerisque fermentum dui faucibus. Sed id semper risus in hendrerit gravida rutrum quisque non. Pretium vulputate sapien nec sagittis. Ut aliquam purus sit amet luctus venenatis lectus magna fringilla. Scelerisque eleifend donec pretium vulputate sapien. A lacus vestibulum sed arcu non odio. Lacus luctus accumsan tortor posuere ac. Vestibulum lectus mauris ultrices eros in cursus. Id nibh tortor id aliquet lectus proin nibh nisl. Fermentum et sollicitudin ac orci. Id faucibus nisl tincidunt eget nullam non nisi. Mi quis hendrerit dolor magna eget est lorem ipsum dolor. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Ut enim blandit volutpat maecenas. Arcu dui vivamus arcu felis.

Eget aliquet nibh praesent tristique magna sit amet. Mi bibendum neque egestas congue quisque egestas diam in. Massa eget egestas purus viverra accumsan in nisl nisi. Ultricies integer quis auctor elit sed vulputate. Sed odio morbi quis commodo odio aenean sed. Sed sed risus pretium quam vulputate. Feugiat in fermentum posuere urna. Tincidunt praesent semper feugiat nibh sed. Non sodales neque sodales ut etiam. Sapien eget mi proin sed libero enim. Vel facilisis volutpat est velit egestas. Purus viverra accumsan in nisl nisi scelerisque. Laoreet sit amet cursus sit amet dictum. Sollicitudin ac orci phasellus egestas tellus rutrum.

=> mercury://example.com/once/twice/thrice/fource.txt Tortor aliquam nulla facilisi cras.

Some of the built-in handlers in this package will output gemtext.

Mercury Protocol + TLS = Gemini Protocol

One can turn a ☿ Mercury Protocol server into a Gemini Protocol server by, very roughly, putting a TLS layer over top of it (and dealing the 6x response status codes).

If one wants to have a Gemini Protocol server, but handle the TLS encryption at another level from server, then (using this package and) setting up a Mercury Protocol server can enable that.

Example Mercury Protocol Server

A very very simple ☿ Mercury Protocol server is shown in the following code.

This particular ☿ Mercury Protocol server just responds to the client with the URI that was in the request, plus the remote address.

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	var handler hg.Handler = hg.DebugHandler

	err := hg.ListenAndServe(":1961", handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

Tilde Example Mercury Protocol Server

Another example ☿ Mercury Protocol server is shown in the following code:

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	var handler hg.Handler = &hg.UserDirHandler{}

	err := hg.ListenAndServe(":1961", handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

Here the handler — hg.UserDirHandler — operates similar to Apache's HTTP Server Project's mod_userdir — in that it enables user-specific directories such as /home/username/mercury_public/ to be accessed over the Mercury Protocol using the tilde path mercury://example.com/~username/

Example Mercury Protocol Servers With Custom Handler

And finally, here is a custom handler being used in a ☿ Mercury Protocol server:

package main

import (
	"github.com/reiver/go-hg"

	"context"
	"io"
)

func main() {

	var handler hg.Handler = myCustomHandler{}

	err := hg.ListenAndServe(":1961", handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

type myCustomHandler struct{}

func (receiver myCustomHandler) ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	io.WriteString(w.Writer(ctx), "Hello world!")
}

Alternatively, this could be made a bit simpler if hg.HandlerFunc() is used:

package main

import (
	"github.com/reiver/go-hg"

	"context"
	"io"
)

func main() {

	var handler hg.Handler = hg.HandlerFunc(helloworld)

	err := hg.ListenAndServe(":1961", handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func helloworld(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	io.WriteString(w.Writer(ctx), "Hello world!")
}

Mercury Protocol Response Helpers

This package provides a number of helper-functions that make responding to a ☿ Mercury Protocol request easier. The helper functions are:

Mercury Protocol Response Basic Usage Intermediate Usage
10 INPUT hg.ServeInput(ctx, w, prompt...)
11 SENSITIVE INPUT hg.ServeSensitiveInput(ctx, w, prompt...)
20 SUCCESS
30 REDIRECT - TEMPORARY hg.ServeRedirectTemporary(ctx, w, url)
31 REDIRECT - PERMANENT hg.ServeRedirectPermanent(ctx, w, url)
40 TEMPORARY FAILURE hg.ServeTemporaryFailure(ctx, w) hg.ServeTemporaryFailure(ctx, w, info)
41 SERVER UNAVAILABLE hg.ServeServerUnavailable(ctx, w) hg.ServeServerUnavailable(ctx, w, info)
42 CGI ERROR hg.ServeCGIError(ctx, w) hg.ServeCGIError(ctx, w, info)
43 PROXY ERROR hg.ServeProxyError(ctx, w) hg.ServeProxyError(ctx, w, info)
44 SLOW DOWN hg.ServeSlowDown(ctx, w, retryAfter)
50 PERMANENT FAILURE hg.ServePermanentFailure(ctx, w) hg.ServePermanentFailure(ctx, w, info)
51 NOT FOUND hg.ServeNotFound(ctx, w) hg.ServeNotFound(ctx, w, info)
52 GONE hg.ServeGone(ctx, w) hg.ServeGone(ctx, w, info)
53 PROXY REQUEST REFUSED hg.ServeProxyRequestRefused(ctx, w) hg.ServeProxyRequestRefused(ctx, w, info)
59 BAD REQUEST hg.ServeBadRequest(ctx, w) hg.ServeBadRequest(ctx, w, info)

Import

To import package hg use import code like the following:

import "github.com/reiver/go-hg"

Installation

To install package hg do the following:

GOPROXY=direct go get github.com/reiver/go-hg

Author

Package hg was written by Charles Iliya Krempeaux

Package Name

The package name of this Go package is hg rather than mercury because Hg is often used as a shorthand for mercury.

Nowadays the word mercury is used to refer to multiple things — a Roman god named “Mercury”, a chemical element named “mercury”, a planet named “mercury”, a space-mission named “Project Mercury”, and now also a network protocol named the “Mercury Protocol”.

The relationship between these different things named “mercury” is as follows —

The Mercury Protocol was named after the Project Mercury space-mission.

The Project Mercury space-mission was named after the Roman god named Mercury. The Project Mercury space-mission also used a modified version astrological-symbol for the planet mercury (☿) for its logo.

The chemical-element mercury was also named after Roman god named Mercury.

An older name for the chemical-element mercury is hydrargyrum.

“Hydrargyrum” is a romanized version of the ancient Greek word “ὑδράργυρος” (hydrargyros). The ancient Greek word “ὑδράργυρος” (hydrargyros) is a compound word: “ὑδρ” + “άργυρος”. The first part ὑδρ- (hydr-) comes from the root ὕδωρ water (although in this context it might be more accurate to interpret it as liquid rather than water). The second part ἄργυρος (argyros) means silver (although in this context it might be more accurate to interpret it as shiny rather than silver). So ὑδράργυρος” (hydrargyros) is water-silver, although perhaps more accurately interpretted as liquid-shiny

“Hg” is the chemical-symbol for the chemical-element mercury because “Hg” is short for “hydrargyrum”.

And thus this, a package that implements the Mercury Protocol, is named hg.

██╗░░██╗██╗░░░██╗██████╗░██████╗░░█████╗░██████╗░░██████╗░██╗░░░██╗██████╗░██╗░░░██╗███╗░░░███╗
██║░░██║╚██╗░██╔╝██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝░╚██╗░██╔╝██╔══██╗██║░░░██║████╗░████║
███████║░╚████╔╝░██║░░██║██████╔╝███████║██████╔╝██║░░██╗░░╚████╔╝░██████╔╝██║░░░██║██╔████╔██║
██╔══██║░░╚██╔╝░░██║░░██║██╔══██╗██╔══██║██╔══██╗██║░░╚██╗░░╚██╔╝░░██╔══██╗██║░░░██║██║╚██╔╝██║
██║░░██║░░░██║░░░██████╔╝██║░░██║██║░░██║██║░░██║╚██████╔╝░░░██║░░░██║░░██║╚██████╔╝██║░╚═╝░██║
╚═╝░░╚═╝░░░╚═╝░░░╚═════╝░╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝░╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝░╚═════╝░╚═╝░░░░░╚═╝

See Also

Documentation

Index

Constants

View Source
const (
	DefaultMetaInput          = StatusTextInput          // 10
	DefaultMetaSensitiveInput = StatusTextSensitiveInput // 11

	DefaultMetaSuccess = StatusTextSuccess // 20

	DefaultMetaTemporaryFailure  = StatusTextTemporaryFailure  // 40
	DefaultMetaServerUnavailable = StatusTextServerUnavailable // 41
	DefaultMetaCGIError          = StatusTextCGIError          // 42
	DefaultMetaProxyError        = StatusTextProxyError        // 43
	DefaultMetaSlowDown          = "3"                         // 44

	DefaultMetaPermanentFailure    = StatusTextPermanentFailure    // 50
	DefaultMetaNotFound            = StatusTextNotFound            // 51
	DefaultMetaGone                = StatusTextGone                // 52
	DefaultMetaProxyRequestRefused = StatusTextProxyRequestRefused // 53
	DefaultMetaBadRequest          = StatusTextBadRequest          // 59
)

These are constants that can be used as default values for a Mercury Protocol's response‐header's meta.

For example usage:

hg.ServeNotFound(ctx, w, hg.DefaultMetaNotFound)

Also for another example usage:

hg.ServeTemporaryFailure(ctx, w, hg.DefaultMetaTemporaryFailure)

To understand these —

The Mercury Protocol is based on Gemini Protocol. And therefore a Mercury Protocol response‐header's structure is defined in the Gemini Protocol's specification. In the Gemini Protocol specification, the response‐header is described as follows:

<STATUS><SPACE><META><CR><LF>

In Go code, this (the Mercury Protocol's response‐header) is equivalent to:

twoDigitStatusNumericalCode + " " + meta + "\r\n"

For most Mercury Protocol response types, the value of the response‐header's ‘meta’ is likely cosmetic. And possibly, no human will ever see them (depending on whether the client software presents them to the user or not). The following constants provide useful default values for these cosmetic meta value's, that can make a programmer's life easier when developing a Mercury Protocol client or server:

  • DefaultMetaSuccess = "success" // 20
  • DefaultMetaTemporaryFailure = "temporary-failure" // 40
  • DefaultMetaServerUnavailable = "server-unavailable" // 41
  • DefaultMetaCGIError = "cgi-error" // 42
  • DefaultMetaProxyError = "proxy-error" // 43
  • DefaultMetaPermanentFailure = "permanent-failure" // 50
  • DefaultMetaNotFound = "not-found" // 51
  • DefaultMetaGone = "gone" // 52
  • DefaultMetaProxyRequestRefused = "proxy-request-refused" // 53
  • DefaultMetaBadRequest = "bad-request" // 59

Two of these default response‐header’s ‘meta’ are (not cosmetic but are) shown to the user. The programmer SHOULD create their own message; but just in case they don't, these default values exist:

• DefaultMetaInput = "input" // 10

• DefaultMetaSensitiveInput = "sensitive-input" // 11

In addition to these, one of these default response‐header is functional.

DefaultMetaSlowDown = "3" // 44

For status 44, the meta is the number of seconds the client MUST wait before making another request. So "3" means "wait 3 seconds before retrying". This SHOULD be chosen by the programmer; but just in case they don't, a default value exists.

View Source
const (
	// A Mercury Protocol server runs over TCP.
	// TCP has communications happening over TCP-ports.
	// A client-server protocol (including the Mercury Protocol) typically defines a default-TCP-port for servers.
	// For the Mercury Protocol, this default-TCP-port is: 1961.
	//
	// This constant — ‘DefaultTCPPort’ — can be used when one wants to use the default-TCP-port for a Mercury Protocol server.
	//
	// For example:
	//
	//	var domain string = "example.com"
	//
	//	var address string = fmt.Sprintf("%s:%d", domain, hg.DefaultTCPPort)
	//
	//	err := hg.ListenAndServe(address, handler)
	DefaultTCPPort       = 1961
	DefaultTCPPortString = "1961"

	// A Gemini Protocol server runs over TLS over TCP.
	// TCP has communications happening over TCP-ports.
	// A client-server protocol (including the Gemini Protocol) typically defines a default-TCP-port for servers.
	// For the Gemini Protocol, this default-TCP-port is: 1965.
	//
	// This constant — ‘DefaultTCPPortTLS’ — can be used when one wants to use the default-TCP-port for a Gemini Protocol server.
	//
	// For example:
	//
	//	var domain string = "example.com"
	//
	//	var address string = fmt.Sprintf("%s:%d", domain, hg.DefaultTCPPortTLS)
	//
	//	err := hg.ListenAndServe(address, handler)
	DefaultTCPPortTLS       = 1965
	DefaultTCPPortTLSString = "1965"
)
View Source
const (
	ErrBadStatusCode             = erorr.Error("bad status code")
	ErrBadResponseHeaderMeta     = erorr.Error("bad response header meta")
	ErrBadTCPAddr                = erorr.Error("bad TCP address")
	ErrCannotParse               = erorr.Error("cannot parse")
	ErrContextDone               = erorr.Error("context done")
	ErrDialError                 = erorr.Error("dial error")
	ErrNilNetworkConnection      = erorr.Error("nil network connection")
	ErrNilReceiver               = erorr.Error("nil receiver")
	ErrNilResponseReader         = erorr.Error("nil response reader")
	ErrNilResponseWriter         = erorr.Error("nil response writer")
	ErrRequestIsNothing          = erorr.Error("request is nothing")
	ErrResponseHeaderMetaTooBig  = erorr.Error("response header meta too big")
	ErrSchemeUnsupported         = erorr.Error("scheme unsupported")
	ErrServerCertificateNotFound = erorr.Error("server did not present any certificate(s)")
	ErrServerShutdown            = erorr.Error("server shutdown")
	ErrTargetTypeUnsupported     = erorr.Error("target type unsupported")
	ErrWriteError                = erorr.Error("write error")
)
View Source
const (
	Scheme    = "mercury"
	SchemeTLS = "gemini"
)
View Source
const (
	StatusInput          = 10
	StatusSensitiveInput = 11

	StatusSuccess = 20

	StatusRedirectTemporary = 30
	StatusRedirectPermanent = 31

	StatusTemporaryFailure  = 40
	StatusServerUnavailable = 41
	StatusCGIError          = 42
	StatusProxyError        = 43
	StatusSlowDown          = 44

	StatusPermanentFailure    = 50
	StatusNotFound            = 51
	StatusGone                = 52
	StatusProxyRequestRefused = 53
	StatusBadRequest          = 59

	// Gemini Protocol only.
	// Not used in the Mercury Protocol.
	StatusCertificateRequired      = 60
	StatusCertificateNotAuthorized = 61
	StatusCertificateNotValid      = 62
)

Constants for the Mercury Protocol status codes.

Can use, for example, with ResponseWriter's WriteHeader method.

For example:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	w.WriteHeader(ctx, hg.StatusNotFound, "uh oh!")
}
View Source
const (
	StatusTextInput          = "input"           // 10
	StatusTextSensitiveInput = "sensitive-input" // 11

	StatusTextSuccess = "success" // 20

	StatusTextRedirectTemporary = "temporary-redirection" // 30
	StatusTextRedirectPermanent = "permanent-redirection" // 31

	StatusTextTemporaryFailure  = "temporary-failure"  // 40
	StatusTextServerUnavailable = "server-unavailable" // 41
	StatusTextCGIError          = "cgi-error"          // 42
	StatusTextProxyError        = "proxy-error"        // 43
	StatusTextSlowDown          = "slow-down"          // 44

	StatusTextPermanentFailure    = "permanent-failure"     // 50
	StatusTextNotFound            = "not-found"             // 51
	StatusTextGone                = "gone"                  // 52
	StatusTextProxyRequestRefused = "proxy-request-refused" // 53
	StatusTextBadRequest          = "bad-request"           // 59

	// Gemini Protocol only.
	// Not used in the Mercury Protocol.
	StatusTextCertificateRequired      = "certificate-required"       // 60
	StatusTextCertificateNotAuthorized = "certificate-not-authorized" // 61
	StatusTextCertificateNotValid      = "certificate-not-valid"      // 62
)
View Source
const DebugHandler internalDebugHandler = internalDebugHandler(0)

The DebugHandler can be used as:

  • a demo Mercury Protocol server,
  • a debugging tool to use with Mercury Protocol clients.

You can use it with code similar to:

const address = ":1961"

err := hg.ListenAndServe(address, hg.DebugHandler)

Variables

This section is empty.

Functions

func ErrorResponse

func ErrorResponse(statuscode int, meta string) error

ErrorResponse returns the appropriate response type for the given status-code & meta.

And note that any Mercury Protocol response type other that “20 SUCCESS” is considered an error.

Example With Bad Request

So, for example, this:

hg.ErrorResponse(59, "")

Would return:

hg.ResponseBadRequest{meta:""}

Example With Temporary Failure

And, for example, this:

hg.ErrorResponse(40, "we seem to be experiencing some technical difficulties")

Would return:

hg.ResponseTemporaryFailure{meta:"we seem to be experiencing some technical difficulties"}

Example With Success

Although note that calling with a status-code of 20 (i.e., the status code for Success) would return nil. So, for example, this:

hg.ErrorResponse(20, "text/gemini")

Would return

nil

Type Switch

This is useful with type switches. For example:

func callMercury(rr hg.ResponseReader, r hg.Request) {

	// ...

	p, err := io.ReadAll(rr.Reader(ctx))

	if nil != err {
		switch casted := err.(type) {
		case hg.ResponseInput:
			//@TODO
		case hg.ResponseSensitiveInput:
			//@TODO

		case hg.ResponseRedirectTemporary:
			//@TODO
		case hg.ResponseRedirectPermanent:
			//@TODO

		case hg.ResponseTemporaryFailure:
			//@TODO
		case hg.ResponseServerUnavailable:
			//@TODO
		case hg.ResponseCGIError:
			//@TODO
		case hg.ResponseProxyError:
			//@TODO
		case hg.ResponseSlowDown:
			//@TODO

		case hg.ResponsePermanentFailure:
			//@TODO
		case hg.ResponseNotFound :
			//@TODO
		case hg.ResponseGone:
			//@TODO
		case hg.ResponseProxyRequestRefused:
			//@TODO
		case hg.ResponseBadRequest:
			//@TODO

		case hg.ResponseCertificateRequired:
			//@TODO
		case hg.ResponseCertificateNotAuthorized:
			//@TODO
		case hg.ResponseCertificateNotValid:
			//@TODO

		case hg.UnknownResponse:
			//@TODO

		default:
			//@TODO
	}

	// ...

}

func GenerateClientCertificate

func GenerateClientCertificate() (*tls.Certificate, error)

GenerateClientCertificate generates a self-signed TLS client certificate and returns it directly as a *tls.Certificate (including private-key).

This is useful when speaking protocols that use TLS client certificates for identity (such as the Gemini Protocol). In the Gemini Protocol, a server may respond with a "60 CLIENT CERTIFICATE REQUIRED" status code, indicating the client must present a certificate. Gemini clients routinely generate throwaway or per-site self-signed certificates on the fly when this happens.

Example usage:

rr, err := hg.DialAndCallTLS(ctx, addr, request, nil)
// ... get back ResponseCertificateRequired ...

cert, err := hg.GenerateClientCertificate()
rr, err = hg.DialAndCallTLS(ctx, addr, request, hg.TLSConfig{
    ClientCertificateFunc: func(host string) *tls.Certificate {
        return cert
    },
})

See also:

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

ListenAndServe listens on the TCP network address `addr` and then spawns a call to the ServeMercury method on the `handler` to serve each incoming connection.

For a very simple example:

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	//@TODO: In your code, you would probably want to use a different handler.
	var handler hg.Handler = hg.DebugHandler

	err := hg.ListenAndServe(":1961", handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func Serve

func Serve(listener net.Listener, handler Handler) error

Serve accepts an incoming Mercury Protocol client connection on the net.Listener `listener`.

For a very simple example:

package main

import (
	"github.com/reiver/go-hg"

	"net"
)

func main() {

	listener, err := net.Listen("tcp", ":1961")
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}

	//@TODO: In your code, you would probably want to use a different handler.
	var handler hg.Handler = hg.DebugHandler

	err = hg.Serve(listener, handler)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func ServeBadRequest

func ServeBadRequest(ctx context.Context, w ResponseWriter, a ...any) error

59 BAD REQUEST

This function sends a “59 BAD REQUEST” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "you did not enter a number"

	hg.ServeBadRequest(ctx, w, message)

	// ...

}

func ServeCGIError

func ServeCGIError(ctx context.Context, w ResponseWriter, a ...any) error

42 CGI ERROR

This function sends a “42 CGI ERROR” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "the program being run just had an unexpected fatal error"

	hg.ServeCGIError(ctx, w, message)

	// ...

}

func ServeGone

func ServeGone(ctx context.Context, w ResponseWriter, a ...any) error

52 GONE

This function sends a “52 GONE” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "he's dead jim"

	hg.ServeGone(ctx, w, message)

	// ...

}

func ServeInput

func ServeInput(ctx context.Context, w ResponseWriter, a ...any) error

10 INPUT

This function sends a “10 INPUT” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var prompt string = "Pick a number between 1 and 10"

	hg.ServeInput(ctx, w, prompt)

	// ...

}

func ServeNotFound

func ServeNotFound(ctx context.Context, w ResponseWriter, a ...any) error

51 NOT FOUND

This function sends a “51 NOT FOUND” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "this is not the gem-page you are looking for"

	hg.ServeNotFound(ctx, w, message)

	// ...

}

func ServePermanentFailure

func ServePermanentFailure(ctx context.Context, w ResponseWriter, a ...any) error

50 PERMANENT FAILURE

This function sends a “50 PERMANENT FAILURE” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "someone deleted the database"

	hg.ServePermanentFailure(ctx, w, message)

	// ...

}

func ServeProxyError

func ServeProxyError(ctx context.Context, w ResponseWriter, a ...any) error

43 PROXY ERROR

This function sends a “43 PROXY ERROR” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "the proxy server providing TLS encryption errored out"

	hg.ServeProxyError(ctx, w, message)

	// ...

}

func ServeProxyRequestRefused

func ServeProxyRequestRefused(ctx context.Context, w ResponseWriter, a ...any) error

53 PROXY REQUEST REFUSED

This function sends a “53 PROXY REQUEST REFUSED” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "you did not enter a number"

	hg.ServeProxyRequestRefused(ctx, w, message)

	// ...

}

func ServeRedirectPermanent

func ServeRedirectPermanent(ctx context.Context, w ResponseWriter, target string) error

31 REDIRECT - PERMANENT

This function sends a “31 REDIRECT - PERMANENT” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	//var url string = "/apple/banana/cherry.txt"
	//var url string = "documents/info.txt"
	var url string = "mercury://example.com/once/twice/thrice/fource.txt"

	hg.ServeRedirectPermanent(ctx, w, url)

	// ...

}

func ServeRedirectTemporary

func ServeRedirectTemporary(ctx context.Context, w ResponseWriter, target string) error

30 REDIRECT - TEMPORARY

This function sends a “30 REDIRECT - TEMPORARY” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	//var url string = "/apple/banana/cherry.txt"
	//var url string = "documents/info.txt"
	var url string = "mercury://example.com/once/twice/thrice/fource.txt"

	hg.ServeRedirectTemporary(ctx, w, url)

	// ...

}

func ServeSensitiveInput

func ServeSensitiveInput(ctx context.Context, w ResponseWriter, a ...any) error

11 SENSITIVE INPUT

This function sends a “11 SENSITIVE INPUT” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var prompt string = "Please enter your password"

	hg.ServeSensitiveInput(ctx, w, prompt)

	// ...

}

func ServeServerUnavailable

func ServeServerUnavailable(ctx context.Context, w ResponseWriter, a ...any) error

41 SERVER UNAVAILABLE

This function sends a “41 SERVER UNAVAILABLE” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "we are upgrading the server"

	hg.ServeServerUnavailable(ctx, w, message)

	// ...

}

func ServeSlowDown

func ServeSlowDown(ctx context.Context, w ResponseWriter, numberOfSecondsToWait uint) error

44 SLOW DOWN

This function sends a “44 SLOW DOWN” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var numberOfSecondsToWait uint = 8

	hg.ServeSlowDown(ctx, w, numberOfSecondsToWait)

	// ...

}

func ServeTemporaryFailure

func ServeTemporaryFailure(ctx context.Context, w ResponseWriter, a ...any) error

40 TEMPORARY FAILURE

This function sends a “40 TEMPORARY FAILURE” Mercury Protocol response.

Example Usage

This is how one might use this helper-function:

func ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

	var message string = "try again later"

	hg.ServeTemporaryFailure(ctx, w, message)

	// ...

}

func StatusText

func StatusText(code int) string

Types

type Field

type Field = field.StringlyField

type FileSystemHandler

type FileSystemHandler struct {
	Root   fs.FS
	Logger Logger
}

FileSystemHandler is used to create a Mercury Protocol server that serves files from a fs.FS file system.

For example usage:

var fshandler hg.FileSystemHandler
fshandler.Root = os.DirFS("/path/to/mercury/root")

var handler hg.Handler = &fshandler

err := hg.ListenAndServe(":1961", handler)

func (FileSystemHandler) ServeMercury

func (receiver FileSystemHandler) ServeMercury(ctx context.Context, w ResponseWriter, r Request)

type Handler

type Handler interface {
	ServeMercury(ctx context.Context, w ResponseWriter, r Request)
}

Handler represents something that responds to a Mercury Protocol request.

Typically, someone who wants to create a custom Mercury Protocol server would create a type that fits this Handler interface, and then (directly or indirectly) pass it to the Serve or ListenAndServe functions.

type HandlerFunc

type HandlerFunc func(context.Context, ResponseWriter, Request)

HandlerFunc is an adapter that allows one to turn a function into a Handler if the function has the same signature as ServeMercury.

For example:

func doIt(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	// ...
}

// ...

var handler hg.Handler = hg.HandlerFunc(doIt)

func (HandlerFunc) ServeMercury

func (fn HandlerFunc) ServeMercury(ctx context.Context, w ResponseWriter, r Request)

type Logger

type Logger interface {
	Begin(fields ...Field) Logger
	End(fields ...Field)
	Error(fields ...Field)
	Debug(fields ...Field)
	Trace(fields ...Field)
}

type Request

type Request struct {
	// contains filtered or unexported fields
}

Request represents a Mercury Protocol request — either received by a server, or being sent by a client.

A client may create a Mercury Protocol request with code similar to the following:

var request hg.Request

err := request.Parse("mercury://example.com/apple/banana/cherry.txt")

A server would receive the request as a parameter to the ServeMercury method:

func (receiver Type) ServeMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {
	// ...
}

NOTE that the .Parse() methods will accept non-mercury URIs. For example:

err := request.Parse("gemini://example.com/apple/banana/cherry.txt")

func (Request) IsNothing

func (receiver Request) IsNothing() bool

func (Request) MarshalText

func (receiver Request) MarshalText() ([]byte, error)

MarshalText makes Request fit the encoding.TextMarshaler interface.

func (*Request) Parse

func (receiver *Request) Parse(src any) error

Parse parses the input ‘value’ and if valid sets the value of the request.

Note that ‘value’ should NOT include the trailing carriage-return and line-feed.

Example Usage:

var request hg.Request

err := request.Parse("mercury://example.com/apple/banana/cherry.txt")

func (Request) RequestValue

func (receiver Request) RequestValue() string

RequestValue returns the value of the request without the trailing "\r\n"

For example, if the full value of the request was:

"mercury://example.com/path/to/file.txt\r\n"

Then RequestValue would return:

"mercury://example.com/path/to/file.txt"

func (Request) Scheme

func (receiver Request) Scheme() string

Scheme returns the Scheme of the URL/URI/IRI in the request.

For example, if the request is:

"mercury://example.com/once/twice/thrice/fource.gmni\r\n"

Then scheme would return:

"mercury"

And, for example, if the request is:

"gemini://example.com/once/twice/thrice/fource.gmni\r\n"

Then scheme would return:

"gemini"

func (Request) String

func (receiver Request) String() string

String returns the full value of the Mercury request. Note that this included the trailing carriage-return and line-feed.

String makes Request fit the fmt.Stringer interface.

func (Request) TCPAddr

func (receiver Request) TCPAddr() (string, bool)

TCPAddr returns the TCP-address that is embedded in the request.

Example usage for the Mercury Protocol:

var req hg.Request

// ...

addr, found := req.TCPAddr()
if !found {
	return errBadRequest
}

rr, err := hg.DialAndCall(ctx, addr, req)

Example usage for the Gemini Protocol:

var req hg.Request

// ...

addr, found := req.TCPAddr()
if !found {
	return errBadRequest
}

rr, err := hg.DialAndCallTLS(ctx, addr, req)

See also:

func (*Request) UnmarshalText

func (receiver *Request) UnmarshalText(text []byte) error

UnmarshalText makes Request fit the encoding.TextUnmarshaler interface.

func (Request) WriteTo

func (receiver Request) WriteTo(w io.Writer) (int64, error)

WriteTo writes the value of the Mercury request (including the trailing carriage-return and line-feed) to ‘w’ until there's no more to write or when an error occurs. The return value ‘n’ is the number of bytes written. Any error encountered during the write is also returned.

type ResponseBadRequest

type ResponseBadRequest struct {
	// contains filtered or unexported fields

} // 59

ResponseBadRequest represents a Mercury Protocol “59 BAD REQUEST” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseBadRequest) Error

func (receiver ResponseBadRequest) Error() string

func (ResponseBadRequest) Meta

func (receiver ResponseBadRequest) Meta() string

func (ResponseBadRequest) StatusCode

func (receiver ResponseBadRequest) StatusCode() int

func (ResponseBadRequest) StatusText

func (receiver ResponseBadRequest) StatusText() string

type ResponseCGIError

type ResponseCGIError struct {
	// contains filtered or unexported fields

} // 42

ResponseCGIError represents a Mercury Protocol “42 CGI ERROR” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseCGIError) Error

func (receiver ResponseCGIError) Error() string

func (ResponseCGIError) Meta

func (receiver ResponseCGIError) Meta() string

func (ResponseCGIError) StatusCode

func (receiver ResponseCGIError) StatusCode() int

func (ResponseCGIError) StatusText

func (receiver ResponseCGIError) StatusText() string

type ResponseCertificateNotAuthorized

type ResponseCertificateNotAuthorized struct {
	// contains filtered or unexported fields

} // 61

ResponseCertificateNotAuthorized represents a "61 CERTIFICATE NOT AUTHORIZED" response (Gemini Protocol only). You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseCertificateNotAuthorized) Error

func (receiver ResponseCertificateNotAuthorized) Error() string

func (ResponseCertificateNotAuthorized) Meta

func (receiver ResponseCertificateNotAuthorized) Meta() string

func (ResponseCertificateNotAuthorized) StatusCode

func (receiver ResponseCertificateNotAuthorized) StatusCode() int

func (ResponseCertificateNotAuthorized) StatusText

func (receiver ResponseCertificateNotAuthorized) StatusText() string

type ResponseCertificateNotValid

type ResponseCertificateNotValid struct {
	// contains filtered or unexported fields

} // 62

ResponseCertificateNotValid represents a "62 CERTIFICATE NOT VALID" response (Gemini Protocol only). You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseCertificateNotValid) Error

func (receiver ResponseCertificateNotValid) Error() string

func (ResponseCertificateNotValid) Meta

func (receiver ResponseCertificateNotValid) Meta() string

func (ResponseCertificateNotValid) StatusCode

func (receiver ResponseCertificateNotValid) StatusCode() int

func (ResponseCertificateNotValid) StatusText

func (receiver ResponseCertificateNotValid) StatusText() string

type ResponseCertificateRequired

type ResponseCertificateRequired struct {
	// contains filtered or unexported fields

} // 60

ResponseCertificateRequired represents a "60 CLIENT CERTIFICATE REQUIRED" response (Gemini Protocol only). You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseCertificateRequired) Error

func (receiver ResponseCertificateRequired) Error() string

func (ResponseCertificateRequired) Meta

func (receiver ResponseCertificateRequired) Meta() string

func (ResponseCertificateRequired) StatusCode

func (receiver ResponseCertificateRequired) StatusCode() int

func (ResponseCertificateRequired) StatusText

func (receiver ResponseCertificateRequired) StatusText() string

type ResponseGone

type ResponseGone struct {
	// contains filtered or unexported fields

} // 52

ResponseGone represents a Mercury Protocol “52 GONE” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseGone) Error

func (receiver ResponseGone) Error() string

func (ResponseGone) Meta

func (receiver ResponseGone) Meta() string

func (ResponseGone) StatusCode

func (receiver ResponseGone) StatusCode() int

func (ResponseGone) StatusText

func (receiver ResponseGone) StatusText() string

type ResponseInput

type ResponseInput struct {
	// contains filtered or unexported fields

} // 10

ResponseInput represents a Mercury Protocol “10 INPUT” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseInput) Error

func (receiver ResponseInput) Error() string

func (ResponseInput) Meta

func (receiver ResponseInput) Meta() string

func (ResponseInput) StatusCode

func (receiver ResponseInput) StatusCode() int

func (ResponseInput) StatusText

func (receiver ResponseInput) StatusText() string

type ResponseNotFound

type ResponseNotFound struct {
	// contains filtered or unexported fields

} // 51

ResponseNotFound represents a Mercury Protocol “51 NOT FOUND” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseNotFound) Error

func (receiver ResponseNotFound) Error() string

func (ResponseNotFound) Meta

func (receiver ResponseNotFound) Meta() string

func (ResponseNotFound) StatusCode

func (receiver ResponseNotFound) StatusCode() int

func (ResponseNotFound) StatusText

func (receiver ResponseNotFound) StatusText() string

type ResponsePermanentFailure

type ResponsePermanentFailure struct {
	// contains filtered or unexported fields

} // 50

ResponsePermanentFailure represents a Mercury Protocol “50 PERMANENT FAILURE” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponsePermanentFailure) Error

func (receiver ResponsePermanentFailure) Error() string

func (ResponsePermanentFailure) Meta

func (receiver ResponsePermanentFailure) Meta() string

func (ResponsePermanentFailure) StatusCode

func (receiver ResponsePermanentFailure) StatusCode() int

func (ResponsePermanentFailure) StatusText

func (receiver ResponsePermanentFailure) StatusText() string

type ResponseProxyError

type ResponseProxyError struct {
	// contains filtered or unexported fields

} // 43

ResponseProxyError represents a Mercury Protocol “43 PROXY ERROR” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseProxyError) Error

func (receiver ResponseProxyError) Error() string

func (ResponseProxyError) Meta

func (receiver ResponseProxyError) Meta() string

func (ResponseProxyError) StatusCode

func (receiver ResponseProxyError) StatusCode() int

func (ResponseProxyError) StatusText

func (receiver ResponseProxyError) StatusText() string

type ResponseProxyRequestRefused

type ResponseProxyRequestRefused struct {
	// contains filtered or unexported fields

} // 53

ResponseProxyRequestRefused represents a Mercury Protocol “53 PROXY REQUEST REFUSED” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseProxyRequestRefused) Error

func (receiver ResponseProxyRequestRefused) Error() string

func (ResponseProxyRequestRefused) Meta

func (receiver ResponseProxyRequestRefused) Meta() string

func (ResponseProxyRequestRefused) StatusCode

func (receiver ResponseProxyRequestRefused) StatusCode() int

func (ResponseProxyRequestRefused) StatusText

func (receiver ResponseProxyRequestRefused) StatusText() string

type ResponseReader

type ResponseReader interface {
	io.Closer
	Read(ctx context.Context, data []byte) (int, error)
	Reader(ctx context.Context) io.Reader
	ReadHeader(ctx context.Context, statusCode *int, meta any) (int, error)

	// ServerCertificate returns the server's leaf certificate from the TLS handshake.
	// Returns nil for plain (non-TLS) connections or if no server cert was presented.
	ServerCertificate() *x509.Certificate

	// ClientCertificate returns the client certificate (including private-key)
	// that was presented during the TLS handshake.
	// Returns nil if no client cert was used, or for plain (non-TLS) connections.
	ClientCertificate() *tls.Certificate
}

ResponseReader is used by a Handler to read a Mercury Protocol response.

To get io.Reader version of ResponseReader call its `Reader` method. For example:

var rr hg.ResponseReader
var err error

rr, err = hg.DialAndCall(ctx, "example.com:1961", request)

// ...

var reader io.Reader = rr.Reader(ctx)

Alternatively, to use a read-method with a context, just do something similar to:

n, err := rr.Read(ctx, p)

Auto-Reading Header

If Read is called before ReadHeader, it will automatically read and consume the response header. If the status code is not "20 SUCCESS", Read returns an error that can be type-switched or used with errors.As to extract the status code and meta. For example:

n, err := rr.Read(ctx, buf)
if nil != err {
	var notFound hg.ResponseNotFound
	if errors.As(err, &notFound) {
		fmt.Println("not found:", notFound.Meta())
	}
}

The possible error types are:

Each has StatusCode() and Meta() methods.

Note that once Read has consumed the header, calling ReadHeader will return an error. If you need to inspect the header before reading the body, call ReadHeader first.

func Call

func Call(ctx context.Context, conn net.Conn, request Request) (ResponseReader, error)

Call uses the TCP connection provided by 'conn' and (speaking the Mercury Protocol) sends the request given by 'request'.

The context 'ctx' controls the lifetime of the request write. If 'ctx' is nil, context.Background() is used. If 'ctx' has a deadline, it is applied as a write deadline on the connection.

What is given by 'request' might be a Request containing something like: "mercury://example.com/path/to/file.txt"

An example of using this might be:

conn, err := net.Dial("tcp", addr)
if nil != err {
	return err
}

ctx := context.Background()

rr, err := hg.Call(ctx, conn, request)

See also:

func DialAndCall

func DialAndCall(ctx context.Context, addr string, request Request) (ResponseReader, error)

DialAndCall makes a TCP connection to the TCP address given by 'addr', and (speaking the Mercury Protocol) sends the request given by 'request'.

The context 'ctx' controls the lifetime of the dial and the request write. If 'ctx' is nil, context.Background() is used. To apply a timeout, use context.WithTimeout or context.WithDeadline.

What is given by 'addr' might be something like: "11.22.33.44:1961", or "example.com:1961"

What is given by 'request' might be a Request containing something like: "mercury://example.com/path/to/file.txt\r\n"

An example of using this might be:

var uri string = "mercury://example.com/once/twice/thrice/fource.gmni"

var request hg.Request
err := request.Parse(uri)
if nil != err {
	return err
}

addr, found := request.TCPAddr()
if !found {
	return errBadRequest
}

ctx := context.Background()

rr, err := hg.DialAndCall(ctx, addr, request)

See also:

func DialAndCallTLS

func DialAndCallTLS(ctx context.Context, addr string, request Request, tlsHandler TLSHandler) (ResponseReader, error)

DialAndCallTLS makes a TLS connection to the TCP address given by 'addr', and sends the request given by 'request'.

This can be used to speak protocols that layer on TLS (such as the Gemini Protocol).

The context 'ctx' controls the lifetime of the dial and the request write. If 'ctx' is nil, context.Background() is used. To apply a timeout, use context.WithTimeout or context.WithDeadline.

The 'tlsHandler' controls server certificate verification and client certificate selection. Passing nil means: accept any server certificate, no client cert.

What is given by 'addr' might be something like: "11.22.33.44:1965", or "example.com:1965"

What is given by 'request' might be a Request containing something like: "gemini://example.com/path/to/file.txt\r\n"

An example of using this might be:

var uri string = "gemini://example.com/once/twice/thrice/fource.gmni"

var request hg.Request
err := request.Parse(uri)
if nil != err {
	return err
}

addr, found := request.TCPAddr()
if !found {
	return errBadRequest
}

ctx := context.Background()

rr, err := hg.DialAndCallTLS(ctx, addr, request, nil)

See also:

func DialAndCallURL

func DialAndCallURL(ctx context.Context, url string, tlsHandler TLSHandler) (ResponseReader, error)

DialAndCallURL makes a TCP connection to the TCP address implied by the URL. and (speaking the Mercury Protocol or the Gemini Protocol) sends the request implied by the URL.

The context 'ctx' controls the lifetime of the dial and the request write. If 'ctx' is nil, context.Background() is used. To apply a timeout, use context.WithTimeout or context.WithDeadline.

The 'tlsHandler' controls server certificate verification and client certificate selection for gemini:// (TLS) URLs. For mercury:// URLs it is ignored. Passing nil means: accept any server certificate, no client cert.

An example of using this for a Gemini URL might be:

var url string = "gemini://example.com/once/twice/thrice/fource.gmni"

ctx := context.Background()

rr, err := hg.DialAndCallURL(ctx, url, nil)

Using it with a Mercury URL looks the same:

var url string = "mercury://example.com/once/twice/thrice/fource.gmni"

ctx := context.Background()

rr, err := hg.DialAndCallURL(ctx, url, nil)

See also:

type ResponseRedirectPermanent

type ResponseRedirectPermanent struct {
	// contains filtered or unexported fields

} // 31

ResponseRedirectPermanent represents a Mercury Protocol “31 REDIRECT - PERMANENT” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseRedirectPermanent) Error

func (receiver ResponseRedirectPermanent) Error() string

func (ResponseRedirectPermanent) Meta

func (receiver ResponseRedirectPermanent) Meta() string

func (ResponseRedirectPermanent) StatusCode

func (receiver ResponseRedirectPermanent) StatusCode() int

func (ResponseRedirectPermanent) StatusText

func (receiver ResponseRedirectPermanent) StatusText() string

type ResponseRedirectTemporary

type ResponseRedirectTemporary struct {
	// contains filtered or unexported fields

} // 30

ResponseRedirectTemporary represents a Mercury Protocol “30 REDIRECT - TEMPORARY” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseRedirectTemporary) Error

func (receiver ResponseRedirectTemporary) Error() string

func (ResponseRedirectTemporary) Meta

func (receiver ResponseRedirectTemporary) Meta() string

func (ResponseRedirectTemporary) StatusCode

func (receiver ResponseRedirectTemporary) StatusCode() int

func (ResponseRedirectTemporary) StatusText

func (receiver ResponseRedirectTemporary) StatusText() string

type ResponseSensitiveInput

type ResponseSensitiveInput struct {
	// contains filtered or unexported fields

} // 11

ResponseSensitiveInput represents a Mercury Protocol “11 SENSITIVE INPUT” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseSensitiveInput) Error

func (receiver ResponseSensitiveInput) Error() string

func (ResponseSensitiveInput) Meta

func (receiver ResponseSensitiveInput) Meta() string

func (ResponseSensitiveInput) StatusCode

func (receiver ResponseSensitiveInput) StatusCode() int

func (ResponseSensitiveInput) StatusText

func (receiver ResponseSensitiveInput) StatusText() string

type ResponseServerUnavailable

type ResponseServerUnavailable struct {
	// contains filtered or unexported fields

} // 41

ResponseServerUnavailable represents a Mercury Protocol “41 SERVER UNAVAILABLE” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseServerUnavailable) Error

func (receiver ResponseServerUnavailable) Error() string

func (ResponseServerUnavailable) Meta

func (receiver ResponseServerUnavailable) Meta() string

func (ResponseServerUnavailable) StatusCode

func (receiver ResponseServerUnavailable) StatusCode() int

func (ResponseServerUnavailable) StatusText

func (receiver ResponseServerUnavailable) StatusText() string

type ResponseSlowDown

type ResponseSlowDown struct {
	// contains filtered or unexported fields

} // 44

ResponseSlowDown represents a Mercury Protocol “44 SLOW DOWN” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseSlowDown) Error

func (receiver ResponseSlowDown) Error() string

func (ResponseSlowDown) Meta

func (receiver ResponseSlowDown) Meta() string

func (ResponseSlowDown) StatusCode

func (receiver ResponseSlowDown) StatusCode() int

func (ResponseSlowDown) StatusText

func (receiver ResponseSlowDown) StatusText() string

type ResponseTemporaryFailure

type ResponseTemporaryFailure struct {
	// contains filtered or unexported fields

} // 40

ResponseTemporaryFailure represents a Mercury Protocol “40 TEMPORARY FAILURE” response. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (ResponseTemporaryFailure) Error

func (receiver ResponseTemporaryFailure) Error() string

func (ResponseTemporaryFailure) Meta

func (receiver ResponseTemporaryFailure) Meta() string

func (ResponseTemporaryFailure) StatusCode

func (receiver ResponseTemporaryFailure) StatusCode() int

func (ResponseTemporaryFailure) StatusText

func (receiver ResponseTemporaryFailure) StatusText() string

type ResponseWriter

type ResponseWriter interface {
	Write(ctx context.Context, p []byte) (n int, err error)
	Writer(ctx context.Context) io.Writer
	WriteHeader(ctx context.Context, statusCode int, meta string) (int, error)
}

ResponseWriter is used by a Handler to construct a Mercury Protocol response.

For example:

func serveMercury(ctx context.Context, w hg.ResponseWriter, r hg.Request) {

	// ...

}

Notice that the first parameter is a ResponseWriter.

type Server

type Server struct {
	Addr    string  // TCP address to listen on; if empty defaults to ":1961"
	Handler Handler // handler to invoke; if nil defaults to hg.DebugHandler
	Logger  Logger

	// ReadTimeout is the maximum duration for reading the request line.
	// If zero, no timeout is applied and a slow client can hold a
	// connection open indefinitely (slowloris DoS vector).
	ReadTimeout time.Duration

	// HandlerTimeout is the default maximum duration for a handler to complete.
	// If a handler implements TimeoutHandler, its Timeout value takes precedence.
	// If zero, no timeout is applied and handlers may run indefinitely.
	HandlerTimeout time.Duration

	// MaxConnections limits the number of concurrent connections the server will handle.
	// If zero or negative, no limit is applied.
	MaxConnections int
	// contains filtered or unexported fields
}

Server is a Mercury Protocol server.

For a simple example:

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	var handler hg.Handler = hg.DebugHandler

	server := &hg.Server{
		Addr:":1961",
		Handler:handler,
	}

	err := server.ListenAndServe()
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func (*Server) ListenAndServe

func (receiver *Server) ListenAndServe() error

ListenAndServe listens on the TCP network address 'server.Addr' and then spawns a call to the ServeMercury method on the 'server.Handler' to serve each incoming connection.

For a simple example:

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	var handler hg.Handler = hg.DebugHandler

	server := &hg.Server{
		Addr:":1961",
		Handler:handler,
	}

	err := server.ListenAndServe()
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func (*Server) Serve

func (receiver *Server) Serve(listener net.Listener) error

Serve accepts an incoming Mercury Protocol client connection on the net.Listener ‘listener’.

For a simple example:

package main

import (
	"github.com/reiver/go-hg"
)

func main() {

	listener, err := net.Listen("tcp", ":1961")
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}

	var handler hg.Handler = hg.DebugHandler

	server := &hg.Server{
		Handler:handler,
	}

	err = server.Serve(listener)
	if nil != err {
		//@TODO: Handle this error better.
		panic(err)
	}
}

func (*Server) Shutdown

func (receiver *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server.

It stops accepting new connections, then waits for active connections to finish. The provided context controls how long Shutdown is willing to wait — if the context expires before all connections are done, Shutdown returns the context's error.

Shutdown is safe to call multiple times — only the first call triggers the shutdown.

func (*Server) Wait

func (receiver *Server) Wait()

Wait blocks until all active connection handlers have finished.

When Shutdown's context expires, Shutdown force-closes all connections and returns immediately — but the handler goroutines may still be running their cleanup (defers, logging, untracking). Wait blocks until those goroutines have fully exited.

Typical usage:

err := server.Shutdown(ctx)
if err != nil {
	// Shutdown timed out, but goroutines are still unwinding.
	server.Wait()
}

If Shutdown completed without error (all connections drained before the context expired), Wait returns immediately.

type TLSConfig

type TLSConfig struct {
	VerifyServerCertificateFunc func(hostname string, rawCerts [][]byte) error
	ClientCertificateFunc       func(hostname string) *tls.Certificate
}

TLSConfig is a TLSHandler implementation built from function fields. Nil fields are treated as no-ops (accept any server, no client cert).

func (TLSConfig) ClientCertificate

func (receiver TLSConfig) ClientCertificate(hostname string) *tls.Certificate

func (TLSConfig) VerifyServerCertificate

func (receiver TLSConfig) VerifyServerCertificate(hostname string, rawCerts [][]byte) error

type TLSHandler

type TLSHandler interface {

	// VerifyServerCertificate is called after the TLS handshake with the server's certificate chain (DER-encoded, leaf first).
	// rawCerts may be empty if the server sends no certificates.
	// Return nil to accept the connection, or an error to reject it.
	VerifyServerCertificate(hostname string, rawCerts [][]byte) error

	// ClientCertificate is called when a client certificate is needed.
	// 'hostname' is the host being connected to without TCP-port.
	// Return nil to proceed without presenting one.
	ClientCertificate(hostname string) *tls.Certificate
}

TLSHandler handles TLS-layer decisions during connection setup.

Used only for Gemini Protocol. Ignored for the Mercury Protocol.

See also:

var DefaultVerifier TLSHandler = defaultVerifier{}

DefaultVerifier is a TLSHandler that applies CA verification only when the server's certificate chains to a system-trusted CA. Self-signed certs are accepted without CA verification (for TOFU workflows).

Behavior:

  • Self-signed cert (CheckSignatureFrom succeeds) -> accepted
  • CA-signed cert, valid -> accepted
  • CA-signed cert, expired or wrong hostname -> rejected
  • No certificates presented -> rejected

type TimeoutHandler

type TimeoutHandler interface {
	Handler
	Timeout() time.Duration
}

TimeoutHandler is an optional interface that a Handler may implement to declare how long it should be allowed to run.

If Timeout returns a positive duration, the server will apply that as a deadline on the context passed to ServeMercury.

If Timeout returns zero or a negative duration, no timeout is applied (the handler is considered long-lived).

Handlers that do not implement this interface fall back to Server.HandlerTimeout.

type UnknownResponse

type UnknownResponse struct {
	// contains filtered or unexported fields
}

UnknownResponse represents an unknown response that this package doesn't have a type for. You might get this from hg.ErrorResponse() or called the .Read(ctx, data) method on a hg.ResponseReader

func (UnknownResponse) Error

func (receiver UnknownResponse) Error() string

func (UnknownResponse) Meta

func (receiver UnknownResponse) Meta() string

func (UnknownResponse) StatusCode

func (receiver UnknownResponse) StatusCode() int

func (UnknownResponse) StatusText

func (receiver UnknownResponse) StatusText() string

type UserDirHandler

type UserDirHandler struct {
	// AllowSymLinks controls whether symbolic links inside a user's mercury_public
	// directory are followed. If false (the default), requests that resolve to a
	// symlink or pass through a symlink are rejected with a not-found response.
	AllowSymLinks bool

	Logger Logger
}

UserDirHandler is a Mercury Protocol handler for tilde (~) capsule sites.

Makes things like this:

mercury://example.com/~username/

Get mapped to:

/home/username/mercury_public/default.gmni

And makes things like this:

mercury://example.com/~username/once/twice/thrice/fource.txt

Get mapped to:

/home/username/mercury_public/once/twice/thrice/fource.txt

func (*UserDirHandler) ServeMercury

func (receiver *UserDirHandler) ServeMercury(ctx context.Context, w ResponseWriter, r Request)

Directories

Path Synopsis
internal
io2

Jump to

Keyboard shortcuts

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