A fast and simple blog backend.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
mediocre-blog/src/gmi/gmi.go

122 lines
3.0 KiB

// Package gmi implements the gemini-based api for the mediocre-blog.
package gmi
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"strings"
"git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini/certificate"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
)
// Params are used to instantiate a new API instance. All fields are required
// unless otherwise noted.
type Params struct {
Logger *mlog.Logger
PublicURL *url.URL
ListenAddr string
CertificatesPath string
}
// SetupCfg implement the cfg.Cfger interface.
func (p *Params) SetupCfg(cfg *cfg.Cfg) {
publicURLStr := cfg.String("gemini-public-url", "gemini://localhost:2065", "URL this service is accessible at")
cfg.StringVar(&p.ListenAddr, "gemini-listen-addr", ":2065", "Address to listen for HTTP requests on")
cfg.StringVar(&p.CertificatesPath, "gemini-certificates-path", "", "Path to directory where gemini certs should be created/stored")
cfg.OnInit(func(context.Context) error {
if p.CertificatesPath == "" {
return errors.New("-gemini-certificates-path is required")
}
var err error
*publicURLStr = strings.TrimSuffix(*publicURLStr, "/")
if p.PublicURL, err = url.Parse(*publicURLStr); err != nil {
return fmt.Errorf("parsing -gemini-public-url: %w", err)
}
return nil
})
}
// Annotate implements mctx.Annotator interface.
func (p *Params) Annotate(a mctx.Annotations) {
a["geminiPublicURL"] = p.PublicURL
a["geminiListenAddr"] = p.ListenAddr
a["geminiCertificatesPath"] = p.CertificatesPath
}
// API will listen on the port configured for it, and serve gemini requests for
// the mediocre-blog.
type API interface {
Shutdown(ctx context.Context) error
}
type api struct {
params Params
srv *gemini.Server
}
// New initializes and returns a new API instance, including setting up all
// listening ports.
func New(params Params) (API, error) {
if err := os.MkdirAll(params.CertificatesPath, 0700); err != nil {
return nil, fmt.Errorf("creating certificate directory %q: %w", params.CertificatesPath, err)
}
certStore := new(certificate.Store)
certStore.Load(params.CertificatesPath)
certStore.Register(params.PublicURL.Hostname())
a := &api{
params: params,
}
a.srv = &gemini.Server{
Addr: params.ListenAddr,
Handler: a.handler(),
GetCertificate: certStore.Get,
}
go func() {
ctx := mctx.WithAnnotator(context.Background(), &a.params)
err := a.srv.ListenAndServe(ctx)
if err != nil && !errors.Is(err, context.Canceled) {
a.params.Logger.Fatal(ctx, "serving gemini server", err)
}
}()
return a, nil
}
func (a *api) Shutdown(ctx context.Context) error {
return a.srv.Shutdown(ctx)
}
func (a *api) handler() gemini.Handler {
return gemini.HandlerFunc(func(
ctx context.Context,
rw gemini.ResponseWriter,
r *gemini.Request,
) {
fmt.Fprintf(rw, "# Test\n\n")
fmt.Fprintf(rw, "HELLO WORLD\n\n")
fmt.Fprintf(rw, "=> gemini://midnight.pub Hit the pub\n\n")
})
}