本次文章的目标是实现一个简单的 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 服务器
}