Go Web 开发秘籍(一)

原文:zh.annas-archive.org/md5/6712F93A50A8E516D2DB7024F42646AC

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Go 是一种设计用于扩展并支持语言级并发性的开源编程语言,这使得开发人员可以轻松编写大型并发的 Web 应用程序。

从创建 Web 应用程序到在 AWS 上部署,这将是一个学习 Go Web 开发的一站式指南。无论您是新手程序员还是专业开发人员,本书都将帮助您快速掌握 Go Web 开发。

本书将专注于在 Go 中编写模块化代码,并包含深入的信息性配方,逐步构建基础。您将学习如何创建服务器、处理 HTML 表单、会话和错误处理、SQL 和 NoSQL 数据库、Beego、创建和保护 RESTful Web 服务、创建、单元测试和调试 WebSockets,以及创建 Go Docker 容器并在 AWS 上部署它们等概念和配方。

通过本书,您将能够将您在 Go 中学到的新技能应用于在任何领域创建和探索 Web 应用程序。

本书适合人群

本书适用于希望使用 Go 编写大型并发 Web 应用程序的开发人员。对 Go 有一定了解的读者会发现本书最有益。

本书内容

第一章《在 Go 中创建您的第一个服务器》解释了如何编写和与 HTTP 和 TCP 服务器交互,使用 GZIP 压缩优化服务器响应,并在 Go Web 应用程序中实现路由和日志记录。

第二章《处理模板、静态文件和 HTML 表单》介绍了如何创建 HTML 模板;从文件系统中提供静态资源;创建、读取和验证 HTML 表单;以及为 Go Web 应用程序实现简单的用户身份验证。

第三章《在 Go 中处理会话、错误和缓存》探讨了实现 HTTP 会话、HTTP cookie、错误处理和缓存,以及使用 Redis 管理 HTTP 会话,这对于在多个数据中心部署的 Web 应用程序是必需的。

第四章《在 Go 中编写和消费 RESTful Web 服务》解释了如何编写 RESTful Web 服务、对其进行版本控制,并创建 AngularJS 与 TypeScript 2、ReactJS 和 VueJS 客户端来消费它们。

第五章《使用 SQL 和 NoSQL 数据库》介绍了在 Go Web 应用程序中使用 MySQL 和 MongoDB 数据库实现 CRUD 操作。

第六章《使用微服务工具包 Go 编写微服务》专注于使用协议缓冲区编写和处理微服务,使用微服务发现客户端(如 Consul),使用 Go Micro 编写微服务,并通过命令行和 Web 仪表板与它们进行交互,以及实现 API 网关模式以通过 HTTP 协议访问微服务。

第七章《在 Go 中使用 WebSocket》介绍了如何编写 WebSocket 服务器及其客户端,以及如何使用 GoLand IDE 编写单元测试并进行调试。

第八章《使用 Go Web 应用程序框架-Beego》介绍了设置 Beego 项目架构,编写控制器、视图和过滤器,实现与 Redis 支持的缓存,以及使用 Nginx 监控和部署 Beego 应用程序。

第九章《使用 Go 和 Docker》介绍了如何编写 Docker 镜像、创建 Docker 容器、用户定义的 Docker 网络、使用 Docker Registry,并运行与另一个 Docker 容器链接的 Go Web 应用程序 Docker 容器。

第十章,保护 Go Web 应用程序,演示了使用 OpenSSL 创建服务器证书和私钥,将 HTTP 服务器转移到 HTTPS,使用 JSON Web Token(JWT)保护 RESTful API,并防止 Go Web 应用程序中的跨站点请求伪造。

第十一章,将 Go Web 应用程序和 Docker 容器部署到 AWS,讨论了设置 EC2 实例,交互以及在其上运行 Go Web 应用程序和 Go Docker 容器。

充分利用本书

读者应具备 Go 的基本知识,并在计算机上安装 Go 以执行说明和代码。

下载示例代码文件

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support并注册,以便将文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packtpub.com

  2. 选择“支持”选项卡。

  3. 单击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的以下软件解压缩或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Go-Web-Development-Cookbook。我们还有来自丰富书籍和视频目录的其他代码包,可在**github.com/PacktPublishing/**上找到。快去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/GoWebDevelopmentCookbook_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:“GZIP 压缩意味着从服务器以.gzip格式向客户端发送响应,而不是发送纯文本响应。”

代码块设置如下:

for 
{
  conn, err := listener.Accept()
  if err != nil 
  {
    log.Fatal("Error accepting: ", err.Error())
  }
  log.Println(conn)
}

任何命令行输入或输出都以以下方式编写:

$ go get github.com/gorilla/handlers
$ go get github.com/gorilla/mux

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会在文本中出现。例如:“AngularJS 客户端页面具有 HTML 表单,其中显示如下的 Id、FirstName 和 LastName 字段。”

警告或重要说明看起来像这样。

提示和技巧看起来像这样。

章节

在本书中,您会经常看到几个标题(准备工作如何做工作原理更多内容另请参阅)。

为了清晰地说明如何完成配方,使用以下各节:

准备工作

本节告诉您该配方中可以期望的内容,并描述了为该配方设置任何软件或任何先决设置所需的步骤。

如何做…

本节包含遵循该配方所需的步骤。

工作原理…

本节通常包括对前一节发生的事情的详细解释。

更多内容…

本节包含有关该配方的其他信息,以使您对该配方更加了解。

另请参阅

本节提供了有关该配方的其他有用信息的链接。

第一章:在 Go 中创建你的第一个服务器

在本章中,我们将涵盖以下内容:

  • 创建一个简单的 HTTP 服务器

  • 在一个简单的 HTTP 服务器上实现基本身份验证

  • 使用 GZIP 压缩优化 HTTP 服务器响应

  • 创建一个简单的 TCP 服务器

  • 从 TCP 连接读取数据

  • 向 TCP 连接写入数据

  • 实现 HTTP 请求路由

  • 使用 Gorilla Mux 实现 HTTP 请求路由

  • 记录 HTTP 请求

介绍

Go 是为了解决多核处理器的新架构带来的问题而创建的,它创建了高性能网络,可以处理数百万个请求和计算密集型任务。Go 的理念是通过实现快速原型设计、减少编译和构建时间以及实现更好的依赖管理来提高生产力。

与大多数其他编程语言不同,Go 提供了net/http包,用于创建 HTTP 客户端和服务器。本章将介绍在 Go 中创建 HTTP 和 TCP 服务器。

我们将从一些简单的示例开始,创建一个 HTTP 和 TCP 服务器,并逐渐转向更复杂的示例,其中我们实现基本身份验证、优化服务器响应、定义多个路由和记录 HTTP 请求。我们还将涵盖 Go 处理程序、Goroutines 和 Gorilla 等概念和关键字-Go 的 Web 工具包。

创建一个简单的 HTTP 服务器

作为程序员,如果你需要创建一个简单的 HTTP 服务器,那么你可以很容易地使用 Go 的net/http包来编写,我们将在这个示例中介绍。

如何做…

在这个示例中,我们将创建一个简单的 HTTP 服务器,当我们在浏览器中浏览http://localhost:8080或在命令行中执行curl http://localhost:8080时,它将呈现 Hello World!执行以下步骤:

  1. 创建http-server.go并复制以下内容:
package main
import 
(
  "fmt"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func main() 
{
  http.HandleFunc("/", helloWorld)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-server.go

它是如何工作的…

一旦我们运行程序,一个 HTTP 服务器将在本地监听端口8080。在浏览器中打开http://localhost:8080将显示来自服务器的 Hello World!,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你好,世界!

让我们理解程序中每一行的含义:

  • package main: 这定义了程序的包名称。

  • import ( "fmt" "log" "net/http" ): 这是一个预处理命令,告诉 Go 编译器包括fmtlognet/http包中的所有文件。

  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" ): 我们使用const关键字在 Go 程序中声明常量。这里我们声明了两个常量-一个是CONN_HOST,值为 localhost,另一个是CONN_PORT,值为8080

  • func helloWorld(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }: 这是一个 Go 函数,它以ResponseWriterRequest作为输入,并在 HTTP 响应流上写入Hello World!

接下来,我们声明了main()方法,程序执行从这里开始,因为这个方法做了很多事情。让我们逐行理解它:

  • http.HandleFunc("/", helloWorld): 在这里,我们使用net/http包的HandleFunc注册了helloWorld函数与/URL 模式,这意味着每当我们访问具有模式/的 HTTP URL 时,helloWorld会被执行,并将(http.ResponseWriter, *http.Request)作为参数传递给它。

  • err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil): 在这里,我们调用http.ListenAndServe来处理每个传入连接的 HTTP 请求,每个连接在一个单独的 Goroutine 中处理。ListenAndServe接受两个参数-服务器地址和处理程序。在这里,我们将服务器地址传递为localhost:8080,处理程序为nil,这意味着我们要求服务器使用DefaultServeMux作为处理程序。

  • if err != nil { log.Fatal("error starting http server : ", err) return}:在这里,我们检查是否有问题启动服务器。如果有问题,那么记录错误并以状态码1退出。

在简单的 HTTP 服务器上实现基本身份验证

一旦创建了 HTTP 服务器,您可能希望限制特定用户访问资源,例如应用程序的管理员。如果是这样,那么您可以在 HTTP 服务器上实现基本身份验证,我们将在这个配方中介绍。

准备工作

由于我们已经在上一个配方中创建了一个 HTTP 服务器,我们只需扩展它以包含基本身份验证。

如何做…

在这个配方中,我们将通过添加BasicAuth函数并修改HandleFunc来调用它来更新我们在上一个配方中创建的 HTTP 服务器。执行以下步骤:

  1. 创建http-server-basic-authentication.go并复制以下内容:
package main
import 
(
  "crypto/subtle"
  "fmt"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  ADMIN_USER = "admin"
  ADMIN_PASSWORD = "admin"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) 
  {
    user, pass, ok := r.BasicAuth()
    if !ok || subtle.ConstantTimeCompare([]byte(user),
    []byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare([]byte(pass), 
    []byte(ADMIN_PASSWORD)) != 1 
    {
      w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
      w.WriteHeader(401)
      w.Write([]byte("You are Unauthorized to access the
      application.\n"))
      return
    }
    handler(w, r)
  }
}
func main() 
{
  http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your
  username and password"))
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-server-basic-authentication.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080上启动。

一旦服务器启动,在浏览器中访问http://localhost:8080将提示您输入用户名和密码。提供adminadmin将在屏幕上呈现 Hello World!对于其他用户名和密码的组合,它将呈现您未经授权访问应用程序。

要从命令行访问服务器,我们必须在curl命令中提供--user标志,如下所示:

$ curl --user admin:admin http://localhost:8080/
Hello World!

我们还可以使用base64编码的username:password令牌访问服务器,我们可以从任何网站(例如https://www.base64encode.org/)获取,并将其作为curl命令中的授权标头传递,如下所示:

$ curl -i -H 'Authorization:Basic YWRtaW46YWRtaW4=' http://localhost:8080/

HTTP/1.1 200 OK
Date: Sat, 12 Aug 2017 12:02:51 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Hello World!

让我们了解我们引入的更改作为这个配方的一部分:

  • import函数添加了一个额外的包,crypto/subtle,我们将使用它来比较用户输入凭据中的用户名和密码。

  • 使用const函数,我们定义了两个额外的常量,ADMIN_USERADMIN_PASSWORD,我们将在验证用户时使用它们。

  • 接下来,我们声明了一个BasicAuth()方法,它接受两个输入参数——一个处理程序,在用户成功验证后执行,和一个领域,返回HandlerFunc,如下所示:

func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc 
{
  return func(w http.ResponseWriter, r *http.Request)
  {
    user, pass, ok := r.BasicAuth()
    if !ok || subtle.ConstantTimeCompare([]byte(user),
    []byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare
    ([]byte(pass),
    []byte(ADMIN_PASSWORD)) != 1
    {
      w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
      w.WriteHeader(401)
      w.Write([]byte("Unauthorized.\n"))
      return
    }
    handler(w, r)
  }
}

在前面的处理程序中,我们首先使用r.BasicAuth()获取请求的授权标头中提供的用户名和密码,然后将其与程序中声明的常量进行比较。如果凭据匹配,则返回处理程序,否则设置WWW-Authenticate以及状态码401,并在 HTTP 响应流上写入You are Unauthorized to access the application

最后,我们在main()方法中引入了一个更改,以从HandleFunc中调用BasicAuth,如下所示:

http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your username and password"))

我们只需传递一个BasicAuth处理程序,而不是nilDefaultServeMux来处理所有带有 URL 模式为/的传入请求。

使用 GZIP 压缩优化 HTTP 服务器响应

GZIP 压缩意味着从服务器以.gzip格式向客户端发送响应,而不是发送纯文本响应,如果客户端/浏览器支持的话,发送压缩响应总是一个好习惯。

通过发送压缩响应,我们节省了网络带宽和下载时间,最终使页面加载更快。 GZIP 压缩的原理是浏览器发送一个请求标头,告诉服务器它接受压缩内容(.gzip.deflate),如果服务器有能力以压缩形式发送响应,则发送压缩形式的响应。如果服务器支持压缩,则它将设置Content-Encoding: gzip作为响应标头,否则它将向客户端发送一个纯文本响应,这清楚地表示要求压缩响应只是浏览器的请求,而不是要求。我们将使用 Gorilla 的 handlers 包在这个配方中实现它。

如何做…

在本教程中,我们将创建一个带有单个处理程序的 HTTP 服务器,该处理程序将在 HTTP 响应流上写入 Hello World!并使用 Gorilla CompressHandler.gzip格式将所有响应发送回客户端。执行以下步骤:

  1. 使用大猩猩处理程序,首先我们需要使用go get命令安装包,或者手动将其复制到$GOPATH/src$GOPATH,如下所示:
$ go get github.com/gorilla/handlers
  1. 创建http-server-mux.go并复制以下内容:
package main
import 
(
  "io"
  "net/http"
  "github.com/gorilla/handlers"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  io.WriteString(w, "Hello World!")
}
func main() 
{
  mux := http.NewServeMux()
  mux.HandleFunc("/", helloWorld)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT,
  handlers.CompressHandler(mux))
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-server-mux.go

工作原理…

运行程序后,HTTP 服务器将在本地监听端口8080

在浏览器中打开http://localhost:8080将显示来自服务器的 Hello World!并显示 Content-Encoding 响应头值 gzip,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你好,世界!

让我们了解程序中每一行的含义:

  • package main:这定义了程序的包名称。

  • import ( "io" "net/http" "github.com/gorilla/handlers" ): 这是一个预处理命令,告诉 Go 编译器包括来自ionet/httpgithub.com/gorilla/handlers包的所有文件。

  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" ): 我们使用 const 关键字在 Go 程序中声明常量。在这里,我们声明了两个常量,一个是值为 localhost 的CONN_HOST,另一个是值为 8080 的CONN_PORT

  • func helloWorld(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello World!")}: 这是一个接受ResponseWriterRequest作为输入参数并在 HTTP 响应流上写入Hello World!的 Go 函数。

接下来,我们声明了main()方法,程序的执行从这里开始。由于这个方法做了很多事情,让我们逐行理解它:

  • mux := http.NewServeMux(): 这将分配并返回一个新的 HTTP 请求多路复用器(ServeMux),它将匹配每个传入请求的 URL 与已注册模式列表,并调用最接近 URL 的模式的处理程序。使用它的好处之一是程序完全控制与服务器一起使用的处理程序,尽管任何使用DefaultServeMux注册的处理程序都将被忽略。

  • http.HandleFunc("/", helloWorld): 在这里,我们使用net/http包的HandleFunchelloWorld函数注册到/URL 模式,这意味着每当我们访问具有/模式的 HTTP URL 时,helloWorld将被执行,并将(http.ResponseWriter, *http.Request)作为参数传递给它。

  • err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, handlers.CompressHandler(mux)): 在这里,我们调用http.ListenAndServe来为我们处理每个传入连接的 HTTP 请求。ListenAndServe接受两个参数——服务器地址和处理程序。在这里,我们将服务器地址传递为localhost:8080,处理程序为CompressHandler,它用.gzip处理程序包装我们的服务器以将所有响应压缩为.gzip格式。

  • if err != nil { log.Fatal("error starting http server: ", err) return}: 在这里,我们检查是否有任何启动服务器的问题。如果有问题,记录错误并以状态码 1 退出。

创建一个简单的 TCP 服务器

每当你需要构建高性能导向系统时,编写 TCP 服务器总是优于 HTTP 服务器的最佳选择,因为 TCP 套接字比 HTTP 更轻。Go 支持并提供了一种方便的方法来编写使用net包的 TCP 服务器,我们将在本教程中介绍。

如何做…

在本教程中,我们将创建一个简单的 TCP 服务器,它将在localhost:8080上接受连接。执行以下步骤:

  1. 创建tcp-server.go并复制以下内容:
package main
import 
(
  "log"
  "net"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  CONN_TYPE = "tcp"
)
func main() 
{
  listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
  if err != nil 
  {
    log.Fatal("Error starting tcp server : ", err)
  }
  defer listener.Close()
  log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
  for 
  {
    conn, err := listener.Accept()
    if err != nil 
    {
      log.Fatal("Error accepting: ", err.Error())
    }
    log.Println(conn)
  }
}
  1. 使用以下命令运行程序:
$ go run tcp-server.go

工作原理…

运行程序后,TCP 服务器将在本地监听端口8080

让我们理解程序中每一行的含义:

  • package main: 这定义了程序的包名称。

  • import ( "log" "net"): 这是一个预处理命令,告诉 Go 编译器包括lognet包中的所有文件。

  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" CONN_TYPE = "tcp" ): 我们使用 const 关键字在 Go 程序中声明常量。在这里,我们声明了三个常量——一个是CONN_HOST,值为localhost,另一个是CONN_PORT,值为8080,最后一个是CONN_TYPE,值为tcp

接下来,我们从main()方法中声明了main()方法,程序执行从这里开始。由于这个方法做了很多事情,让我们逐行理解它:

  • listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT): 这将在本地端口8080上创建一个 TCP 服务器。

  • if err != nil { log.Fatal("Error starting tcp server: ", err) }: 在这里,我们检查是否有问题启动 TCP 服务器。如果有问题,就记录错误并以状态码 1 退出。

  • defer listener.Close(): 这个延迟语句在应用程序关闭时关闭 TCP 套接字监听器。

接下来,我们在一个常量循环中接受 TCP 服务器的传入请求,如果在接受请求时出现任何错误,我们将记录并退出;否则,我们只是在服务器控制台上打印连接对象,如下所示:

for 
{
  conn, err := listener.Accept()
  if err != nil 
  {
    log.Fatal("Error accepting: ", err.Error())
  }
  log.Println(conn)
}

从 TCP 连接读取数据

在任何应用程序中最常见的情况之一是客户端与服务器进行交互。TCP 是这种交互中最广泛使用的协议之一。Go 提供了一种方便的方式通过实现缓冲的Input/Output来读取传入连接数据,我们将在这个示例中介绍。

准备就绪…

由于我们已经在之前的示例中创建了一个 TCP 服务器,我们将更新它以从传入连接中读取数据。

如何做…

在这个示例中,我们将更新main()方法,调用handleRequest方法并传递连接对象以读取和打印服务器控制台上的数据。执行以下步骤:

  1. 创建tcp-server-read-data.go并复制以下内容:
package main
import 
(
  "bufio"
  "fmt"
  "log"
  "net"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  CONN_TYPE = "tcp"
)
func main() 
{
  listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
  if err != nil 
  {
    log.Fatal("Error starting tcp server : ", err)
  }
  defer listener.Close()
  log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
  for 
  {
    conn, err := listener.Accept()
    if err != nil 
    {
      log.Fatal("Error accepting: ", err.Error())
    }
    go handleRequest(conn)
  }
}
func handleRequest(conn net.Conn) 
{
  message, err := bufio.NewReader(conn).ReadString('\n')
  if err != nil 
  {
    fmt.Println("Error reading:", err.Error())
  }
  fmt.Print("Message Received from the client: ", string(message))
  conn.Close()
}
  1. 使用以下命令运行程序:
$ go run tcp-server-read-data.go

工作原理…

一旦我们运行程序,TCP 服务器将在本地端口8080上开始监听。从命令行执行echo命令将向 TCP 服务器发送消息:

$ echo -n "Hello to TCP server\n" | nc localhost 8080

这显然会将其记录到服务器控制台,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们理解这个示例中引入的变化:

  1. 首先,我们使用go关键字从main()方法中调用handleRequest,这意味着我们在 Goroutine 中调用函数,如下所示:
func main() 
{
  ...
  go handleRequest(conn)
  ...
}
  1. 接下来,我们定义了handleRequest函数,它将传入的连接读入缓冲区,直到第一个\n出现,并在控制台上打印消息。如果在读取消息时出现任何错误,则打印错误消息以及错误对象,最后关闭连接,如下所示:
func handleRequest(conn net.Conn) 
{
  message, err := bufio.NewReader(conn).ReadString('\n')
  if err != nil 
  {
    fmt.Println("Error reading:", err.Error())
  }
  fmt.Print("Message Received: ", string(message))
  conn.Close()
}

向 TCP 连接写入数据

在任何 Web 应用程序中,另一个常见且重要的情况是向客户端发送数据或响应客户端。Go 提供了一种方便的方式,以字节的形式在连接上写入消息,我们将在这个示例中介绍。

准备就绪…

由于我们已经在之前的示例中创建了一个 TCP 服务器,用于读取传入连接的数据,所以我们只需更新它以将消息写回客户端。

如何做…

在这个示例中,我们将更新程序中的handleRequest方法,以便向客户端写入数据。执行以下步骤:

  1. 创建tcp-server-write-data.go并复制以下内容:
package main
import 
(
  "bufio"
  "fmt"
  "log"
  "net"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  CONN_TYPE = "tcp"
)
func main() 
{
  listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
  if err != nil 
  {
    log.Fatal("Error starting tcp server : ", err)
  }
  defer listener.Close()
  log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
  for 
  {
    conn, err := listener.Accept()
    if err != nil 
    {
      log.Fatal("Error accepting: ", err.Error())
    }
    go handleRequest(conn)
  }
}
func handleRequest(conn net.Conn) 
{
  message, err := bufio.NewReader(conn).ReadString('\n')
  if err != nil 
  {
    fmt.Println("Error reading: ", err.Error())
  }
  fmt.Print("Message Received:", string(message))
  conn.Write([]byte(message + "\n"))
  conn.Close()
}
  1. 使用以下命令运行程序:
$ go run tcp-server-write-data.go

工作原理…

一旦我们运行程序,TCP 服务器将在本地端口8080上开始监听。从命令行执行echo命令,如下所示:

$ echo -n "Hello to TCP server\n" | nc localhost 8080

这将为我们提供来自服务器的以下响应:

Hello to TCP server

让我们看看我们在这个示例中引入的更改,以便向客户端写入数据。handleRequest中的一切都与上一个示例中完全相同,只是我们引入了一行新的代码,将数据作为字节数组写入连接,如下所示:

func handleRequest(conn net.Conn) 
{
  ...
  conn.Write([]byte(message + "\n"))
  ...
}

实现 HTTP 请求路由

大多数情况下,您必须在 Web 应用程序中定义多个 URL 路由,这涉及将 URL 路径映射到处理程序或资源。在这个示例中,我们将学习如何在 Go 中实现它。

如何做…

在这个示例中,我们将定义三个路由,如//login/logout,以及它们的处理程序。执行以下步骤:

  1. 创建http-server-basic-routing.go并复制以下内容:
package main
import 
(
  "fmt"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func login(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Login Page!")
}
func logout(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Logout Page!")
}
func main() 
{
  http.HandleFunc("/", helloWorld)
  http.HandleFunc("/login", login)
  http.HandleFunc("/logout", logout)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-server-basic-routing.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080,并且从浏览器或命令行访问http://localhost:8080/http://localhost:8080/loginhttp://localhost:8080/logout将呈现相应处理程序定义中的消息。例如,从命令行执行http://localhost:8080/,如下所示:

$ curl -X GET -i http://localhost:8080/

这将为我们提供来自服务器的以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们也可以从命令行执行http://localhost:8080/login,如下所示:

$ curl -X GET -i http://localhost:8080/login

这将为我们提供来自服务器的以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们编写的程序:

  1. 我们首先定义了三个处理程序或 Web 资源,如下所示:
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func login(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Login Page!")
}
func logout(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Logout Page!")
}

在这里,helloWorld处理程序在 HTTP 响应流上写入Hello World!。类似地,登录和注销处理程序在 HTTP 响应流上写入Login Page!Logout Page!

  1. 接下来,我们使用http.HandleFunc()DefaultServeMux上注册了三个 URL 路径——//login/logout。如果传入的请求 URL 模式与注册的路径之一匹配,那么相应的处理程序将被调用,并将(http.ResponseWriter*http.Request)作为参数传递给它,如下所示:
func main() 
{
  http.HandleFunc("/", helloWorld)
  http.HandleFunc("/login", login)
  http.HandleFunc("/logout", logout)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}

使用 Gorilla Mux 实现 HTTP 请求路由

Go 的net/http包为 HTTP 请求的 URL 路由提供了许多功能。它做得不太好的一件事是动态 URL 路由。幸运的是,我们可以通过gorilla/mux包实现这一点,我们将在这个示例中介绍。

如何做…

在这个示例中,我们将使用gorilla/mux来定义一些路由,就像我们在之前的示例中所做的那样,以及它们的处理程序或资源。正如我们在之前的示例中已经看到的,要使用外部包,首先我们必须使用go get命令安装包,或者我们必须手动将其复制到$GOPATH/src$GOPATH。我们在这个示例中也会这样做。执行以下步骤:

  1. 使用go get命令安装github.com/gorilla/mux,如下所示:
$ go get github.com/gorilla/mux
  1. 创建http-server-gorilla-mux-routing.go并复制以下内容:
package main
import 
(
  "net/http"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
var GetRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("Hello World!"))
  }
)
var PostRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("It's a Post Request!"))
  }
)
var PathVariableHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    vars := mux.Vars(r)
    name := vars["name"]
    w.Write([]byte("Hi " + name))
  }
)
func main() 
{
  router := mux.NewRouter()
  router.Handle("/", GetRequestHandler).Methods("GET")
  router.Handle("/post", PostRequestHandler).Methods("POST")
  router.Handle("/hello/{name}", 
  PathVariableHandler).Methods("GET", "PUT")
  http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}
  1. 使用以下命令运行程序:
$ go run http-server-gorilla-mux-routing.go

它是如何工作…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080,并且从浏览器或命令行访问http://localhost:8080/http://localhost:8080/posthttp://localhost:8080/hello/foo将产生相应处理程序定义中的消息。例如,从命令行执行http://localhost:8080/,如下所示:

$ curl -X GET -i http://localhost:8080/

这将为我们提供来自服务器的以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们也可以从命令行执行http://localhost:8080/hello/foo,如下所示:

$ curl -X GET -i http://localhost:8080/hello/foo

这将为我们提供来自服务器的以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们在这个示例中所做的代码更改:

  1. 首先,我们定义了GetRequestHandlerPostRequestHandler,它们只是在 HTTP 响应流上写入一条消息,如下所示:
var GetRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("Hello World!"))
  }
)
var PostRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("It's a Post Request!"))
  }
)
  1. 接下来,我们定义了PathVariableHandler,它提取请求路径变量,获取值,并将其写入 HTTP 响应流,如下所示:
var PathVariableHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    vars := mux.Vars(r)
    name := vars["name"]
    w.Write([]byte("Hi " + name))
  }
)
  1. 然后,我们将所有这些处理程序注册到gorilla/mux路由器中,并对其进行实例化,调用 mux 路由器的NewRouter()处理程序,如下所示:
func main() 
{
  router := mux.NewRouter()
  router.Handle("/", GetRequestHandler).Methods("GET")
  router.Handle("/post", PostCallHandler).Methods("POST")
  router.Handle("/hello/{name}", PathVariableHandler).
  Methods("GET", "PUT")
  http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}

记录 HTTP 请求

在故障排除 Web 应用程序时,记录 HTTP 请求总是很有用,因此记录具有适当消息和记录级别的请求/响应是一个好主意。Go 提供了log包,可以帮助我们在应用程序中实现日志记录。然而,在这个示例中,我们将使用 Gorilla 日志处理程序来实现它,因为该库提供了更多功能,比如记录 Apache Combined 日志格式和 Apache Common 日志格式,这些功能目前还不受 Go log包支持。

准备就绪…

由于我们已经在之前的示例中创建了一个 HTTP 服务器并使用 Gorilla Mux 定义了路由,我们将更新它以整合 Gorilla 日志处理程序。

如何做…

让我们使用 Gorilla 处理程序实现日志记录。执行以下步骤:

  1. 使用go get命令安装github.com/gorilla/handlergithub.com/gorilla/mux包,如下所示:
$ go get github.com/gorilla/handlers
$ go get github.com/gorilla/mux
  1. 创建http-server-request-logging.go并复制以下内容:
package main
import 
(
  "net/http"
  "os"
  "github.com/gorilla/handlers"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
var GetRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("Hello World!"))
  }
)
var PostRequestHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    w.Write([]byte("It's a Post Request!"))
  }
)
var PathVariableHandler = http.HandlerFunc
(
  func(w http.ResponseWriter, r *http.Request) 
  {
    vars := mux.Vars(r)
    name := vars["name"]
    w.Write([]byte("Hi " + name))
  }
)
func main() 
{
  router := mux.NewRouter()
  router.Handle("/", handlers.LoggingHandler(os.Stdout,
  http.HandlerFunc(GetRequestHandler))).Methods("GET")
  logFile, err := os.OpenFile("server.log",
  os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
  router.Handle("/post", handlers.LoggingHandler(logFile,
  PostRequestHandler)).Methods("POST")
  router.Handle("/hello/{name}",
  handlers.CombinedLoggingHandler(logFile,
  PathVariableHandler)).Methods("GET")
  http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}
  1. 运行程序,使用以下命令:
$ go run http-server-request-logging.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

从命令行执行GET请求,如下所示:

$ curl -X GET -i http://localhost:8080/

这将在 Apache Common 日志格式中记录请求的详细信息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们也可以从命令行执行http://localhost:8080/hello/foo,如下所示:

$ curl -X GET -i http://localhost:8080/hello/foo

这将在server.log中以 Apache Combined 日志格式记录请求的详细信息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解一下在这个示例中我们做了什么:

  1. 首先,我们导入了两个额外的包,一个是os,我们用它来打开一个文件。另一个是github.com/gorilla/handlers,我们用它来导入用于记录 HTTP 请求的日志处理程序,如下所示:
import ( "net/http" "os" "github.com/gorilla/handlers" "github.com/gorilla/mux" )
  1. 接下来,我们修改了main()方法。使用router.Handle("/", handlers.LoggingHandler(os.Stdout,

http.HandlerFunc(GetRequestHandler))).Methods("GET"),我们用 Gorilla 日志处理程序包装了GetRequestHandler,并将标准输出流作为写入器传递给它,这意味着我们只是要求在控制台上以 Apache Common 日志格式记录每个 URL 路径为/的请求。

  1. 接下来,我们以只写模式创建一个名为server.log的新文件,或者如果它已经存在,则打开它。如果有任何错误,那么记录下来并以状态码 1 退出,如下所示:
logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil 
{
  log.Fatal("error starting http server : ", err)
  return
}
  1. 使用router.Handle("/post", handlers.LoggingHandler(logFile, PostRequestHandler)).Methods("POST"),我们用 Gorilla 日志处理程序包装了GetRequestHandler,并将文件作为写入器传递给它,这意味着我们只是要求在名为/hello/{name}的文件中以 Apache Common 日志格式记录每个 URL 路径为/post的请求。

  2. 使用router.Handle("/hello/{name}", handlers.CombinedLoggingHandler(logFile, PathVariableHandler)).Methods("GET"),我们用 Gorilla 日志处理程序包装了GetRequestHandler,并将文件作为写入器传递给它,这意味着我们只是要求在名为server.log的文件中以 Apache Combined 日志格式记录每个 URL 路径为/hello/{name}的请求。

第二章:使用模板、静态文件和 HTML 表单

在本章中,我们将涵盖以下内容:

  • 创建您的第一个模板

  • 通过 HTTP 提供静态文件

  • 使用 Gorilla Mux 通过 HTTP 提供静态文件

  • 创建您的第一个 HTML 表单

  • 阅读您的第一个 HTML 表单

  • 验证您的第一个 HTML 表单

  • 上传您的第一个文件

介绍

我们经常希望创建 HTML 表单,以便以指定的格式从客户端获取信息,将文件或文件夹上传到服务器,并生成通用的 HTML 模板,而不是重复相同的静态文本。有了本章涵盖的概念知识,我们将能够在 Go 中高效地实现所有这些功能。

在本章中,我们将从创建基本模板开始,然后继续从文件系统中提供静态文件,如.js.cssimages,最终创建、读取和验证 HTML 表单,并将文件上传到服务器。

创建您的第一个模板

模板允许我们定义动态内容的占位符,可以由模板引擎在运行时替换为值。然后可以将它们转换为 HTML 文件并发送到客户端。在 Go 中创建模板非常容易,使用 Go 的html/template包,我们将在本示例中介绍。

如何做…

在这个示例中,我们将创建一个first-template.html,其中包含一些占位符,其值将在运行时由模板引擎注入。执行以下步骤:

  1. 通过执行以下 Unix 命令在templates目录中创建first-template.html
$ mkdir templates && cd templates && touch first-template.html
  1. 将以下内容复制到first-template.html中:
<html>
  <head>
    <meta charset="utf-8">
    <title>First Template</title>
    <link rel="stylesheet" href="/static/stylesheets/main.css">
  </head>
  <body>
    <h1>Hello {{.Name}}!</h1>
    Your Id is {{.Id}}
  </body>
</html>

上述模板有两个占位符,{{.Name}}{{.Id}},它们的值将由模板引擎在运行时替换或注入。

  1. 创建first-template.go,在其中我们将为占位符填充值,生成 HTML 输出,并将其写入客户端,如下所示:
import 
(
  "fmt"
  "html/template"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
type Person struct 
{
  Id   string
  Name string
}
func renderTemplate(w http.ResponseWriter, r *http.Request) 
{
  person := Person{Id: "1", Name: "Foo"}
  parsedTemplate, _ := template.ParseFiles("templates/
  first-template.html")
  err := parsedTemplate.Execute(w, person)
  if err != nil 
  {
    log.Printf("Error occurred while executing the template
    or writing its output : ", err)
    return
  }
}
func main() 
{
  http.HandleFunc("/", renderTemplate)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}

一切就绪后,目录结构应如下所示:

  1. 使用以下命令运行程序:
$ go run first-template.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080上启动。

浏览http://localhost:8080将显示模板引擎提供的 Hello Foo!,如下截图所示:

从命令行执行curl -X GET http://localhost:8080如下:

$ curl -X GET http://localhost:8080

这将导致服务器返回以下响应:

让我们了解我们编写的 Go 程序:

  • type Person struct { Id string Name string }: 在这里,我们定义了一个person结构类型,具有IdName字段。

类型定义中的字段名称应以大写字母开头;否则,将导致错误并且不会在模板中被替换。

接下来,我们定义了一个renderTemplate()处理程序,它执行了许多操作。

  • person := Person{Id: "1", Name: "Foo"}: 在这里,我们初始化了一个person结构类型,其中Id1NameFoo

  • parsedTemplate, _ := template.ParseFiles("templates/first-template.html"): 在这里,我们调用html/template包的ParseFiles,它创建一个新模板并解析我们传入的文件名,即templates目录中的first-template.html。生成的模板将具有输入文件的名称和内容。

  • err := parsedTemplate.Execute(w, person): 在这里,我们在解析的模板上调用Execute处理程序,它将person数据注入模板,生成 HTML 输出,并将其写入 HTTP 响应流。

  • if err != nil {log.Printf("Error occurred while executing the template or writing its output : ", err) return }: 在这里,我们检查执行模板或将其输出写入响应流时是否出现任何问题。如果有问题,我们将记录错误并以状态码 1 退出。

通过 HTTP 提供静态文件

在设计 Web 应用程序时,最好的做法是从文件系统或任何内容传递网络CDN)(如 Akamai 或 Amazon CloudFront)提供静态资源,例如.js.cssimages,而不是从 Web 服务器提供。这是因为所有这些类型的文件都是静态的,不需要处理;那么为什么我们要给服务器增加额外的负载呢?此外,它有助于提高应用程序的性能,因为所有对静态文件的请求都将从外部来源提供,并因此减少了对服务器的负载。

Go 的net/http包足以通过FileServer从文件系统中提供静态资源,我们将在本教程中介绍。

准备就绪…

由于我们已经在上一个教程中创建了一个模板,我们将扩展它以从static/css目录中提供静态.css文件。

如何做…

在本教程中,我们将创建一个文件服务器,它将从文件系统中提供静态资源。执行以下步骤:

  1. static/css目录中创建main.css,如下所示:
$ mkdir static && cd static && mkdir css && cd css && touch main.css
  1. 将以下内容复制到main.css中:
body {color: #00008B}
  1. 创建serve-static-files.go,在那里我们将创建FileServer,它将为所有带有/static的 URL 模式从文件系统中的static/css目录提供资源,如下所示:
package main
import 
(
  "fmt"
  "html/template"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
type Person struct 
{
  Name string
  Age string
}
func renderTemplate(w http.ResponseWriter, r *http.Request) 
{
  person := Person{Id: "1", Name: "Foo"}
  parsedTemplate, _ := template.ParseFiles("templates/
  first-template.html")
  err := parsedTemplate.Execute(w, person)
  if err != nil 
  {
    log.Printf("Error occurred while executing the template 
    or writing its output : ", err)
    return
  }
}
func main() 
{
  fileServer := http.FileServer(http.Dir("static"))
  http.Handle("/static/", http.StripPrefix("/static/", fileServer))
  http.HandleFunc("/", renderTemplate)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 更新first-template.html(在我们的上一个教程中创建)以包含来自文件系统中的static/css目录的main.css
<html>
  <head>
    <meta charset="utf-8">
    <title>First Template</title>
    <link rel="stylesheet" href="/static/css/main.css">
  </head>
  <body>
    <h1>Hello {{.Name}}!</h1>
    Your Id is {{.Id}}
  </body>
</html>

一切就绪后,目录结构应如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 使用以下命令运行程序:
$ go run serve-static-files.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080。浏览http://localhost:8080将显示与上一个教程中相同的输出,但是这次文本颜色已从默认的黑色更改为蓝色,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们查看 Chrome DevTools 的网络选项卡,我们可以看到main.css是从文件系统中的static/css目录加载的。

让我们了解我们在本教程的main()方法中引入的更改:

  • fileServer := http.FileServer(http.Dir("static")):在这里,我们使用net/http包的FileServer处理程序创建了一个文件服务器,它从文件系统中的static目录提供 HTTP 请求。

  • http.Handle("/static/", http.StripPrefix("/static/", fileServer)):在这里,我们使用net/http包的HandleFunchttp.StripPrefix("/static/", fileServer)处理程序注册到/staticURL 模式,这意味着每当我们访问带有/static模式的 HTTP URL 时,http.StripPrefix("/static/", fileServer)将被执行,并将(http.ResponseWriter, *http.Request)作为参数传递给它。

  • http.StripPrefix("/static/", fileServer):这将返回一个处理程序,通过从请求 URL 的路径中删除/static来提供 HTTP 请求,并调用文件服务器。StripPrefix通过用 HTTP 404 回复处理不以前缀开头的路径的请求。

使用 Gorilla Mux 通过 HTTP 提供静态文件

在上一个教程中,我们通过 Go 的 HTTP 文件服务器提供了static资源。在本教程中,我们将看看如何通过 Gorilla Mux 路由器提供它,这也是创建 HTTP 路由器的最常见方式之一。

准备就绪…

由于我们已经在上一个教程中创建了一个模板,该模板从文件系统中的static/css目录中提供main.css,因此我们将更新它以使用 Gorilla Mux 路由器。

如何做…

  1. 使用go get命令安装github.com/gorilla/mux包,如下所示:
$ go get github.com/gorilla/mux
  1. 创建serve-static-files-gorilla-mux.go,在那里我们将创建一个 Gorilla Mux 路由器,而不是 HTTPFileServer,如下所示:
package main
import 
(
  "html/template"
  "log"
  "net/http"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
type Person struct 
{
  Id string
  Name string
}
func renderTemplate(w http.ResponseWriter, r *http.Request) 
{
  person := Person{Id: "1", Name: "Foo"}
  parsedTemplate, _ := template.ParseFiles("templates/
  first-template.html")
  err := parsedTemplate.Execute(w, person)
  if err != nil 
  {
    log.Printf("Error occurred while executing the template 
    or writing its output : ", err)
    return
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/", renderTemplate).Methods("GET")
  router.PathPrefix("/").Handler(http.StripPrefix("/static",
  http.FileServer(http.Dir("static/"))))
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run serve-static-files-gorilla-mux.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080上启动。

浏览http://localhost:8080将显示与我们上一个示例中看到的相同的输出,如下屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们在本示例的main()方法中引入的更改:

  • router :=mux.NewRouter():在这里,我们调用mux路由器的NewRouter()处理程序实例化了gorilla/mux`路由器。

  • router.HandleFunc("/",renderTemplate).Methods("GET"):在这里,我们使用renderTemplate处理程序注册了/ URL 模式。这意味着renderTemplate将对每个 URL 模式为/`的请求执行。

  • router.PathPrefix("/").Handler(http.StripPrefix("/static", http.FileServer(http.Dir("static/"))):在这里,我们将/`注册为一个新的路由,并设置处理程序在调用时执行。

  • http.StripPrefix("/static", http.FileServer(http.Dir("static/"))):这返回一个处理程序,通过从请求 URL 的路径中删除/static并调用文件服务器来提供 HTTP 请求。StripPrefix`通过回复 HTTP 404 来处理不以前缀开头的路径的请求。

创建您的第一个 HTML 表单

每当我们想要从客户端收集数据并将其发送到服务器进行处理时,实现 HTML 表单是最佳选择。我们将在本示例中介绍这个。

如何做…

在本示例中,我们将创建一个简单的 HTML 表单,其中包含两个输入字段和一个提交表单的按钮。执行以下步骤:

  1. templates目录中创建login-form.html,如下所示:
$ mkdir templates && cd templates && touch login-form.html
  1. 将以下内容复制到login-form.html中:
<html>
  <head>
    <title>First Form</title>
  </head>
  <body>
    <h1>Login</h1>
    <form method="post" action="/login">
      <label for="username">Username</label>
      <input type="text" id="username" name="username">
      <label for="password">Password</label>
      <input type="password" id="password" name="password">
      <button type="submit">Login</button>
    </form>
  </body>
</html>

上述模板有两个文本框——用户名密码——以及一个登录按钮。

单击登录按钮后,客户端将对在 HTML 表单中定义的操作进行POST调用,我们的情况下是/login

  1. 创建html-form.go,在那里我们将解析表单模板并将其写入 HTTP 响应流,如下所示:
package main
import 
(
  "html/template"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func login(w http.ResponseWriter, r *http.Request) 
{
  parsedTemplate, _ := template.ParseFiles("templates/
  login-form.html")
  parsedTemplate.Execute(w, nil)
}
func main() 
{
  http.HandleFunc("/", login)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}

一切就绪后,目录结构应如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 使用以下命令运行程序:
$ go run html-form.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080上启动。浏览http://localhost:8080将显示一个 HTML 表单,如下屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们编写的程序:

  • func login(w http.ResponseWriter, r *http.Request) { parsedTemplate, _ := template.ParseFiles("templates/login-form.html") parsedTemplate.Execute(w, nil) }:这是一个接受ResponseWriterRequest作为输入参数的 Go 函数,解析login-form.html`并返回一个新模板。

  • http.HandleFunc("/", login):在这里,我们使用net/http包的HandleFunc将登录函数注册到/ URL 模式,这意味着每次访问/模式的 HTTP URL 时,登录函数都会被执行,传递ResponseWriterRequest`作为参数。

  • err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil):在这里,我们调用http.ListenAndServe来提供处理每个传入连接的 HTTP 请求的服务。ListenAndServe接受两个参数——服务器地址和处理程序——其中服务器地址为localhost:8080,处理程序为nil`。

  • if err != nil { log.Fatal("error starting http server : ", err) return}:在这里,我们检查是否启动服务器时出现问题。如果有问题,记录错误并以状态码1`退出。

阅读您的第一个 HTML 表单

一旦提交 HTML 表单,我们必须在服务器端读取客户端数据以采取适当的操作。我们将在本示例中介绍这个。

准备好…

由于我们已经在上一个示例中创建了一个 HTML 表单,我们只需扩展该示例以读取其字段值。

如何做…

  1. 使用以下命令安装github.com/gorilla/schema包:
$ go get github.com/gorilla/schema
  1. 创建html-form-read.go,在这里我们将使用github.com/gorilla/schema包解码 HTML 表单字段,并在 HTTP 响应流中写入 Hello,后跟用户名。
package main
import 
(
  "fmt"
  "html/template"
  "log"
  "net/http"
  "github.com/gorilla/schema"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
type User struct 
{
  Username string
  Password string
}
func readForm(r *http.Request) *User 
{
  r.ParseForm()
  user := new(User)
  decoder := schema.NewDecoder()
  decodeErr := decoder.Decode(user, r.PostForm)
  if decodeErr != nil 
  {
    log.Printf("error mapping parsed form data to struct : ",
    decodeErr)
  }
  return user
}
func login(w http.ResponseWriter, r *http.Request) 
{
  if r.Method == "GET" 
  {
    parsedTemplate, _ := template.ParseFiles("templates/
    login-form.html")
    parsedTemplate.Execute(w, nil)
  } 
  else 
  {
    user := readForm(r)
    fmt.Fprintf(w, "Hello "+user.Username+"!")
  }
}
func main() 
{
  http.HandleFunc("/", login)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run html-form-read.go

工作原理…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080。浏览http://localhost:8080将显示一个 HTML 表单,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一旦我们输入用户名和密码并单击登录按钮,我们将在服务器的响应中看到 Hello,后跟用户名,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解一下我们在这个配方中引入的更改:

  1. 使用import ("fmt" "html/template" "log" "net/http" "github.com/gorilla/schema"),我们导入了两个额外的包——fmtgithub.com/gorilla/schema——它们有助于将structsForm值相互转换。

  2. 接下来,我们定义了User struct类型,它具有UsernamePassword字段,如下所示:

type User struct 
{
  Username string
  Password string
}
  1. 接下来,我们定义了readForm处理程序,它以HTTP 请求作为输入参数,并返回User,如下所示:
func readForm(r *http.Request) *User {
 r.ParseForm()
 user := new(User)
 decoder := schema.NewDecoder()
 decodeErr := decoder.Decode(user, r.PostForm)
 if decodeErr != nil {
 log.Printf("error mapping parsed form data to struct : ", decodeErr)
 }
 return user
 }

让我们详细了解一下这个 Go 函数:

  • r.ParseForm(): 在这里,我们将请求体解析为一个表单,并将结果放入r.PostFormr.Form中。

  • user := new(User): 在这里,我们创建了一个新的User struct类型。

  • decoder := schema.NewDecoder(): 在这里,我们正在创建一个解码器,我们将使用它来用Form值填充一个用户struct

  • decodeErr := decoder.Decode(user, r.PostForm): 在这里,我们将从POST体参数中解码解析的表单数据到一个用户struct中。

r.PostForm只有在调用ParseForm之后才可用。

  • if decodeErr != nil { log.Printf("error mapping parsed form data to struct : ", decodeErr) }: 在这里,我们检查是否有任何将表单数据映射到结构体的问题。如果有,就记录下来。

然后,我们定义了一个login处理程序,它检查调用处理程序的 HTTP 请求是否是GET请求,然后从模板目录中解析login-form.html并将其写入 HTTP 响应流;否则,它调用readForm处理程序,如下所示:

func login(w http.ResponseWriter, r *http.Request) 
{
  if r.Method == "GET" 
  {
    parsedTemplate, _ := template.ParseFiles("templates/
    login-form.html")
    parsedTemplate.Execute(w, nil)
  } 
  else 
  {
    user := readForm(r)
    fmt.Fprintf(w, "Hello "+user.Username+"!")
  }
}

验证您的第一个 HTML 表单

大多数情况下,我们在处理客户端输入之前必须对其进行验证,这可以通过 Go 中的许多外部包来实现,例如gopkg.in/go-playground/validator.v9gopkg.in/validator.v2github.com/asaskevich/govalidator

在这个配方中,我们将使用最著名和常用的验证器github.com/asaskevich/govalidator来验证我们的 HTML 表单。

准备工作…

由于我们已经在上一个配方中创建并读取了一个 HTML 表单,我们只需扩展它以验证其字段值。

如何做…

  1. 使用以下命令安装github.com/asaskevich/govalidatorgithub.com/gorilla/schema包:
$ go get github.com/asaskevich/govalidator
$ go get github.com/gorilla/schema
  1. 创建html-form-validation.go,在这里我们将读取一个 HTML 表单,使用github.com/gorilla/schema对其进行解码,并使用github.com/asaskevich/govalidator对其每个字段进行验证,验证标签定义在User struct中。
package main
import 
(
  "fmt"
  "html/template"
  "log"
  "net/http"
  "github.com/asaskevich/govalidator"
  "github.com/gorilla/schema"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  USERNAME_ERROR_MESSAGE = "Please enter a valid Username"
  PASSWORD_ERROR_MESSAGE = "Please enter a valid Password"
  GENERIC_ERROR_MESSAGE = "Validation Error"
)
type User struct 
{
  Username string `valid:"alpha,required"`
  Password string `valid:"alpha,required"`
}
func readForm(r *http.Request) *User 
{
  r.ParseForm()
  user := new(User)
  decoder := schema.NewDecoder()
  decodeErr := decoder.Decode(user, r.PostForm)
  if decodeErr != nil 
  {
    log.Printf("error mapping parsed form data to struct : ",
    decodeErr)
  }
  return user
}
func validateUser(w http.ResponseWriter, r *http.Request, user *User) (bool, string) 
{
  valid, validationError := govalidator.ValidateStruct(user)
  if !valid 
  {
    usernameError := govalidator.ErrorByField(validationError,
    "Username")
    passwordError := govalidator.ErrorByField(validationError,
    "Password")
    if usernameError != "" 
    {
      log.Printf("username validation error : ", usernameError)
      return valid, USERNAME_ERROR_MESSAGE
    }
    if passwordError != "" 
    {
      log.Printf("password validation error : ", passwordError)
      return valid, PASSWORD_ERROR_MESSAGE
    }
  }
  return valid, GENERIC_ERROR_MESSAGE
}
func login(w http.ResponseWriter, r *http.Request) 
{
  if r.Method == "GET" 
  {
    parsedTemplate, _ := template.ParseFiles("templates/
    login-form.html")
    parsedTemplate.Execute(w, nil)
  } 
  else 
  {
    user := readForm(r)
    valid, validationErrorMessage := validateUser(w, r, user)
    if !valid 
    {
      fmt.Fprintf(w, validationErrorMessage)
      return
    }
    fmt.Fprintf(w, "Hello "+user.Username+"!")
  }
}
func main() 
{
  http.HandleFunc("/", login)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run html-form-validation.go

工作原理…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080。浏览http://localhost:8080将显示一个 HTML 表单,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后提交具有有效值的表单:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它将在浏览器屏幕上显示 Hello,后跟用户名,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在任何字段中提交值为非字母的表单将显示错误消息。例如,提交用户名值为1234的表单:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它将在浏览器上显示错误消息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此外,我们可以从命令行提交 HTML 表单,如下所示:

$ curl --data "username=Foo&password=password" http://localhost:8080/

这将给我们在浏览器中得到的相同输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解一下我们在这个示例中引入的更改:

  1. 使用import ("fmt", "html/template", "log", "net/http" "github.com/asaskevich/govalidator" "github.com/gorilla/schema" ),我们导入了一个额外的包——github.com/asaskevich/govalidator,它可以帮助我们验证结构。

  2. 接下来,我们更新了User struct类型,包括一个字符串字面标签,keyvalidvaluealpha, required,如下所示:

type User struct 
{
  Username string `valid:"alpha,required"`
  Password string 
  valid:"alpha,required"
}
  1. 接下来,我们定义了一个validateUser处理程序,它接受ResponseWriterRequestUser作为输入,并返回boolstring,分别是结构的有效状态和验证错误消息。在这个处理程序中,我们调用govalidatorValidateStruct处理程序来验证结构标签。如果在验证字段时出现错误,我们将调用govalidatorErrorByField处理程序来获取错误,并将结果与验证错误消息一起返回。

  2. 接下来,我们更新了login处理程序,调用validateUser并将(w http.ResponseWriter, r *http.Request, user *User)作为输入参数传递给它,并检查是否有任何验证错误。如果有错误,我们将在 HTTP 响应流中写入错误消息并返回它。

上传您的第一个文件

在任何 Web 应用程序中,最常见的情景之一就是上传文件或文件夹到服务器。例如,如果我们正在开发一个求职门户网站,那么我们可能需要提供一个选项,申请人可以上传他们的个人资料/简历,或者,比如说,我们需要开发一个电子商务网站,其中客户可以使用文件批量上传他们的订单。

在 Go 中实现上传文件的功能非常容易,使用其内置的包,我们将在本示例中进行介绍。

如何做…

在这个示例中,我们将创建一个带有file类型字段的 HTML 表单,允许用户选择一个或多个文件通过表单提交上传到服务器。执行以下步骤:

  1. templates目录中创建upload-file.html,如下所示:
$ mkdir templates && cd templates && touch upload-file.html
  1. 将以下内容复制到upload-file.html中:
<html>
  <head>
    <meta charset="utf-8">
    <title>File Upload</title>
  </head>
  <body>
    <form action="/upload" method="post" enctype="multipart/
    form-data">
      <label for="file">File:</label>
      <input type="file" name="file" id="file">
      <input type="submit" name="submit" value="Submit">
    </form>
  </body>
</html>

在前面的模板中,我们定义了一个file类型的字段,以及一个Submit按钮。

点击“提交”按钮后,客户端将对请求的主体进行编码,并对表单操作进行POST调用,这在我们的情况下是/upload

  1. 创建upload-file.go,在其中我们将定义处理程序来渲染文件上传模板,从请求中获取文件,处理它,并将响应写入 HTTP 响应流,如下所示:
package main
import 
(
  "fmt"
  "html/template"
  "io"
  "log"
  "net/http"
  "os"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func fileHandler(w http.ResponseWriter, r *http.Request) 
{
  file, header, err := r.FormFile("file")
  if err != nil 
  {
    log.Printf("error getting a file for the provided form key : ",
    err)
    return
  }
  defer file.Close()
  out, pathError := os.Create("/tmp/uploadedFile")
  if pathError != nil 
  {
    log.Printf("error creating a file for writing : ", pathError)
    return
  }
  defer out.Close()
  _, copyFileError := io.Copy(out, file)
  if copyFileError != nil 
  {
    log.Printf("error occurred while file copy : ", copyFileError)
  }
  fmt.Fprintf(w, "File uploaded successfully : "+header.Filename)
}
func index(w http.ResponseWriter, r *http.Request) 
{
  parsedTemplate, _ := template.ParseFiles("templates/
  upload-file.html")
  parsedTemplate.Execute(w, nil)
}
func main() 
{
  http.HandleFunc("/", index)
  http.HandleFunc("/upload", fileHandler)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}

一切就绪后,目录结构应该如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 使用以下命令运行程序:
$ go run upload-file.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080。浏览http://localhost:8080将会显示文件上传表单,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在选择文件后按下“提交”按钮将会在服务器上创建一个名为uploadedFile的文件,位于/tmp目录中。您可以通过执行以下命令来查看:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此外,成功上传将在浏览器上显示消息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解一下我们编写的 Go 程序:

我们定义了fileHandler()处理程序,它从请求中获取文件,读取其内容,最终将其写入服务器上的文件。由于这个处理程序做了很多事情,让我们逐步详细介绍一下:

  • file, header, err := r.FormFile("file"): 在这里,我们调用 HTTP 请求的FormFile处理程序,以获取提供的表单键对应的文件。

  • if err != nil { log.Printf("error getting a file for the provided form key : ", err) return }: 在这里,我们检查是否在从请求中获取文件时出现了任何问题。如果有问题,记录错误并以状态码1退出。

  • defer file.Close(): defer语句会在函数返回时关闭file

  • out, pathError := os.Create("/tmp/uploadedFile"): 在这里,我们创建了一个名为uploadedFile的文件,放在/tmp目录下,权限为666,这意味着客户端可以读写但不能执行该文件。

  • if pathError != nil { log.Printf("error creating a file for writing : ", pathError) return }: 在这里,我们检查在服务器上创建文件时是否出现了任何问题。如果有问题,记录错误并以状态码1退出。

  • _, copyFileError := io.Copy(out, file): 在这里,我们将从接收到的文件中的内容复制到/tmp目录下创建的文件中。

  • fmt.Fprintf(w, "File uploaded successfully : "+header.Filename): 在这里,我们向 HTTP 响应流写入一条消息和文件名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值