ZetCode

Go HTTP 服务器

最后修改时间 2024 年 4 月 11 日

在本文中,我们将介绍如何在 Golang 中创建简单的 HTTP 服务器。

HTTP

超文本传输协议(Hypertext Transfer Protocol (HTTP))是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP 协议是万维网数据通信的基础。

Go http

在 Go 中,我们使用 http 包来创建 GET 和 POST 请求。该包提供了 HTTP 客户端和服务器实现。

Go http 类型

客户端向服务器发送请求以接收资源。

type Request struct

Request 表示服务器收到的、由客户端发送的 HTTP 请求。

type Response struct

Response 表示 HTTP 请求的响应。

type ResponseWriter interface

ResponseWriter 接口由 HTTP 处理程序使用,用于构造 HTTP 响应。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Handler 响应 HTTP 请求。ServeHTTP 将回复头和数据写入 ResponseWriter,然后返回。

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc 类型是一个适配器,允许将普通函数用作 HTTP 处理程序。

Go HTTP 服务器 Handle

Handle 函数为给定的 URL 注册一个处理程序。处理程序的目的是为客户端的请求创建回复。

main.go
package main

import (
    "fmt"
    "net/http"
)

type CounterHandler struct {
    counter int
}

func (ct *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Println(ct.counter)
    ct.counter++
    fmt.Fprintln(w, "Counter:", ct.counter)
}

func main() {

    th := &CounterHandler{counter: 0}
    http.Handle("/count", th)
    http.ListenAndServe(":8080", nil)
}

每次访问该 URL 时,计数器都会增加,并返回该值。

type CounterHandler struct {
    counter int
}

func (ct *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Println(ct.counter)
    ct.counter++
    fmt.Fprintln(w, "Counter:", ct.counter)
}

CounterHandler 包含 counter 变量和 ServeHTTP 函数的实现。该函数会增加计数器,并将一条消息写入 http.ResponseWriter

th := &CounterHandler{counter: 0}
http.Handle("/count", th)

CounterHandler 被创建并使用 Handle 注册。

$ curl localhost:8080/count
Counter: 1
$ curl localhost:8080/count
Counter: 2
$ curl localhost:8080/count
Counter: 3
$ curl localhost:8080/count
Counter: 4

Go HTTP 服务器 HandleFunc

使用 HandleFunc 函数,我们可以为给定的 URL 模式注册一个处理程序函数。HandleFunc 函数是创建处理程序的便捷方式。

main.go
package main

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

func main() {

    http.HandleFunc("/", HelloHandler)

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func HelloHandler(w http.ResponseWriter, _ *http.Request) {

    fmt.Fprintf(w, "Hello, there\n")
}

该示例创建了一个监听在 8080 端口的简单 HTTP 服务器。收到请求后,服务器会回复“Hello, there”消息。

http.HandleFunc("/", HelloHandler)

我们使用 HandleFunc/ 模式映射到 HelloHandler

log.Fatal(http.ListenAndServe(":8080", nil))

ListenAndServe 监听 TCP 网络地址,然后处理传入连接上的请求。

func HelloHandler(w http.ResponseWriter, _ *http.Request) {
    fmt.Fprintf(w, "Hello, there\n")
}

处理程序响应 HTTP 请求。它接受两个参数:响应写入器和请求对象。

$ go run main.go

我们启动服务器。

$ curl localhost:8080/
Hello, there

我们使用 curl 工具生成一个请求。

Go HTTP 状态码

HTTP 响应状态码指示特定的 HTTP 请求是否已成功完成。

响应分为五类

响应的状态码使用 WriteHeader 函数写入。

main.go
package main

import (
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) {
        w.WriteHeader(http.StatusOK)
    })

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

我们将 http.StatusOK 发送到 /status 路径。

$ curl -I localhost:8080/status
HTTP/1.1 200 OK
Date: Sat, 23 Apr 2022 12:59:52 GMT

Go HTTP 服务器未找到处理程序

如果服务器资源找不到,将向客户端返回 404 错误代码。

main.go
package main

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

func main() {

    http.HandleFunc("/about", func(w http.ResponseWriter, _ *http.Request) {
        fmt.Fprintln(w, "about page")
    })

    http.HandleFunc("/news", func(w http.ResponseWriter, _ *http.Request) {
        fmt.Fprintln(w, "news page")
    })

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        if r.URL.Path != "/" {
            w.WriteHeader(404)
            w.Write([]byte("404 - not found\n"))
            return
        }

        fmt.Fprintln(w, "home page")
    })

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

在示例中,我们有三个端点。对于这三个端点之外的任何内容,我们都返回 404 错误消息。

$ curl localhost:8080/about
about page
$ curl localhost:8080/
home page
$ curl localhost:8080/contact
404 - not found

Go HTTP 服务器获取头

HTTP 头允许客户端和服务器在 HTTP 请求或响应中传递附加信息。HTTP 头是名称/值对,由冒号分隔。

User-Agent 请求头是一个字符串,允许服务器和网络对等方识别请求用户代理的应用程序、操作系统、供应商和/或版本。

main.go
package main

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

func main() {

    http.HandleFunc("/ua", func(w http.ResponseWriter, r *http.Request) {

        ua := r.Header.Get("User-Agent")

        fmt.Fprintf(w, "User agent: %s\n", ua)
    })

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Header 字段,我们获取 User-Agent 头并将其返回给调用者。

client.go
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {

    resp, err := http.Get("https://:8080/ua")

    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(body))
}

该示例创建了一个简单的 Go 客户端,它向 /ua 路径生成一个 GET 请求。

Go URL 路径参数

我们可以将数据通过 URL 发送到服务器。

main.go
package main

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

func main() {

    http.HandleFunc("/", HelloServer)
    fmt.Println("Server started at port 8080")

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

func HelloServer(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintf(w, "Hello, %s!\n", r.URL.Path[1:])
}

在代码示例中,我们使用 r.URL.Path[1:] 获取 URL 路径值,并使用该数据构建一条消息。

$ curl localhost:8080/John
Hello, John!

我们将名字放在 URL 路径中;服务器会回复一个问候。

Go 查询参数

查询字符串是统一资源定位符 (URL) 的一部分,用于为指定的参数分配值。

通用 URL 的形式如下:

scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]

查询参数以 ? 字符开头。多个查询参数以 & 字符分隔。

https://example.com/path/page?name=John&occupation=teacher

这是一个带有两个查询参数的 URL 的示例。

main.go
package main

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

func main() {

    http.HandleFunc("/", handler)

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {

    keys, ok := r.URL.Query()["name"]

    name := "guest"

    if ok {

        name = keys[0]
    }

    fmt.Fprintf(w, "Hello %s!\n", name)
}

在代码示例中,我们接受一个 name 参数。我们使用 r.URL.Query()["name"] 获取该参数。

$ curl localhost:8080/?name=Peter
Hello Peter!

我们将名字作为查询参数发送;服务器会回复一条消息。

Go 文件服务器

使用 http.FileServer,我们将文件发送给客户端。

public/about.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>About page</title>
</head>
<body>
    <p>
        About page
    </p>
</body>
</html>

这是 about.html 页面。

public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
    <p>
        Home page
    </p>

</body>
</html>

这是主页。

main.go
package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
)

func main() {

    fileServer := http.FileServer(http.Dir("./public"))
    http.Handle("/", fileServer)

    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Hello there!\n")
    })

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

在代码示例中,我们有一个文件服务器和一个简单的 hello 处理程序。

fileServer := http.FileServer(http.Dir("./public"))
http.Handle("/", fileServer)

文件服务器使用 Handle 注册;它服务来自 public 目录的文件。

$ curl localhost:8080
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
    <p>
        Home page
    </p>

</body>
</html>
$ curl localhost:8080/about.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>About page</title>
</head>
<body>
    <p>
        About page
    </p>
</body>
</html>

我们请求主页和关于页面。

Go 处理 GET/POST 请求

在下面的示例中,服务器处理来自客户端的 GET 和 POST 请求。

注意:在生产应用程序中,使用 POST 请求处理表单需要额外的安全措施,例如 CSRF 保护。
form.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Form</title>
</head>
<body>

    <form method="POST" action="/">
        <div>
            <label>Name:</label><input name="name" type="text">
        </div>
        <div>
            <label>Occupation:</label><input name="occupation" type="text">
        </div>
        <button type="submit" value="submit">Submit</button>
    </form>

</body>
</html>

HTML 页面展示了一个简单的表单。

main.go
package main

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

func process(w http.ResponseWriter, r *http.Request) {

    if r.URL.Path != "/" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    switch r.Method {
    case "GET":

        http.ServeFile(w, r, "form.html")
    case "POST":

        if err := r.ParseForm(); err != nil {
            fmt.Fprintf(w, "ParseForm() err: %v", err)
            return
        }

        name := r.FormValue("name")
        occupation := r.FormValue("occupation")

        fmt.Fprintf(w, "%s is a %s\n", name, occupation)

    default:
        fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.")
    }
}

func main() {

    http.HandleFunc("/", process)

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

对于 GET 请求,我们发送一个带有表单的网页。对于 POST 请求,我们处理表单中的数据。

case "GET":

    http.ServeFile(w, r, "form.html")

如果请求是 GET 请求,我们将 form.html 发送给客户端。

case "POST":

    if err := r.ParseForm(); err != nil {
        fmt.Fprintf(w, "ParseForm() err: %v", err)
        return
    }

在 POST 请求的情况下,我们调用 ParseForm 函数;它解析 URL 中的原始查询并更新 r.Form

name := r.FormValue("name")
occupation := r.FormValue("occupation")

fmt.Fprintf(w, "%s is a %s\n", name, occupation)

我们使用 FormValue 获取表单值并构建一条消息。

Go HTTP 提供图片

在下面的示例中,我们服务一张图片。

main.go
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {

    handler := http.HandlerFunc(handleRequest)

    http.Handle("/image", handler)

    fmt.Println("Server started at port 8080")
    http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {

    buf, err := ioutil.ReadFile("sid.png")

    if err != nil {

        log.Fatal(err)
    }

    w.Header().Set("Content-Type", "image/png")
    w.Write(buf)
}

在代码示例中,我们创建了一个简单的 Web 服务器,它向客户端发送一张图片。图片位于当前工作目录中。

handler := http.HandlerFunc(handleRequest)

http.Handle("/image", handler)

我们将一个处理程序映射到 /image 路径。

func handleRequest(w http.ResponseWriter, r *http.Request) {
...

处理程序函数接受两个参数:http.ResponseWriterhttp.Request

buf, err := ioutil.ReadFile("sid.png")

我们将图片读入缓冲区。

w.Header().Set("Content-Type", "image/png")

我们设置了头部。Content-Type 内容类型用于 PNG 图片。

w.Write(buf)

图片数据通过 Write 写入响应体。

Go HTTP 服务器模板

Go 具有内置的模板包,用于生成动态 HTML 内容。

layout.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Users</title>
</head>

<body>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Occupation</th>
            </tr>
        </thead>
        <tbody>
            {{ range .Users}}
            <tr>
                <td>{{.Name}}</td>
                <td>{{.Occupation}}</td>
            </tr>
            {{ end }}
        </tbody>
    </table>
</body>
</html>

输出是一个 HTML 文件。数据被插入到 HTML 表格中。

main.go
package main

import (
    "html/template"
    "log"
    "net/http"
)

type User struct {
    Name       string
    Occupation string
}

type Data struct {
    Users []User
}

func main() {

    tmp := template.Must(template.ParseFiles("layout.html"))

    http.HandleFunc("/users", func(w http.ResponseWriter, _ *http.Request) {

        data := Data{
            Users: []User{
                {Name: "John Doe", Occupation: "gardener"},
                {Name: "Roger Roe", Occupation: "driver"},
                {Name: "Peter Smith", Occupation: "teacher"},
            },
        }
        tmp.Execute(w, data)
    })

    log.Println("Listening...")
    http.ListenAndServe(":8080", nil)
}

Web 服务器为 /users URL 路径返回一个带有用户表的 HTML 页面。

来源

Go net/http 包 - 参考

在本文中,我们创建了简单的 Go HTTP 服务器。

作者

我叫 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程