D devnotes.gmi => devnotes.gmi +0 -6
@@ 1,6 0,0 @@
-# Development notes
-
-The core goal here is to add support for a natively integrated embedded gemini server in the watch mode.
-However, while trivial to do in its core, it would also be good to take this occasion to work on making the watch features more easy to toggle and choose.
-
-=> https://git.sr.ht/~adnano/go-gemini the gemini server library i will probably use
M go.mod => go.mod +2 -0
@@ 21,6 21,7 @@ require (
)
require (
+ git.sr.ht/~adnano/go-gemini v0.2.3 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
@@ 35,6 36,7 @@ require (
golang.org/x/net v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
+ golang.org/x/text v0.7.0 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
M go.sum => go.sum +6 -0
@@ 1,3 1,5 @@
+git.sr.ht/~adnano/go-gemini v0.2.3 h1:oJ+Y0/mheZ4Vg0ABjtf5dlmvq1yoONStiaQvmWWkofc=
+git.sr.ht/~adnano/go-gemini v0.2.3/go.mod h1:hQ75Y0i5jSFL+FQ7AzWVAYr5LQsaFC7v3ZviNyj46dY=
git.sr.ht/~justinsantoro/gemtext v0.0.0-20220805224016-0f9b62e585a8 h1:1QVRbgTKSTeo+XXFh4Cm8X5ibhN6OiAWWcu/ZAwXzEw=
git.sr.ht/~justinsantoro/gemtext v0.0.0-20220805224016-0f9b62e585a8/go.mod h1:DIXGwCsBn8cQxd21fPDLW8ww5tNq8/P4ZdUXXzqNPrg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ 76,6 78,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ 89,7 92,10 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
M preview/managed/context.go => preview/managed/context.go +7 -3
@@ 1,14 1,18 @@
package managed
-import "sync"
+import (
+ "context"
+ "sync"
+)
type ManagedContext struct {
wg *sync.WaitGroup
errors chan error
+ RunCtx context.Context
}
-func New(wg *sync.WaitGroup, errors chan error) ManagedContext {
- return ManagedContext{wg, errors}
+func New(wg *sync.WaitGroup, errors chan error, ctx context.Context) ManagedContext {
+ return ManagedContext{wg, errors, ctx}
}
func (m *ManagedContext) Done(err error) {
M preview/manager.go => preview/manager.go +11 -6
@@ 5,17 5,20 @@ package preview
import (
"context"
"errors"
+ "os"
+ "path/filepath"
"sync"
"time"
"git.sr.ht/~artemis/cap/preview/managed"
+ "git.sr.ht/~artemis/cap/preview/servers"
"git.sr.ht/~artemis/cap/preview/watch"
"github.com/withmandala/go-log"
)
type Manager interface {
AddWatcher(watcher watch.WatchCapability)
- AddContentServer(server ContentServerCapability)
+ AddContentServer(server servers.ContentServerCapability)
// Starts all the requested capabilities then hangs until all capability routines are stopped
Watch(ctx context.Context, capsuleRoot string, rebuild watch.RebuildCallback) error
}
@@ 26,7 29,7 @@ type Stoppable interface {
type PreviewManager struct {
watchers []watch.WatchCapability
- servers []ContentServerCapability
+ servers []servers.ContentServerCapability
logger *log.Logger
}
@@ 40,21 43,22 @@ func (p *PreviewManager) AddWatcher(watcher watch.WatchCapability) {
}
}
-func (p *PreviewManager) AddContentServer(server ContentServerCapability) {
+func (p *PreviewManager) AddContentServer(server servers.ContentServerCapability) {
p.servers = append(p.servers, server)
}
func (p *PreviewManager) Watch(runCtx context.Context, capsuleRoot string, rebuild watch.RebuildCallback) error {
wg := sync.WaitGroup{}
routineErrors := make(chan error, len(p.servers)+len(p.watchers))
- shutdownCtx, shutdown := context.WithTimeout(runCtx, time.Second*5)
- ctx := managed.New(&wg, routineErrors)
+ ctx := managed.New(&wg, routineErrors, runCtx)
hasErrored := false
var started []Stoppable
for _, server := range p.servers {
- err := server.Start(&ctx, capsuleRoot)
+ builtContentRoot := filepath.Join(capsuleRoot, ".public")
+ capsuleFS := os.DirFS(builtContentRoot)
+ err := server.Start(&ctx, capsuleFS)
if err != nil {
p.logger.Errorf("Server startup error: %s", err)
@@ 76,6 80,7 @@ func (p *PreviewManager) Watch(runCtx context.Context, capsuleRoot string, rebui
}
}
+ shutdownCtx, shutdown := context.WithTimeout(runCtx, time.Second*5)
if hasErrored {
for _, running := range started {
running.Stop(shutdownCtx)
A preview/servers/capability.go => preview/servers/capability.go +16 -0
@@ 0,0 1,16 @@
+package servers
+
+import (
+ "context"
+ "io/fs"
+
+ "git.sr.ht/~artemis/cap/preview/managed"
+)
+
+// Generic embedded server interface for the preview environment
+type ContentServerCapability interface {
+ // Starts the embedded server on another routine
+ Start(ctx *managed.ManagedContext, capsuleRoot fs.FS) error
+ // Stops the embedded server
+ Stop(context.Context)
+}
A preview/servers/gemini.go => preview/servers/gemini.go +60 -0
@@ 0,0 1,60 @@
+package servers
+
+import (
+ "context"
+ "io/fs"
+ "time"
+
+ "git.sr.ht/~adnano/go-gemini"
+ "git.sr.ht/~adnano/go-gemini/certificate"
+ "git.sr.ht/~artemis/cap/preview/managed"
+ "github.com/withmandala/go-log"
+)
+
+// CreateEmbeddedGeminiServer provides a Gemini server implementation
+func CreateEmbeddedGeminiServer(address string, logger *log.Logger) ContentServerCapability {
+ return &EmbeddedGeminiServer{
+ address: address,
+ logger: logger,
+ }
+}
+
+// EmbeddedGeminiServer is the embedded Gemini server implementation
+type EmbeddedGeminiServer struct {
+ address string
+ logger *log.Logger
+ server *gemini.Server
+}
+
+// Start implements ContentServerCapability
+func (e *EmbeddedGeminiServer) Start(ctx *managed.ManagedContext, contentRoot fs.FS) error {
+ certStore := &certificate.Store{}
+ certStore.Register("*")
+
+ e.server = &gemini.Server{
+ Addr: e.address,
+ GetCertificate: certStore.Get,
+ ReadTimeout: 2 * time.Second,
+ WriteTimeout: 2 * time.Second,
+ Handler: gemini.FileServer(contentRoot),
+ }
+
+ go func(ctx *managed.ManagedContext, e *EmbeddedGeminiServer) {
+ err := e.server.ListenAndServe(ctx.RunCtx)
+ if err != nil {
+ e.logger.Errorf("Gemini server error: %s", err.Error())
+ ctx.Done(err)
+ }
+ ctx.Done(nil)
+ }(ctx, e)
+ e.logger.Infof("Embedded Gemini server running on gemini://%s", e.address)
+
+ return nil
+}
+
+// Stop implements ContentServerCapability
+func (e *EmbeddedGeminiServer) Stop(ctx context.Context) {
+ if e.server != nil {
+ e.server.Shutdown(ctx)
+ }
+}
R preview/content_servers.go => preview/servers/http.go +10 -17
@@ 1,55 1,48 @@
-// the http.go file defines the embedded HTTP server logic
-package preview
+package servers
import (
"context"
+ "io/fs"
"net/http"
- "path/filepath"
"time"
"git.sr.ht/~artemis/cap/preview/managed"
"github.com/withmandala/go-log"
)
-// Generic embedded server interface for the preview environment
-type ContentServerCapability interface {
- // Starts the embedded server on another routine
- Start(ctx *managed.ManagedContext, capsuleRoot string) error
- // Stops the embedded server
- Stop(context.Context)
-}
-
-// CreateHttpEmbeddedServer provides a HTTP server implementation for the embedded server
-func CreateHttpEmbeddedServer(address string, logger *log.Logger) ContentServerCapability {
+// CreateEmbeddedHttpServer provides a HTTP server implementation
+func CreateEmbeddedHttpServer(address string, logger *log.Logger) ContentServerCapability {
return &EmbeddedHttpServer{
address: address,
logger: logger,
}
}
-// HTTP server implementation for the embedded server
+// EmbeddedHttpServer is the embedded HTTP server implementation
type EmbeddedHttpServer struct {
address string
server *http.Server
logger *log.Logger
}
-func (e *EmbeddedHttpServer) Start(ctx *managed.ManagedContext, capsuleRoot string) error {
+func (e *EmbeddedHttpServer) Start(ctx *managed.ManagedContext, contentRoot fs.FS) error {
e.server = &http.Server{
Addr: e.address,
ReadTimeout: 2 * time.Second,
WriteTimeout: 2 * time.Second,
- Handler: http.FileServer(http.Dir(filepath.Join(capsuleRoot, ".public"))),
+ Handler: http.FileServer(http.FS(contentRoot)),
}
go func(ctx *managed.ManagedContext, server *http.Server) {
err := server.ListenAndServe()
if err != http.ErrServerClosed {
+ e.logger.Errorf("HTTP server error: %s", err.Error())
ctx.Done(err)
}
ctx.Done(nil)
}(ctx, e.server)
- e.logger.Infof("Embedded server running on http://%s", e.address)
+ e.logger.Infof("Embedded HTTP server running on http://%s", e.address)
+
return nil
}
M types.go => types.go +4 -3
@@ 97,9 97,10 @@ type Config struct {
// WatchCMD contains the subcommand's flags and arguments
type WatchCMD struct {
- DisableHttpServer bool `arg:"-d,--disable-http" help:"don't start the embedded http server"`
- DisableAutoRebuild bool `arg:"-r,--disable-auto-rebuild" help:"don't start to watch for changes (a rebuild can still be triggered by sending USR1 to the process)"`
- ListenAddress string `arg:"-l,--listen" default:"127.0.0.1:8080" help:"specifies the address the embedded http server will listen on"`
+ DisableHttpServer bool `arg:"-d,--disable-http" help:"don't start the embedded http server"`
+ DisableAutoRebuild bool `arg:"-r,--disable-auto-rebuild" help:"don't start to watch for changes (a rebuild can still be triggered by sending USR1 to the process)"`
+ HttpListenAddress string `arg:"-l,--listen" default:"127.0.0.1:8080" help:"specifies the address the embedded http server will listen on"`
+ GeminiListenAddress string `arg:"-g,--gemini-listen" default:"127.0.0.1:1965" help:"specifies the address the embedded gemini server will listen on"`
}
// Args contains all flags, arguments, and subcommands for cap(1)
M watcher.go => watcher.go +3 -1
@@ 5,6 5,7 @@ import (
"git.sr.ht/~artemis/cap/domain"
"git.sr.ht/~artemis/cap/preview"
+ "git.sr.ht/~artemis/cap/preview/servers"
"git.sr.ht/~artemis/cap/preview/watch"
)
@@ 19,7 20,8 @@ func Watch(root string, args *Args) error {
manager := preview.CreateManager(domain.L)
if !args.WatchCmd.DisableHttpServer {
- manager.AddContentServer(preview.CreateHttpEmbeddedServer(args.WatchCmd.ListenAddress, domain.L))
+ manager.AddContentServer(servers.CreateEmbeddedHttpServer(args.WatchCmd.HttpListenAddress, domain.L))
+ manager.AddContentServer(servers.CreateEmbeddedGeminiServer(args.WatchCmd.GeminiListenAddress, domain.L))
}
manager.AddWatcher(watch.CreateSignalWatcher(domain.L))