gemini

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jan 20, 2024 License: MIT Imports: 21 Imported by: 47

README

go-gemini

godocs.io builds.sr.ht status

Package gemini implements the Gemini protocol in Go. It provides an API similar to that of net/http to facilitate the development of Gemini clients and servers.

Compatible with version v0.16.0 of the Gemini specification.

Usage

import "git.sr.ht/~adnano/go-gemini"

Note that some filesystem-related functionality is only available on Go 1.16 or later as it relies on the io/fs package.

Examples

There are a few examples provided in the examples directory. To run an example:

go run examples/server.go

Contributing

Send patches and questions to ~adnano/go-gemini-devel.

Subscribe to release announcements on ~adnano/go-gemini-announce.

License

go-gemini is licensed under the terms of the MIT license (see LICENSE). Portions of this library were adapted from Go and are governed by a BSD-style license (see LICENSE-GO). Those files are marked accordingly.

Documentation

Overview

Package gemini provides Gemini client and server implementations.

Client is a Gemini client.

client := &gemini.Client{}
ctx := context.Background()
resp, err := client.Get(ctx, "gemini://example.com")
if err != nil {
	// handle error
}
defer resp.Body.Close()
// ...

Server is a Gemini server.

server := &gemini.Server{
	ReadTimeout:  10 * time.Second,
	WriteTimeout: 10 * time.Second,
}

Servers should be configured with certificates:

certificates := &certificate.Store{}
certificates.Register("localhost")
err := certificates.Load("/var/lib/gemini/certs")
if err != nil {
	// handle error
}
server.GetCertificate = certificates.Get

Mux is a Gemini request multiplexer. Mux can handle requests for multiple hosts and paths.

mux := &gemini.Mux{}
mux.HandleFunc("example.com", func(ctx context.Context, w gemini.ResponseWriter, r *gemini.Request) {
	fmt.Fprint(w, "Welcome to example.com")
})
mux.HandleFunc("example.org/about.gmi", func(ctx context.Context, w gemini.ResponseWriter, r *gemini.Request) {
	fmt.Fprint(w, "About example.org")
})
mux.HandleFunc("/images/", func(ctx context.Context, w gemini.ResponseWriter, r *gemini.Request) {
	w.WriteHeader(gemini.StatusGone, "Gone forever")
})
server.Handler = mux

To start the server, call ListenAndServe:

ctx := context.Background()
err := server.ListenAndServe(ctx)
if err != nil {
	// handle error
}

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidRequest  = errors.New("gemini: invalid request")
	ErrInvalidResponse = errors.New("gemini: invalid response")

	// ErrBodyNotAllowed is returned by ResponseWriter.Write calls
	// when the response status code does not permit a body.
	ErrBodyNotAllowed = errors.New("gemini: response status code does not allow body")
)

Errors.

Functions

func ParseLines added in v0.1.4

func ParseLines(r io.Reader, handler func(Line)) error

ParseLines parses Gemini text from the provided io.Reader. It calls handler with each line that it parses.

func QueryEscape added in v0.1.9

func QueryEscape(query string) string

QueryEscape escapes a string for use in a Gemini URL query. It is like url.PathEscape except that it also replaces plus signs with their percent-encoded counterpart.

func QueryUnescape added in v0.1.9

func QueryUnescape(query string) (string, error)

QueryUnescape unescapes a Gemini URL query. It is identical to url.PathUnescape.

func ServeFile added in v0.1.2

func ServeFile(w ResponseWriter, fsys fs.FS, name string)

ServeFile responds to the request with the contents of the named file or directory. If the provided name is constructed from user input, it should be sanitized before calling ServeFile.

Types

type Client

type Client struct {
	// TrustCertificate is called to determine whether the client should
	// trust the certificate provided by the server.
	// If TrustCertificate is nil or returns nil, the client will accept
	// any certificate. Otherwise, the certificate will not be trusted
	// and the request will be aborted.
	//
	// See the tofu submodule for an implementation of trust on first use.
	TrustCertificate func(hostname string, cert *x509.Certificate) error

	// DialContext specifies the dial function for creating TCP connections.
	// If DialContext is nil, the client dials using package net.
	DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
}

A Client is a Gemini client. Its zero value is a usable client.

func (*Client) Do added in v0.1.3

func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)

Do sends a Gemini request and returns a Gemini response. The context controls the entire lifetime of a request and its response: obtaining a connection, sending the request, and reading the response header and body.

An error is returned if there was a Gemini protocol error. A non-2x status code doesn't cause an error.

If the returned error is nil, the user is expected to close the Response.

func (*Client) Get added in v0.1.3

func (c *Client) Get(ctx context.Context, url string) (*Response, error)

Get sends a Gemini request for the given URL. The context controls the entire lifetime of a request and its response: obtaining a connection, sending the request, and reading the response header and body.

An error is returned if there was a Gemini protocol error. A non-2x status code doesn't cause an error.

If the returned error is nil, the user is expected to close the Response.

For more control over requests, use NewRequest and Client.Do.

type Handler added in v0.1.14

type Handler interface {
	ServeGemini(context.Context, ResponseWriter, *Request)
}

A Handler responds to a Gemini request.

ServeGemini should write the response header and data to the ResponseWriter and then return. Returning signals that the request is finished; it is not valid to use the ResponseWriter after or concurrently with the completion of the ServeGemini call.

The provided context is canceled when the client's connection is closed or the ServeGemini method returns.

Handlers should not modify the provided Request.

func FileServer

func FileServer(fsys fs.FS) Handler

FileServer returns a handler that serves Gemini requests with the contents of the provided file system.

To use the operating system's file system implementation, use os.DirFS:

gemini.FileServer(os.DirFS("/tmp"))

func LoggingMiddleware added in v0.1.20

func LoggingMiddleware(h Handler) Handler

LoggingMiddleware returns a handler that wraps h and logs Gemini requests and their responses to the log package's standard logger. Requests are logged with the format "gemini: {host} {URL} {status code} {bytes written}".

func NotFoundHandler added in v0.1.15

func NotFoundHandler() Handler

NotFoundHandler returns a simple request handler that replies to each request with a “51 Not found” reply.

func StatusHandler added in v0.1.15

func StatusHandler(status Status, meta string) Handler

StatusHandler returns a request handler that responds to each request with the provided status code and meta.

func StripPrefix added in v0.1.15

func StripPrefix(prefix string, h Handler) Handler

StripPrefix returns a handler that serves Gemini requests by removing the given prefix from the request URL's Path (and RawPath if set) and invoking the handler h. StripPrefix handles a request for a path that doesn't begin with prefix by replying with a Gemini 51 not found error. The prefix must match exactly: if the prefix in the request contains escaped characters the reply is also a Gemini 51 not found error.

func TimeoutHandler added in v0.1.15

func TimeoutHandler(h Handler, dt time.Duration, message string) Handler

TimeoutHandler returns a Handler that runs h with the given time limit.

The new Handler calls h.ServeGemini to handle each request, but if a call runs for longer than its time limit, the handler responds with a 40 Temporary Failure status code and the given message in its response meta. After such a timeout, writes by h to its ResponseWriter will return context.DeadlineExceeded.

type HandlerFunc added in v0.1.14

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

The HandlerFunc type is an adapter to allow the use of ordinary functions as Gemini handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.

func (HandlerFunc) ServeGemini added in v0.1.14

func (f HandlerFunc) ServeGemini(ctx context.Context, w ResponseWriter, r *Request)

ServeGemini calls f(ctx, w, r).

type Line

type Line interface {
	// String formats the line for use in a Gemini text response.
	String() string
	// contains filtered or unexported methods
}

Line represents a line of a Gemini text response.

type LineHeading1

type LineHeading1 string

LineHeading1 is a first-level heading line.

func (LineHeading1) String

func (l LineHeading1) String() string

type LineHeading2

type LineHeading2 string

LineHeading2 is a second-level heading line.

func (LineHeading2) String

func (l LineHeading2) String() string

type LineHeading3

type LineHeading3 string

LineHeading3 is a third-level heading line.

func (LineHeading3) String

func (l LineHeading3) String() string
type LineLink struct {
	URL  string
	Name string
}

LineLink is a link line.

func (LineLink) String

func (l LineLink) String() string

type LineListItem

type LineListItem string

LineListItem is an unordered list item line.

func (LineListItem) String

func (l LineListItem) String() string

type LinePreformattedText

type LinePreformattedText string

LinePreformattedText is a preformatted text line.

func (LinePreformattedText) String

func (l LinePreformattedText) String() string

type LinePreformattingToggle

type LinePreformattingToggle string

LinePreformattingToggle is a preformatting toggle line.

func (LinePreformattingToggle) String

func (l LinePreformattingToggle) String() string

type LineQuote

type LineQuote string

LineQuote is a quote line.

func (LineQuote) String

func (l LineQuote) String() string

type LineText

type LineText string

LineText is a text line.

func (LineText) String

func (l LineText) String() string

type Mux added in v0.1.22

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

Mux is a Gemini request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/" (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both "/images/" and "/images/thumbnails/", the latter handler will be called for paths beginning "/images/thumbnails/" and the former will receive requests for any other paths in the "/images/" subtree.

Note that since a pattern ending in a slash names a rooted subtree, the pattern "/" matches all paths not matched by other registered patterns, not just the URL with Path == "/".

Patterns may optionally begin with a host name, restricting matches to URLs on that host only. Host-specific patterns take precedence over general patterns, so that a handler might register for the two patterns "/search" and "search.example.com/" without also taking over requests for "gemini://example.com/".

Wildcard patterns can be used to match multiple hostnames. For example, the pattern "*.example.com" will match requests for "blog.example.com" and "gemini.example.com", but not "example.org".

If a subtree has been registered and a request is received naming the subtree root without its trailing slash, Mux redirects that request to the subtree root (adding the trailing slash). This behavior can be overridden with a separate registration for the path without the trailing slash. For example, registering "/images/" causes Mux to redirect a request for "/images" to "/images/", unless "/images" has been registered separately.

Mux also takes care of sanitizing the URL request path and redirecting any request containing . or .. elements or repeated slashes to an equivalent, cleaner URL.

func (*Mux) Handle added in v0.1.22

func (mux *Mux) Handle(pattern string, handler Handler)

Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.

func (*Mux) HandleFunc added in v0.1.22

func (mux *Mux) HandleFunc(pattern string, handler HandlerFunc)

HandleFunc registers the handler function for the given pattern.

func (*Mux) Handler added in v0.1.22

func (mux *Mux) Handler(r *Request) Handler

Handler returns the handler to use for the given request, consulting r.URL.Scheme, r.URL.Host, and r.URL.Path. It always returns a non-nil handler. If the path is not in its canonical form, the handler will be an internally-generated handler that redirects to the canonical path. If the host contains a port, it is ignored when matching handlers.

func (*Mux) ServeGemini added in v0.1.22

func (mux *Mux) ServeGemini(ctx context.Context, w ResponseWriter, r *Request)

ServeGemini dispatches the request to the handler whose pattern most closely matches the request URL.

type Request

type Request struct {
	// URL specifies the URL being requested.
	URL *url.URL

	// For client requests, Host optionally specifies the server to
	// connect to. It may be of the form "host" or "host:port".
	// If empty, the value of URL.Host is used.
	// For international domain names, Host may be in Punycode or
	// Unicode form. Use golang.org/x/net/idna to convert it to
	// either format if needed.
	// This field is ignored by the Gemini server.
	Host string

	// For client requests, Certificate optionally specifies the
	// TLS certificate to present to the other side of the connection.
	// This field is ignored by the Gemini server.
	Certificate *tls.Certificate
	// contains filtered or unexported fields
}

A Request represents a Gemini request received by a server or to be sent by a client.

func NewRequest

func NewRequest(rawurl string) (*Request, error)

NewRequest returns a new request. The returned Request is suitable for use with Client.Do.

Callers should be careful that the URL query is properly escaped. See the documentation for QueryEscape for more information.

func ReadRequest added in v0.1.10

func ReadRequest(r io.Reader) (*Request, error)

ReadRequest reads and parses an incoming request from r.

ReadRequest is a low-level function and should only be used for specialized applications; most code should use the Server to read requests and handle them via the Handler interface.

func (*Request) Conn added in v0.1.15

func (r *Request) Conn() net.Conn

Conn returns the network connection on which the request was received. Conn returns nil for client requests.

func (*Request) ServerName added in v0.1.18

func (r *Request) ServerName() string

ServerName returns the value of the TLS Server Name Indication extension sent by the client. ServerName returns an empty string for client requests.

func (*Request) TLS

func (r *Request) TLS() *tls.ConnectionState

TLS returns information about the TLS connection on which the request was received. TLS returns nil for client requests.

func (*Request) WriteTo added in v0.1.18

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

WriteTo writes r to w in the Gemini request format. This method consults the request URL only.

type Response

type Response struct {
	// Status is the response status code.
	Status Status

	// Meta returns the response meta.
	// For successful responses, the meta should contain the media type of the response.
	// For failure responses, the meta should contain a short description of the failure.
	Meta string

	// Body represents the response body.
	//
	// The response body is streamed on demand as the Body field
	// is read. If the network connection fails or the server
	// terminates the response, Body.Read calls return an error.
	//
	// The Gemini client guarantees that Body is always
	// non-nil, even on responses without a body or responses with
	// a zero-length body. It is the caller's responsibility to
	// close Body.
	Body io.ReadCloser
	// contains filtered or unexported fields
}

Response represents the response from a Gemini request.

The Client returns Responses from servers once the response header has been received. The response body is streamed on demand as the Body field is read.

func ReadResponse added in v0.1.10

func ReadResponse(r io.ReadCloser) (*Response, error)

ReadResponse reads a Gemini response from the provided io.ReadCloser.

func (*Response) Conn added in v0.1.15

func (r *Response) Conn() net.Conn

Conn returns the network connection on which the response was received.

func (*Response) TLS

func (r *Response) TLS() *tls.ConnectionState

TLS returns information about the TLS connection on which the response was received.

func (*Response) WriteTo added in v0.1.18

func (r *Response) WriteTo(w io.Writer) (int64, error)

WriteTo writes r to w in the Gemini response format, including the header and body.

This method consults the Status, Meta, and Body fields of the response. The Response Body is closed after it is sent.

type ResponseWriter

type ResponseWriter interface {
	// SetMediaType sets the media type that will be sent by Write for a
	// successful response. If no media type is set, a default media type of
	// "text/gemini" will be used.
	//
	// Setting the media type after a call to Write or WriteHeader has
	// no effect.
	SetMediaType(mediatype string)

	// Write writes the data to the connection as part of a Gemini response.
	//
	// If WriteHeader has not yet been called, Write calls WriteHeader with
	// StatusSuccess and the media type set in SetMediaType before writing the data.
	// If no media type was set, Write uses a default media type of
	// "text/gemini".
	Write([]byte) (int, error)

	// WriteHeader sends a Gemini response header with the provided
	// status code and meta.
	//
	// If WriteHeader is not called explicitly, the first call to Write
	// will trigger an implicit call to WriteHeader with a successful
	// status code and the media type set in SetMediaType.
	//
	// The provided code must be a valid Gemini status code.
	// The provided meta must not be longer than 1024 bytes.
	// Only one header may be written.
	WriteHeader(status Status, meta string)

	// Flush sends any buffered data to the client.
	Flush() error
}

A ResponseWriter interface is used by a Gemini handler to construct a Gemini response.

A ResponseWriter may not be used after the Handler.ServeGemini method has returned.

type Server

type Server struct {
	// Addr optionally specifies the TCP address for the server to listen on,
	// in the form "host:port". If empty, ":1965" (port 1965) is used.
	// See net.Dial for details of the address format.
	Addr string

	// The Handler to invoke.
	Handler Handler

	// ReadTimeout is the maximum duration for reading the entire
	// request.
	//
	// A ReadTimeout of zero means no timeout.
	ReadTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out
	// writes of the response.
	//
	// A WriteTimeout of zero means no timeout.
	WriteTimeout time.Duration

	// GetCertificate returns a TLS certificate based on the given
	// hostname.
	//
	// If GetCertificate is nil or returns nil, then no certificate
	// will be used and the connection will be aborted.
	//
	// See the certificate submodule for a certificate store that creates
	// and rotates certificates as needed.
	GetCertificate func(hostname string) (*tls.Certificate, error)

	// ErrorLog specifies an optional logger for errors accepting connections,
	// unexpected behavior from handlers, and underlying file system errors.
	// If nil, logging is done via the log package's standard logger.
	ErrorLog interface {
		Printf(format string, v ...interface{})
	}
	// contains filtered or unexported fields
}

A Server defines parameters for running a Gemini server. The zero value for Server is a valid configuration.

func (*Server) Close added in v0.1.14

func (srv *Server) Close() error

Close immediately closes all active net.Listeners and connections. For a graceful shutdown, use Shutdown.

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe(ctx context.Context) error

ListenAndServe listens for requests at the server's configured address. ListenAndServe listens on the TCP network address srv.Addr and then calls Serve to handle requests on incoming connections. If the provided context expires, ListenAndServe closes l and returns the context's error.

If srv.Addr is blank, ":1965" is used.

ListenAndServe always returns a non-nil error. After Shutdown or Closed, the returned error is context.Canceled.

func (*Server) Serve

func (srv *Server) Serve(ctx context.Context, l net.Listener) error

Serve accepts incoming connections on the Listener l, creating a new service goroutine for each. The service goroutines read the requests and then call the appropriate Handler to reply to them. If the provided context expires, Serve closes l and returns the context's error.

Serve always closes l and returns a non-nil error. After Shutdown or Close, the returned error is context.Canceled.

func (*Server) ServeConn added in v0.1.15

func (srv *Server) ServeConn(ctx context.Context, conn net.Conn) error

ServeConn serves a Gemini response over the provided connection. It closes the connection when the response has been completed. If the provided context expires before the response has completed, ServeConn closes the connection and returns the context's error.

func (*Server) Shutdown added in v0.1.14

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

Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners and then waiting indefinitely for connections to close. If the provided context expires before the shutdown is complete, Shutdown returns the context's error.

When Shutdown is called, Serve and ListenAndServe immediately return an error. Make sure the program doesn't exit and waits instead for Shutdown to return.

Once Shutdown has been called on a server, it may not be reused; future calls to methods such as Serve will return an error.

type Status added in v0.1.2

type Status int

Status represents a Gemini status code.

const (
	StatusInput                    Status = 10
	StatusSensitiveInput           Status = 11
	StatusSuccess                  Status = 20
	StatusRedirect                 Status = 30
	StatusPermanentRedirect        Status = 31
	StatusTemporaryFailure         Status = 40
	StatusServerUnavailable        Status = 41
	StatusCGIError                 Status = 42
	StatusProxyError               Status = 43
	StatusSlowDown                 Status = 44
	StatusPermanentFailure         Status = 50
	StatusNotFound                 Status = 51
	StatusGone                     Status = 52
	StatusProxyRequestRefused      Status = 53
	StatusBadRequest               Status = 59
	StatusCertificateRequired      Status = 60
	StatusCertificateNotAuthorized Status = 61
	StatusCertificateNotValid      Status = 62
)

Gemini status codes.

func (Status) Class added in v0.1.2

func (s Status) Class() Status

Class returns the status class for the status code. 1x becomes 10, 2x becomes 20, and so on.

func (Status) String added in v0.1.15

func (s Status) String() string

String returns a text for the status code. It returns the empty string if the status code is unknown.

type Text

type Text []Line

Text represents a Gemini text response.

func ParseText added in v0.1.4

func ParseText(r io.Reader) (Text, error)

ParseText parses Gemini text from the provided io.Reader.

func (Text) String

func (t Text) String() string

String writes the Gemini text response to a string and returns it.

Directories

Path Synopsis
Package certificate provides functions for creating and storing TLS certificates.
Package certificate provides functions for creating and storing TLS certificates.
Package tofu implements trust on first use using hosts and fingerprints.
Package tofu implements trust on first use using hosts and fingerprints.

Jump to

Keyboard shortcuts

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