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

Go 1.22 HTTP Mux:同一路径同时处理接口与文件系统

解决Go标准库ServeMux中根路径与静态文件共存及自定义404的问题

一、实现根路径走index函数,其他路径走静态文件服务

你的当前代码中,重复注册GET /路由会导致先注册的Handler生效,后注册的被忽略。要实现GET /由index处理、GET /{anythingelse}由静态文件服务处理,可通过一个中间Handler来区分请求路径:

package main

import (
	"embed"
	"log"
	"net/http"
	"io/fs"
)

//go:embed assets
var staticFiles embed.FS

func main() {
	// 从嵌入文件系统中获取assets子目录
	htmlContent, err := fs.Sub(staticFiles, "assets")
	if err != nil {
		log.Fatalf("Failed to get assets subdir: %v", err)
	}
	staticServer := http.FileServer(http.FS(htmlContent))

	mux := http.NewServeMux()

	// 统一处理根路径及静态文件请求
	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			// 根路径请求交给index函数处理
			index(w, r)
		} else {
			// 其他路径交给静态文件服务处理
			staticServer.ServeHTTP(w, r)
		}
	})

	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

// index 处理根路径的模板渲染逻辑
func index(w http.ResponseWriter, r *http.Request) {
	// 此处替换为你的模板解析、执行代码
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Welcome to the Index Page!"))
}

二、标准库ServeMux实现类似httprouter的NotFound机制

标准库http.ServeMux没有内置的NotFound回调,但可以通过以下两种方式实现自定义404处理:

方法1:通过包装ResponseWriter捕获状态码

创建一个自定义的ResponseWriter来记录响应状态,若ServeMux返回404,则调用自定义NotFound逻辑:

// statusRecorder 包装ResponseWriter以捕获响应状态码
type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (sr *statusRecorder) WriteHeader(code int) {
	sr.status = code
	sr.ResponseWriter.WriteHeader(code)
}

// customNotFound 自定义404处理函数
func customNotFound(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	w.Write([]byte("Oops! The page you're looking for doesn't exist."))
}

func main() {
	// ... 初始化静态文件、mux及路由的代码 ...

	// 用自定义Handler包裹ServeMux
	customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		sr := &statusRecorder{w, http.StatusOK}
		mux.ServeHTTP(sr, r)
		// 若ServeMux返回404,调用自定义NotFound处理
		if sr.status == http.StatusNotFound {
			customNotFound(w, r)
		}
	})

	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", customHandler))
}

方法2:注册兜底路由(Go 1.22+推荐)

Go 1.22及以上版本支持/*这种尾部通配符路由,可在所有具体路由之后注册一个兜底路由,处理所有未匹配的请求:

func main() {
	// ... 初始化静态文件、mux及根路径路由的代码 ...

	// 注册兜底路由,处理所有未被匹配的GET请求
	mux.HandleFunc("GET /*", customNotFound)

	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

若使用Go 1.22之前的版本,可注册GET /作为兜底路由(需确保它是最后注册的),但这种方式会匹配所有未被更具体路由处理的请求,需注意与静态文件服务的逻辑兼容。

内容的提问来源于stack exchange,提问作者George Livanoss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:57:03