goweb内置的响应2

http.FileServer

http.FileServer 是 Go 语言 net/http 包中用于创建一个能处理文件请求的 HTTP 处理程序的函数。它可以将本地文件系统中的文件和目录以 HTTP 服务的形式提供给客户端访问,常用于搭建静态文件服务器,比如提供 HTML、CSS、JavaScript、图片等静态资源。

使用示例

go 复制代码
package main

import (
    "net/http"
)

func main() {
    // 创建一个文件服务器处理程序,根目录为当前目录下的 "static" 文件夹
    fileServer := http.FileServer(http.Dir("static"))
    // 将文件服务器处理程序注册到根路径 "/"
    http.Handle("/", fileServer)

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

在这个示例里,客户端可以通过访问 http://localhost:8080 来访问 static 目录下的文件。

源码分析

scss 复制代码
// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
//
// To use the operating system's file system implementation,
// use http.Dir:
//
//     http.Handle("/", http.FileServer(http.Dir("/tmp")))
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
func FileServer(root FileSystem) Handler {
    return &fileHandler{root}
}

FileServer 函数接收一个 http.FileSystem 类型的参数 root,它代表文件系统的根目录,然后返回一个 fileHandler 结构体实例。

fileHandler 结构体

arduino 复制代码
type fileHandler struct {
    root FileSystem
}

fileHandler 结构体包含一个 FileSystem 类型的字段 root,用来指定文件系统的根目录。

ServeHTTP 方法实现

go 复制代码
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
    upath := r.URL.Path
    if !strings.HasPrefix(upath, "/") {
        upath = "/" + upath
        r.URL.Path = upath
    }
    path := cleanPath(upath)

    // redirect .../index.html to .../
    if strings.HasSuffix(path, "/index.html") {
        http.Redirect(w, r, path[:len(path)-len("index.html")], http.StatusMovedPermanently)
        return
    }

    f.serveFile(w, r, path)
}

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

  1. 路径清理

    • 确保请求的 URL 路径以 / 开头,如果不是则添加。
    • 使用 cleanPath 函数清理路径,去除多余的 ... 等部分。
  2. 重定向处理

    • 如果请求的路径以 /index.html 结尾,使用 http.Redirect 函数将请求重定向到去除 index.html 后的路径,状态码为 301(永久重定向)。
  3. 处理文件请求

    • 调用 serveFile 方法处理文件请求。

serveFile 方法

go 复制代码
func (f *fileHandler) serveFile(w ResponseWriter, r *Request, name string) {
    const indexPage = "/index.html"

    // Open the file
    file, err := f.root.Open(name)
    if err != nil {
        if os.IsNotExist(err) {
            http.NotFound(w, r)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer file.Close()

    // Get the file info
    info, err := file.Stat()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // If it's a directory, try to serve the index page
    if info.IsDir() {
        if strings.HasSuffix(name, "/") {
            name = name + indexPage
        } else {
            http.Redirect(w, r, path.Base(name)+"/", http.StatusMovedPermanently)
            return
        }
        file, err = f.root.Open(name)
        if err != nil {
            if os.IsNotExist(err) {
                http.NotFound(w, r)
                return
            }
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer file.Close()
        info, err = file.Stat()
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }

    // Serve the file
    http.ServeContent(w, r, info.Name(), info.ModTime(), file)
}

serveFile 方法的主要功能是处理文件或目录的请求,具体步骤如下:

  1. 打开文件

    • 尝试使用 f.root.Open 方法打开指定路径的文件,如果文件不存在则返回 404 错误,如果出现其他错误则返回 500 错误。
  2. 获取文件信息

    • 使用 file.Stat 方法获取文件的元信息,如文件大小、修改时间等。
  3. 处理目录请求

    • 如果请求的是一个目录,会尝试查找该目录下的 index.html 文件。
    • 如果目录路径不以 / 结尾,会将请求重定向到添加 / 后的路径。
    • 若找不到 index.html 文件则返回 404 错误。
  4. 提供文件内容

    • 使用 http.ServeContent 函数将文件内容发送给客户端。

http.FileServer 是一个方便实用的工具,借助它可以轻松搭建静态文件服务器。通过分析源码,我们能了解到它是如何处理文件和目录请求、重定向路径以及处理错误情况的。在实际应用中,要留意文件系统的权限问题以及对文件路径的安全处理,防止出现目录遍历等安全漏洞。

http.Dir

http.Dir 是 Go 语言 net/http 包中的一个类型,它用于将本地文件系统的目录封装成一个实现了 http.FileSystem 接口的对象。http.FileSystem 接口定义了文件系统操作的基本方法,使得 http.FileServer 等可以使用它来访问本地文件系统中的文件和目录,从而实现静态文件服务。

使用示例

go 复制代码
package main

import (
    "net/http"
)

func main() {
    // 使用 http.Dir 将本地的 "static" 目录封装成一个 http.FileSystem 对象
    fileSystem := http.Dir("static")
    // 创建一个文件服务器处理程序,使用封装后的文件系统
    fileServer := http.FileServer(fileSystem)
    // 将文件服务器处理程序注册到根路径 "/"
    http.Handle("/", fileServer)

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

在这个示例中,客户端可以通过访问 http://localhost:8080 来访问 static 目录下的文件。

源码分析

go 复制代码
// Dir implements FileSystem using the native file system restricted to a
// specific directory tree.
//
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
//
// Note that Dir does not provide the ReadDir method that a "real" file system
// like os.DirFS provides.
type Dir string

http.Dir 本质上是一个 string 类型的别名,它表示本地文件系统中的一个目录路径。

实现 http.FileSystem 接口

http.FileSystem 接口定义如下:

go 复制代码
// A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
type FileSystem interface {
    Open(name string) (File, error)
}

http.Dir 实现了 Open 方法:

go 复制代码
// Open implements FileSystem using os.Open, opening files
// read-only.
func (d Dir) Open(name string) (File, error) {
    if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
        return nil, errors.New("http: invalid character in file path")
    }
    dir := string(d)
    if dir == "" {
        dir = "."
    }
    fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
    f, err := os.Open(fullName)
    if err != nil {
        return nil, err
    }
    return f, nil
}

Open 方法的具体执行步骤如下:

  1. 路径合法性检查

    • 检查 name 中是否包含非 / 的路径分隔符(在不同操作系统中路径分隔符可能不同),如果包含则返回错误。
  2. 处理目录路径

    • 如果 d 为空字符串,则将其设置为当前目录 .
  3. 构建完整路径

    • 使用 filepath.Join 函数将目录路径和请求的文件名拼接成完整的本地文件路径。这里使用 filepath.FromSlash/ 分隔的路径转换为本地文件系统的路径分隔符。
    • 使用 path.Clean 函数清理路径,去除多余的 ... 等部分。
  4. 打开文件

    • 使用 os.Open 函数以只读模式打开文件。
    • 如果打开文件时出现错误,则返回错误信息;否则返回打开的文件对象。

http.Dir 是一个简单而实用的工具,它将本地文件系统的目录封装成 http.FileSystem 接口的实现,使得 http.FileServer 等可以方便地访问本地文件。通过分析源码,我们可以看到它是如何处理路径、构建完整的文件路径以及打开文件的。需要注意的是,http.Dir 仅实现了 Open 方法,没有提供 ReadDir 方法,这在某些需要列出目录内容的场景下可能会有局限性。

http.ServeFile

http.ServeFile 是一个实用函数,其作用是将指定的文件内容发送给客户端。此函数常用于静态文件服务,比如提供 HTML、CSS、JavaScript、图片等静态资源。在实现文件下载、展示静态页面等场景时

使用示例

go 复制代码
package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 向客户端发送当前目录下的 index.html 文件
        http.ServeFile(w, r, "index.html")
    })

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

在这个示例中,当客户端访问服务器的根路径时,服务器会将 index.html 文件的内容发送给客户端。

源码分析

go 复制代码
// ServeFile replies to the request with the contents of the named file or directory.
//
// If the provided name is a directory, ServeFile will first attempt to
// send a file named "index.html" or "index.htm" in that directory.
// If neither file is present, and the directory name doesn't end in a
// slash, ServeFile redirects to the same URL with a trailing slash.
// If the directory name does end in a slash, ServeFile lists the
// directory contents if the server has been configured to allow that.
//
// As a security measure, ServeFile will reject requests where r.URL.Path
// contains a ".." path element; see sanitizePath for details.
//
// As a special case, ServeFile redirects any request ending in "/index.html"
// to the same path, without the final "index.html".
func ServeFile(w ResponseWriter, r *Request, name string) {
    name = filepath.FromSlash(path.Clean("/" + r.URL.Path))
    if strings.HasSuffix(name, "/index.html") {
        http.Redirect(w, r, strings.TrimSuffix(name, "index.html"), http.StatusMovedPermanently)
        return
    }
    fs := Dir(".")
    serveFile(w, r, fs, name, true)
}

ServeFile 函数接收三个参数:

  • whttp.ResponseWriter 类型,用于向客户端发送响应。
  • r*http.Request 类型,代表客户端的 HTTP 请求。
  • namestring 类型,表示要发送的文件或目录的名称。

执行流程

  1. 路径清理与重定向处理

    • 使用 path.Clean 函数清理请求的 URL 路径,并使用 filepath.FromSlash/ 分隔的路径转换为本地文件系统的路径分隔符。
    • 如果路径以 /index.html 结尾,使用 http.Redirect 函数将请求重定向到去除 index.html 后的路径,状态码为 301(永久重定向)。
  2. 创建文件系统对象

    • 创建一个 http.Dir 类型的文件系统对象 fs,根目录为当前目录 .
  3. 调用 serveFile 函数

    • 调用内部的 serveFile 函数来实际处理文件或目录的请求。

serveFile 函数(内部实现)

scss 复制代码
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
    const indexPage = "/index.html"

    // Open the file
    f, err := fs.Open(name)
    if err != nil {
        if os.IsNotExist(err) {
            http.NotFound(w, r)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer f.Close()

    // Get the file info
    d, err := f.Stat()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // If it's a directory, try to serve the index page
    if d.IsDir() {
        if strings.HasSuffix(name, "/") {
            name = name + indexPage
            f, err = fs.Open(name)
            if err != nil {
                if os.IsNotExist(err) {
                    http.NotFound(w, r)
                    return
                }
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            defer f.Close()
            d, err = f.Stat()
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
        } else {
            if redirect {
                localRedirect(w, r, path.Base(name)+"/")
                return
            }
            http.NotFound(w, r)
            return
        }
    }

    // Serve the file
    ServeContent(w, r, d.Name(), d.ModTime(), f)
}

serveFile 函数的主要功能是处理文件或目录的请求,具体步骤如下:

  1. 打开文件

    • 尝试使用 fs.Open 方法打开指定路径的文件,如果文件不存在则返回 404 错误,如果出现其他错误则返回 500 错误。
  2. 获取文件信息

    • 使用 f.Stat 方法获取文件的元信息,如文件大小、修改时间等。
  3. 处理目录请求

    • 如果请求的是一个目录,会尝试查找该目录下的 index.html 文件。
    • 如果目录路径不以 / 结尾,会根据 redirect 参数决定是否将请求重定向到添加 / 后的路径。
    • 若找不到 index.html 文件则返回 404 错误。
  4. 提供文件内容

    • 使用 http.ServeContent 函数将文件内容发送给客户端。

http.ServeFile 是一个方便实用的函数,它简化了将本地文件内容发送给客户端的过程。通过分析源码,我们可以了解到它是如何处理文件和目录请求、重定向路径以及处理错误情况的。在实际应用中,需要注意文件系统的权限问题以及对文件路径的安全处理,防止出现目录遍历等安全漏洞。

​ ​

http.ServeContent

http.ServeContent是 Go 语言 net/http 包中的一个重要函数,其主要功能是将文件内容高效地发送给客户端,同时支持 HTTP 范围请求(Range requests)和内容协商(Content negotiation)。HTTP 范围请求允许客户端请求文件的部分内容,这在处理大文件下载、视频流等场景中非常有用;内容协商则允许服务器根据客户端的偏好(如接受的内容类型、字符编码等)选择合适的响应内容。

使用示例

go 复制代码
package main

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

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        file, err := os.Open("example.txt")
        if err != nil {
            http.Error(w, "File not found", http.StatusNotFound)
            return
        }
        defer file.Close()

        fileInfo, err := file.Stat()
        if err != nil {
            http.Error(w, "Error getting file info", http.StatusInternalServerError)
            return
        }

        // 使用 ServeContent 发送文件内容
        http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
    })

    http.ListenAndServe(":8080", nil)
}

在这个示例中,当客户端访问服务器的根路径时,服务器会打开 example.txt 文件,并使用 http.ServeContent 将文件内容发送给客户端。

源码分析

go 复制代码
// ServeContent replies to the request using the content in the
// provided ReadSeeker. The main benefit of ServeContent over io.Copy
// is that it handles Range requests properly, sets the MIME type, and
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
// and If-Range requests.
//
// name is the name of the file to use in the Content-Disposition header
// (if the client requests it), and modtime is the last-modified time to use
// in the response headers. If the modtime is the zero time, ServeContent
// uses the current time.
//
// The content's Seek method must work: ServeContent uses
// a seek to the end of the content to determine its size.
//
// If the caller has set w's ETag header, ServeContent uses it to handle
// requests using If-Match, If-None-Match, and If-Range.
//
// Note that *os.File implements the ReadSeeker interface.
func ServeContent(w ResponseWriter, r *Request, name string, modtime time.Time, content io.ReadSeeker) {
    sizeFunc := func() (int64, error) {
        pos, err := content.Seek(0, io.SeekCurrent)
        if err != nil {
            return 0, err
        }
        size, err := content.Seek(0, io.SeekEnd)
        if err != nil {
            return 0, err
        }
        _, err = content.Seek(pos, io.SeekStart)
        if err != nil {
            return 0, err
        }
        return size, nil
    }

    ctype := mime.TypeByExtension(filepath.Ext(name))
    if ctype == "" {
        // Read a chunk to decide between utf-8 text and binary
        var buf [512]byte
        n, _ := io.ReadFull(content, buf[:])
        ctype = http.DetectContentType(buf[:n])
        _, err := content.Seek(0, io.SeekStart)
        if err != nil {
            http.Error(w, "seeker can't seek", http.StatusInternalServerError)
            return
        }
    }
    w.Header().Set("Content-Type", ctype)

    // ... 其他代码,处理范围请求、条件请求等 ...
}

ServeContent 函数接收五个参数:

  • whttp.ResponseWriter 类型,用于向客户端发送响应。
  • r*http.Request 类型,代表客户端的 HTTP 请求。
  • namestring 类型,表示文件的名称,用于设置 Content-Disposition 头。
  • modtimetime.Time 类型,表示文件的最后修改时间,用于设置 Last-Modified 头。
  • contentio.ReadSeeker 类型,代表文件内容的读取器,要求支持 Seek 方法。

执行流程

1. 获取文件大小
go 复制代码
sizeFunc := func() (int64, error) {
    pos, err := content.Seek(0, io.SeekCurrent)
    if err != nil {
        return 0, err
    }
    size, err := content.Seek(0, io.SeekEnd)
    if err != nil {
        return 0, err
    }
    _, err = content.Seek(pos, io.SeekStart)
    if err != nil {
        return 0, err
    }
    return size, nil
}

定义一个匿名函数 sizeFunc 来获取文件的大小。该函数通过 Seek 方法先记录当前位置,然后将文件指针移动到文件末尾获取文件大小,最后再将文件指针移回原来的位置。

2. 确定内容类型
css 复制代码
ctype := mime.TypeByExtension(filepath.Ext(name))
if ctype == "" {
    // Read a chunk to decide between utf-8 text and binary
    var buf [512]byte
    n, _ := io.ReadFull(content, buf[:])
    ctype = http.DetectContentType(buf[:n])
    _, err := content.Seek(0, io.SeekStart)
    if err != nil {
        http.Error(w, "seeker can't seek", http.StatusInternalServerError)
        return
    }
}
w.Header().Set("Content-Type", ctype)

首先尝试根据文件扩展名确定内容类型。如果无法确定,则读取文件的前 512 字节,使用 http.DetectContentType 函数自动检测内容类型。最后将确定的内容类型设置到响应头的 Content-Type 字段中。

3. 处理范围请求和条件请求

ServeContent 函数还会处理各种条件请求(如 If-MatchIf-Unmodified-SinceIf-None-MatchIf-Modified-SinceIf-Range)和范围请求(Range)。例如,对于范围请求,它会根据客户端请求的范围读取文件的部分内容并发送给客户端:

arduino 复制代码
ranges, err := parseRange(rangeHeader, size)
if err != nil {
    if err == errNoOverlap {
        w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
        w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
    } else {
        w.Header().Del("Content-Range")
        http.Error(w, "Invalid range", http.StatusRequestedRangeNotSatisfiable)
    }
    return
}
if sumRangesSize(ranges) > size {
    // The total number of bytes in all the ranges
    // is larger than the size of the file by
    // itself, so this is probably an attack, or a
    // dumb client. Ignore the range request.
    ranges = nil
}

根据解析的范围请求,服务器会判断请求是否合法,并相应地设置响应头和状态码。

4. 发送文件内容

最后,根据处理结果,将文件内容(可能是全量内容或部分内容)发送给客户端:

scss 复制代码
if len(ranges) == 1 {
    // 处理单个范围请求
    ra := ranges[0]
    if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
        http.Error(w, "seeker can't seek", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Range", ra.contentRange(size))
    w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
    w.WriteHeader(http.StatusPartialContent)
    http.ServeContentRange(w, content, ra.length)
} else if len(ranges) > 1 {
    // 处理多个范围请求
    sendMultipleRanges(w, r, content, ranges, ctype, size)
} else {
    // 处理无范围请求(全量内容)
    w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
    if w.Header().Get("Content-Disposition") == "" {
        w.Header().Set("Content-Disposition", "inline; filename="+quoteFilename(name))
    }
    io.Copy(w, content)
}

http.ServeContent 是一个功能强大且灵活的函数,它封装了处理文件内容发送、范围请求和条件请求的复杂逻辑,使得开发者可以方便地实现高效的文件服务。通过分析源码,我们可以深入了解它是如何处理文件大小获取、内容类型确定、范围请求解析和内容发送等关键步骤的。在实际应用中,使用 http.ServeContent 可以提高文件服务的性能和兼容性,特别是在处理大文件和支持断点续传等场景下

http.NotFound

http.NotFound 是 Go 语言 net/http 包中的一个实用函数,其主要用途是当客户端请求的资源在服务器上不存在时,向客户端返回一个标准的 404 错误响应。404 状态码在 HTTP 协议里代表 "未找到",意味着客户端请求的 URL 对应的资源在服务器上找不到。

使用示例

go 复制代码
package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 模拟资源不存在的情况,返回 404 错误
        http.NotFound(w, r)
    })

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

在这个示例中,无论客户端请求什么路径,服务器都会返回 404 错误。

源码分析

vbscript 复制代码
// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) {
    Error(w, "404 page not found", StatusNotFound)
}

http.NotFound 函数接收两个参数:

  • whttp.ResponseWriter 类型,用于向客户端发送响应。
  • r*http.Request 类型,代表客户端的 HTTP 请求。

执行流程

http.NotFound 函数内部调用了 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 函数的具体执行步骤如下:

  1. 设置响应头

    • 设置 Content-Typetext/plain; charset=utf-8,表示响应内容是纯文本,使用 UTF - 8 字符编码。
    • 设置 X - Content - Type - Optionsnosniff,这是一个安全相关的头信息,用于防止浏览器进行内容类型嗅探,确保浏览器按照服务器指定的内容类型处理响应。
  2. 设置状态码

    • 使用 w.WriteHeader(code) 方法设置 HTTP 状态码,在 http.NotFound 中,这个状态码是 http.StatusNotFound(值为 404)。
  3. 写入错误信息

    • 使用 io.WriteString 函数将错误信息写入响应体,在 http.NotFound 中,错误信息是 "404 page not found"

相关推荐
鬼火儿2 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin2 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧3 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧3 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧3 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧3 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧3 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧3 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧4 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang4 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构