diff options
Diffstat (limited to 'src/gmi')
-rw-r--r-- | src/gmi/gmi.go | 122 |
1 files changed, 121 insertions, 1 deletions
diff --git a/src/gmi/gmi.go b/src/gmi/gmi.go index 358d935..6e2d79f 100644 --- a/src/gmi/gmi.go +++ b/src/gmi/gmi.go @@ -1,2 +1,122 @@ -// Package gmi contains utilities for working with gemini and gemtext +// 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") + }) +} |