1
0
Fork 0
mirror of https://git.sr.ht/~adnano/go-gemini synced 2024-05-13 03:06:10 +02:00

Update documentation

This commit is contained in:
adnano 2020-10-12 16:34:52 -04:00
parent 2370c42d8d
commit a33a5be063
9 changed files with 101 additions and 119 deletions

106
README.md
View File

@ -2,113 +2,13 @@
[![GoDoc](https://godoc.org/git.sr.ht/~adnano/gmi?status.svg)](https://godoc.org/git.sr.ht/~adnano/gmi)
Package `gmi` implements the [Gemini protocol](https://gemini.circumlunar.space)
in Go.
Package `gmi` implements the [Gemini protocol](https://gemini.circumlunar.space) in Go.
It aims to provide an API similar to that of `net/http` to make it easy to
develop Gemini clients and servers.
It aims to provide an API similar to that of `net/http` to make it easy to develop Gemini clients and servers.
## Examples
There are a few examples provided in the `examples` directory.
Some examples might require you to generate TLS certificates.
To run the examples:
go run -tags=example ./examples/server
## Overview
A quick overview of the Gemini protocol:
1. Client opens connection
2. Server accepts connection
3. Client and server complete a TLS handshake
4. Client validates server certificate
5. Client sends request
6. Server sends response header
7. Server sends response body (only for successful responses)
8. Server closes connection
9. Client handles response
The way this is implemented in this package is like so:
1. Client makes a request with `NewRequest`. The client then sends the request
with `(*Client).Send(*Request) (*Response, error)`. The client then determines whether
to trust the certificate (see [Trust On First Use](#trust-on-first-use)).
2. Server recieves the request and constructs a response.
The server calls the `Serve(*ResponseWriter, *Request)` method on the
`Handler` field. The handler writes the response. The server then closes
the connection.
3. Client recieves the response as a `*Response`. The client then handles the
response.
## Trust On First Use
`gmi` makes it easy to implement Trust On First Use in your clients.
The default client loads known hosts from `$XDG_DATA_HOME/gemini/known_hosts`.
If that is all you need, you can simply use the top-level `Send` function:
```go
// Send uses the default client, which will load the default list of known hosts.
req := gmi.NewRequest("gemini://example.com")
gmi.Send(req)
```
Clients can also load their own list of known hosts:
```go
client := &gmi.Client{}
if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil {
log.Fatal(err)
}
```
Clients can then specify how to trust certificates in the `TrustCertificate`
field:
```go
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error {
// If the certificate is in the known hosts list, allow the connection
return knownHosts.Lookup(hostname, cert)
}
```
Advanced clients can prompt the user for what to do when encountering an unknown
certificate. See `examples/client` for an example.
## Client Authentication
Gemini takes advantage of client certificates for authentication.
If a server responds with `StatusCertificateRequired`, clients will generate a
certificate for the site and resend the request with the provided certificate.
The default client handles this for you. Other clients must specify the field
`GetCertificate`:
```go
// GetCertificate is called when a server requests a certificate.
// The returned certificate, if not nil, will be used when resending the request.
client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate {
// If the certificate is in the store, return it
if cert, ok := store[hostname]; ok {
return cert
}
// Otherwise, generate a certificate
duration := time.Hour
cert, err := gmi.NewCertificate(hostname, duration)
if err != nil {
return nil
}
// Store and return the certificate
store[hostname] = &cert
return &cert
}
```
Servers can then authenticate their clients with the fingerprint of their
certificates.
See `examples/auth` for an example server which authenticates its users with
username and password, and uses their client certificate to remember sessions.
go run ./examples/server.go

View File

@ -16,6 +16,7 @@ import (
)
// CertificateStore maps hostnames to certificates.
// The zero value of CertificateStore is an empty store ready to use.
type CertificateStore struct {
store map[string]tls.Certificate
}

View File

@ -198,7 +198,7 @@ type Client struct {
// The returned certificate will be used when sending the request again.
// If the certificate is nil, the request will not be sent again and
// the response will be returned.
GetCertificate func(hostname string, store CertificateStore) *tls.Certificate
GetCertificate func(hostname string, store *CertificateStore) *tls.Certificate
// TrustCertificate, if not nil, will be called to determine whether the
// client should trust the given certificate.
@ -279,7 +279,7 @@ func (c *Client) Send(req *Request) (*Response, error) {
return resp, nil
}
if c.GetCertificate != nil {
if cert := c.GetCertificate(req.Hostname(), c.CertificateStore); cert != nil {
if cert := c.GetCertificate(req.Hostname(), &c.CertificateStore); cert != nil {
req.Certificate = cert
return c.Send(req)
}

80
doc.go Normal file
View File

@ -0,0 +1,80 @@
/*
Package gmi implements the Gemini protocol.
Send makes a Gemini request:
req := gmi.NewRequest("gemini://example.com")
err := gmi.Send(req)
if err != nil {
// handle error
}
For control over client behavior, create a Client:
var client gmi.Client
err := client.Send(req)
if err != nil {
// handle error
}
The default client loads known hosts from "$XDG_DATA_HOME/gemini/known_hosts".
Custom clients can load their own list of known hosts:
err := client.KnownHosts.LoadFrom("path/to/my/known_hosts")
if err != nil {
// handle error
}
Clients can control when to trust certificates with TrustCertificate:
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error {
return knownHosts.Lookup(hostname, cert)
}
If a server responds with StatusCertificateRequired, the default client will generate a certificate and resend the request with it. Custom clients can specify GetCertificate:
client.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
// If the certificate is in the store, return it
if cert, err := store.Lookup(hostname); err == nil {
return &cert
}
// Otherwise, generate a certificate
duration := time.Hour
cert, err := gmi.NewCertificate(hostname, duration)
if err != nil {
return nil
}
// Store and return the certificate
store.Add(hostname, cert)
return &cert
}
Server is a Gemini server.
var server gmi.Server
Servers must be configured with certificates:
var server gmi.Server
server.CertificateStore.Load("/var/lib/gemini/certs")
Servers can accept requests for multiple hosts and schemes:
server.HandleFunc("example.com", func(rw *gmi.ResponseWriter, req *gmi.Request) {
fmt.Fprint(rw, "Welcome to example.com")
})
server.HandleFunc("example.org", func(rw *gmi.ResponseWriter, req *gmi.Request) {
fmt.Fprint(rw, "Welcome to example.org")
})
server.HandleSchemeFunc("http", "example.net", func(rw *gmi.ResponseWriter, req *gmi.Request) {
fmt.Fprint(rw, "Proxied content from example.net")
})
To start the server, call ListenAndServe:
err := server.ListenAndServe()
if err != nil {
// handle error
}
*/
package gmi

View File

@ -1,4 +1,4 @@
// +build example
// +build ignore
package main

View File

@ -1,4 +1,4 @@
// +build example
// +build ignore
package main

View File

@ -1,4 +1,4 @@
// +build example
// +build ignore
package main
@ -46,7 +46,7 @@ func init() {
}
return err
}
client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate {
client.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
// If the certificate is in the store, return it
if cert, err := store.Lookup(hostname); err == nil {
return cert

View File

@ -1,4 +1,4 @@
// +build example
// +build ignore
package main
@ -9,14 +9,16 @@ import (
)
func main() {
mux := &gmi.ServeMux{}
mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
server := gmi.Server{}
var server gmi.Server
if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil {
log.Fatal(err)
}
log.Print(server.CertificateStore)
server.Handle("localhost", mux)
server.ListenAndServe()
var mux gmi.ServeMux
mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
server.Handle("localhost", &mux)
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}

View File

@ -1,4 +1,3 @@
// Package gmi implements the Gemini protocol
package gmi
import (
@ -56,7 +55,7 @@ func init() {
setupDefaultClientOnce.Do(setupDefaultClient)
return knownHosts.Lookup(hostname, cert)
}
DefaultClient.GetCertificate = func(hostname string, store CertificateStore) *tls.Certificate {
DefaultClient.GetCertificate = func(hostname string, store *CertificateStore) *tls.Certificate {
// If the certificate is in the store, return it
if cert, err := store.Lookup(hostname); err == nil {
return cert