goweb内置的响应1

net/http 使用源码分析

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

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

ServeHTTP方法接收两个参数:

  • http.ResponseWriter:用于向客户端发送 HTTP 响应。
  • *http.Request:代表客户端的 HTTP 请求。

示例代码

go 复制代码
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文件中:

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

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

http.ServeMux

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

go 复制代码
// 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:

go 复制代码
// 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方法:

scss 复制代码
// 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 请求:

go 复制代码
// 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 响应。它通常用于处理那些没有匹配到任何路由规则的请求。

使用示例

go 复制代码
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 文件中,下面是其源码:

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 函数用于向客户端发送一个带有指定错误信息和状态码的响应,其源码如下:

go 复制代码
// 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文件中,下面是其源码:

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)
}
go 复制代码
// 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 链接,提示用户重定向信息

​ ​

http.StripPrefix

http.StripPrefix 是 Go 语言 net/http 包中的一个函数,它的主要作用是创建一个新的 HTTP 处理程序。这个新处理程序会在处理请求之前,从请求的 URL 路径中移除指定的前缀,然后将处理工作委托给另一个提供的处理程序。

使用示例

go 复制代码
package main

import (
    "fmt"
    "net/http"
)

// 定义一个简单的处理函数
func simpleHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Path after stripping prefix: %s", r.URL.Path)
}

func main() {
    // 创建一个处理程序,移除 "/static/" 前缀后将请求交给 simpleHandler 处理
    http.Handle("/static/", http.StripPrefix("/static/", http.HandlerFunc(simpleHandler)))

    // 启动 HTTP 服务器,监听 8080 端口
    fmt.Println("Starting server on :8080")
    http.ListenAndServe(":8080", nil)
}

在这个例子中,如果客户端请求 /static/css/style.csshttp.StripPrefix 会移除 /static/ 前缀,将 /css/style.css 作为路径传递给 simpleHandler 处理。

源码分析

http.StripPrefix 函数的定义如下:

go 复制代码
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h Handler) Handler {
    if prefix == "" {
        return h
    }
    return HandlerFunc(func(w ResponseWriter, r *Request) {
        if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
            r2 := new(Request)
            *r2 = *r
            r2.URL = new(url.URL)
            *r2.URL = *r.URL
            r2.URL.Path = p
            h.ServeHTTP(w, r2)
        } else {
            http.NotFound(w, r)
        }
    })
}

源码详细解释

  1. 参数检查

    ini 复制代码
    if prefix == "" {
        return h
    }

    如果传入的前缀 prefix 为空字符串,那么直接返回原处理程序 h,因为不需要移除任何前缀。

  2. 创建新的处理程序

    go 复制代码
    return HandlerFunc(func(w ResponseWriter, r *Request) {
        // ...
    })

    使用 HandlerFunc 类型创建一个新的处理程序。HandlerFunc 是一个函数类型,它实现了 http.Handler 接口的 ServeHTTP 方法。

  3. 移除前缀并处理请求

    ini 复制代码
    if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
        r2 := new(Request)
        *r2 = *r
        r2.URL = new(url.URL)
        *r2.URL = *r.URL
        r2.URL.Path = p
        h.ServeHTTP(w, r2)
    }
    • 使用 strings.TrimPrefix 函数尝试从请求的 URL 路径中移除指定的前缀。
    • 如果移除成功(即移除后的路径长度小于原路径长度),则创建一个新的 http.Request 对象 r2,并复制原请求的所有信息。
    • 修改新请求对象 r2 的 URL 路径为移除前缀后的路径。
    • 调用原处理程序 hServeHTTP 方法,将新请求对象 r2 传递给它进行处理。
  4. 处理未匹配的请求

    arduino 复制代码
    } else {
        http.NotFound(w, r)
    }

    如果请求的 URL 路径不包含指定的前缀,那么调用 http.NotFound 函数返回一个 404 错误响应。

http.StripPrefix 是一个非常实用的工具,它允许你在处理请求之前对 URL 路径进行预处理,移除不必要的前缀。这在处理静态文件服务、API 路由等场景中非常有用。通过分析源码,我们可以看到它是如何创建新的请求对象、修改路径并将处理工作委托给原处理程序的,同时也处理了未匹配前缀的情况。

http.TimeoutHandler

http.TimeoutHandler 是 Go 语言 net/http 包中的一个函数,它用于为 HTTP 请求处理设置超时时间。当一个请求的处理时间超过预设的超时时间时,会返回一个超时响应给客户端,避免客户端长时间等待无响应的请求。

使用示例

go 复制代码
package main

import (
    "fmt"
    "net/http"
    "time"
)

// 模拟一个耗时的处理函数
func slowHandler(w http.ResponseWriter, r *http.Request) {
    time.Sleep(5 * time.Second)
    fmt.Fprint(w, "Slow handler completed")
}

func main() {
    // 设置超时时间为 2 秒
    timeoutHandler := http.TimeoutHandler(http.HandlerFunc(slowHandler), 2*time.Second, "Request timed out")

    // 注册处理程序
    http.Handle("/slow", timeoutHandler)

    // 启动 HTTP 服务器
    fmt.Println("Starting server on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println("Error starting server:", err)
    }
}

在这个示例中,slowHandler 函数模拟了一个耗时 5 秒的处理过程,而 http.TimeoutHandler 设置的超时时间为 2 秒。当客户端请求 /slow 路径时,如果处理时间超过 2 秒,客户端将收到 "Request timed out" 的响应。

源码分析

go 复制代码
// TimeoutHandler returns a Handler that runs h with the given time limit.
//
// The new Handler calls h.ServeHTTP to handle each request, but if a
// call runs for longer than its time limit, the handler responds with
// a 503 Service Unavailable error and the given message in its body.
// (If msg is empty, a suitable default message will be sent.)
// After such a timeout, writes by h to its ResponseWriter will return
// ErrHandlerTimeout.
//
// TimeoutHandler buffers all Handler writes to memory and does not
// support the Hijacker or Flusher interfaces.
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
    return &timeoutHandler{
        handler: h,
        timeout: dt,
        msg:     msg,
    }
}

TimeoutHandler 函数接收三个参数:

  • h:一个 http.Handler 类型的处理程序,代表实际要执行的请求处理逻辑。
  • dt:一个 time.Duration 类型的超时时间,指定了处理请求的最大允许时间。
  • msg:一个字符串类型的超时消息,当请求处理超时时,会将该消息作为响应体返回给客户端。

timeoutHandler 结构体

go 复制代码
type timeoutHandler struct {
    handler Handler
    timeout time.Duration
    msg     string
}

timeoutHandler 结构体包含三个字段,分别存储传入的处理程序、超时时间和超时消息。

ServeHTTP 方法实现

scss 复制代码
func (th *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    done := make(chan struct{})
    tw := &timeoutWriter{
        w:   w,
        h:   make(http.Header),
        msg: th.msg,
    }
    go func() {
        th.handler.ServeHTTP(tw, r)
        close(done)
    }()
    select {
    case <-done:
        tw.mu.Lock()
        defer tw.mu.Unlock()
        copyHeader(w.Header(), tw.h)
        w.WriteHeader(tw.code)
        w.Write(tw.wbuf.Bytes())
    case <-time.After(th.timeout):
        tw.mu.Lock()
        defer tw.mu.Unlock()
        if !tw.wroteHeader {
            http.Error(w, tw.errorMessage(), http.StatusServiceUnavailable)
            tw.timedOut = true
        }
    }
}

ServeHTTP 方法是 timeoutHandler 结构体实现 http.Handler 接口的方法,其执行流程如下:

  1. 创建通道和包装响应写入器

    • 创建一个 done 通道,用于通知请求处理是否完成。
    • 创建一个 timeoutWriter 结构体实例 tw,用于包装原始的 http.ResponseWriter,并记录响应信息。
  2. 启动 goroutine 处理请求

    • 启动一个新的 goroutine 来执行实际的请求处理逻辑 th.handler.ServeHTTP(tw, r)
    • 当处理完成后,关闭 done 通道。
  3. 使用 select 语句等待结果

    • 如果 done 通道接收到信号,说明请求处理在超时时间内完成。此时,将 tw 中记录的响应头、状态码和响应体复制到原始的 http.ResponseWriter 中并发送给客户端。
    • 如果 time.After(th.timeout) 通道接收到信号,说明请求处理超时。此时,检查是否已经写入响应头,如果没有,则使用 http.Error 函数返回一个 503 状态码和超时消息给客户端,并标记 tw.timedOuttrue

timeoutWriter 结构体

go 复制代码
type timeoutWriter struct {
    w    http.ResponseWriter
    h    http.Header
    wbuf bytes.Buffer

    code int
    wroteHeader bool
    timedOut    bool
    mu          sync.Mutex
    msg         string
}

timeoutWriter 结构体用于包装原始的 http.ResponseWriter,并记录响应头、状态码、响应体等信息。同时,它使用互斥锁 mu 来保证并发安全。

http.TimeoutHandler 是一个非常实用的工具,它可以帮助我们避免长时间无响应的请求阻塞服务器资源。通过使用 goroutine 和通道,结合 select 语句进行超时控制,实现了对请求处理时间的有效管理。需要注意的是,TimeoutHandler 会将处理程序的所有写入操作缓冲到内存中,并且不支持 HijackerFlusher 接口。

相关推荐
EvanSun__2 小时前
Flask 框架引入
后端·python·flask
pianmian12 小时前
Spring 项目骨架
java·后端·spring
小程序设计3 小时前
【springboot+vue】高校迎新平台管理系统(源码+文档+调试+基础修改+答疑)
vue.js·spring boot·后端
海梨花4 小时前
字节一面 面经(补充版)
jvm·redis·后端·面试·juc
野生程序员y4 小时前
深入解析Spring AOP核心原理
java·后端·spring
波波烤鸭4 小时前
Spring Boot 原理与性能优化实战
spring boot·后端·性能优化
shellvon4 小时前
从抓包到攻防:解锁API安全设计的秘密
后端·安全
言之。4 小时前
Django REST Framework响应类Response详解
后端·python·django
Abadbeginning4 小时前
FastSoyAdmin centos7云服务器+宝塔部署
vue.js·后端·python