《Go Web 编程》读书笔记
GO Web 编程
Go 语言介绍
第一章:Go与web应用
HTTP
- HTTP 是一种无状态、由文本构成的请求-响应(request-response)协议,这种协议使用的是客户端-服务器(client-server)计算模型
- CGI 通用网管接口(Common Gateway Interface),允许web服务器与一个独立运行于web服务器进程之外的进程对接
- SSI(server-side includes)服务器端,允许开发者在HTML文件里包含一些指令,衍生出了JSP(Java Server Pages),ASP(Active Server Pages)等Web模板引擎
- http请求
- 请求行(request-line)
- 零个或任意多个请求首部(header)
- 一个空行
- 可选的报文主体(body)
- 请求方法
- GET
- POST
- HEAD
- PUT
- DELETE
- TRACE
- OPTIONS
- CONNECT
- PATCH
- http响应
- 一个状态行
- 零个或任意数量的响应首部
- 一个空行
- 一个可选的报文主体
- 响应状态码
- 1XX 情报状态码
- 2XX 成功状态码
- 3XX 重定向状态码
- 4XX 客户端错误状态码
- 5XX 服务器错误状态码
- 响应首部
- Allow
- Content-Length
- Content-Type
- Date
- Location
- Server
- Set-Cookie
- WWW-Authenticate
URI
- URI一般格式为:
<方案名称>:<分层部分>[ ? <查询参数>] [ # <片段>]
- 因每个URL都是一个单独的字符串,所以URL里不能包含空格,?和#这些符号也不能做其它用途,我们需要用URL编码(百分号编码)对这些字符进行转换,做法是**将该字符在ASCII码中的字节值转换为16进制,并在前面加上%**,例如空格就被转换为%20。
处理器
Web应用中的处理器出了要接收和处理客户端发来的请求,还需要调用模板引擎,然后由模板引擎生成HTML并把数据填充至将要回传给客户端的响应报文中
模板引擎(template engine)
- 静态模板
- 动态模板
第二章:ChitChat论坛
- 请求的接收和处理是所有 Web 应用的核心。
- 多路复用器会将 HTTP 请求重定向到正确的处理器进行处理,针对静态文件的请求也是如此。
- 处理器函数是一种接受 ResponseWriter 和 Request 指针作为参数的 Go 函数。
- cookie 可以用作一种访问控制机制。
- 对模板文件以及数据进行语法分析会产生相应的 HTML, 这些 HTML 会被用作返回给浏览器的响应数据。
- 通过使用 sql 包以及相应的 SQL 语句,用户可以将数据持久地存储在关系数据库中。
第三章:接收请求
net/http标准库
net/http 标准库通常包括两个部分,客户端和服务器,我们可以通过ListenAndServe创建一个简陋的服务器
1 | package main |
这会使用默认的80端口进行网络连接,并且使用默认的多路复用器DefaultServeMux,我们也可以通过Server结构进行更详细的配置
1 | func main() { |
处理器和处理函数
处理器
前面的代码会返回404响应,因为我们还没有为请求编写相应的处理器。一个处理器就是一个拥有ServeHTTP方法的接口
1 | type Handler interface { |
我们通过实现这个接口来编写处理器
1 | type MyHandler struct{} |
我们可以设置多个处理器
1 | type HelloHandler struct{} |
我们看一下Handle函数再源码中的定义
1 | func Handle(pattern string, handler Handler) { |
实际上是在调用DefaultServeMux的某个方法,前面我们已经提到过了DefaultServeMux是个默认多路复用器,实际上它也是个Handler处理器,因为他是ServeMux结构的一个实例,而ServeMux也实现了Handler接口的ServeHTTP方法。这样就可以对不同的请求做出不同的响应。
处理器函数
处理器函数是与处理器拥有同样行为的函数,它们与ServeHTTP拥有同样的函数签名。
1 | func hello (w http.ResponseWriter, r *http.Request) { |
HandleFunc是Go语言拥有的一种函数类型,它可以把一个带有正确签名的f转换为带有方法f的handler。
来看看HandleFunc的源码
1 | func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { |
Handle函数是不是似曾相识!
串联多个处理器和处理器函数
1 | func hello(w *http.ResponseWriter, r *Request) { |
ServeMux
ServeMux是一个HTTP请求多路复用器,负责接收HTTP请求并根据请求中的URL将请求重定向到正确的处理器。ServeMux包含一个映射,这个映射会将URL映射至相应的处理器。值得一提的是,ServeMux无法使用变量实现URL模式匹配,因此必要时我们完全可以用其它自建的多路复用器来代替ServeMux,如HttpRouter等高效轻量的第三方复用器
第四章:处理请求
请求和响应
Request结构
- URL字段
- Header字段
- Body字段
- Form, PostForm, MultipartForm字段
在处理器函数中,我们可以通过Request获取各个字段的详细信息
1 | func get_request_value(w *http.ResponseWriter, r *http.Request) { |
Go与HTML表单
用户在表单中输入的数据会以键值对的形式记录在请求的主体中,其中表单中的enctype属性决定了以何种形式发送键值对。默认属性值为application/x-www-form-urlencoded,这个属性会把表单中的数据编码一个连续的长查询字符串,另一种编码方式为multipart/form-data,表单中的数据会被转换为一条MIME报文,每个键值对都构成了这个报文的一部分。简单来说,当表单只需要传送简单数据时,默认编码更加简单,高效;而当表单需要传输大量数据(如文件)时,使用后一种编码方式会更好。 有些时候,用户可以通过Base64编码,以文本方式传送二进制数据。
使用Request结构获取表单数据的一般步骤是:
- 调用ParseForm或者ParseMultipartForm方法进行语法分析
- 访问Form, PostForm, MultipartForm等字段获取数据
1 | func get_form_data1(w http.ResponseWriter, r *http.Request) { |
当使用multipart/form-data编码时,表单数据会被存储到MultipartForm字段中
1 | func get_form_data2(w http.ResponseWriter, r *http.Request) { |
我们也可以使用FormValue或者PostFormValue快速获取表单值,也两个方法会自动调用ParseForm或者ParseMultipartForm方法,其中PostFormValue只会返回表单键值对而不会返回URL键值对
使用FormFile方法可以快速的获取被上传的文件
1 | func process(w http.ResponseWriter, r *http.Request) { |
ResponseWriter
- Write 接收一个字节数组,并写入到HTTP响应主体中
- WriteHeader 改变HTTP响应状态码
- Header 修改HTTP响应首部
1 | package main |
cookie
Go与cookie
响应头部没有设置Expires字段的通常被成为会话cookie,浏览器关闭或刷新cookie就会消失,设置了Expires字段的通常被称为持久cookie,在过期时间之前会一直存在。
1 | func set_cookie(w http.ResponseWriter, r *http.Request) { |
cookie实现闪现消息
1 | func setMessage(w http.ResponseWriter, r *http.Request) { |
第五章:内容展示
- 在 Web 应用中,模板引擎会把模板和数据进行合并,生成将要返回给客户端的 HTML。
- Go 的标准模板引擎定义在 html/template 包当中。
- Go 模板引擎的工作方式就是对一个模板进行语法分析,接着在执行这个模板的时候,将一个ResponseWriter 以及一些数据传递给它。被调用的模板引擎会对传入的已分析模板以及数据进行合并,然后把合并的结果传递给 ResponseWriter。
- Go 的模板拥有一系列丰富多样并且威力强大的动作,这些动作就是一系列命令,它们可以告诉模板应该以何种方式与数据合并。
- 除了动作之外,模板还可以包含参数、管道和变量:其中参数用于表示模板中的数据值,管道用于串联起多个参数和函数,至于变量则会作为动作的组件而存在。
- Go 拥有一系列受限的模板函数。此外,通过创建一个函数映射并将它与模板进行绑定,用户也可以创建出自己的模板函数。
- Go 的模板引擎可以根据数据所在的位置改变数据的显示方式,这种上下文感知特性能够有效地防御 XSS 攻击。
- 人们在设计一个拥有一致外观和使用感受的 Web 应用时,常常会用到 Web 布局,Go可以使用嵌套模板来实现 Web 布局。
第六章:存储数据
- 通过使用结构将数据存储在内存里面,以此来构建数据缓存机制并提高响应速度。
- 通过使用 CSV 或者 gob 二进制格式将数据存储在文件里面,可以对用户提交的文件进行处理,或者为缓存数据提供备份。
- 通过使用 database/sql 包,可以对关系数据库执行 CRUD 操作,并在不同的数据之间建立起相应的关系。
- 通过 Sqlx 和 Gorm 这样的第三方数据访问库,可以使用威力更强大的工具去操纵数据库中的数据。
第七章:Go Web 服务
- 编写 Web 服务是 Go 语言目前非常常见的用途之一,了解如何构建 Web 服务是一项非常有价值的技能。
- Web 服务主要分为两种类型——一种是基于 SOAP 的 Web 服务,而另一种则是基于 REST 的 Web 服务
- SOAP 是一种协议,它能够对定义在 XML 中的结构化数据进行交换。但是,因为 SOAP 的 WSDL 报文有可能会变得非常复杂,所以基于 SOAP 的 Web 服务没有基于 REST 的 Web 服务那么流行。
- 基于 REST 的 Web 服务通过 HTTP 协议向外界公开自己拥有的资源,并允许外界通过 HTTP 协议对这些资源执行指定的动作。
- 创建和分析 XML 以及 JSON 的步骤都是相似的,用户要么根据指定的结构去生成 XML 或者JSON,要么从指定的结构里面提取数据到 XML 或者 JSON 里面,前一种操作称为封装,而后一种操作则称为解封。
第八章:应用测试
- Go 通过 go test 命令为用户提供了内置的测试工具,并提供了 testing 包以便实现单元测试。
- testing 包提供了基本的功能测试以及基准测试能力。
- 对于 Go 语言来说,Web 应用的单元测试可以通过 testing/httptest 包来完成。
- 使用测试替身可以让测试用例变得更加独立
- 实现测试替身的一种方法是使用依赖注入设计模式。
- Go 语言拥有许多第三方测试库,其中包括对 Go 的测试功能进行扩展的 Gocheck 包,以及实现了行为驱动测试的 Ginkgo 包。
第九章:发挥 Go 的并发优势
- Go web 服务器本身是并发的,服务器会把接收到的每条请求都放到独立的 goroutine 里运行。
- 并发和并行是两个相辅相成的概念,但它们并不相同。并发指的是两个或多个任务在同一时间段内启动、运行和结束,并且这些任务可能会彼此互动,而并行则是单纯地同时运行多个任务。
- Go 通过 goroutine 和通道这两个重要的特性直接支持并发,但 Go 并不直接支特并行。
- goroutine 用于编写并发程序,而通道则用于为不同的 goroutine 之间提供通信功能。
- 无缓冲通道都是同步的,尝试向一个已经包含数据的无缓冲通道推入新的数据将被阻塞;但是,有缓冲通道在被填满之前都是异步的。
- select 语句可以以先到先服务的方式,从多个通道里选出一个已经准备好执行接收操作的通道
- WaitGroup 同样可以用于对多个通道进行同步。
- 并发程序的性能一般都会比相应的非并发程序要高,而具体提升多少则取决于所使用的算法(即使在只使用一个 CPU 的情况下,也是如此)。
- 在条件允许的情况下,并发的 Web 应用将自动地获得并行带来的优势。
第十章:Go 的部署
- 部署 Go Web 服务最简单的方法就是直接将二进制可执行文件放置到服务器里面(这个服务器可以是虚拟机,也可以是实际存在的服务器),然后通过配置 Upstart 来保证服务可以随系统启动并持续地运行下去。
- Docker 是一种最近开始崭露头角并且威力强大的 Web 服务和 Web 应用部署方式。用户首先需要将被部署的 Go Web服务 Docker 化为容器,然后才能在本地 Docker 宿主或者云端的远程 Docker 宿主上部署这个容器。