一、初体验与Handle
1、初体验
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
http.ListenAndServe("localhost:8080", nil) // DefaultServerMux
}
2、Handle请求
创建Web Server两种方法:
方式一:
http.ListenAndServe()
http.ListenAndServeTLS()
方式二(http.Server可配置):
server.ListenAndServe()
server.ListenAndServeTLS()
(1)自定义Handler
HTTP请求 ==>> MyHandler
package main
import "net/http"
type myHandler struct{}
func (mux *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
func main() {
mh := myHandler{}
server := http.Server{
Addr: "localhost:8080",
Handler: &mh,
}
server.ListenAndServe()
}
(2)添加多个Handler
通过http.Hander实现
package main
import "net/http"
type helloHandler struct{}
func (mux *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
type aboutHandler struct{}
func (mux *aboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("About"))
}
func main() {
mh := helloHandler{}
a := aboutHandler{}
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
http.Handle("/hello", &mh)
http.Handle("/about", &a)
server.ListenAndServe()
}
结果访问:
http://localhost:8080/about
http://localhost:8080/hello
(3)通过HandleFunc实现
内部调用的还是http.Handle函数
package main
import "net/http"
func welcome(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome!!"))
}
func main() {
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Home!!"))
})
http.Handle("/welcome", http.HandlerFunc(welcome))
server.ListenAndServe()
}
结果访问:
http://localhost:8080/home
http://localhost:8080/welcome
3、内置Handler
(1)http.NotFoundHandler
- func NotFoundHandler() Handler
- 返回一个 handler,它给每个请求的响应都是"404 page not found"
(2)http.RedirectHandler
- func RedirectHandler(url string, code int) Handler
- 返回一个 handler,它把每个请求使用给定的状态码跳转到指定的 URL。
- url,要跳转到的 URL
- code,跳转的状态码(3xx),常见的:StatusMovedPermanently、StatusFound 或 StatusSeeOther 等
(3)http.StripPrefix
- func StripPrefix(prefix string, h handler) Handler
- 返回一个 handler,它从请求 URL 中去掉指定的前缀,然后再调用另一个 handler。
- 如果请求的 URL 与提供的前缀不符,那么 404
- 略像中间件
- prefix,URL 将要被移除的字符串前缀
- h,是一个 handler,在移除字符串前缀之后,这个 handler 将会接收到请求
- 修饰了另一个 Handler
(4)http.TimeoutHandler
- func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
- 返回一个 handler,它用来在指定时间内运行传入的 h。
- 也相当于是一个修饰器
- h,将要被修饰的 handler
- dt,第一个 handler 允许的处理时间
- msg,如果超时,那么就把 msg 返回给请求,表示响应时间过长
(5)http.FileServer
- func FileServer(root FileSystem) Handler
- 返回一个 handler,使用基于 root 的文件系统来响应请求
- type FileSystem interface {
Open(name string) (File, error)
} - 使用时需要用到操作系统的文件系统,所以还需要委托给:
- type Dir string
- func (d Dir) Open(name string) (File, error)
<1> 目录结构
<2> 配置index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello wielun</h1>
</body>
</html>
<3> 配置main.go
package main
import "net/http"
func main() {
// 方式一
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// http.ServeFile(w, r, "wwwroot"+r.URL.Path)
// })
// http.ListenAndServe(":8080", nil)
// 方式二
http.ListenAndServe(":8080", http.FileServer(http.Dir("wwwroot")))
}
二、请求Request
1、URL Fragment
- 如果从浏览器发出的请求,那么你无法提取出 Fragment 字段的值
- 浏览器在发送请求时会把 fragment 部分去掉
- 但不是所有的请求都是从浏览器发出的(例如从 HTTP 客户端包)。
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.URL.Fragment)
})
server.ListenAndServe()
}
访问:
可以发现没有返回值
http://localhost:8080/url#frag
2、Request Header
- 请求和响应(Request、Response)的 headers 是通过 Header 类型来描述的,它是一个 map,用来表述 HTTP Header 里的 Key-Value 对。
- Header map 的 key 是 string 类型,value 是 []string
- 设置 key 的时候会创建一个空的 []string 作为 value,value 里面第一个元素就是新 header 的值;
- 为指定的 key 添加一个新的 header 值,执行 append 操作即可
- r.Header
- 返回 map
- r.Header["Accept-Encoding"]
- 返回:[gzip, deflate]([]string 类型)
- r.Header.Get("Accept-Encoding")
- 返回:gzip, deflate(string 类型)
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.Header)
fmt.Fprintln(w, r.Header["Accept-Encoding"])
fmt.Fprintln(w, r.Header.Get("Accept-Encoding"))
})
server.ListenAndServe()
}
访问:
# 新建文件test.http
GET http://localhost:8080/header HTTP/1.1
3、Request Body
- 请求和响应的 bodies 都是使用 Body 字段来表示的
- Body 是一个 io.ReadCloser 接口
- 一个 Reader 接口
- 一个 Closer 接口
- Reader 接口定义了一个 Open 方法:
- 参数:[]byte
- 返回:byte 的数量、可选的错误
- Closer 接口定义了一个 Close 方法:
- 没有参数,返回可选的错误
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
length := r.ContentLength
body := make([]byte, length)
r.Body.Read(body)
fmt.Fprintln(w, string(body))
})
server.ListenAndServe()
}
访问:
# 新建文件test.http
POST http://localhost:8080/post HTTP/1.1
Content-Type: application/json
{
"name": "sample"
}
4、查询参数
package main
import (
"log"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
url := r.URL
query := url.Query()
id := query["id"]
log.Println(id)
name := query.Get("name")
log.Println(name)
})
server.ListenAndServe()
}
访问:
http://localhost:8080/home?id=1&name=wielun&id=2&name=yang
2021/08/04 20:43:00 [1 2]
2021/08/04 20:43:00 wielun
三、Form表单
1、Form字段
- Request 上的函数允许我们从 URL 或/和 Body 中提取数据,通过这些字段:
- Form
- PostForm
- MultipartForm
- Form 里面的数据是 key-value 对。
- 通常的做法是:
- 先调用 ParseForm 或 ParseMultipartForm 来解析 Request
- 然后相应的访问 Form、PostForm 或 MultipartForm 字段
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.Form)
})
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form</title>
</head>
<body>
<form action="http://localhost:8080/process" method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="submit">
</form>
</body>
</html>
访问:
通过浏览器直接打开html
2、PostForm字段
- 前例中,如果只想得到 first_name 这个 Key 的 Value,可使用 r.Form[“first_name”],它返回含有一个元素的 slice:[“hello”]
- 如果表单和 URL 里有同样的 Key,那么它们都会放在一个 slice 里:表单里的值靠前,URL 的值靠后
- 如果只想要表单的 key-value 对,不要 URL 的,可以使用 PostForm 字段。
- PostForm 只支持 application/x-www-form-urlencoded
- 想要得到 multipart key-value 对,必须使用 MultipartForm 字段
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.PostForm)
})
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form</title>
</head>
<body>
<form action="http://localhost:8080/process?first_name=Nick" method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="submit">
</form>
</body>
</html>
访问:
用Form的话,first_name里面也会有Nick
3、MultipartForm字段
- 想要使用 MultipartForm 这个字段的话,首先需要调用 ParseMultipartForm 这个方法
- 该方法会在必要时调用 ParseForm 方法
- 参数是需要读取数据的长度
- MultipartForm 只包含表单的 key-value 对
- 返回类型是一个 struct 而不是 map。这个 struct 里有两个 map:
- key 是 string,value 是 []string
- 空的(key 是 string,value 是文件)
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Fprintln(w, r.MultipartForm)
})
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form</title>
</head>
<body>
<form action="http://localhost:8080/process?first_name=Nick" method="post" enctype="multipart/form-data">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="submit">
</form>
</body>
</html>
访问:
第二个map返回上传文件
4、FormValue和PostFormValue方法
- FormValue 方法会返回 Form 字段中指定 key 对应的第一个 value
- 无需调用 ParseForm 或 ParseMultipartForm
- PostFormValue 方法也一样,但只能读取 PostForm
- FormValue 和 PostFormValue 都会调用 ParseMultipartForm 方法
- 但如果表单的 enctype 设为 multipart/form-data,那么即使你调用ParseMultipartForm 方法,也无法通过 FormValue 获得想要的值。
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
// r.ParseMultipartForm(1024)
fmt.Fprintln(w, r.FormValue("first_name"))
})
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form</title>
</head>
<body>
<form action="http://localhost:8080/process?first_name=Nick" method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="submit">
</form>
</body>
</html>
访问:
5、上传文件
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fileHeader := r.MultipartForm.File["uploaded"][0]
file, err := fileHeader.Open()
if err == nil {
data, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintln(w, string(data))
}
}
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form</title>
</head>
<body>
<form action="http://localhost:8080/process?hello=world&thread=123" method="post" enctype="multipart/form-data">
<input type="text" name="hello" value="wielun">
<input type="text" name="post" value="456">
<input type="file" name="uploaded">
<input type="submit">
</form>
</body>
</html>
访问:
创建个test.txt,里面写了hello wielun,并上传上去
6、上传文件之FormFile方法
更加方便快捷
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
// r.ParseMultipartForm(1024)
// fileHeader := r.MultipartForm.File["uploaded"][0]
// file, err := fileHeader.Open()
file, _, err := r.FormFile("uploaded")
if err == nil {
data, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintln(w, string(data))
}
}
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
四、ResponseWriter
1、介绍
- 从服务器向客户端返回响应需要使用
- ResponseWriter ResponseWriter 是一个接口,handler 用它来返回响应
- 真正支撑 ResponseWriter 的幕后 struct 是非导出的 http.response
2、内置Response
- NotFound 函数,包装一个 404 状态码和一个额外的信息
- ServeFile 函数,从文件系统提供文件,返回给请求者
- ServeContent 函数,它可以把实现了 io.ReadSeeker 接口的任何东西里面的内容返回给请求者
- 还可以处理 Range 请求(范围请求),如果只请求了资源的一部分内容,那么 ServeContent 就可以如此响应。而 ServeFile 或 io.Copy 则不行。
- Redirect 函数,告诉客户端重定向到另一个 URL
3、写入到ResponseWriter
- Write 方法接收一个 byte 切片作为参数,然后把它写入到 HTTP 响应的 Body 里面。
- 如果在 Write 方法被调用时,header 里面没有设定 content type,那么数据的前 512 字节就会被用来检测content type
package main
import (
"net/http"
)
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html>
<head><title>Go Web</title></head>
<body><h1>Hello Wielun</h1></body>
</html>`
w.Write([]byte(str))
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/write", writeExample)
server.ListenAndServe()
}
访问:
http://localhost:8080/write
4、WriteHeader方法
- WriteHeader 方法接收一个整数类型(HTTP 状态码)作为参数,并把它作为 HTTP 响应的状态码返回
- 如果该方法没有显式调用,那么在第一次调用 Write 方法前,会隐式的调用 WriteHeader(http.StatusOK)
- 所以 WriteHeader 主要用来发送错误类的 HTTP 状态码
- 调用完 WriteHeader 方法之后,仍然可以写入到ResponseWriter,但无法再修改 header了
package main
import (
"fmt"
"net/http"
)
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html>
<head><title>Go Web</title></head>
<body><h1>Hello Wielun</h1></body>
</html>`
w.Write([]byte(str))
}
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/write", writeExample)
http.HandleFunc("/writeheader", writeHeaderExample)
server.ListenAndServe()
}
访问:
http://localhost:8080/writeheader
5、Header方法
- Header 方法返回 headers 的 map,可以进行修改
- 修改后的 headers 将会体现在返回给客户端的 HTTP 响应里
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Post struct {
User string
Threads []string
}
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html>
<head><title>Go Web</title></head>
<body><h1>Hello Wielun</h1></body>
</html>`
w.Write([]byte(str))
}
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
func headerExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://www.baidu.com")
w.WriteHeader(302)
}
func jsonExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "wielun",
Threads: []string{"first", "second", "third"},
}
json, _ := json.Marshal(post)
w.Write(json)
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/write", writeExample)
http.HandleFunc("/writeheader", writeHeaderExample)
http.HandleFunc("/redirect", headerExample)
http.HandleFunc("/json", jsonExample)
server.ListenAndServe()
}
访问:
http://localhost:8080/redirect
http://localhost:8080/json
五、模板
1、简介与引擎
(1)简介
- Web 模板就是预先设计好的 HTML 页面,它可以被模板引擎反复的使用,来产生 HTML 页面
- Go 的标准库提供了 text/template,html/template 两个模板库
- 大多数 Go 的 Web 框架都使用这些库作为 默认的模板引擎
(2)Go模板引擎
- 主要使用的是 text/template,HTML 相关的部分使用了 html/template,是个混合体。
- 模板可以完全无逻辑,但又具有足够的嵌入特性
- 和大多数模板引擎一样,Go Web 的模板位于无逻辑和嵌入逻辑之间的某个地方
(3)Go模板引擎工作原理
- 在 Web 应用中,通产是由 handler 来触发模板引擎
- handler 调用模板引擎,并将使用的模板传递给引擎
- 通常是一组模板文件和动态数据
- 模板引擎生成 HTML,并将其写入到 ResponseWriter
- ResponseWriter 再将它加入到 HTTP 响应中,返回给客户端
(4)关于模板
- 模板必须是可读的文本格式,扩展名任意。对于 Web 应用通常就是 HTML
- 里面会内嵌一些命令(叫做 action)
- text/template 是通用模板引擎,html/template 是 HTML 模板引擎
- action 位于双层花括号之间:{{ . }}
- 这里的 . 就是一个 action
- 它可以命令模板引擎将其替换成一个值
2、使用模板引擎
- 解析模板源(可以是字符串或模板文件),从而创建一个解析好的 模板的 struct
- 执行解析好的模板,并传入 ResponseWriter 和 数据。
- 这会触发模板引擎组合解析好的模板和数据,来产生最终的 HTML,并将它传递给 ResponseWriter
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "Hello World")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
tmpl.html:
同级目录下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Template</title>
</head>
<body>
{{ . }}
</body>
</html>
3、解析模板
- 解析模板文件,并创建一个解析好的模板 struct,后续可以被执行
- ParseFiles 函数是 Template struct 上 ParseFiles 方法的简便调用
- 调用 ParseFiles 后,会创建一个新的模板,模板的名字是文件名
- New 函数
- ParseFiles 的参数数量可变,但只返回一个模板
- 当解析多个文件时,第一个文件作为返回的模板(名、内容),其余的作为 map,供后续执行使用
(1)ParseFiles
package main
import (
"text/template"
)
func main() {
// t, _ := template.ParseFiles()
t := template.New("tmpl.html")
t, _ = t.ParseFiles("tmpl.html")
}
(2)ParseGlob
- 使用模式匹配来解析特定的文件
package main
import (
"text/template"
)
func main() {
t, _ := template.ParseGlob("*.html")
}
(3)解析模板之Parse
- 可以解析字符串模板,其它方式最终都会调用 Parse
(4)Lookup方法
- 可以包裹一个函数,返回到一个模板的指针 和 一个错误。
- 如果错误不为 nil,那么就 panic
4、执行模板
Execute:
参数是 ResponseWriter、数据
单模板:很适用
模板集:只用第一个模板
ExecuteTemplate:
参数是:ResponseWriter、模板名、数据
模板集:很适用
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html")
t.Execute(w, "Hello World")
ts, _ := template.ParseFiles("t1.html", "t2.html")
ts.ExecuteTemplate(w, "t2.html", "Hello World")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5、demo
(1) 目录结构
(2) templates
about.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About</title>
<link rel="stylesheet" href="/css/about.css">
</head>
<body>
<h1 class="about">About me</h1>
<img src="/img/about.jpg" alt="">
</body>
</html>
contact.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact</title>
<link rel="stylesheet" href="/css/contact.css">
</head>
<body>
<h1 class="contact">Contact</h1>
<img src="/img/contact.jpg" alt="">
</body>
</html>
home.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="/css/home.css">
</head>
<body>
<h1 class="home">Home</h1>
<img src="/img/home.jpg" alt="">
</body>
</html>
(3) css
about.css:
.about {
color: green;
}
contact.css:
.contact {
color: red;
}
home.css:
.home {
color:orange;
}
(4) main.go
package main
import (
"html/template"
"log"
"net/http"
)
func main() {
templates := loadTemplates()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Path[1:]
t := templates.Lookup(fileName)
if t != nil {
err := t.Execute(w, nil)
if err != nil {
log.Fatalln(err.Error())
}
} else {
w.WriteHeader(http.StatusNotFound)
}
})
http.Handle("/css/", http.FileServer(http.Dir("wwwroot")))
http.Handle("/img/", http.FileServer(http.Dir("wwwroot")))
http.ListenAndServe("localhost:8080", nil)
}
func loadTemplates() *template.Template {
result := template.New("templates")
template.Must(result.ParseGlob("templates/*.html"))
return result
}
(5) 访问
http://localhost:8080/about.html
http://localhost:8080/contact.html
http://localhost:8080/home.html
6、Action
(1)简介
- Action 就是 Go 模板中嵌入的命令,位于两组花括号之间 {{ xxx }}
- . 就是一个 Action,而且是最重要的一个。它代表了传入模板的数据
- Action 主要可以分为五类:
- 条件类
- 迭代/遍历类
- 设置类
- 包含类
- 定义类
(2)Action使用
main.go:
package main
import (
"math/rand"
"net/http"
"text/template"
"time"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
rand.Seed(time.Now().Unix())
t.Execute(w, rand.Intn(10) > 5)
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
tmpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Action</title>
</head>
<body>
{{ if . }}
Number is greater than 5!
{{ else }}
Number is 5 or less!
{{ end }}
</body>
</html>
(3)迭代/遍历Action
简介:
{{ range array }}
Dot is set to the element {{ . }}
{{ end }}
- 这类 Action 用来遍历数组、slice、map 或 channel 等数据结构
- “.”用来表示每次迭代循环中的元素
main.go:
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
dayOfWeek := []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
t.Execute(w, dayOfWeek)
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
tmpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Action</title>
</head>
<body>
{{ range . }}
<li>{{ . }}</li>
{{ end }}
</body>
</html>
访问:
http://localhost:8080/process
(4)设置Action
简介:
{{ with arg }}
Dot is set to arg
{{ end }}
它允许在指定范围内,让“.”来表示其它指定的值(arg)
main.go:
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "wielun")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
tmpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Action</title>
</head>
<body>
<div>The dot is {{ . }}</div>
<div>
{{ with "world" }}
Now the dot is set to {{ . }}
{{ end }}
</div>
<div>The dot is {{. }} again</div>
</body>
</html>
访问:
http://localhost:8080/process
(5)包含Action
简介:
{{ template "name" }}
它允许你在模板中包含其它的模板
{{ template "name" arg }} # 给被包含模板传递参数
main.go:
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html", "t2.html")
t.Execute(w, "wielun")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
t1.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Action</title>
</head>
<body>
<div>This is t1.html</div>
<div>This is the value of the dot in t1.html - [{{ . }}]</div>
<hr />
{{ template "t2.html" . }}
<hr />
<div>This is t1.html after</div>
</body>
</html>
t2.html:
<div style="background-color: yellowgreen;">
This is t2.html <br>
This is the value of the dot in t2.html - [{{ . }}]
</div>
访问:
http://localhost:8080/process
7、管道
(1)简介
- 管道是按顺序连接到一起的参数、函数和方法。
- 和 Unix 的管道类似
- 例如:{{ p1 | p2 | p3 }}
- p1、p2、p3 要么是参数,要么是函数
- 管道允许我们把参数的输出发给下一个参数,下一个参数由管道(|)分隔开。
(2)main.go
package main
import (
"net/http"
"text/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html")
t.Execute(w, "wielun")
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
(3)t1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pipeline</title>
</head>
<body>
{{ 12.46 | printf "%.2f" }}
</body>
</html>
(4)访问
http://localhost:8080/process
8、函数
(1)简介
- 参数可以是一个函数
- Go 模板引擎提供了一些基本的内置函数,功能比较有限。例如 fmt.Sprint 的各类变体等
- 开发者可以自定义函数:
- 可以接收任意数量的输入参数
- 返回:
- 一个值
- 一个值+一个错误
(2)内置函数
- define、template、block
- html、js、urlquery。对字符串进行转义,防止安全问题
- 如果是 Web 模板,那么不会需要经常使用这些函数。
- index
- print/printf/println
- len
- with
(3)如何自定义函数
简介:
- template.Funcs(funcMap FuncMap) *Template
- type FuncMap map[string]interface{}
- value 是函数
- 可以有任意数量的参数
- 返回单个值的函数或返回一个值+一个错误的函数
- 1、创建一个 FuncMap(map 类型)。
- key 是函数名
- value 就是函数
- 2、把 FuncMap 附加到模板
main.go:
package main
import (
"net/http"
"text/template"
"time"
)
func formatDate(t time.Time) string {
layout := "2006-01-02"
return t.Format(layout)
}
func process(w http.ResponseWriter, r *http.Request) {
funcMap := template.FuncMap{"fdate": formatDate}
t := template.New("t1.html").Funcs(funcMap)
t.ParseFiles("t1.html")
t.Execute(w, time.Now())
}
func main() {
server := http.Server{
Addr: "localhost:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
t1.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pipeline</title>
</head>
<body>
{{ . | fdate }}
</body>
</html>
访问:
http://localhost:8080/process
9、Layout模板
(1)如何制作
- Include(包含)action 的形式:{{ template “name” . }}
- 以这种方式做 layout 模板是不可行的(例子)
- 正确的做法是在模板文件里面使用 define action 再定义一个模板
- 也可以在多个模板文件里,定义同名的模板(例子)
(2)main.go
package main
import (
"net/http"
"text/template"
)
func main() {
http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html", "home.html")
t.ExecuteTemplate(w, "layout", "wielun")
})
http.ListenAndServe("localhost:8080", nil)
}
(3)layout.html
{{ define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Layout</title>
</head>
<body>
<h1>layout</h1>
{{ template "content" . }}
</body>
</html>
{{ end }}
(4)home.html
{{ define "content" }}
<h1>Home</h1>
<h2>{{ . }}</h2>
{{ end }}
(5)访问
http://localhost:8080/home
六、路由
1、基本使用
(1)目录结构
(2)main.go
package main
import (
"action/controller"
"net/http"
)
func main() {
controller.RegisterRoutes()
http.ListenAndServe("localhost:8080", nil)
}
(3)HTML
layout.html:
{{ define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Layout</title>
</head>
<body>
<h1>layout</h1>
{{ template "content" . }}
</body>
</html>
{{ end }}
home.html:
{{ define "content" }}
<h1>Home</h1>
<h2>{{ . }}</h2>
{{ end }}
about.html:
{{ define "content" }}
<h1>about</h1>
<h2>{{ . }}</h2>
{{ end }}
(4)controller目录文件
controller.go:
package controller
func RegisterRoutes() {
// static resources
registerHomeRoutes()
registerAboutRoutes()
registerContactRoutes()
}
home.go:
package controller
import (
"net/http"
"text/template"
)
func registerHomeRoutes() {
http.HandleFunc("/home", handleHome)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html", "home.html")
t.ExecuteTemplate(w, "layout", "wielun")
}
about.go:
package controller
import (
"net/http"
"text/template"
)
func registerAboutRoutes() {
http.HandleFunc("/about", handleAbout)
}
func handleAbout(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html", "about.html")
t.ExecuteTemplate(w, "layout", "")
}
contact.go:
package controller
import (
"net/http"
"text/template"
)
func registerContactRoutes() {
http.HandleFunc("/contact", handleContact)
}
func handleContact(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html")
t.ExecuteTemplate(w, "layout", "")
}
(5)访问
http://localhost:8080/home
http://localhost:8080/about
http://localhost:8080/contact
2、路由参数
(1)目录结构
(2)company.go
package controller
import (
"net/http"
"regexp"
"strconv"
"text/template"
)
func registerCompanyRoutes() {
http.HandleFunc("/companies", handleCompanies)
http.HandleFunc("/companies/", handleCompany)
}
func handleCompanies(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html", "companies.html")
t.ExecuteTemplate(w, "layout", nil)
}
func handleCompany(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html", "company.html")
pattern, _ := regexp.Compile(`/companies/(\d+)`)
matches := pattern.FindStringSubmatch(r.URL.Path)
if len(matches) > 0 {
companyId, _ := strconv.Atoi(matches[1])
t.ExecuteTemplate(w, "layout", companyId)
} else {
w.WriteHeader(http.StatusNotFound)
}
}
(3)controller.go
package controller
func RegisterRoutes() {
// static resources
registerHomeRoutes()
registerAboutRoutes()
registerContactRoutes()
registerCompanyRoutes()
}
(4)HTML
companies.html:
{{ define "content" }}
<h1>Companies</h1>
<ul>
<li>Huawei</li>
<li>Baidu</li>
<li>Tengxun</li>
<li>Google</li>
</ul>
{{ end }}
company.html:
{{ define "content" }}
<h1>Company: {{ . }} </h1>
{{ end }}
(5)访问
http://localhost:8080/companies
http://localhost:8080/companies/123
3、第三方路由器
-
gorilla/mux:灵活性高、功能强大、性能相对差一些
-
httprouter:注重性能、功能简单
-
编写你自己的路由规则
七、JSON
1、对于未知结构的JSON
- map[string]interface{} 可以存储任意 JSON 对象
- []interface{} 可以存储任意的 JSON 数组
2、读取JSON
- 需要一个解码器:dec := json.NewDecoder(r.Body)
- 参数需实现 Reader 接口
- 在解码器上进行解码:dec.Decode(&query)
3、写入JSON
- 需要一个编码器:enc := json.NewEncoder(w)
- 参数需实现 Writer 接口
- 编码:enc.Encode(results)
4、示例
(1)目录
(2)main.go
package main
import (
"encoding/json"
"log"
"net/http"
"request/model"
)
func main() {
http.HandleFunc("/companies", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
dec := json.NewDecoder(r.Body)
company := model.Company{}
err := dec.Decode(&company)
if err != nil {
log.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
enc := json.NewEncoder(w)
err = enc.Encode(company)
if err != nil {
log.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
})
http.ListenAndServe("localhost:8080", nil)
}
(3)model.go
package model
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Country string `json:"country"`
}
(4)test.http
POST http://localhost:8080/companies HTTP/1.1
content-type : application/json
{
"id": 123,
"name": "Google",
"country": "USA"
}
5、Marshal和Unmarshal
(1)简介
- Marshal(编码): 把 go struct 转化为 json 格式
- MarshalIndent,带缩进
- Unmarshal(解码): 把 json 转化为 go struct
(2)目录
(3)main.go
package main
import (
"encoding/json"
"fmt"
"request/model"
)
func main() {
jsonStr := `
{
"id": 123,
"name": "Google",
"country": "USA"
}`
c := model.Company{}
_ = json.Unmarshal([]byte(jsonStr), &c)
fmt.Println(c)
bytes, _ := json.Marshal(c)
fmt.Println(string(bytes))
bytes1, _ := json.MarshalIndent(c, "", " ")
fmt.Println(string(bytes1))
}
(4)model.go
package model
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Country string `json:"country"`
}
6、两种方式区别
-
针对 string 或 bytes:
- Marshal => String
- Unmarshal <= String
-
针对 stream:
- Encode => Stream,把数据写入到 io.Writer
- Decode <= Stream,从 io.Reader 读取数据
八、中间件
1、简介
(1)什么是中间件
(2)创建中间件
type MyMiddleware struct {
Next http.Handler
}
func(m MyMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 在 next handler 之前做一些事情
m.Next.ServeHTTP(w, r)
// 在 next handler 之后做一些事情
}
(3)中间件用途
- Logging
- 安全
- 请求超时
- 响应压缩
- …
2、示例
(1)目录
(2)main.go
package main
import (
"encoding/json"
"net/http"
"request/middleware"
"request/model"
)
func main() {
http.HandleFunc("/companies", func(w http.ResponseWriter, r *http.Request) {
c := model.Company{
ID: 123,
Name: "Google",
Country: "USA",
}
enc := json.NewEncoder(w)
enc.Encode(c)
})
http.ListenAndServe("localhost:8080", new(middleware.AuthMiddleware))
}
(3)model.go
package model
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Country string `json:"country"`
}
(4)auth.go
package middleware
import (
"net/http"
)
type AuthMiddleware struct {
Next http.Handler
}
func (am *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if am.Next == nil {
am.Next = http.DefaultServeMux
}
auth := r.Header.Get("Authorization")
if auth != "" {
am.Next.ServeHTTP(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
}
(5)test.http
### Without Auth
GET http://localhost:8080/companies HTTP/1.1
### With Auth
GET http://localhost:8080/companies HTTP/1.1
Authorization: wielun