2.2.1goweb内置的 HTTP 处理程序

net/http 使用源码分析

在 Go 语言的 HTTP 服务器里,HTTP handler 是实现了http.Handler接口的对象。该接口定义如下:

复制代码
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ServeHTTP方法接收两个参数:

  • http.ResponseWriter:用于向客户端发送 HTTP 响应。
  • *http.Request:代表客户端的 HTTP 请求。
示例代码
复制代码
package main

import (
    "fmt"
    "net/http"
)

// 自定义的HTTP处理程序
type HelloHandler struct{}

// 实现http.Handler接口的ServeHTTP方法
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    // 创建自定义handler的实例
    hello := HelloHandler{}
    // 注册handler到默认的ServeMux
    http.Handle("/", hello)
    // 启动HTTP服务器,监听8080端口
    fmt.Println("Starting server on :8080")
    http.ListenAndServe(":8080", nil)
}

在上述代码中,HelloHandler结构体实现了http.Handler接口,其ServeHTTP方法会向客户端发送 "Hello, World!" 消息。

源码分析

http.Handler接口定义在net/http/server.go文件中:

复制代码
// Handler is an interface implemented by an HTTP handler.
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

该接口仅有一个方法ServeHTTP,任何实现了这个方法的类型都可作为 HTTP handler 使用。

http.ServeMux

http.ServeMux是一个 HTTP 请求多路复用器,它会将不同的请求路径映射到对应的 handler。其定义如下:

复制代码
// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that most closely matches the
// URL.
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

ServeMux结构体包含一个映射m,用于存储路径模式和对应的 handler。http.Handle函数会向ServeMux注册 handler:

复制代码
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

ServeMuxServeHTTP方法会根据请求的路径查找对应的 handler 并调用其ServeHTTP方法:

复制代码
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
http.ListenAndServe

http.ListenAndServe函数用于启动一个 HTTP 服务器,它会监听指定的地址和端口,并处理 HTTP 请求:

复制代码
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

http.ListenAndServe函数创建了一个http.Server实例,并调用其ListenAndServe方法启动服务器。

内置的 HTTP 处理程序

http.NotFoundHandler 功能概述

http.NotFoundHandlernet/http 包提供的一个内置处理程序,当客户端请求的资源不存在时,使用这个处理程序可以返回一个标准的 404 响应。它通常用于处理那些没有匹配到任何路由规则的请求。

使用示例
复制代码
package main

import (
    "net/http"
)

func main() {
    // 注册一个空的路由,这里没有具体的处理逻辑
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 处理其他特定逻辑
    })

    // 对于所有未匹配的路由,使用 NotFoundHandler 返回 404 响应
    http.HandleFunc("/notfound", http.NotFoundHandler().ServeHTTP)

    // 启动服务器,监听 8080 端口
    http.ListenAndServe(":8080", nil)
}

在这个示例中,当客户端请求 /notfound 路径时,会触发 http.NotFoundHandler,返回 404 状态码和相应的提示信息。

源码分析

http.NotFoundHandler 的定义和实现位于 net/http/server.go 文件中,下面是其源码:

复制代码
// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return NotFoundHandlerFunc }

// NotFoundHandlerFunc is the default handler for http.NotFoundHandler.
func NotFoundHandlerFunc(w ResponseWriter, r *Request) {
    http.Error(w, "404 page not found", http.StatusNotFound)
}
  • NotFoundHandler 是一个函数,它返回一个实现了 http.Handler 接口的处理程序。在这个例子中,返回的是 NotFoundHandlerFunc
  • NotFoundHandlerFunc 是一个具体的函数,它实现了 http.Handler 接口的 ServeHTTP 方法所需的签名。当调用这个函数时,它会使用 http.Error 函数向客户端发送一个 404 状态码和相应的错误信息。

http.Error 函数

http.Error 函数用于向客户端发送一个带有指定错误信息和状态码的响应,其源码如下:

复制代码
// Error replies to the request with the specified error message and HTTP code.
// It does not otherwise end the request; the caller should ensure no further
// writes are done to w.
// The error message should be plain text.
func Error(w ResponseWriter, error string, code int) {
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.WriteHeader(code)
    io.WriteString(w, error)
}
  • http.Error 函数首先设置响应头,指定内容类型为纯文本(text/plain)并设置字符集为 utf-8,同时设置 X-Content-Type-Optionsnosniff 以防止浏览器进行内容类型嗅探。
  • 然后,使用 w.WriteHeader(code) 设置 HTTP 状态码。
  • 最后,使用 io.WriteString 函数将错误信息写入响应体。

RedirectHandler

http.RedirectHandler的定义和实现位于net/http/server.go文件中,下面是其源码:

复制代码
// RedirectHandler returns a request handler that redirects
// each request it receives to the given url using the given
// status code.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func RedirectHandler(url string, code int) Handler {
    return redirectHandler{url, code}
}

type redirectHandler struct {
    url  string
    code int
}

func (rh redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
    Redirect(w, r, rh.url, rh.code)
}

// Redirect replies to the request with a redirect to url,
// which may be a path relative to the request path.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func Redirect(w ResponseWriter, r *Request, url string, code int) {
    if u, err := urlparse.Parse(url); err == nil {
        // If url was relative, make absolute by
        // combining with request path.
        // The browser would probably do this for us,
        // but doing it ourselves is more reliable.
        //
        // RFC 2616 says that the Location header must be an absolute URI,
        // but we obey the spirit of the RFC and allow relative URIs so
        // long as they can be made absolute relative to the request URI.
        if u.Scheme == "" && u.Host == "" {
            oldpath := r.URL.Path
            if oldpath == "" { // should not happen, but avoid a crash if it does
                oldpath = "/"
            }
            // no leading http://server
            if url == "" || url[0] != '/' {
                // make relative path absolute
                last := len(oldpath) - 1
                if last >= 0 && oldpath[last] != '/' {
                    oldpath = oldpath + "/"
                }
                url = oldpath + url
            }
            var query string
            if i := strings.Index(url, "?"); i != -1 {
                url, query = url[:i], url[i:]
            }
            // clean up but preserve trailing slash
            trailing := strings.HasSuffix(url, "/")
            url = path.Clean(url)
            if trailing && !strings.HasSuffix(url, "/") {
                url = url + "/"
            }
            url = url + query
        }
    }
    w.Header().Set("Location", hexEscapeNonASCII(url))
    w.WriteHeader(code)
    // RFC 2616 recommends that a short note "SHOULD" be included in the
    // response because older user agents may not understand 301/307.
    // Shouldn't send the response for POST or HEAD; that leaves GET.
    if r.Method == "GET" {
        body := "<a href=\"" + html.EscapeString(url) + "\">" + http.StatusText(code) + "</a>.\n"
        io.WriteString(w, body)
    }
}
  • http.Redirect函数首先会处理相对 URL,将其转换为绝对 URL。
  • 然后,设置响应头的Location字段为目标 URL,并使用w.WriteHeader(code)设置重定向状态码。
  • 最后,如果请求方法是GET,会在响应体中返回一个简单的 HTML 链接,提示用户重定向信息
相关推荐
研究司马懿14 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘2 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤2 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto4 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo