Golang Unix域套接字HTTP服务器请求返回404问题排查
基于Unix域套接字的Golang HTTP服务器404问题解决
问题场景
尝试用Golang实现基于Unix域套接字(UDS)的HTTP服务器,代码如下:
package main import ( "fmt" "log" "net/http" "net" "os" "encoding/json" "os/signal" "syscall" ) type UnixListener struct { ServerMux *http.ServeMux SocketPath string UID int GID int SocketFileMode os.FileMode } ////////////////////////////////////////////////////////////////////// // Start the HTTP server with UNIX socket. ////////////////////////////////////////////////////////////////////// func (l *UnixListener) Start() error { listener, err := net.Listen("unix", l.SocketPath) if err != nil { return err } if err := os.Chown(l.SocketPath, l.UID, l.GID); err != nil { return err } if err := os.Chmod(l.SocketPath, l.SocketFileMode); err != nil { return err } go func(){ shutdown(listener) }() svr := http.Server{ Handler: l.ServerMux, } if err := svr.Serve(listener); err != nil { return err } else { return nil } } ////////////////////////////////////////////////////////////////////// // Shutdown the HTTP server. ////////////////////////////////////////////////////////////////////// func shutdown(listener net.Listener) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) s := <-c listener.Close() log.Fatalf("[FATAL] Caught a signal. sinal=%s", s) } func hello(w http.ResponseWriter, r *http.Request) { log.Println("Received request %s", r.RequestURI) fmt.Fprintf(w, "Hello World!\n") w.Header().Set("ContentType", "application/json") w.WriteHeader(http.StatusOK) data := map[string]string { "name" : "satish"} resp, _ := json.Marshal(data) if _, err := w.Write(resp); err != nil { fmt.Printf("Error writing response") } } func main() { mux := http.NewServeMux() mux.HandleFunc("/hello", hello) unixListener := UnixListener{ ServerMux: mux, SocketPath: "/temp/test.sock", UID: 501, GID: 20, SocketFileMode: 0644, } if err := unixListener.Start(); err != nil { log.Fatalf("[FATAL] HTTP server error: %s", err) } }
使用以下curl命令请求时返回404 Not Found:
curl -vvv --unix-socket /temp/test.sock http:/hello
curl输出日志:
* Trying /Users/sburnwal/Projects/ACI/temp/test.sock:0... * Connected to hello (/temp/test.sock) port 80 (#0) > GET / HTTP/1.1 > Host: hello > User-Agent: curl/7.84.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 404 Not Found < Content-Type: text/plain; charset=utf-8 < X-Content-Type-Options: nosniff < Date: Mon, 14 Nov 2022 02:38:11 GMT < Content-Length: 19 < 404 page not found * Connection #0 to host hello left intact
问题原因分析
- curl命令URL格式错误:从curl日志可见,实际发送的请求路径是
/而非/hello。因为使用了http:/hello(单斜杠),curl无法正确解析路径,默认请求根路径/,而服务器只注册了/hello的处理函数,因此返回404。 - 代码中的小问题:
hello函数中使用log.Println格式化字符串,无法正确打印请求URI;- 设置响应头时,键名应为
Content-Type而非ContentType,否则无法正确设置响应的Content-Type; - 忽略了
json.Marshal的错误,可能导致潜在问题; - 关闭服务器时直接调用
listener.Close(),没有实现优雅关闭,可能强制中断正在处理的请求。
修正方案
1. 修正curl请求命令
使用正确的URL格式(双斜杠),可指定任意Host(比如localhost):
curl -vvv --unix-socket /temp/test.sock http://localhost/hello
2. 修正后的服务器代码
package main import ( "context" "encoding/json" "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" "time" ) type UnixListener struct { ServerMux *http.ServeMux SocketPath string UID int GID int SocketFileMode os.FileMode } // Start the HTTP server with UNIX socket. func (l *UnixListener) Start() error { listener, err := net.Listen("unix", l.SocketPath) if err != nil { return err } // 修改权限后启动服务,避免权限问题 if err := os.Chown(l.SocketPath, l.UID, l.GID); err != nil { listener.Close() os.Remove(l.SocketPath) return err } if err := os.Chmod(l.SocketPath, l.SocketFileMode); err != nil { listener.Close() os.Remove(l.SocketPath) return err } svr := http.Server{ Handler: l.ServerMux, } // 优雅关闭协程 go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) s := <-c log.Printf("[INFO] Caught signal, starting graceful shutdown: %s", s) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := svr.Shutdown(ctx); err != nil { log.Fatalf("[FATAL] Shutdown failed: %s", err) } log.Println("[INFO] Server shutdown completed") // 关闭后删除套接字文件 os.Remove(l.SocketPath) }() log.Printf("[INFO] Server listening on UDS: %s", l.SocketPath) if err := svr.Serve(listener); err != http.ErrServerClosed { // 非预期关闭时清理套接字 os.Remove(l.SocketPath) return err } return nil } func hello(w http.ResponseWriter, r *http.Request) { log.Printf("Received request: %s", r.RequestURI) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) data := map[string]string{"name": "satish"} resp, err := json.Marshal(data) if err != nil { log.Printf("Error marshaling response: %s", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if _, err := w.Write(resp); err != nil { log.Printf("Error writing response: %s", err) } } func main() { mux := http.NewServeMux() mux.HandleFunc("/hello", hello) unixListener := UnixListener{ ServerMux: mux, SocketPath: "/temp/test.sock", UID: 501, GID: 20, SocketFileMode: 0644, } // 启动前检查并清理已存在的套接字文件,避免绑定失败 if _, err := os.Stat(unixListener.SocketPath); err == nil { if err := os.Remove(unixListener.SocketPath); err != nil { log.Fatalf("[FATAL] Failed to remove existing socket: %s", err) } } if err := unixListener.Start(); err != nil { log.Fatalf("[FATAL] HTTP server error: %s", err) } }
修正说明
- 修复curl命令的URL格式,确保请求正确的
/hello路径; - 替换
log.Println为log.Printf,正确打印请求URI; - 修正响应头键名为
Content-Type; - 处理
json.Marshal的错误,避免静默失败; - 使用
http.Server.Shutdown实现优雅关闭,确保正在处理的请求完成; - 启动前检查并清理已存在的套接字文件,避免绑定失败;
- 关闭服务器后自动删除套接字文件,避免残留。
内容的提问来源于stack exchange,提问作者Satish Burnwal
相关产品推荐
相关产品推荐

