Mercurial Hosting > d2o
changeset 8:2ffb8028ccbb
add loggingmodes and fix path matching
| author | Atarwn Gard <a@qwa.su> |
|---|---|
| date | Tue, 17 Mar 2026 19:55:07 +0500 |
| parents | 8e4813b4e509 |
| children | ec97184ea63d |
| files | icf/icf.go main.go |
| diffstat | 2 files changed, 164 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/icf/icf.go Tue Mar 10 12:12:33 2026 +0500 +++ b/icf/icf.go Tue Mar 17 19:55:07 2026 +0500 @@ -305,7 +305,7 @@ capName := pat[1:end] rest := pat[end+1:] - for split := 1; split <= len(inp); split++ { + for split := len(inp); split >= 1; split-- { score, finalRem, ok := matchCaptures(rest, inp[split:], caps) if ok { if capName != "_" {
--- a/main.go Tue Mar 10 12:12:33 2026 +0500 +++ b/main.go Tue Mar 17 19:55:07 2026 +0500 @@ -1,15 +1,15 @@ package main import ( + "bufio" "crypto/tls" - "bufio" "fmt" "io" "log" - "net/textproto" "net" "net/http" "net/http/httputil" + "net/textproto" "net/url" "os" "path" @@ -18,12 +18,52 @@ "runtime" "strconv" "strings" + "time" - "d2o/fcgi" "d2o/icf" ) +type loggingMode uint8 + +const ( + logNone loggingMode = iota + logAccess + logVerbose +) + +var ( + curLoggingMode loggingMode = logVerbose + accessLogger = log.New(os.Stderr, "", log.LstdFlags) +) + +func setLoggingMode(m loggingMode) { + curLoggingMode = m + switch m { + case logNone: + log.SetOutput(io.Discard) + case logAccess: + log.SetOutput(io.Discard) + case logVerbose: + // keep standard logger output + } +} + +func accessEnabled() bool { return curLoggingMode == logAccess || curLoggingMode == logVerbose } +func verboseEnabled() bool { return curLoggingMode == logVerbose } + +func accessPrintf(format string, args ...any) { + if accessEnabled() { + accessLogger.Printf(format, args...) + } +} + +func verbosePrintf(format string, args ...any) { + if verboseEnabled() { + log.Printf(format, args...) + } +} + func main() { cfgPath := "/etc/d2obase" if len(os.Args) > 1 { @@ -42,11 +82,22 @@ for _, d := range cfg.Abstract("d2o") { switch d.Key { + case "logging": + switch strings.ToLower(safeArg(d.Args, 0)) { + case "", "verbose": + setLoggingMode(logVerbose) + case "none": + setLoggingMode(logNone) + case "access": + setLoggingMode(logAccess) + default: + log.Fatalf("d2o: unknown logging mode %q (expected none|access|verbose)", safeArg(d.Args, 0)) + } case "threads": n, err := strconv.Atoi(safeArg(d.Args, 0)) if err == nil && n > 0 { runtime.GOMAXPROCS(n) - log.Printf("d2o: GOMAXPROCS = %d", n) + verbosePrintf("d2o: GOMAXPROCS = %d", n) } } } @@ -79,7 +130,7 @@ func (pc portConfig) listen(h http.Handler) error { if !pc.isTLS { - log.Printf("d2o: listening on %s (http)", pc.addr) + verbosePrintf("d2o: listening on %s (http)", pc.addr) return http.ListenAndServe(pc.addr, h) } cert, err := tls.LoadX509KeyPair(pc.certFile, pc.keyFile) @@ -93,7 +144,7 @@ if err != nil { return fmt.Errorf("d2o: listen %s: %w", pc.addr, err) } - log.Printf("d2o: listening on %s (https)", pc.addr) + verbosePrintf("d2o: listening on %s (https)", pc.addr) return http.Serve(ln, h) } @@ -147,6 +198,9 @@ } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + rr := &respRecorder{ResponseWriter: w, status: 0} + host := stripPort(r.Host) reqPath := path.Clean(r.URL.Path) @@ -155,14 +209,18 @@ dirs, caps = h.cfg.Match(host) } if dirs == nil { - http.Error(w, "not found", http.StatusNotFound) + http.Error(rr, "not found", http.StatusNotFound) + rr.ensureStatus(http.StatusNotFound) + accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) return } - h.serve(w, r, dirs, caps) + h.serve(rr, r, dirs, caps) + rr.ensureStatus(http.StatusOK) + accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) } -func (h *handler) serve(w http.ResponseWriter, r *http.Request, dirs []icf.Directive, _ map[string]string) { +func (h *handler) serve(w http.ResponseWriter, r *http.Request, dirs []icf.Directive, caps map[string]string) { var ( rootDir string rootShow bool @@ -199,22 +257,39 @@ if rdirCode == 0 { rdirCode = http.StatusFound } + verbosePrintf("d2o: rdir %d -> %s", rdirCode, rdirURL) http.Redirect(w, r, rdirURL, rdirCode) return } if rprxAddr != "" { + verbosePrintf("d2o: rprx -> %s", rprxAddr) serveReverseProxy(w, r, rprxAddr) return } if fcgiAddr != "" && matchGlob(fcgiPat, r.URL.Path) { + verbosePrintf("d2o: fcgi -> %s (%s)", fcgiAddr, r.URL.Path) if err := serveFCGI(w, r, fcgiAddr, rootDir); err != nil { - log.Printf("d2o: fcgi error: %v", err) + verbosePrintf("d2o: fcgi error: %v", err) http.Error(w, "gateway error", http.StatusBadGateway) } return } if rootDir != "" { - serveStatic(w, r, rootDir, rootShow, ndex, fcgiAddr, fcgiPat) + fsPath := path.Clean(r.URL.Path) + displayPath := fsPath + if user, ok := caps["user"]; ok && user != "" { + mount := "/~" + user + if fsPath == mount || strings.HasPrefix(fsPath, mount+"/") { + trimmed := strings.TrimPrefix(fsPath, mount) + if trimmed == "" { + trimmed = "/" + } + fsPath = trimmed + } + } + + verbosePrintf("d2o: static -> %s (%s)", rootDir, r.URL.Path) + serveStatic(w, r, rootDir, rootShow, ndex, fcgiAddr, fcgiPat, fsPath, displayPath) return } @@ -226,8 +301,8 @@ // serveStatic serves files from rootDir. // rootIndex == nil: directory listing forbidden (hide). // rootIndex != nil: try each as index candidate; if none found, show listing. -func serveStatic(w http.ResponseWriter, r *http.Request, rootDir string, show bool, ndex []string, fcgiAddr, fcgiPat string) { - fpath := filepath.Join(rootDir, filepath.FromSlash(path.Clean(r.URL.Path))) +func serveStatic(w http.ResponseWriter, r *http.Request, rootDir string, show bool, ndex []string, fcgiAddr, fcgiPat string, fsPath, displayPath string) { + fpath := filepath.Join(rootDir, filepath.FromSlash(path.Clean(fsPath))) info, err := os.Stat(fpath) if os.IsNotExist(err) { @@ -260,7 +335,7 @@ http.Error(w, "forbidden", http.StatusForbidden) return } - listDir(w, r, fpath, r.URL.Path) + listDir(w, r, fpath, displayPath) return } @@ -301,8 +376,19 @@ return } proxy := httputil.NewSingleHostReverseProxy(u) + if verboseEnabled() { + origDirector := proxy.Director + proxy.Director = func(req *http.Request) { + origDirector(req) + log.Printf("d2o: rprx upstream request: %s %s", req.Method, req.URL.String()) + } + proxy.ModifyResponse = func(resp *http.Response) error { + log.Printf("d2o: rprx upstream response: %d %s", resp.StatusCode, resp.Status) + return nil + } + } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { - log.Printf("d2o: rprx error: %v", err) + verbosePrintf("d2o: rprx error: %v", err) http.Error(w, "bad gateway", http.StatusBadGateway) } proxy.ServeHTTP(w, r) @@ -413,4 +499,65 @@ regPat := "^" + strings.ReplaceAll(regexp.QuoteMeta(pattern), regexp.QuoteMeta("*"), ".*") + "$" matched, err := regexp.MatchString(regPat, s) return err == nil && matched -} \ No newline at end of file +} + +type respRecorder struct { + http.ResponseWriter + status int + bytes int64 +} + +func (rr *respRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { + h, ok := rr.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, fmt.Errorf("hijack not supported") + } + return h.Hijack() +} + +func (rr *respRecorder) Flush() { + if f, ok := rr.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +func (rr *respRecorder) Push(target string, opts *http.PushOptions) error { + if p, ok := rr.ResponseWriter.(http.Pusher); ok { + return p.Push(target, opts) + } + return http.ErrNotSupported +} + +func (rr *respRecorder) ReadFrom(src io.Reader) (int64, error) { + // Preserve io.Copy optimizations and count bytes. + rf, ok := rr.ResponseWriter.(io.ReaderFrom) + if !ok { + return io.Copy(rr, src) + } + if rr.status == 0 { + rr.status = http.StatusOK + } + n, err := rf.ReadFrom(src) + rr.bytes += n + return n, err +} + +func (rr *respRecorder) WriteHeader(code int) { + rr.status = code + rr.ResponseWriter.WriteHeader(code) +} + +func (rr *respRecorder) Write(p []byte) (int, error) { + if rr.status == 0 { + rr.status = http.StatusOK + } + n, err := rr.ResponseWriter.Write(p) + rr.bytes += int64(n) + return n, err +} + +func (rr *respRecorder) ensureStatus(defaultCode int) { + if rr.status == 0 { + rr.status = defaultCode + } +}
