Go入门(八)之 Web编程(net/http)

一、初体验与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

在这里插入图片描述
在这里插入图片描述

1.Go环境配置 1.1. Go安装 1.2. GOPATH 与工作空间 1.3. Go 命令 1.4. Go开发工具 1.5. 小结 2.Go语言基础 2.1. 你好,Go 2.2. Go基础 2.3. 流程和函数 2.4. struct 2.5. 面向对象 2.6. interface 2.7. 并发 2.8. 小结 3.Web基础 3.1 web工作方式 3.2 Go搭建一个简单的web服务 3.3 Go如何使得web工作 3.4 Go的http包详解 3.5 小结 4.表单 4.1 处理表单的输入 4.2 验证表单的输入 4.3 预防跨站脚本 4.4 防止多次递交表单 4.5 处理文件上传 4.6 小结 5.访问数据库 5.1 database/sql接口 5.2 使用MySQL数据库 5.3 使用SQLite数据库 5.4 使用PostgreSQL数据库 5.5 使用beedb库进行ORM开发 5.6 NOSQL数据库操作 5.7 小结 6.session和数据存储 6.1 session和cookie 6.2 Go如何使用session 6.3 session存储 6.4 预防session劫持 6.5 小结 7.文本文件处理 7.1 XML处理 7.2 JSON处理 7.3 正则处理 7.4 模板处理 7.5 文件操作 7.6 字符串处理 7.7 小结 8.Web服务 8.1 Socket编程 8.2 WebSocket 8.3 REST 8.4 RPC 8.5 小结 9.安全与加密 9.1 预防CSRF攻击 9.2 确保输入过滤 9.3 避免XSS攻击 9.4 避免SQL注入 9.5 存储密码 9.6 加密和解密数据 9.7 小结 10.国际化和本地化 10.1 设置默认地区 10.2 本地化资源 10.3 国际化站点 10.4 小结 11.错误处理,调试和测试 11.1 错误处理 11.2 使用GDB调试 11.3 Go怎么写测试用例 11.4 小结 12.部署与维护 12.1 应用日志 12.2 网站错误处理 12.3 应用部署 12.4 备份和恢复 12.5 小结 13.如何设计一个Web框架  13.1 项目规划  13.2 自定义路由器设计 13.3 controller设计 13.4 日志和配置设计 13.5 实现博客的增删改 13.6 小结  14.扩展Web框架 14.1 静态文件支持 14.2 Session支持 14.3 表单支持 14.4 用户认证 14.5 多语言支持 14.6 pprof支持 14.7 小结
Module 1, Learning Go Web Development, starts off with introducing and setting up Go before you move on to produce responsive servers that react to certain web endpoint. You will then implement database connections to acquire data and then present it to our users using different template packages. Later on, you will learn about sessions and cookies to retain information before delving with the basics of microservices. By the end of this module, we will be covering the testing, debugging, and the security aspect. Module 2, Go Programming Blueprints, has a project-based approach where you will be building chat application, adding authentication, and adding your own profile pictures in different ways. You will learn how Go makes it easy to build powerful command-line tools to find domain names before building a highly scalable Twitter polling and vote counting engine powered by NSQ and MongoDB. Later on it covers the functionalities of RESTful Data Web Service API and Google Places API before you move on to build a simple but powerful filesystem backup tool for our code projects. Module 3, Mastering Concurrency in Go, introduces you to Concurrency in Go where you will be understanding the Concurrency model and developing a strategy for designing applications. You will learn to create basic and complex communication channels between our goroutines to manage data not only across single or multithreaded systems but also distributed systems. Later on you will be tackling a real-world problem, that is, being able to develop a high performance web server that can handle a very large volume of live, active traffic. You will then learn how to scale your application and make it capable of being expanded in scope, design, and/ or capacity. It will then focus on when and where to implement concurrent patterns, utilize parallelism, and ensure data consistency. At the end of this module, we will be logging and testing concurrency before we finally look at the best practices on how to implement complicated and advanced techniques offered by Go.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wielun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值