Skip to content

Latest commit

 

History

History
849 lines (614 loc) · 29.1 KB

File metadata and controls

849 lines (614 loc) · 29.1 KB

九、来到服务器端

本章包含以下配方:

  • 创建 TCP 服务器
  • 创建 UDP 服务器
  • 处理多个客户端
  • 创建 HTTP 服务器
  • 处理 HTTP 请求
  • 创建 HTTP 中间件层
  • 提供静态文件
  • 为使用模板生成的内容提供服务
  • 处理重定向
  • 处理饼干
  • 正常关闭 HTTP 服务器
  • 提供安全的 HTTP 内容
  • 解析表单变量

介绍

本章涵盖从实现简单的 TCP 和 UDP 服务器到旋转 HTTP 服务器的主题。这些方法将引导您从 HTTP 请求处理(为静态内容提供服务)到提供安全的 HTTP 内容。

检查 Go 是否正确安装。第 1 章**与环境交互检索 Golang 版本配方中的准备部分将帮助您。

确保端口80807070未被其他应用程序使用。

创建 TCP 服务器

在*连接网络一章中,*介绍了 TCP 连接的客户端。在此配方中,将描述服务器端。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe01
  2. 导航到该目录。
  3. 创建具有以下内容的servertcp.go文件:
        package main

        import (
          "bufio"
          "fmt"
          "io"
          "net"
        )

        func main() {

          l, err := net.Listen("tcp", ":8080")
          if err != nil {
            panic(err)
          }
          for {
            fmt.Println("Waiting for client...")
            conn, err := l.Accept()
            if err != nil {
              panic(err)
            }

            msg, err := bufio.NewReader(conn).ReadString('\n')
            if err != nil {
              panic(err)
            }
            _, err = io.WriteString(conn, "Received: "+string(msg))
            if err != nil {
              fmt.Println(err)
            }
            conn.Close()
          }
        }
  1. 通过go run servertcp.go执行代码:

  1. 打开另一个终端并执行nc localhost 8080
  2. 写任何文本,例如,Hello
  3. 请参见输出:

它是如何工作的。。。

可以使用net包创建 TCP 服务器。网络包包含创建TCPListenerListen函数,可以Accept客户端连接。Accept方法调用TCPListener块,直到收到客户端连接。如果客户端连接出现,Accept方法返回TCPConn连接。TCPConn是与客户端的连接,用于读取和写入数据。

TCPConn实现ReaderWriter接口。所有写入和读取数据的方法都可以使用。请注意,有一个分隔符用于读取数据,否则,如果客户端强制关闭连接,将接收 EOF。

请注意,此实现一次只处理一个客户机。

创建 UDP 服务器

用户数据报协议UDP)是互联网的基本协议之一。此食谱将向您展示如何侦听 UDP 数据包并读取内容。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe02
  2. 导航到该目录。
  3. 创建具有以下内容的serverudp.go文件:
        package main

        import (
          "fmt"
          "log"
          "net"
        )

        func main() {

          pc, err := net.ListenPacket("udp", ":7070")
          if err != nil {
            log.Fatal(err)
          }
          defer pc.Close()

          buffer := make([]byte, 2048)
          fmt.Println("Waiting for client...")
          for {
            _, addr, err := pc.ReadFrom(buffer)
            if err == nil {
              rcvMsq := string(buffer)
              fmt.Println("Received: " + rcvMsq)
              if _, err := pc.WriteTo([]byte("Received: "+rcvMsq), addr);
              err != nil {
                fmt.Println("error on write: " + err.Error())
              }
            } else {
              fmt.Println("error: " + err.Error())
            }
          }
        }
  1. 通过go run serverudp.go:启动服务器

  1. 打开另一个终端并执行nc -u localhost 7070
  2. 向终端写入任何消息,例如,Hello,点击输入
  3. 请参见输出:

它是如何工作的。。。

与 TCP 服务器一样,UDP 服务器可以在net包的帮助下创建。通过使用ListenPacket功能,创建PacketConn

PacketConn没有像TCPConn那样实现ReaderWriter接口。读取接收到的数据包时,应使用ReadFrom方法。ReadFrom方法阻塞,直到收到数据包。在此之后,返回客户端的Addr(记住 UDP 不是基于连接的)。为了响应客户,可以使用PacketConnWriteTo方法;在本例中,这将使用消息和Addr,即客户端Addr

处理多个客户端

前面的方法说明了如何创建 UDP 和 TCP 服务器。示例代码未准备好同时处理多个客户端。在本食谱中,我们将介绍如何在任何给定时间处理更多客户机。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe03
  2. 导航到该目录。
  3. 创建具有以下内容的multipletcp.go文件:
        package main

        import (
          "fmt"
          "log"
          "net"
        )

        func main() {

          pc, err := net.ListenPacket("udp", ":7070")
          if err != nil {
            log.Fatal(err)
          }
          defer pc.Close()

          buffer := make([]byte, 2048)
          fmt.Println("Waiting for client...")
          for {

            _, addr, err := pc.ReadFrom(buffer)
            if err == nil {
              rcvMsq := string(buffer)
              fmt.Println("Received: " + rcvMsq)
              if _, err := pc.WriteTo([]byte("Received: "+rcvMsq), addr);
              err != nil {
                fmt.Println("error on write: " + err.Error())
              }
            } else {
              fmt.Println("error: " + err.Error())
            }
          }

        }
  1. 通过go run multipletcp.go执行代码。
  2. 打开另外两个终端,执行nc localhost 8080
  3. 向两个打开的端子写入内容,然后查看输出。以下两个图像是连接的客户端。

服务器正在运行的终端中的输出:

它是如何工作的。。。

TCP 服务器实现的工作原理与上一个配方相同,即本章中的创建 TCP 服务器。实现得到了增强,能够同时处理多个客户端。请注意,我们现在在单独的goroutine中处理已接受的连接。这意味着服务器可以通过Accept方法继续接受客户端连接

因为 UDP 协议不是有状态的,并且不保持任何连接,所以多个客户端的处理被转移到应用程序逻辑,您需要识别客户端和数据包序列。只有对客户端的写入响应才能与 goroutines 的使用并行。

创建 HTTP 服务器

在 Go 中创建 HTTP 服务器非常简单,标准库提供了更多的方法。让我们看看最基本的一个。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe04
  2. 导航到该目录。
  3. 创建具有以下内容的httpserver.go文件:
        package main

        import (
          "fmt"
          "net/http"
        )

        type SimpleHTTP struct{}

        func (s SimpleHTTP) ServeHTTP(rw http.ResponseWriter,
                            r *http.Request) {
          fmt.Fprintln(rw, "Hello world")
        }

        func main() {
          fmt.Println("Starting HTTP server on port 8080")
          // Eventually you can use
          // http.ListenAndServe(":8080", SimpleHTTP{})
          s := &http.Server{Addr: ":8080", Handler: SimpleHTTP{}}
          s.ListenAndServe()
        }
  1. 通过go run httpserver.go执行代码。
  2. 请参见输出:

  1. 在浏览器中访问 URLhttp://localhost:8080或使用curlHello world内容应显示为:

它是如何工作的。。。

net/http包包含几种创建 HTTP 服务器的方法。最简单的是从net/http包中实现Handler接口。Handler接口需要该类型来实现ServeHTTP方法。此方法处理请求和响应。

服务器本身是以来自net/http包的Server结构的形式创建的。Server结构需要HandlerAddr字段。通过调用方法ListenAndServe,服务器开始提供给定地址上的内容。

如果使用ServerServe方法,则必须提供Listener

net/http包还提供了默认服务器,如果ListenAndServe作为net/http包的函数调用,则可以使用该服务器。它使用HandlerAddr,与Server结构相同。在内部,创建了Server

处理 HTTP 请求

应用程序通常使用 URL 路径和 HTTP 方法来定义应用程序的行为。这个配方将说明如何利用标准库来处理不同的 URL 和方法。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe05
  2. 导航到该目录。
  3. 创建具有以下内容的handle.go文件:
        package main

        import (
          "fmt"
          "net/http"
        )

        func main() {

          mux := http.NewServeMux()
          mux.HandleFunc("/user", func(w http.ResponseWriter, 
                         r *http.Request) {
            if r.Method == http.MethodGet {
              fmt.Fprintln(w, "User GET")
            }
            if r.Method == http.MethodPost {
              fmt.Fprintln(w, "User POST")
            }
          })

          // separate handler
          itemMux := http.NewServeMux()
          itemMux.HandleFunc("/items/clothes", func(w http.ResponseWriter,
                             r *http.Request) {
            fmt.Fprintln(w, "Clothes")
          })
          mux.Handle("/items/", itemMux)

          // Admin handlers
          adminMux := http.NewServeMux()
          adminMux.HandleFunc("/ports", func(w http.ResponseWriter,
                              r *http.Request) {
            fmt.Fprintln(w, "Ports")
          })

          mux.Handle("/admin/", http.StripPrefix("/admin",
                                adminMux))

          // Default server
          http.ListenAndServe(":8080", mux)

        }
  1. 通过go run handle.go执行代码。

  2. 在浏览器中或通过curl检查以下 URL:

    • http://localhost:8080/user
    • http://localhost:8080/items/clothes
    • http://localhost:8080/admin/ports
  3. 请参见输出:

它是如何工作的。。。

net/http包包含ServeMux结构,它实现了Server结构中要使用的Handler接口,但也包含了如何定义不同路径处理的机制。ServeMux指针包含接受路径的方法HandleFuncHandle,并且HandlerFunc函数处理给定路径的请求,或者其他处理程序也会这样做

有关如何使用这些功能,请参见前面的示例。Handler接口和HandlerFunc需要使用请求和响应参数实现函数。这样您就可以访问这两个结构。请求本身允许访问Headers、HTTP 方法和其他请求参数。

创建 HTTP 中间件层

具有 web UI 或 REST API 的现代应用程序通常使用中间件机制来记录活动,或保护给定接口的安全性。在此配方中,将介绍这种中间件层的实现。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe06
  2. 导航到该目录。
  3. 创建具有以下内容的middleware.go文件:
        package main

        import (
          "io"
          "net/http"
        )

        func main() {

          // Secured API
          mux := http.NewServeMux()
          mux.HandleFunc("/api/users", Secure(func(w http.ResponseWriter,
                         r *http.Request) {
            io.WriteString(w,  `[{"id":"1","login":"ffghi"},
                           {"id":"2","login":"ffghj"}]`)
          }))

          http.ListenAndServe(":8080", mux)

        }

        func Secure(h http.HandlerFunc) http.HandlerFunc {
          return func(w http.ResponseWriter, r *http.Request) {
            sec := r.Header.Get("X-Auth")
            if sec != "authenticated" {
              w.WriteHeader(http.StatusUnauthorized)
              return
            }
            h(w, r) // use the handler
          }

        }
  1. 通过go run middleware.go执行代码。
  2. 通过执行这两个命令(第一个没有,第二个有X-Auth头),使用curl检查 URLhttp://localhost:8080/api/users
    • curl -X GET -I http://localhost:8080/api/users
    • curl -X GET -H "X-Auth: authenticated" -I http://localhost:8080/api/users
  3. 请参见输出:

  1. 使用X-User头测试 URLhttp://localhost:8080/api/profile
  2. 请参见输出:

它是如何工作的。。。

上例中中间件的实现利用了 Golang 的功能作为一等公民特性。原始的HandlerFunc被包裹在一个HandlerFunc中,用于检查X-Auth标题。然后使用Secure功能来保护HandlerFunc,在ServeMuxHandleFunc方法中使用。

请注意,这只是一个简单的示例,但通过这种方式,您可以实现更复杂的解决方案。例如,可以从Header令牌中提取用户身份,随后,可以将新类型的处理程序定义为type AuthHandler func(u *User,w http.ResponseWriter, r *http.Request)。然后,函数WithUserServeMux创建HandlerFunc

提供静态文件

几乎任何 web 应用程序都需要提供静态文件。使用标准库可以轻松实现 JavaScript 文件、静态 HTML 页面或 CSS 样式表的服务。这道菜会告诉你怎么做。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe07
  2. 导航到该目录。
  3. 创建具有以下内容的文件welcome.txt
        Hi, Go is awesome!
  1. 创建文件夹html,导航到该文件夹并创建包含以下内容的文件page.html
        <html>
          <body>
            Hi, I'm HTML body for index.html!
          </body>
        </html>
  1. 创建具有以下内容的static.go文件:
        package main

        import (
          "net/http"
        )

        func main() {

          fileSrv := http.FileServer(http.Dir("html"))
          fileSrv = http.StripPrefix("/html", fileSrv)

          http.HandleFunc("/welcome", serveWelcome)
          http.Handle("/html/", fileSrv)
          http.ListenAndServe(":8080", nil)
        }

        func serveWelcome(w http.ResponseWriter, r *http.Request) {
          http.ServeFile(w, r, "welcome.txt")
        }
  1. 通过go run static.go执行代码。
  2. 使用浏览器或curl实用程序检查以下 URL:
    • http://localhost:8080/html/page.html
    • http://localhost:8080/welcome
  3. 请参见输出:

它是如何工作的。。。

net/http包提供了ServeFileFileServer功能,这些功能是为静态文件服务而设计的。ServeFile函数仅使用给定的文件路径参数使用ResponseWriterRequest,并将文件内容写入响应。

FileServer函数创建使用FileSystem参数的整个Handler。前面的示例使用了实现FileSystem接口的Dir类型。FileSystem接口需要实现Open方法,该方法使用字符串并返回给定路径的实际File

为使用模板生成的内容提供服务

出于某些目的,不需要使用所有 JavaScript 创建高度动态的 web UI,而使用生成内容的静态内容就足够了。Go 标准库提供了一种构建动态生成内容的方法。此配方为 Go 标准库模板提供了一条线索。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe08
  2. 导航到该目录。
  3. 创建具有以下内容的文件template.tpl
        <html>
          <body>
            Hi, I'm HTML body for index.html!
          </body>
        </html>
  1. 创建具有以下内容的文件dynamic.go
        package main

        import "net/http"
        import "html/template"

        func main() {
          tpl, err := template.ParseFiles("template.tpl")
          if err != nil {
            panic(err)
          }

          http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
            err := tpl.Execute(w, "John Doe")
            if err != nil {
              panic(err)
            }
          })
          http.ListenAndServe(":8080", nil)
        }
  1. 通过go run dynamic.go执行代码。
  2. 检查 URLhttp://localhost:8080并查看输出:

它是如何工作的。。。

Go 标准库还包含用于模板化内容的包。包html/templatetext/template提供解析模板并使用它们创建输出的函数。解析使用ParseXXX函数或新创建的Template结构指针的方法完成。前面的示例使用了html/template包的ParseFiles函数。

模板本身是基于文本的文档或包含动态变量的文本片段。模板的使用基于将模板文本与包含模板中变量值的结构合并。为了将模板与这样的结构合并,这里有ExecuteExecuteTemplate方法。请注意,这些方法使用编写器接口,在那里写入输出;本例中使用了ResponseWriter

文档中很好地解释了模板语法和功能

处理重定向

重定向是告知客户端内容已移动,或者需要查找其他位置以完成请求的常用方式。此配方描述了使用标准库实现重定向的方法。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe09
  2. 导航到该目录。
  3. 创建具有以下内容的文件redirect.go
        package main

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

        func main() {
          log.Println("Server is starting...")

          http.Handle("/secured/handle",
               http.RedirectHandler("/login", 
                      http.StatusTemporaryRedirect))
          http.HandleFunc("/secured/hadlefunc", 
               func(w http.ResponseWriter, r *http.Request) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
          })
          http.HandleFunc("/login", func(w http.ResponseWriter,
                          r *http.Request) {
            fmt.Fprintf(w, "Welcome user! Please login!\n")
          })
          if err := http.ListenAndServe(":8080", nil); err != nil {
            panic(err)
          }
        }
  1. 通过go run redirect.go执行代码。
  2. 使用curl -v -L http://localhost:8080/s ecured/handle查看重定向是否有效:

它是如何工作的。。。

net/http包包含执行重定向的简单方法。RedirectHandler可以被利用。该函数使用将重定向请求的URL和将发送到客户端的status code。函数本身将结果发送到Handler,可以在ServeMuxHandle方法中使用Handler(本例直接使用包中的默认值)。

第二种方法是使用Redirect函数,它为您执行重定向。该函数使用ResponseWriter、请求指针以及与RequestHandler相同的 URL 和状态码,发送给客户端。

也可以通过手动设置Location标题和写入正确的状态代码来完成重定向。Go 库仅使开发人员易于使用。

处理饼干

Cookie 提供了一种在客户端轻松存储数据的方法。此配方说明了如何在标准库的帮助下设置、检索和删除 cookie。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe10
  2. 导航到该目录。
  3. 创建具有以下内容的文件cookies.go
        package main

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

        const cookieName = "X-Cookie"

        func main() {
          log.Println("Server is starting...")

          http.HandleFunc("/set", func(w http.ResponseWriter,
                          r *http.Request) {
            c := &http.Cookie{
              Name: cookieName,
              Value: "Go is awesome.",
              Expires: time.Now().Add(time.Hour),
              Domain: "localhost",
            }
            http.SetCookie(w, c)
            fmt.Fprintln(w, "Cookie is set!")
          })
          http.HandleFunc("/get", func(w http.ResponseWriter,
                          r *http.Request) {
            val, err := r.Cookie(cookieName)
            if err != nil {
              fmt.Fprintln(w, "Cookie err: "+err.Error())
              return
            }
            fmt.Fprintf(w, "Cookie is: %s \n", val.Value)
            fmt.Fprintf(w, "Other cookies")
            for _, v := range r.Cookies() {
              fmt.Fprintf(w, "%s => %s \n", v.Name, v.Value)
            }
          })
          http.HandleFunc("/remove", func(w http.ResponseWriter,
                          r *http.Request) {
            val, err := r.Cookie(cookieName)
            if err != nil {
              fmt.Fprintln(w, "Cookie err: "+err.Error())
              return
            }
            val.MaxAge = -1
            http.SetCookie(w, val)
            fmt.Fprintln(w, "Cookie is removed!")
          })
          if err := http.ListenAndServe(":8080", nil); err != nil {
            panic(err)
          }
        }
  1. 通过go run cookies.go执行代码。
  2. 按以下顺序访问 URL,请参见:

它是如何工作的。。。

net/http包还提供了对 cookie 进行操作的功能和机制。示例代码介绍了如何设置/获取和删除 cookie。SetCookie函数接受表示 cookies 的Cookie结构指针,当然也接受ResponseWriter。直接在Cookie结构中设置NameValueDomain和到期。在幕后,SetCookie函数写入头来设置 cookies。

cookie 值可以从Request结构中检索。如果请求中存在 cookie,则带有 name 参数的方法Cookie返回指向Cookie的指针。

要列出请求中的所有 cookie,可以调用方法Cookies。此方法返回Cookie结构指针的切片。

为了让客户端知道应该删除 cookie,可以检索具有给定名称的Cookie,并且MaxAge字段应该设置为负值。请注意,这不是 Go 特性,而是客户端应该采用的工作方式

正常关闭 HTTP 服务器

第一章与环境的互动中,介绍了如何实现优雅关机的机制。在这个配方中,我们将描述如何关闭 HTTP 服务器并给它时间来处理现有的客户机。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe11
  2. 导航到该目录。
  3. 创建具有以下内容的文件gracefully.go
        package main

        import (
          "context"
          "fmt"
          "log"
          "net/http"
          "os"
          "os/signal"
          "time"
        )

        func main() {

          mux := http.NewServeMux()
          mux.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
            fmt.Fprintln(w, "Hello world!")
          })

          srv := &http.Server{Addr: ":8080", Handler: mux}
          go func() {
            if err := srv.ListenAndServe(); err != nil {
              log.Printf("Server error: %s\n", err)
            }
          }()

          log.Println("Server listening on : " + srv.Addr)

          stopChan := make(chan os.Signal)
          signal.Notify(stopChan, os.Interrupt)

          <-stopChan // wait for SIGINT
          log.Println("Shutting down server...")

          ctx, cancel := context.WithTimeout(
            context.Background(),
            5*time.Second)
          srv.Shutdown(ctx)
          <-ctx.Done()
          cancel()
          log.Println("Server gracefully stopped")
        }
  1. 通过go run gracefully.go执行代码。
  2. 等待服务器开始侦听:

  1. 将浏览器连接到http://localhost:8080;这将导致浏览器等待响应 10 秒钟。

  2. 在 10 秒内按Ctrl+C发送SIGINT信号。

  3. 尝试从其他选项卡再次连接(服务器应拒绝其他连接)。

  4. 请参见终端中的输出:

它是如何工作的。。。

来自net/http包的Server提供了正常关闭连接的方法。前面的代码在单独的goroutine中启动 HTTP 服务器,并在变量中保留对Server结构的引用

通过调用Shutdown方法,Server开始拒绝新连接,并关闭打开的侦听器和空闲连接。然后它无限期地等待已经挂起的连接,直到这些连接变为空闲。关闭所有连接后,服务器将关闭。注意,Shutdown方法消耗Context。如果提供的Context在停机前过期,则返回Context错误,并且Shutdown不再阻塞。

提供安全的 HTTP 内容

此配方描述了创建 HTTP 服务器的最简单方法,该服务器通过 TLS/SSL 层为内容提供服务。

准备

准备私钥和自签名 X-509 证书。为此,可以使用 OpenSSL 实用程序。通过执行命令openssl genrsa -out server.key 2048,使用 RSA 算法导出的私钥被生成到文件server.key。基于此私钥,可以通过调用openssl req -new -x509 -sha256 -key server.key -out server.crt -days 365生成 X-509 证书。创建了server.crt文件。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe12
  2. 导航到该目录。
  3. 将创建的server.keyserver.crt文件放入其中。
  4. 创建具有以下内容的文件servetls.go
        package main

        import (
          "fmt"
          "net/http"
        )

        type SimpleHTTP struct{}

          func (s SimpleHTTP) ServeHTTP(rw http.ResponseWriter,
                              r *http.Request) {
            fmt.Fprintln(rw, "Hello world")
          }

          func main() {
            fmt.Println("Starting HTTP server on port 8080")
            // Eventually you can use
            // http.ListenAndServe(":8080", SimpleHTTP{})
            s := &http.Server{Addr: ":8080", Handler: SimpleHTTP{}}
            if err := s.ListenAndServeTLS("server.crt", "server.key");
            err != nil {
              panic(err)
            }
          }
  1. 通过go run servetls.go执行服务器。
  2. 访问 URLhttps://localhost:8080(使用 HTTPS 协议)。如果使用curl实用程序,则必须使用--insecure标志,因为我们的证书是自签名且不受信任的:

它是如何工作的。。。

除了ListenAndServe函数外,在net/http包中还存在用于通过 SSL/TLS 服务 HTTP 的 TLS 变体。使用ServerListenAndServeTLS方法,提供安全 HTTP。ListenAndServeTLS使用私钥和 X-509 证书的路径。当然,可以使用直接来自net/http包的功能ListenAndServeTLS

解析表单变量

HTTPPOST表单是以结构化方式将信息传递给服务器的一种非常常见的方式。这个食谱展示了如何在服务器端解析和访问这些内容。

怎么做。。。

  1. 打开控制台,创建文件夹chapter09/recipe12
  2. 导航到该目录。
  3. 创建具有以下内容的文件form.go
        package main

        import (
          "fmt"
          "net/http"
        )

        type StringServer string

        func (s StringServer) ServeHTTP(rw http.ResponseWriter,
                              req *http.Request) {
          fmt.Printf("Prior ParseForm: %v\n", req.Form)
          req.ParseForm()
          fmt.Printf("Post ParseForm: %v\n", req.Form)
          fmt.Println("Param1 is : " + req.Form.Get("param1"))
          rw.Write([]byte(string(s)))
        }

        func createServer(addr string) http.Server {
          return http.Server{
            Addr: addr,
            Handler: StringServer("Hello world"),
          }
        }

        func main() {
          s := createServer(":8080")
          fmt.Println("Server is starting...")
          if err := s.ListenAndServe(); err != nil {
            panic(err)
          }
        }
  1. 通过go run form.go执行代码。
  2. 打开第二个终端,使用curl执行POST
 curl -X POST -H "Content-Type: app
lication/x-www-form-urlencoded" -d "param1=data1&param2=data2" "localhost:8080?
param1=overriden&param3=data3"
  1. 请参阅服务器运行的第一个终端中的输出:

它是如何工作的。。。

net/http包的Request结构包含Form字段,该字段包含POST表单变量和合并的 URL 查询变量。前面代码中的重要步骤是对Request指针调用ParseForm方法。此方法调用导致将POST表单值和查询值解析为Form变量。请注意,如果使用了Form字段上的Get方法,则参数的POST值优先。事实上,FormPostForm字段属于url.Values类型。

如果只需要访问POST表单中的参数,则提供RequestPostForm字段。这只保留了POST身体的一部分。