fix(handler/file): fix deadlock, conn leak, and nil Addr; add 39 tests

Fix bugs in the file handler: unbuffered send channel deadlock on Close,
conn leak when send drops on done, and Addr() returning nil. Add done-
priority select in send(), explicit ln.Close() in Close(), Unwrap() on
responseWriter for http.ResponseController, and nil guards.

Also add package doc, NewHandler doc, and 39 unit tests covering the
listener, responseWriter, handler lifecycle, auth, and file serving.
This commit is contained in:
ginuerzh
2026-05-27 22:49:59 +08:00
parent a2cef9639d
commit 7e3183488b
2 changed files with 1048 additions and 6 deletions
+37 -6
View File
@@ -1,3 +1,5 @@
// Package file provides a static file-serving handler for GOST.
// It serves files from a configured directory over HTTP via the GOST proxy chain.
package file
import (
@@ -31,6 +33,9 @@ type fileHandler struct {
recorder recorder.RecorderObject
}
// NewHandler creates a static file-serving handler. It registers as "file" in the
// handler registry and serves files from the directory specified by the "file.dir"
// or "dir" metadata key.
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
@@ -60,8 +65,9 @@ func (h *fileHandler) Init(md md.Metadata) (err error) {
}
h.ln = &singleConnListener{
conn: make(chan net.Conn),
conn: make(chan net.Conn, 1),
done: make(chan struct{}),
addr: &listenerAddr{},
}
go h.server.Serve(h.ln)
@@ -74,11 +80,14 @@ func (h *fileHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
clientAddr = srcAddr.String()
}
remoteAddr := conn.RemoteAddr()
localAddr := conn.LocalAddr()
h.options.Logger.WithFields(map[string]any{
"client": clientAddr,
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
}).Infof("%s - %s", conn.RemoteAddr(), conn.LocalAddr())
"remote": remoteAddr.String(),
"local": localAddr.String(),
}).Infof("%s - %s", remoteAddr, localAddr)
h.ln.send(conn)
@@ -86,6 +95,9 @@ func (h *fileHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
}
func (h *fileHandler) Close() error {
if h.ln != nil {
h.ln.Close()
}
return h.server.Close()
}
@@ -149,9 +161,16 @@ func (h *fileHandler) handleFunc(w http.ResponseWriter, r *http.Request) {
}
}
h.handler.ServeHTTP(rw, r)
if h.handler != nil {
h.handler.ServeHTTP(rw, r)
}
}
type listenerAddr struct{}
func (a *listenerAddr) Network() string { return "file" }
func (a *listenerAddr) String() string { return "file" }
type singleConnListener struct {
conn chan net.Conn
addr net.Addr
@@ -187,10 +206,16 @@ func (l *singleConnListener) Addr() net.Addr {
}
func (l *singleConnListener) send(conn net.Conn) {
select {
case <-l.done:
conn.Close()
return
default:
}
select {
case l.conn <- conn:
case <-l.done:
return
conn.Close()
}
}
@@ -210,3 +235,9 @@ func (w *responseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
// Unwrap returns the underlying ResponseWriter, allowing http.ResponseController
// to discover optional interfaces (Flusher, Hijacker, etc.) on the wrapped writer.
func (w *responseWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}