Golang中http.FileServer与自定义路由无法协同工作的问题

Golang中http.FileServer与自定义路由无法协同工作的问题

package router

import (
	"errors"
	"net/http"
	"regexp"
)

type route struct {
	Path    string
	Method  string
	Handler Handler
}

type Router struct {
	routes []route
}

type Handler func(http.ResponseWriter, *http.Request)

// 将方法、路径和处理函数添加到Router结构体的路由切片中
func (r *Router) AddRoute(method, path string, handler Handler) {
	r.routes = append(r.routes, route{Path: path, Method: method, Handler: handler})
}

// 为在其他包中使用而创建的路由器方法:Get、Post、Put、Delete
func (r *Router) Get(path string, handler Handler) {
	r.AddRoute("GET", path, handler)
}

func (r *Router) Post(path string, handler Handler) {
	r.AddRoute("POST", path, handler)
}

func (r *Router) Put(path string, handler Handler) {
	r.AddRoute("PUT", path, handler)
}

func (r *Router) Delete(path string, handler Handler) {
	r.AddRoute("DELETE", path, handler)
}

// 从客户端请求中查找处理函数(如果它存在于Router中)
func (r *Router) getHandler(path, method string) (Handler, error) {
	for _, route := range r.routes {
		regex := regexp.MustCompile(route.Path)
		if regex.MatchString(path) && route.Method == method {
			return route.Handler, nil
		}
	}
	return nil, errors.New("not foundm")
}

// 用于实现http.Handler接口
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Path
	method := req.Method
	handler, err := r.getHandler(path, method)
	if err != nil {
		http.NotFound(w, req)
		return
	}
	handler(w, req)
}

// 调用此函数可获得 *Router 类型,并使用Router类型的所有方法
func NewRouter() *Router {
	return &Router{}
}

以上是我的自定义路由器代码。

package main

import (
	router "asciiWeb/back"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"text/template"
)

var tmp *template.Template

func init() {
	tmp = template.Must(template.ParseGlob("front/*.html"))
}

func main() {
	route := router.NewRouter()
	route.Get("/api/home", home)
	route.Get("/api/index", index)
	route.Post("/api/convert", convert)
	route.Post("/api/upload", upload)
	http.Handle("/api/", route)
	http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("./static"))))
	fmt.Println("http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		return
	}
}

func home(w http.ResponseWriter, req *http.Request) {
	if err := tmp.ExecuteTemplate(w, "index.html", nil); err != nil {
		return
	}
}
func convert(w http.ResponseWriter, req *http.Request) {
	err := req.ParseForm()
	if err != nil {
		return
	}
	font := "--font=" + req.Form.Get("fs")
	text := req.Form.Get("name")
	res := ""
	for _, word := range strings.Fields(text) {
		cmd := exec.Command("./ascii", font, word)
		output, err := cmd.Output()
		if err != nil {
			fmt.Println("********", err)
			return
		}
		res += string(output) + "\n"
	}
	if err := tmp.ExecuteTemplate(w, "index.html", res); err != nil {
		return
	}
}

如果我运行这段代码,我的CSS文件无法与HTML连接。 如果我使用Go默认的多路复用器,一切正常。为什么我的路由器不能正常工作?


更多关于Golang中http.FileServer与自定义路由无法协同工作的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中http.FileServer与自定义路由无法协同工作的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的自定义路由器与 http.FileServer 无法协同工作的原因在于路由匹配逻辑。你的路由器使用正则表达式匹配路径,而 http.DefaultServeMux 使用前缀匹配。当请求 /static/style.css 时,它首先被你的路由器 /api/ 路径捕获,而不是传递给文件服务器。

以下是具体问题和解决方案:

问题分析:

  1. 你的路由器 getHandler 方法使用 regexp.MustCompile(route.Path) 进行正则匹配
  2. 注册路由时:http.Handle("/api/", route) 会匹配所有以 /api/ 开头的路径
  3. 但你的路由器实现中,/api/ 这个路径模式会匹配任何包含 /api/ 的路径,包括 /static/api/...

解决方案:修改路由器匹配逻辑,使用精确前缀匹配:

// 修改 getHandler 方法,使用前缀匹配而不是正则表达式
func (r *Router) getHandler(path, method string) (Handler, error) {
    for _, route := range r.routes {
        // 使用 strings.HasPrefix 进行前缀匹配
        if strings.HasPrefix(path, route.Path) && route.Method == method {
            return route.Handler, nil
        }
    }
    return nil, errors.New("not found")
}

// 或者更精确的路径匹配(推荐)
func (r *Router) getHandler(path, method string) (Handler, error) {
    for _, route := range r.routes {
        // 精确匹配或路径前缀匹配
        if (path == route.Path || strings.HasPrefix(path, route.Path + "/")) && route.Method == method {
            return route.Handler, nil
        }
    }
    return nil, errors.New("not found")
}

更好的解决方案:修改主函数中的路由注册顺序和方式:

func main() {
    route := router.NewRouter()
    route.Get("/api/home", home)
    route.Get("/api/index", index)
    route.Post("/api/convert", convert)
    route.Post("/api/upload", upload)
    
    // 创建自定义的多路复用器
    mux := http.NewServeMux()
    
    // 先注册静态文件服务(更具体的路径)
    mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("./static"))))
    
    // 再注册API路由
    mux.Handle("/api/", route)
    
    // 可选:添加根路径重定向或处理
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/" {
            http.Redirect(w, r, "/api/home", http.StatusFound)
            return
        }
        http.NotFound(w, r)
    })
    
    fmt.Println("http://localhost:8080")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}

或者修复路由器使其正确处理路径匹配:

// 修改路由结构体,添加是否是前缀匹配的标志
type route struct {
    Path     string
    Method   string
    Handler  Handler
    IsPrefix bool  // 新增:标识是否是前缀匹配
}

// 修改 AddRoute 方法,自动检测是否是前缀路由
func (r *Router) AddRoute(method, path string, handler Handler) {
    isPrefix := strings.HasSuffix(path, "/")
    r.routes = append(r.routes, route{
        Path:     path,
        Method:   method,
        Handler:  handler,
        IsPrefix: isPrefix,
    })
}

// 修改 getHandler 方法
func (r *Router) getHandler(path, method string) (Handler, error) {
    for _, route := range r.routes {
        matched := false
        
        if route.IsPrefix {
            // 前缀匹配:路径以 route.Path 开头
            matched = strings.HasPrefix(path, route.Path)
        } else {
            // 精确匹配
            matched = path == route.Path
        }
        
        if matched && route.Method == method {
            return route.Handler, nil
        }
    }
    return nil, errors.New("not found")
}

根本原因: Go 的 http.Handlehttp.HandleFunc 使用前缀匹配,而你的路由器使用正则表达式匹配。当注册 /api/ 时,它会拦截所有以 /api/ 开头的请求,但你的正则匹配逻辑可能不够精确,导致其他路径也被错误匹配。

最简单的修复方法是使用 http.NewServeMux() 创建新的多路复用器,并确保路由注册顺序正确(更具体的路由先注册)。

回到顶部