183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
type (
|
|
// struct for holding response details
|
|
responseData struct {
|
|
status int
|
|
size int
|
|
}
|
|
|
|
// our http.ResponseWriter implementation
|
|
loggingResponseWriter struct {
|
|
http.ResponseWriter // compose original http.ResponseWriter
|
|
responseData *responseData
|
|
}
|
|
)
|
|
|
|
func (r *loggingResponseWriter) Write(b []byte) (int, error) {
|
|
size, err := r.ResponseWriter.Write(b) // write response using original http.ResponseWriter
|
|
r.responseData.size += size // capture size
|
|
return size, err
|
|
}
|
|
|
|
func (r *loggingResponseWriter) WriteHeader(statusCode int) {
|
|
r.ResponseWriter.WriteHeader(statusCode) // write status code using original http.ResponseWriter
|
|
r.responseData.status = statusCode // capture status code
|
|
}
|
|
|
|
func WithLogging(h http.Handler) http.Handler {
|
|
loggingFn := func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
|
|
responseData := &responseData{
|
|
status: 0,
|
|
size: 0,
|
|
}
|
|
lrw := loggingResponseWriter{
|
|
ResponseWriter: w, // compose original http.ResponseWriter
|
|
responseData: responseData,
|
|
}
|
|
h.ServeHTTP(&lrw, r) // inject our implementation of http.ResponseWriter
|
|
|
|
duration := time.Since(start)
|
|
|
|
slog.Info("Request:",
|
|
slog.String("method", r.Method),
|
|
slog.String("path", r.RequestURI),
|
|
slog.String("url", r.URL.Path),
|
|
slog.String("host", r.Host),
|
|
slog.Int("status", responseData.status),
|
|
slog.Int64("duration", duration.Microseconds()),
|
|
slog.Int("size", responseData.size),
|
|
)
|
|
|
|
}
|
|
return http.HandlerFunc(loggingFn)
|
|
}
|
|
|
|
func pingHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("OK"))
|
|
}
|
|
|
|
func main() {
|
|
logLevel := &slog.LevelVar{} // INFO
|
|
|
|
opts := slog.HandlerOptions{
|
|
Level: logLevel,
|
|
}
|
|
logLevel.Set(slog.LevelDebug)
|
|
handler := slog.NewTextHandler(os.Stdout, &opts)
|
|
logger := slog.New(handler)
|
|
slog.SetDefault(logger)
|
|
|
|
var host string
|
|
var port string
|
|
var directory string
|
|
|
|
app := &cli.App{
|
|
Name: "snice",
|
|
Usage: "Serve Static Files",
|
|
Version: "v0.2.0",
|
|
DefaultCommand: "",
|
|
Commands: []*cli.Command{
|
|
{
|
|
Name: "serve",
|
|
Aliases: []string{"s"},
|
|
Usage: "Serve directory",
|
|
Action: func(cCtx *cli.Context) error {
|
|
var addr string = host + ":" + port
|
|
|
|
srv := &http.Server{
|
|
Addr: addr,
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/ping", http.HandlerFunc(pingHandler))
|
|
fileHandler := http.FileServer(http.Dir(directory))
|
|
mux.Handle("/", fileHandler)
|
|
srv.Handler = WithLogging(mux)
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
slog.Error("Listen error", err)
|
|
}
|
|
slog.Info(fmt.Sprintf("Serving directory %q on http://%v", directory, listener.Addr()))
|
|
err = srv.Serve(listener)
|
|
if err != nil {
|
|
slog.Error("Serve error", err)
|
|
}
|
|
return nil
|
|
},
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "directory",
|
|
Aliases: []string{"dir", "d"},
|
|
EnvVars: []string{"DIRECTORY"},
|
|
Value: "/srv",
|
|
Usage: "Directory to serve",
|
|
Destination: &directory,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "host",
|
|
EnvVars: []string{"HOST"},
|
|
Value: "0.0.0.0",
|
|
Usage: "Host to listen on",
|
|
Destination: &host,
|
|
}},
|
|
},
|
|
{
|
|
Name: "healthcheck",
|
|
Aliases: []string{"hc"},
|
|
Usage: "Call healthcheck endpoint",
|
|
Action: func(cCtx *cli.Context) error {
|
|
url := fmt.Sprintf("http://127.0.0.1:%s/ping", port)
|
|
slog.Debug("Healthcheck: ", slog.String("url", url))
|
|
res, err := http.Get(url)
|
|
if err != nil {
|
|
return cli.Exit("FAIL", 1)
|
|
}
|
|
if res.StatusCode == 200 {
|
|
return cli.Exit("OK", 0)
|
|
}
|
|
slog.Debug(fmt.Sprintf("Status: %d\n", res.StatusCode))
|
|
return cli.Exit("FAIL", 1)
|
|
}}},
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}},
|
|
&cli.StringFlag{
|
|
Name: "port",
|
|
Aliases: []string{"p"},
|
|
EnvVars: []string{"PORT"},
|
|
Value: "3000",
|
|
Usage: "Port to serve on",
|
|
Destination: &port,
|
|
},
|
|
},
|
|
EnableBashCompletion: true,
|
|
Compiled: time.Time{},
|
|
Authors: []*cli.Author{},
|
|
Reader: nil,
|
|
Writer: nil,
|
|
ErrWriter: nil,
|
|
SliceFlagSeparator: "",
|
|
DisableSliceFlagSeparator: false,
|
|
UseShortOptionHandling: true,
|
|
Suggest: true,
|
|
AllowExtFlags: false,
|
|
SkipFlagParsing: false,
|
|
}
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|