Go 是一门刚开始崭露头角的语言,它是为了让人们能够简单而高效地编写后端系 统而创建的。这门语言拥有众多的先进特性,如函数式编程方面的特性、内置了对并发 编程的支持、现代化的包管理系统、垃圾收集特性、以及一些包罗万象威力强大的标准 库,而且如果需要我们还可以引入第三方开源库。 使用 Go 语言进行 Web 开发正变得日益流行,很多大公司都在使用,如 Google、 Facebook、腾讯、百度、阿里巴巴、京东、小米以及 360、美团、滴滴以及新浪等。
a) 方式一(建议使用):在 webapp 目录中右键→在命令提示符中打开 执行 go build main.go 命令;然后在当前目录中就会生成一个 main.exe 的 二进制可执行文件;最后再执行 ./main.exe 就可以启动服务器
b) 方式二:在 webapp 目录中右键→在命令提示符中打开 执行 go install webapp 命令;然后在 bin 目录中会生成一个 webapp.exe 的二进制可执行文件;进入 bin 目录之后再 bin 目录中执行 ./webapp.exe 就 可以启动服务器
/hello
Go 提供了一系列用于创建 Web 服务器的标准库,而且通过 Go 创建一个服务器的 步骤非常简单,只要通过 net/http 包调用 ListenAndServe 函数并传入网络地址以及负 责处理请求的处理器( handler )作为参数就可以了。如果网络地址参数为空字符串,那 么服务器默认使用 80 端口进行网络连接;如果处理器参数为 nil,那么服务器将使用默 认的多路复用器 DefaultServeMux,当然,我们也可以通过调用 NewServeMux 函数创 建一个多路复用器。多路复用器接收到用户的请求之后根据请求的 URL 来判断使用哪 个处理器来处理请求,找到后就会重定向到对应的处理器来处理请求,
package main
import (
"fmt" "net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "正在通过处理器函数处理你的请求")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "正在通过处理器处理你的请求")
}
func main() {
myHandler := MyHandler{}
//调用处理器
http.Handle("/", &myHandler)
http.ListenAndServe(":8080", nil)
}
Handle 方法的说明
关于处理器 Handler 的说明
也就是说只要某个结构体实现了 Handler 接口中的 ServeHTTP 方法那么它就是一个处理
我们还可以通过 Server 结构对服务器进行更详细的配置
package main
import (
"fmt"
"net/http"
"time"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "测试通过 Server 结构详细配置服务器")
}
func main() {
myHandler := MyHandler{}
//通过 Server 结构对服务器进行更详细的配置
server := http.Server{
Addr: ":8080",
Handler: &myHandler,
ReadTimeout: 2 * time.Second,
}
server.ListenAndServe()
}
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "通过自己创建的多路复用器来处理请求")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/myMux", handler)
http.ListenAndServe(":8080", mux)
}
结构体 ServeMux
结构体 ServeMux 的相关方法
因为编写 Web 应用必须对 HTTP 有所了解,所以接下来我们对 HTTP 进行介绍。
3.1 HTTP 协议简介
HTTP 超文本传输协议 (HTTP-Hypertext transfer protocol),是一个属于应用层的
面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于 1990
年提出,经过几年的使用与发展,得到不断地完善和扩展。它是一种详细规定了浏览器
和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。
客户端与服务端通信时传输的内容我们称之为报文
HTTP 就是一个通信规则,这个规则规定了客户端发送给服务器的报文格式,也规
定了服务器发送给客户端的报文格式。实际我们要学习的就是这两种报文。客户端发送
给服务器的称为”请求报文“,服务器发送给客户端的称为”响应报文“。
超文本传输协议的前身是世外桃源(Xanadu)项目,超文本的概念是泰德˙纳尔森(Ted Nelson)在 1960 年代提出的。进入哈佛大学后,纳尔森一直致力于超文本协议和该项目 的研究,但他从未公开发表过资料。1989 年,蒂姆˙伯纳斯˙李(Tim Berners Lee)在 CERN(欧 洲原子核研究委员会 = European Organization for Nuclear Research)担任软件咨询师 的时候,开发了一套程序,奠定了万维网(WWW = World Wide Web)的基础。1990 年 12 月,超文本在 CERN 首次上线。1991 年夏天,继 Telnet 等协议之后,超文本转移协 议成为互联网诸多协议的一分子。 当时,Telnet 协议解决了一台计算机和另外一台计算机之间一对一的控制型通信的 要求。邮件协议解决了一个发件人向少量人员发送信息的通信要求。文件传输协议解决 一台计算机从另外一台计算机批量获取文件的通信要求,但是它不具备一边获取文件一 边显示文件或对文件进行某种处理的功能。新闻传输协议解决了一对多新闻广播的通信 要求。而超文本要解决的通信要求是:在一台计算机上获取并显示存放在多台计算机里 的文本、数据、图片和其他类型的文件;它包含两大部分:超文本转移协议和超文本标 记语言(HTML)。HTTP、HTML 以及浏览器的诞生给互联网的普及带来了飞跃。
3.4 HTTP1.0 和 HTTP1.1 的区别
在 HTTP1.0 版本中,浏览器请求一个带有图片的网页,会由于下载图片而与服务器
之间开启一个新的连接;但在 HTTP1.1 版本中,允许浏览器在拿到当前请求对应的全部
资源后再断开连接,提高了效率。
HTTP 1.1 是目前使用最为广泛的一个版本,而最新的一个版本则是 HTTP 2.0,又称 HTTP/2。在开放互联网上 HTTP 2.0 将只用于 https://网址。HTTPS,即 SSL(Secure Socket Layer,安全套接字层)之上的 HTTP,实际上就是在 SSL/TLS 连接的上层进行 HTTP通信。
备注:SSL 最初由 Netscape 公司开发,之后由 IETF(Internet Engineering Task Force,
互联网工程任务组)接手并将其改名为 TLS(Transport Layer Security,传输层安全协议)
请求首行(请求行); 请求头信息(请求头); 空行 请求体;
GET /Hello/index.jsp HTTP/1.1 Accept: */* Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E) Accept-Encoding: gzip, deflate Host: localhost:8080 Connection: Keep-Alive Cookie: JSESSIONID=C55836CDA892D9124C03CF8FE8311B15
POST /Hello/target.html
HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */* Referer: http://localhost:8080/Hello/ Accept-Language: zh-CN
响应首行(响应行); 响应头信息(响应头); 空行; 响应体;
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=UTF-8 Content-Length: 274 Date: Tue, 07 Apr 2015 10:08:26 GMT
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" > <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Hello</h1> </body> </html>
import ( "database/sql" _ "github.com/go-sql-driver/mysql" )
var ( Db *sql.DB err error )
func init() {
Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/test")
if err != nil {
panic(err.Error())
}
}
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) UNIQUE NOT NULL,
PASSWORD VARCHAR(100) NOT NULL,
email VARCHAR(100)
)import ( "fmt"
"webproject/utils"
)
type User struct {
ID int
Username string
Password string
Email string
}func (user *User) AddUser() error { // 写 sql 语句
sqlStr := "insert into users(username , password , email) values(?,?,?)"
//预编译
stmt, err := utils.Db.Prepare(sqlStr)
if err != nil {
fmt.Println("预编译出现异常:", err) return err
}
//执行
_, erro := stmt.Exec(user.Username, user.Password, user.Email)
if erro != nil {
fmt.Println("执行出现异常:", erro) return erro
}
return nil
}
l Prepare 方法的说明
l Stmt 结构体及它的方法的说明
func (user *User) AddUser() error {
// 写 sql 语句
sqlStr := "insert into users(username , password , email) values(?,?,?)"
//执行
_, erro := utils.Db.Exec(sqlStr, user.Username, user.Password, user.Email)
if erro != nil {
fmt.Println("执行出现异常:", erro) return erro
}
return nil
}
l 请同学们仿照着添加操作将删除和更新完成
4.2 单元测试
4.3.1 简介
顾名思义,单元测试( unit test),就是一种为验证单元的正确性而设置的自动化测
试,一个单元就是程序中的一个模块化部分。一般来说,一个单元通常会与程序中的一
个函数或者一个方法相对应,但这并不是必须的。Go 的单元测试需要用到 testing 包
以及 go test 命令,而且对测试文件也有以下要求
1) 被测试的源文件和测试文件必须位于同一个包下
2) 测试文件必须要以_test.go 结尾
l 虽然 Go 对测试文件_test.go 的前缀没有强制要求,不过一般我们都设置为被测试的文件的文件名,如:要对 user.go 进行测试,那么测试文件的
名字我们通常设置为 user_test.go
3) 测试文件中的测试函数为 TestXxx(t *testing.T)
l 其中 Xxx 的首字母必须是大写的英文字母
l 函数参数必须是 test.T 的指针类型(如果是 Benchmark 测试则参数是 testing.B 的指针类型)
func TestAddUser(t *testing.T) {
fmt.Println("测试添加用户:")
user := &User{
Username: "admin3",
Password: "123456",
Email: "admin3@qq.com",
}
//将 user 添加到数据库中 user.AddUser()
} func TestUser(t *testing.T) {
t.Run("正在测试添加用户:", testAddUser)
t.Run("正在测试获取一个用户:", testGetUserById)
}
//子测试函数
func testAddUser(t *testing.T) {
fmt.Println("测试添加用户:")
user := &User{
Username: "admin5",
Password: "123456",
Email: "admin5@qq.com",
}
//将 user 添加到数据库中
user.AddUser()
}
//子测试函数
func testGetUserByID(t *testing.T) { u := &User{}
user, _ := u.GetUserByID(1)
fmt.Println("用户的信息是:", *user)
}
func TestMain(m *testing.M) {
fmt.Println("开始测试")
m.Run()
}
func TestUser(t *testing.T) {
t.Run("正在测试添加用户:", testAddUser)
}
func testAddUser(t *testing.T) {
fmt.Println("测试添加用户:")
user := &User{
Username: "admin5",
Password: "123456",
Email: "admin5@qq.com",
}
//将 user 添加到数据库中
user.AddUser()
}
l Main 的说明
4.4 获取一条记录
ü 根据用户的 id 从数据库中获取一条记录
func (user *User) GetUserByID(userId int) (*User, error) {
//写 sql 语句
sqlStr := "select id , username , password , email from users where id = ?"
//执行 sql
row := utils.Db.QueryRow(sqlStr, userId)
//声明三个变量
var username string
var password string
var email string
//将各个字段中的值读到以上三个变量中
err := row.Scan(&userId, &username, &password, &email)
if err != nil {
return nil, err
}
//将三个变量的值赋给 User 结构体
u := &User{
ID: userId,
Username: username,
Password: password,
Email: email,
}
return u, nil
}
4.5 获取多条记录
ü 从数据库中查询出所有的记录
ü 代码实现:
l Rows 结构体说明
l Next 方法和 Scan 方法说明
Go 语言的 net/http 包提供了一系列用于表示 HTTP 报文的结构,我们可以使用它
处理请求和发送相应,其中 Request 结构代表了客户端发送的请求报文,下面让我们看
一下 Request 结构体
5.1 获取请求 URL
Request 结构中的 URL 字段用于表示请求行中包含的 URL,改字段是一个指向
url.URL 结构的指针,让我们来看一下 URL 结构
1) Path 字段、
l 获取请求的 URL
l 例如:
- 通过 r.URL.Path 只能得到 /hello
2) RawQuery 字段
l 获取请求的 URL 后面?后面的查询字符串
l 例如:
- 通过 r.URL.RawQuery 得到的是 username=admin&password=123456
5.2 获取请求头中的信息
通过 Request 结果中的 Header 字段用来获取请求头中的所有信息,Header 字段的类型是 Header 类型,而 Header 类型是一个 map[string][]string,string 类型的 key, string 切片类型的值。下面是 Header 类型及它的方法:
map[User-Agent:[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36]
Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,ima ge/apng,*/*;q=0.8]
Accept-Encoding:[gzip, deflate, br] Accept-Language:[zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7] Connection:[keep-alive] Upgrade-Insecure-Requests:[1]]
- 结果
[gzip, deflate, br]
l 方式二:r.Header.Get(“Accept-Encoding”)
- 得到的是字符串形式的值,多个值使用逗号分隔
- 结果
gzip, deflate, br
5.3 获取请求体中的信息
请求和响应的主体都是有 Request 结构中的 Body 字段表示,这个字段的类型是
io.ReadCloser 接口,该接口包含了 Reader 接口和 Closer 接口,Reader 接口拥有 Read
方法,Closer 接口拥有 Close 方法
<form action="http://localhost:8080/getBody" method="POST"> 用 户 名 : <input type="text" name="username" value="hanzong"><br/> 密 码 : <input type="password" name="password" value="666666"><br/> <input type="submit"> </form>
package main
import (
"fmt" "net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//获取内容的长度
length := r.ContentLength //创建一个字节切片
body := make([]byte, length) //读取请求体
r.Body.Read(body)
fmt.Fprintln(w, "请求体中的内容是:", string(body))
}
func main() { http.HandleFunc("/getBody", handler)
http.ListenAndServe(":8080", nil)
}
c) 在浏览器上显示的结果
请求体中的内容是: username=hanzong&password=666666
5.4 获取请求参数
下面我们就通过 net/http 库中的 Request 结构的字段以及方法获取请求 URL 后面
的请求参数以及 form 表单中提交的请求参数
5.4.1 Form 字段
1) 类型是 url.Values 类型,Form 是解析好的表单数据,包括 URL 字段的 query
参数和 POST 或 PUT 的表单数据。
2) Form 字段只有在调用 Request 的 ParseForm 方法后才有效。在客户端,会忽略请求中的本字段而使用 Body 替代
3) 获取 5.3 中的表单中提交的请求参数(username 和 password)
func handler(w http.ResponseWriter, r *http.Request) {
//解析表单
r.ParseForm()
//获取请求参数
fmt.Fprintln(w, "请求参数为:", r.Form)
} <form action="http://localhost:8080/getBodyusername=admin&pwd=123456" method="POST"> 用 户 名 : <input type="text" name="username" value="hanzong"><br/> 密 码 : <input type="password" name="password" value="666666"><br/> <input type="submit" </form>
func handler(w http.ResponseWriter, r *http.Request) {
//解析表单 r.ParseForm()
//获取请求参数
fmt.Fprintln(w, "请求参数为:", r.PostForm)
}
func handler(w http.ResponseWriter, r *http.Request) { //获取请求参数
fmt.Fprintln(w, "请求参数 username 的值为:", r.FormValue("username"))
}func handler(w http.ResponseWriter, r *http.Request) {
//获取请求参数
fmt.Fprintln(w, "请求参数 username 的值为:", r.PostFormValue("username"))
}
<form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data"> 用 户 名 : <input type="text" name="username" value="hanzong"><br/> 文件:<input type="file" name="photo" /><br/> <input type="submit" value="提交"/> </form>
package main
import (
"fmt" "net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//解析表单
r.ParseMultipartForm(1024)
//打印表单数据
fmt.Fprintln(w, r.MultipartForm)
}
func main() {
http.HandleFunc("/upload", handler)
http.ListenAndServe(":8080", nil)
}
3) 浏览器显示结果
&{map[username:[hanzong]] map[photo:[0xc042126000]]}
l 结果中有两个映射,第一个映射映射的是用户名;第二个映射的值是一个地址
l MuiltipartForm 字段的类型为 *multipart.Form,multipart 包下 Form 结构的指针类型
l 打开上传的文件
func handler(w http.ResponseWriter, r *http.Request) { //解析表单
r.ParseMultipartForm(1024)
fileHeader := r.MultipartForm.File["photo"][0]
file, err := fileHeader.Open()
if err == nil {
data, err := ioutil.ReadAll(file) if err == nil {
fmt.Fprintln(w, string(data))
}
}
}func handler(w http.ResponseWriter, r *http.Request) {
file, _, err := r.FormFile("photo")
if err == nil {
data, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintln(w, string(data))
}
}
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("你的请求我已经收到"))
} HTTP/1.1 200 OK Date: Fri, 10 Aug 2018 01:09:27 GMT Content-Length: 27 Content-Type: text/plain; charset=utf-8
func handler(w http.ResponseWriter, r *http.Request) {
html := `<html> <head>
<title>测试响应内容为网页</title>
<meta charset="utf-8"/>
</head>
<body>
我是以网页的形式响应过来的!
</body>
</html>`
w.Write([]byte(html))
}HTTP/1.1 200 OK Date: Fri, 10 Aug 2018 01:26:58 GMT Content-Length: 195 Content-Type: text/html; charset=utf-8
func handler(w http.ResponseWriter, r *http.Request) {
//设置响应头中内容的类型
w.Header().Set("Content-Type", "application/json")
user := User{
ID: 1,
Username: "admin",
Password: "123456",
}
//将 user 转换为 json 格式
json, _ := json.Marshal(user) w.Write(json)
}HTTP/1.1 200 OK Content-Type: application/json Date: Fri, 10 Aug 2018 01:58:02 GMT Content-Length: 47
func handler(w http.ResponseWriter, r *http.Request) {
//以下操作必须要在 WriteHeader 之前进行
w.Header().Set("Location", "https:www.baidu.com")
w.WriteHeader(302)
}
HTTP/1.1 302 Found Location: https:www.baidu.com Date: Fri, 10 Aug 2018 01:45:04 GMT Content-Length: 0 Content-Type: text/plain; charset=utf-8
Go 为我们提供了 text/template 库和 html/template 库这两个模板引擎,模板引擎通过将数据和模板组合在一起生成最终的 HTML,而处理器负责调用模板引擎并将引擎生成的 HTMl 返回给客户端。
Go 的模板都是文本文档(其中 Web 应用的模板通常都是 HTML),它们都嵌入了一些称为动作的指令。从模板引擎的角度来说,模板就是嵌入了动作的文本(这些文本通常包含在模板文件里面),而模板引擎则通过分析并执行这些文本来生成出另外一些文本。
<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body> //嵌入动作
{{.}}
</body>
</html> func handler(w http.ResponseWriter, r *http.Request) { //解析模板文件
t, _ := template.ParseFiles("hello.html") //执行模板
t.Execute(w, "Hello World!")
}
t, _ := template.ParseFiles("hello.html") t := template.New("hello.html")
t, _ = t.ParseFiles("hello.html")t := template.Must(template.ParseFiles("hello.html")
t, _ := template.ParseGlob("*.html")t, _ := template.ParseFiles("hello.html", "hello2.html")t.ExecuteTemplate(w, "hello2.html", "我要在 hello2.html 中显示")
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
<!-- 嵌入动作 -->
{{if .}}
你已经成年了!
{{else}}
你还未成年
{{end}}
</body>
</html>
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//声明一个变量
age := 16
//执行模板
t.Execute(w, age > 18)
}
<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body> <!-- 嵌入动作 --> {{range .}}
<a href="#">{{.}}</a> {{else}}
没有遍历到任何内容
{{end}}
</body>
</html> func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//声明一个字符串切片
stars := []string{"马蓉", "李小璐", "白百何"}
//执行模板
t.Execute(w, stars)
}
浏览器中的结果
l 如果迭代之后是一个个的结构体,获取结构体中的字段值使用 .字段名方式获取
{{range . }}
获取结构体的 Name 字段名 {{ .Name }}
{{ end }}
l 迭代 Map 时可以设置变量,变量以$开头:
{{ range $k , $v := . }}
键是 {{ $k }} , 值是 {{ $v }}
{{ end }}
l 迭代管道
{{ c1 | c2 | c3 }}
n c1、c2 和 c3 可以是参数或者函数。管道允许用户将一个参数的输出传递给下一个参数,各个参数之间使用 | 分割。
6.4.2 设置动作
设置动作允许在指定的范围内对点(.)设置值。
l 格式一:
<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body> <!-- 嵌入动作 -->
<div>得到的数据是:{{.}}</div> {{with "太子"}} <div>替换之后的数据是:{{.}}</div> {{end}}
<hr/> {{with ""}}
<div>看一下现在的数据是:{{.}}</div> {{else}} <div>数据没有被替换,还是:{{.}}</div> {{end}}
</body>
</html>
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//执行模板
t.Execute(w, "狸猫")
}<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body> <!-- 嵌入动作 -->
<div>从后台得到的数据是:{{.}}</div> <!-- 包含 hello2.html 模板 -->
{{ template "hello2.html"}} <div>hello.html 文件内容结束</div> <hr/>
<div>将 hello.html 模板文件中的数据传递给 hello2.html
模板文件</div> {{ template "hello2.html" . }}
</body>
</html>
<html>
<head>
<title>hello2 模板文件</title> <meta charset="utf-8"/>
</head>
<body>
<!-- 嵌入动作 -->
<div>hello2.html 模板文件中的数据是:{{.}}</div>
</body>
</html>
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html", "hello2.html"))
//执行模板
t.Execute(w, "测试包含")
}
<!-- 定义模板 -->
{{ define "model"}}
<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body>
{{ template "content"}}
</body>
</html>
{{ end }}<!-- 定义模板 -->
{{ define "model"}}
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
{{ template "content"}}
</body>
</html>
{{ end }}
{{ define "content"}}
<a href="#">点我有惊喜</a>
{{ end }}func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//执行模板
t.ExecuteTemplate(w, "model", "")
}
<!-- 定义模板 --> {{ define "model"}} <html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body>
{{ template "content"}} </body>
</html>
{{ end }}<html>
<head>
<title>content 模板文件</title> <meta charset="utf-8"/>
</head>
<body>
<!-- 定义 content 模板 -->
{{ define “content” }}
<h1>我是 content1.html 模板文件中的内容</h1>
{{ end }}
</body>
</html>
<html>
<head>
<title>content 模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
<!-- 定义 content 模板 -->
{{ define “content” }}
<h1>我是 content2.html 模板文件中的内容</h1>
{{ end }}
</body>
</html>func handler(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
var t *template.Template
if rand.Intn(5) > 2 {
//解析模板文件
t = template.Must(template.ParseFiles("hello.html", "content1.html"))
} else {
//解析模板文件
t = template.Must(template.ParseFiles("hello.html",
"content2.html"))
}
//执行模板
t.ExecuteTemplate(w, "model", "")
}
{{ block arg }}
如果找不到模板我就要显示了
{{ end }} {{ define "model"}}
<html>
<head> <title>模板文件</title> <meta charset="utf-8"/>
</head>
<body>
{{ block "content" .}}
如果找不到就显示我
{{ end }}
</body>
</html>
{{ end }}
func handler(w http.ResponseWriter, r *http.Request) { rand.Seed(time.Now().Unix())
var t *template.Template if rand.Intn(5) > 2 {
//解析模板文件
t = template.Must(template.ParseFiles("hello.html", "content1.html"))
} else { //解析模板文件
t = template.Must(template.ParseFiles("hello.html")
}
//执行模板
t.ExecuteTemplate(w, "model", "")
}
HTTP 是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不
能区分中两次请求是否由一个客户端发出。这样的设计严重阻碍的 Web 程序的设计。
如:在我们进行网购时,买了一条裤子,又买了一个手机。由于 http 协议是无状态的,
如果不通过其他手段,服务器是不能知道用户到底买了什么。而 Cookie 就是解决方案
之一。
7.1 Cookie
7.1.1 简介
Cookie 实际上就是服务器保存在浏览器上的一段信息。浏览器有了 Cookie 之后,
每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以
根据该信息处理请求。
7.1.2 Cookie 的运行原理
1) 第一次向服务器发送请求时在服务器端创建 Cookie
2) 将在服务器端创建的 Cookie 以响应头的方式发送给浏览器
func handler(w http.ResponseWriter, r *http.Request) {
cookie1 := http.Cookie{
Name: "user1",
Value: "admin",
HttpOnly: true,
}
cookie2 := http.Cookie{
Name: "user2", Value: "superAdmin", HttpOnly: true,
}
//将 Cookie 发送给浏览器,即添加第一个
Cookie w.Header().Set("Set-Cookie", cookie1.String())
//再添加一个
Cookie w.Header().Add("Set-Cookie", cookie2.String())
}HTTP/1.1 200 OK Set-Cookie: user1=admin; HttpOnly Set-Cookie: user2=superAdmin; HttpOnly Date: Sun, 12 Aug 2018 07:24:49 GMT Content-Length: 0 Content-Type: text/plain; charset=utf-8
GET /cookie HTTP/1.1 Host: localhost:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/ apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: user1=admin; user2=superAdmin
func handler(w http.ResponseWriter, r *http.Request) {
cookie1 := http.Cookie{
Name: "user1",
Value: "admin",
HttpOnly: true,
}
cookie2 := http.Cookie{ Name: "user2", Value: "superAdmin", HttpOnly: true,}
http.SetCookie(w, &cookie1)
http.SetCookie(w, &cookie2)
}
func handler(w http.ResponseWriter, r *http.Request) {
//获取请求头中的
Cookie cookies := r.Header["Cookie"]
fmt.Fprintln(w, cookies)
}
func handler(w http.ResponseWriter, r *http.Request) {
cookie := http.Cookie{ Name: "user",
Value: "persistAdmin",
HttpOnly: true,
MaxAge: 60,
}
//将 Cookie 发送给浏览器
w.Header().Set("Set-Cookie", cookie.String())
}HTTP/1.1 200 OK Set-Cookie: user=persistAdmin; Max-Age=60; HttpOnly Date: Sun, 12 Aug 2018 07:32:57 GMT Content-Length: 0 Content-Type: text/plain; charset=utf-8
7.2 Session
7.2.1 简介
使用 Cookie 有一个非常大的局限,就是如果 Cookie 很多,则无形的增加了客户
端与服务端的数据传输量。而且由于浏览器对 Cookie 数量的限制,注定我们不能再
Cookie 中保存过多的信息,于是 Session 出现。
Session 的作用就是在服务器端保存一些用户的数据,然后传递给用户一个特殊
的 Cookie,这个 Cookie 对应着这个服务器中的一个 Session,通过它就可以获取到保
存用户信息的 Session,进而就知道是那个用户再发送请求。
7.2.2 Session 的运行原理
1) 第一次向服务器发送请求时创建 Session,给它设置一个全球唯一的 ID(可以通过UUID 生成)
2) 创建一个 Cookie,将 Cookie 的 Value 设置为 Session 的 ID 值,并将 Cookie 发送给浏览器
3) 以后再发送请求浏览器就会携带着该 Cookie
4) 服务器获取 Cookie 并根据它的 Value 值找到服务器中对应的 Session,也就知道了请求是那个用户发的
对于 HTML 页面中的 css 以及 js 等静态文件,需要使用使用 net/http 包下的以下
方法来处理
1) StripPrefix 函数
2) FileServer 函数
3) 例如:
a) 项目的静态文件的目录结构如下:
b) index.html 模板文件中引入的 css 样式的地址如下:
c) 对静态文件的处理
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("views/static"))))
l /static/会匹配 以 /static/开发的路径,当浏览器请求 index.html 页面中的 style.css 文件时,static 前缀会被替换为 views/staic,然后去 views/static/css
目录中取查找 style.css 文件