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 小时前
[Golang 修仙之路] Go语言:内存管理
后端·面试
几颗流星2 小时前
Rust 常用语法速记 - 循环
后端·rust
易协同低代码2 小时前
KK部署与配置
后端
郭京京2 小时前
goweb内置的响应1
后端·go
EvanSun__2 小时前
Flask 框架引入
后端·python·flask
pianmian13 小时前
Spring 项目骨架
java·后端·spring
小程序设计4 小时前
【springboot+vue】高校迎新平台管理系统(源码+文档+调试+基础修改+答疑)
vue.js·spring boot·后端
海梨花4 小时前
字节一面 面经(补充版)
jvm·redis·后端·面试·juc