aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-10-12 16:34:52 -0400
committeradnano <[email protected]>2020-10-12 16:34:59 -0400
commita33a5be0634cbbca6c6f8eb2fd53bceb014fed02 (patch)
treea23fdfdab8807baa8a604023f25d729f3edaabf1
parentUse a map for registering server handlers (diff)
downloadgo-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.tar.xz
go-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.zip
Update documentation
-rw-r--r--README.md106
-rw-r--r--cert.go1
-rw-r--r--client.go4
-rw-r--r--doc.go80
-rw-r--r--examples/auth.go (renamed from examples/auth/auth.go)2
-rw-r--r--examples/cert.go (renamed from examples/cert/cert.go)2
-rw-r--r--examples/client.go (renamed from examples/client/client.go)4
-rw-r--r--examples/server.go (renamed from examples/server/server.go)16
-rw-r--r--gemini.go3
9 files changed, 100 insertions, 118 deletions
diff --git a/README.md b/README.md
index 72476de..3d605ca 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/cert.go b/cert.go
index 94ad90a..c242b96 100644
--- a/cert.go
+++ b/cert.go
@@ -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
}
diff --git a/client.go b/client.go
index 5a2512c..ee0797f 100644
--- a/client.go
+++ b/client.go
@@ -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)
}
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..3d07754
--- /dev/null
+++ b/doc.go
@@ -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
diff --git a/examples/auth/auth.go b/examples/auth.go
index 9a7c0bd..9f3045f 100644
--- a/examples/auth/auth.go
+++ b/examples/auth.go
@@ -1,4 +1,4 @@
-// +build example
+// +build ignore
package main
diff --git a/examples/cert/cert.go b/examples/cert.go
index 4431319..4c08578 100644
--- a/examples/cert/cert.go
+++ b/examples/cert.go
@@ -1,4 +1,4 @@
-// +build example
+// +build ignore
package main
diff --git a/examples/client/client.go b/examples/client.go
index ee65c9d..d2819ec 100644
--- a/examples/client/client.go
+++ b/examples/client.go
@@ -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
diff --git a/examples/server/server.go b/examples/server.go
index 01890ba..a4ed808 100644
--- a/examples/server/server.go
+++ b/examples/server.go
@@ -1,4 +1,4 @@
-// +build example
+// +build ignore
package main
@@ -9,14 +9,16 @@ import (
)
func main() {
- mux := &gmi.ServeMux{}
+ var server gmi.Server
+ if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil {
+ log.Fatal(err)
+ }
+
+ var mux gmi.ServeMux
mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
- server := gmi.Server{}
- if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil {
+ server.Handle("localhost", &mux)
+ if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
- log.Print(server.CertificateStore)
- server.Handle("localhost", mux)
- server.ListenAndServe()
}
diff --git a/gemini.go b/gemini.go
index f03af9e..5d8960e 100644
--- a/gemini.go
+++ b/gemini.go
@@ -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