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 链接,提示用户重定向信息
相关推荐
GetcharZp4 小时前
xterm.js 终端神器到底有多强?用了才知道!
前端·后端·go
纪元A梦6 小时前
华为OD机试真题——阿里巴巴找黄金宝箱Ⅰ(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
java·c语言·javascript·c++·python·华为od·go
勤劳的牛马6 小时前
🚀 Go切片传参到底是值传递还是引用传递?
go
郝同学的测开笔记6 小时前
云原生探索系列(十七):Go 语言sync.Cond
后端·云原生·go
楽码12 小时前
理解go社区如何解决for循环赋值问题
后端·go·编程语言
一个热爱生活的普通人13 小时前
如何用go语言实现类似AOP的功能
后端·面试·go
GetcharZp13 小时前
用 Go 语言也能优雅输出表格!一招学会 tablewriter
后端·go
张帅涛_6661 天前
golang goroutine(协程)和 channel(管道) 案例解析
jvm·golang·go
栩栩云生1 天前
📥 x-cmd install | Orbiton:极简至上的终端文本编辑器与轻量级 IDE
go·命令行