mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-24 02:11:54 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
159 lines
3.6 KiB
Go
159 lines
3.6 KiB
Go
package route
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
|
"github.com/yusing/godoxy/internal/health/monitor"
|
|
"github.com/yusing/godoxy/internal/logging/accesslog"
|
|
gphttp "github.com/yusing/godoxy/internal/net/gphttp"
|
|
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
|
"github.com/yusing/godoxy/internal/types"
|
|
gperr "github.com/yusing/goutils/errs"
|
|
"github.com/yusing/goutils/task"
|
|
)
|
|
|
|
type (
|
|
FileServer struct {
|
|
*Route
|
|
|
|
middleware *middleware.Middleware
|
|
handler http.Handler
|
|
accessLogger accesslog.AccessLogger
|
|
}
|
|
)
|
|
|
|
var _ types.FileServerRoute = (*FileServer)(nil)
|
|
|
|
func handler(root string, spa bool, index string) http.Handler {
|
|
if !spa {
|
|
return http.FileServer(http.Dir(root))
|
|
}
|
|
indexPath := filepath.Join(root, index)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
urlPath := path.Clean(r.URL.Path)
|
|
if urlPath == "/" {
|
|
http.ServeFile(w, r, indexPath)
|
|
return
|
|
}
|
|
fullPath := filepath.Join(root, filepath.FromSlash(urlPath))
|
|
stat, err := os.Stat(fullPath)
|
|
if err == nil && !stat.IsDir() {
|
|
http.ServeFile(w, r, fullPath)
|
|
return
|
|
}
|
|
http.ServeFile(w, r, indexPath)
|
|
})
|
|
}
|
|
|
|
func NewFileServer(base *Route) (*FileServer, error) {
|
|
s := &FileServer{Route: base}
|
|
|
|
s.Root = filepath.Clean(s.Root)
|
|
if !path.IsAbs(s.Root) {
|
|
return nil, errors.New("`root` must be an absolute path")
|
|
}
|
|
|
|
if s.Index == "" {
|
|
s.Index = "/index.html"
|
|
} else if s.Index[0] != '/' {
|
|
s.Index = "/" + s.Index
|
|
}
|
|
s.handler = handler(s.Root, s.SPA, s.Index)
|
|
|
|
if len(s.Middlewares) > 0 {
|
|
mid, err := middleware.BuildMiddlewareFromMap(s.Alias, s.Middlewares)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.middleware = mid
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Start implements task.TaskStarter.
|
|
func (s *FileServer) Start(parent task.Parent) error {
|
|
s.task = parent.Subtask("fileserver."+s.Name(), false)
|
|
|
|
pathPatterns := s.PathPatterns
|
|
switch {
|
|
case len(pathPatterns) == 0:
|
|
case len(pathPatterns) == 1 && pathPatterns[0] == "/":
|
|
default:
|
|
mux := gphttp.NewServeMux()
|
|
patErrs := gperr.NewBuilder("invalid path pattern(s)")
|
|
for _, p := range pathPatterns {
|
|
patErrs.Add(mux.Handle(p, s.handler))
|
|
}
|
|
if err := patErrs.Error(); err != nil {
|
|
s.task.Finish(err)
|
|
return err
|
|
}
|
|
s.handler = mux
|
|
}
|
|
|
|
if s.middleware != nil {
|
|
next := s.handler
|
|
s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
s.middleware.ServeHTTP(next.ServeHTTP, w, r)
|
|
})
|
|
}
|
|
|
|
if s.UseAccessLog() {
|
|
var err error
|
|
s.accessLogger, err = accesslog.NewAccessLogger(s.task, s.AccessLog)
|
|
if err != nil {
|
|
s.task.Finish(err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(s.Rules) > 0 {
|
|
s.handler = s.Rules.BuildHandler(s.handler.ServeHTTP)
|
|
}
|
|
|
|
if s.UseHealthCheck() {
|
|
s.HealthMon = monitor.NewMonitor(s)
|
|
if err := s.HealthMon.Start(s.task); err != nil {
|
|
log.Warn().EmbedObject(s).Err(err).Msg("health monitor error")
|
|
s.HealthMon = nil
|
|
}
|
|
}
|
|
|
|
ep := entrypoint.FromCtx(parent.Context())
|
|
if ep == nil {
|
|
err := errors.New("entrypoint not initialized")
|
|
s.task.Finish(err)
|
|
return err
|
|
}
|
|
|
|
if err := ep.StartAddRoute(s); err != nil {
|
|
s.task.Finish(err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *FileServer) RootPath() string {
|
|
return s.Root
|
|
}
|
|
|
|
// ServeHTTP implements http.Handler.
|
|
func (s *FileServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
if s.accessLogger != nil {
|
|
rec := accesslog.GetResponseRecorder(w)
|
|
w = rec
|
|
defer func() {
|
|
s.accessLogger.LogRequest(req, rec.Response())
|
|
accesslog.PutResponseRecorder(rec)
|
|
}()
|
|
}
|
|
s.handler.ServeHTTP(w, req)
|
|
}
|