实现一个简单的 Web 服务器,处理 GET 和 POST 请求 | 豆包MarsCode AI刷题

0 阅读3分钟

本次文章的目标是实现一个简单的 Web 服务器,能够处理 GET 和 POST 请求,并且能够返回静态文件和动态内容。为了完成这个任务,我采用了以下的实现思路:

  • 首先使用 net 包提供的 Listen 函数,创建一个 TCP 监听器,用于监听客户端的连接请求。
  • 然后使用 net 包提供的 Accept 方法,接受客户端的连接,并且创建一个新的 goroutine 来处理每个连接。
  • 使用 bufio 包提供的 ReadLine 方法,读取客户端发送的 HTTP 请求行,并且解析出请求方法、请求路径和请求协议。
  • 然后根据请求方法和请求路径,判断是返回静态文件还是动态内容。如果是静态文件,我使用 os 包提供的 Open 方法,打开对应的文件,并且将文件内容写入响应体。如果是动态内容,我使用自定义的 Handler 接口,调用对应的处理函数,并且将返回值写入响应体。
  • 最后使用 fmt 包提供的 Fprintf 方法,构造 HTTP 响应头,并且将响应头和响应体一起发送给客户端。

代码

以下是我实现的 Web 服务器的代码:

package main
​
import (
    "bufio"
    "fmt"
    "io"
    "net"
    "os"
    "strings"
)
​

Handler 接口定义处理动态内容的方法

type Handler interface {
    ServeHTTP(w io.Writer, r *Request)
}

Request 结构体封装了 HTTP 请求的信息

type Request struct {
    Method string // 请求方法
    Path   string // 请求路径
}

FileHandler 结构体实现了 Handler 接口,用于返回静态文件


type FileHandler struct {
    Root string // 静态文件根目录
}

ServeHTTP 方法根据请求路径打开对应的文件,并将文件内容写入响应体

func (fh *FileHandler) ServeHTTP(w io.Writer, r *Request) {
    filePath := fh.Root + r.Path // 拼接文件路径
    file, err := os.Open(filePath) // 打开文件
    if err != nil {
        fmt.Fprintf(w, "HTTP/1.1 404 Not Found\r\n") // 如果文件不存在,返回 404 错误
        fmt.Fprintf(w, "Content-Type: text/plain\r\n")
        fmt.Fprintf(w, "\r\n")
        fmt.Fprintf(w, "File not found: %s\r\n", filePath)
        return
    }
    defer file.Close()
    fmt.Fprintf(w, "HTTP/1.1 200 OK\r\n") // 如果文件存在,返回 200 成功
    fmt.Fprintf(w, "Content-Type: text/plain\r\n")
    fmt.Fprintf(w, "\r\n")
    io.Copy(w, file) // 将文件内容复制到响应体
}

HelloHandler 结构体实现了 Handler 接口,用于返回动态内容 ServeHTTP 方法根据请求路径返回动态内容

type HelloHandler struct{}
​
func (hh *HelloHandler) ServeHTTP(w io.Writer, r *Request) {
    fmt.Fprintf(w, "HTTP/1.1 200 OK\r\n") // 返回 200 成功
    fmt.Fprintf(w, "Content-Type: text/plain\r\n")
    fmt.Fprintf(w, "\r\n")
    fmt.Fprintf(w, "Hello, %s!\r\n", r.Path[1:]) // 根据请求路径返回问候语
}

Server 结构体封装了 Web 服务器的信息

type Server struct {
    Addr    string              // 服务器地址
    Handler map[string]Handler // 处理函数映射表
}

NewServer 函数创建一个新的 Web 服务器

func NewServer(addr string) *Server {
    return &Server{
        Addr:    addr,
        Handler: make(map[string]Handler),
    }
}
// Handle 函数注册一个处理函数到 Web 服务器
func (s *Server) Handle(pattern string, handler Handler) {
    s.Handler[pattern] = handler
}

Run 函数启动 Web 服务器

func (s *Server) Run() {
    listener, err := net.Listen("tcp", s.Addr) // 创建 TCP 监听器
    if err != nil {
        fmt.Println("Error listening:", err)
        os.Exit(1)
    }
    defer listener.Close()
    fmt.Println("Listening on", s.Addr)
    for {
        conn, err := listener.Accept() // 接受客户端连接
        if err != nil {
            fmt.Println("Error accepting:", err)
            continue
        }
        go s.handleConnection(conn) // 创建 goroutine 处理连接
    }
}

handleConnection 函数处理每个客户端连接

func (s *Server) handleConnection(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn) // 创建缓冲读取器
    line, _, err := reader.ReadLine() // 读取请求行
    if err != nil {
        fmt.Println("Error reading:", err)
        return
    }
    parts := strings.Split(string(line), " ") // 分割请求行
    if len(parts) != 3 { // 如果请求行格式不正确,返回 400 错误
        fmt.Fprintf(conn, "HTTP/1.1 400 Bad Request\r\n")
        fmt.Fprintf(conn, "Content-Type: text/plain\r\n")
        fmt.Fprintf(conn, "\r\n")
        fmt.Fprintf(conn, "Bad request: %s\r\n", string(line))
        return
    }
    method := parts[0] // 请求方法
    path := parts[1]   // 请求路径
    request := &Request{Method: method, Path: path} // 创建请求对象
    handler, ok := s.Handler[path] // 根据请求路径查找处理函数
    if !ok { // 如果没有找到处理函数,使用默认的 FileHandler
        handler = &FileHandler{Root: "."}
    }
    handler.ServeHTTP(conn, request) // 调用处理函数,返回响应
}

main

func main() {
    server := NewServer(":8080")          // 创建 Web 服务器,监听 8080 端口
    server.Handle("/hello", &HelloHandler{}) // 注册动态内容处理函数
    server.Run()                          // 启动 Web 服务器
}