aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Pervaz Boocha <mboocha@sudomsg.com>2025-08-02 22:23:13 +0530
committerMarc Pervaz Boocha <mboocha@sudomsg.com>2025-08-02 23:02:30 +0530
commit178e0bfc2d8709ae4d3abd7519b260df09b2d3b5 (patch)
tree10cbdd013c62586190074d9f088e2269f88c4cab
parentFixed Broken .gitignore (diff)
downloadgopkgserver-main.tar
gopkgserver-main.tar.gz
gopkgserver-main.tar.bz2
gopkgserver-main.tar.lz
gopkgserver-main.tar.xz
gopkgserver-main.tar.zst
gopkgserver-main.zip
Switched to go.sudomsg.com/kitHEADv0.1.0main
-rw-r--r--cmd/gopkgserver/main.go26
-rw-r--r--go.mod7
-rw-r--r--go.sum4
-rw-r--r--logging/handler/handler.go75
-rw-r--r--logging/log.go56
-rw-r--r--main.go87
-rw-r--r--repo/repo.go7
-rw-r--r--repo/repo_test.go137
-rw-r--r--robot.go17
9 files changed, 189 insertions, 227 deletions
diff --git a/cmd/gopkgserver/main.go b/cmd/gopkgserver/main.go
index 9e40b72..7248136 100644
--- a/cmd/gopkgserver/main.go
+++ b/cmd/gopkgserver/main.go
@@ -1,30 +1,10 @@
package main
import (
- "context"
- "flag"
- "go-pkg-server/logging"
- "log/slog"
- "os"
- "os/signal"
-
- gopkgserver "go-pkg-server"
+ "go.sudomsg.com/gopkgserver"
+ "go.sudomsg.com/kit/runner"
)
func main() {
- ctx := context.Background()
- defer func() {
- err := recover()
- if err != nil {
- logging.RecoverLog(ctx, err)
- os.Exit(2)
- }
- }()
-
- ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
- defer stop()
-
- if err := gopkgserver.Run(ctx, flag.CommandLine, os.Args[1:]); err != nil {
- slog.Log(ctx, slog.LevelError, "Program returned an error", "error", err)
- }
+ runner.Load(gopkgserver.Run)
}
diff --git a/go.mod b/go.mod
index a12fb29..ac10f3e 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,9 @@ module go.sudomsg.com/gopkgserver
go 1.24.1
-require github.com/pelletier/go-toml/v2 v2.2.4
+require (
+ github.com/pelletier/go-toml/v2 v2.2.4
+ go.sudomsg.com/kit v0.1.1
+)
+
+require golang.org/x/sync v0.16.0 // indirect
diff --git a/go.sum b/go.sum
index 3cf50e1..e5f4434 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,6 @@
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+go.sudomsg.com/kit v0.1.1 h1:mrwLCE+1jnjaA5aj0AE6cZ9YdRgg/GDsR/OX/5faJ14=
+go.sudomsg.com/kit v0.1.1/go.mod h1:rFO8lXUV2Ij1deDS0OrfdZ4xpH5akf0jHtVwC0N8fBQ=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
diff --git a/logging/handler/handler.go b/logging/handler/handler.go
deleted file mode 100644
index 1501715..0000000
--- a/logging/handler/handler.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package handler
-
-import (
- "go-pkg-server/logging"
- "log/slog"
- "net/http"
- "time"
-)
-
-type traceWriter struct {
- http.ResponseWriter
- status int
- bytes int
-}
-
-var _ http.ResponseWriter = &traceWriter{}
-
-func (t *traceWriter) Write(b []byte) (int, error) {
- if t.status == 0 {
- t.WriteHeader(http.StatusOK)
- }
- c, err := t.ResponseWriter.Write(b)
- t.bytes += c
- return c, err
-}
-
-func (tw *traceWriter) WriteHeader(statusCode int) {
- if tw.status == 0 {
- tw.status = http.StatusOK
- }
- tw.status = statusCode
- tw.ResponseWriter.WriteHeader(statusCode)
-}
-
-func New(next http.Handler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- logger := logging.FromContext(ctx)
-
- defer func() {
- err := recover()
- if err != nil {
- logging.RecoverLog(ctx, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- }()
-
- logger = logger.With("method", r.Method,
- "host", r.Host,
- "remote_address", r.RemoteAddr,
- "path", r.RequestURI,
- )
-
- ctx = logging.WithLogger(ctx, logger)
-
- r = r.WithContext(ctx)
-
- tw := &traceWriter{ResponseWriter: w}
-
- start := time.Now()
- next.ServeHTTP(tw, r)
- duration := time.Since(start)
-
- logger.Log(ctx, slog.LevelInfo, "Request Handled",
- "protocol", r.Proto,
- "bytes_recieved", r.ContentLength,
- "status", tw.status,
- "time", duration,
- "bytes_sent", tw.bytes,
- "user_agent", r.UserAgent(),
- "referer", r.Referer(),
- )
-
- }
-}
diff --git a/logging/log.go b/logging/log.go
deleted file mode 100644
index 8e3078f..0000000
--- a/logging/log.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package logging
-
-import (
- "context"
- "io"
- "log/slog"
- "os"
- "runtime"
-)
-
-type Config struct {
- Level slog.Level `toml:"level"`
- File string `toml:"file"`
-}
-
-func SetupLogger(cfg Config) *slog.Logger {
- opts := &slog.HandlerOptions{Level: cfg.Level}
- var handler slog.Handler
-
- var writter io.Writer
- writter = os.Stdout
- if cfg.File != "" {
- f, _ := os.OpenFile(cfg.File, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
- writter = f
- }
- handler = slog.NewJSONHandler(writter, opts)
-
- logger := slog.New(handler)
- slog.SetDefault(logger)
- return logger
-}
-
-type contextKeyType struct{}
-
-var contextKey contextKeyType
-
-func WithLogger(ctx context.Context, logger *slog.Logger) context.Context {
- return context.WithValue(ctx, contextKey, logger)
-}
-
-func FromContext(ctx context.Context) *slog.Logger {
- if logger, ok := ctx.Value(contextKey).(*slog.Logger); ok {
- return logger
- }
- return slog.Default()
-}
-
-func RecoverLog(ctx context.Context, err any) {
- logger := FromContext(ctx)
-
- const size = 64 << 10
- buf := make([]byte, size)
- buf = buf[:runtime.Stack(buf, false)]
-
- logger.Log(ctx, slog.LevelError, "Program Paniced", "error", err, "stack", string(buf))
-}
diff --git a/main.go b/main.go
index 6404940..3fe9001 100644
--- a/main.go
+++ b/main.go
@@ -3,31 +3,24 @@ package gopkgserver
import (
"context"
_ "embed"
- "errors"
"flag"
"fmt"
- "io"
+
"log/slog"
- "net"
"net/http"
"os"
"github.com/pelletier/go-toml/v2"
-
- "go-pkg-server/logging"
- "go-pkg-server/logging/handler"
- "go-pkg-server/repo"
+ "go.sudomsg.com/gopkgserver/repo"
+ httpServer "go.sudomsg.com/kit/http"
+ "go.sudomsg.com/kit/logging"
+ logHandler "go.sudomsg.com/kit/logging/http"
)
-type ServerConfig struct {
- Proto string `toml:"proto"`
- Address string `toml:"address"`
-}
-
type Config struct {
- Server ServerConfig `toml:"server"`
- Log logging.Config `toml:"log"`
- Repos map[string]repo.Repo `toml:"repo"`
+ Server []httpServer.ServerConfig `toml:"server"`
+ Logging logging.LogConfig `toml:"logging"`
+ Repos map[string]repo.Repo `toml:"repo"`
}
func Run(ctx context.Context, fs *flag.FlagSet, args []string) error {
@@ -40,8 +33,10 @@ func Run(ctx context.Context, fs *flag.FlagSet, args []string) error {
return err
}
- logger := logging.SetupLogger(cfg.Log)
- ctx = logging.WithLogger(ctx, logger)
+ if err := logging.Setup(cfg.Logging); err != nil {
+ return err
+ }
+ ctx = logging.WithLogger(ctx, slog.Default())
h, err := repo.New(cfg.Repos)
if err != nil {
@@ -52,7 +47,12 @@ func Run(ctx context.Context, fs *flag.FlagSet, args []string) error {
mux.Handle("GET /robots.txt", Robot())
mux.Handle("GET /", h)
- return RunServer(ctx, handler.New(mux), cfg.Server)
+ handler := logHandler.New(mux, nil)
+
+ lns, err := httpServer.OpenListeners(ctx, cfg.Server)
+ defer lns.CloseAll()
+
+ return httpServer.RunHTTPServers(ctx, lns, handler)
}
func LoadConfig(cfgFile string) (Config, error) {
@@ -66,56 +66,5 @@ func LoadConfig(cfgFile string) (Config, error) {
return Config{}, fmt.Errorf("parse config: %w", err)
}
- if cfg.Server.Proto == "" {
- cfg.Server.Proto = "tcp"
- }
-
return cfg, nil
}
-
-func RunServer(ctx context.Context, mux http.Handler, config ServerConfig) error {
- logger := logging.FromContext(ctx)
-
- srv := &http.Server{
- Handler: mux,
- BaseContext: func(l net.Listener) context.Context {
- logger := logger.With("address", l.Addr())
- ctx = logging.WithLogger(ctx, logger)
- return ctx
- },
- ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
- }
-
- ln, err := net.Listen(config.Proto, config.Address)
- if err != nil {
- return err
- }
-
- logger.Log(ctx, slog.LevelError, "Server Started on", "address", ln.Addr())
-
- go func() {
- <-ctx.Done()
- shutdownCtx := context.Background()
- // We received an interrupt signal, shut down.
- if err := srv.Shutdown(shutdownCtx); err != nil {
- slog.Log(shutdownCtx, slog.LevelError, "HTTP server Shutdown", "error", err)
- }
- }()
-
- if err := srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
- return err
- }
-
- return nil
-}
-
-func Robot() http.HandlerFunc {
- robots := `User-agent: *
-Disallow: /
-`
- return func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Cache-Control", "public, max-age=86400, immutable")
- w.Header().Set("Content-Type", "text/plain")
- io.WriteString(w, robots)
- }
-}
diff --git a/repo/repo.go b/repo/repo.go
index 2c052d6..b3eeef0 100644
--- a/repo/repo.go
+++ b/repo/repo.go
@@ -6,7 +6,7 @@ import (
"fmt"
"html/template"
"net/http"
- "strings"
+ "path"
)
//go:embed meta.html
@@ -45,8 +45,9 @@ func New(repo map[string]Repo) (*RepoHandler, error) {
}
func (h *RepoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- key := fmt.Sprintf("%s%s", r.Host, r.URL.Path)
- key = strings.TrimSuffix(key, "/")
+ host := r.Host
+ path := path.Clean(r.URL.Path)
+ key := host + path
page, ok := h.Pages[key]
if !ok {
diff --git a/repo/repo_test.go b/repo/repo_test.go
new file mode 100644
index 0000000..57f249c
--- /dev/null
+++ b/repo/repo_test.go
@@ -0,0 +1,137 @@
+package repo_test
+
+import (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "go.sudomsg.com/gopkgserver/repo"
+)
+
+func TestMetaExec(t *testing.T) {
+ t.Parallel()
+
+ meta, err := repo.NewMeta()
+ if err != nil {
+ t.Fatalf("failed to create Meta: %v", err)
+ }
+
+ pkg := "example.com/foo"
+ r := repo.Repo{
+ VCS: "git",
+ Repository: "https://github.com/user/foo",
+ Home: "https://github.com/user/foo",
+ Directory: "https://github.com/user/foo/tree/master{/dir}",
+ File: "https://github.com/user/foo/blob/master{/dir}/{file}#L{line}",
+ }
+
+ html, err := meta.Exec(pkg, r)
+ if err != nil {
+ t.Fatalf("Exec failed: %v", err)
+ }
+
+ htmlStr := string(html)
+ if !strings.Contains(htmlStr, `<meta name="go-import"`) {
+ t.Fatalf("missing go-import meta tag: %q", htmlStr)
+ }
+ if !strings.Contains(htmlStr, `<meta name="go-source"`) {
+ t.Fatalf("missing go-source meta tag: %q", htmlStr)
+ }
+ if !strings.Contains(htmlStr, pkg) {
+ t.Fatalf("missing package name in output: %q", htmlStr)
+ }
+}
+
+func TestServeHTTP(t *testing.T) {
+ t.Parallel()
+
+ repos := map[string]repo.Repo{
+ "example.com/foo": {
+ VCS: "git",
+ Repository: "https://github.com/user/foo",
+ Home: "https://github.com/user/foo",
+ Directory: "https://github.com/user/foo/tree/master{/dir}",
+ File: "https://github.com/user/foo/blob/master{/dir}/{file}#L{line}",
+ },
+ }
+
+ t.Run("returns meta tag for go-get=1", func(t *testing.T) {
+ t.Parallel()
+
+ handler, err := repo.New(repos)
+ if err != nil {
+ t.Fatalf("failed to create handler: %v", err)
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "http://example.com/foo?go-get=1", nil)
+ w := httptest.NewRecorder()
+
+ handler.ServeHTTP(w, req)
+
+ resp := w.Result()
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("expected %v, got %v", http.StatusOK, resp.StatusCode)
+ }
+
+ var buf bytes.Buffer
+ buf.ReadFrom(resp.Body)
+ body := buf.String()
+
+ if !strings.Contains(body, `<meta name="go-import"`) {
+ t.Fatalf("expected go-import tag in body, got: %v", body)
+ }
+ })
+
+ t.Run("redirects to pkg.go.dev otherwise", func(t *testing.T) {
+ t.Parallel()
+
+ handler, err := repo.New(repos)
+ if err != nil {
+ t.Fatalf("failed to create handler: %v", err)
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "http://example.com/foo", nil)
+ w := httptest.NewRecorder()
+
+ handler.ServeHTTP(w, req)
+
+ resp := w.Result()
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusFound {
+ t.Fatalf("expected %v, got %v", http.StatusFound, resp.StatusCode)
+ }
+ loc, err := resp.Location()
+ if err != nil {
+ t.Fatalf("Invalid redirect location: %v", err)
+ }
+
+ if !strings.HasPrefix(loc.String(), "https://pkg.go.dev/example.com/foo") {
+ t.Fatalf("unexpected redirect location: %v", loc)
+ }
+ })
+
+ t.Run("returns 404 for unknown path", func(t *testing.T) {
+ t.Parallel()
+
+ handler, err := repo.New(repos)
+ if err != nil {
+ t.Fatalf("failed to create handler: %v", err)
+ }
+
+ w := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "http://example.com/bar", nil)
+ handler.ServeHTTP(w, req)
+
+ resp := w.Result()
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusNotFound {
+ t.Fatalf("expected %v, got %v", http.StatusNotFound, resp.StatusCode)
+ }
+ })
+}
diff --git a/robot.go b/robot.go
new file mode 100644
index 0000000..7426eda
--- /dev/null
+++ b/robot.go
@@ -0,0 +1,17 @@
+package gopkgserver
+
+import (
+ "io"
+ "net/http"
+)
+
+func Robot() http.HandlerFunc {
+ robots := `User-agent: *
+Disallow: /
+`
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "public, max-age=86400, immutable")
+ w.Header().Set("Content-Type", "text/plain")
+ io.WriteString(w, robots)
+ }
+}