You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因分析

  1. curl命令URL格式错误:从curl日志可见,实际发送的请求路径是/而非/hello。因为使用了http:/hello(单斜杠),curl无法正确解析路径,默认请求根路径/,而服务器只注册了/hello的处理函数,因此返回404。
  2. 代码中的小问题:
    • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:21:00