Go Web

scheme://host[:port#]/path/…/[?query-string][#anchor]

  • scheme:指定底层使用的协议
  • host:HTTP 服务器的 IP 地址或者域名
  • port#:HTTP 服务器的端口号,默认端口是 80(对于 HTTP)或 443(对于 HTTPS),如果使用了其他端口,必须指明
  • path:访问资源的路径
  • query-string:发送给 HTTP 服务器的数据,通常用于传递参数
  • anchor:锚,用于指定页面中的某个位置

Go Web项目结构

把静态问及教案放入到特定的文件夹中,使用Go语言的文件服务就可以加载

--项目名
	--src
	--static
		--css
		--images
		--js
	--view
		--index.html
	--main.go

静态资源不需要被服务器解析
view视图
go语言只支持模板,不支持动态服务端脚本语言
html/template 包提供了html模板支持,把HTML当作模板可以在访问控制器时显示HTML模板
通过控制器访问HTML,控制器请求页面

搭建web服务器

  • r.ParseForm
    • 默认情况下,会解析最多10MB的请求体数据,通过 http.MaxFormSize调整限制
    • 解析HTTP请求中的表单数据,并将其存储到r.Form和r.PostForm中
    • 请求体的 MIME 类型不是 application/x-www-form-urlencoded,ParseForm() 不会解析请求体(例如:application/json类型)
    • 对于文件上传(multipart/form-data),需要使用 r.ParseMultipartForm(),而不是 r.ParseForm()
  • r.ParseMultipartForm
// 参数用于指定解析表单数据的内存限制,以字节为单位
func (r *Request) ParseMultipartForm(maxMemory int64) error
  • r.Form和r.PostForm
    • 当直接访问 r.Form 或 r.PostForm时,Go需要手动调用ParseForm
    • map[string][]string 类型
    • r.Form:用于存储查询参数(URL中的 ?key=value 部分)
      • 请求体中的表单数据(如 POST 请求的表单数据)
      • 如果查询参数和请求体中有重复的键,请求体中的值会覆盖查询参数中的值
    • r.PostForm:仅存储请求体中的表单数据
      • 如 POST 请求的 application/x-www-form-urlencoded 或 multipart/form-data 数据
  • Fprintf
    • 将格式化的数据写入到io.Writer接口实现的对象中,向流中推入信息
    • 参数一:实现io.Writer接口的对象
    • 参数二:格式化字符串
    • 参数三:是一个可变参数列表,提供格式化字符串中的参数
  • http.HandleFunc
    • 将请求路径与处理函数关联
    • 参数一:请求的路径,访问时资源路径
    • 参数二:处理匹配该路径的HTTP请求,函数签名必须是func(http.ResponseWriter, *http.Request)
  • http.ListenAndServe
    • 监听指定的网络地址,并使用默认的http.Handler处理传入的请求
    • 参数一:监听的网络地址":port"或"host:port",默认本地主机IP地址(0.0.0.0)
    • 参数二:实现http.Handler接口的对象,处理传入的HTTP请求,nil 默认使用http.DefaultServeMux(默认的多路复用器)
package main
import (
    "fmt"
    "net/http"
    "strings"
    "log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()  // 解析参数,默认是不会解析的
    fmt.Println(r.Form)  // 这些信息是输出到服务器端的打印信息
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println("raw URL", r.URL.String())
    fmt.Println(r.Form["url_long"])
    for k, v := range r.Form {
        fmt.Println("key:", k)
        fmt.Println("val:", strings.Join(v, ""))
    }
    fmt.Fprintf(w, "Hello astaxie!") // 这个写入到 w 的是输出到客户端的
}
func main() {
    http.HandleFunc("/", sayhelloName) // 设置访问的路由
    err := http.ListenAndServe(":9090", nil) // 设置监听的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
/*map[]
path /
scheme
raw URL /
[]
// 请求路径中没有指明协议,所以没有获取到
map[]
path /favicon.ico
scheme
raw URL /favicon.ico
[]*/

在这里插入图片描述

  • application/json:用于表示 JSON 格式的请求体,适用于现代 Web API 中的大部分场景
  • application/x-www-form-urlencoded:默认的表单提交格式,适用于简单的键值对数据提交
  • multipart/form-data:用于上传文件或发送复杂的数据结构,比如带有文件上传的表单
  • text/plain:用于纯文本数据,比如日志文件或简单的文本信息
// 指定请求体的MIME类型
r.Header().Set("Content-Type", "application/json")
func welcome(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "服务器返回的信息<b>加粗</b>")
}

func main() {
	http.HandleFunc("/", welcome)
	http.ListenAndServe("localhost:8081", nil)
}

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

func welcome(w http.ResponseWriter, r *http.Request) {
	// 默认响应头是纯文本数据
	// 修改为超文本标记语言
	w.Header().Set("Content-Type", "text/html;charset=utf-8")
	fmt.Fprint(w, "服务器返回的信息<b>加粗</b>")
}

func main() {
	http.HandleFunc("/", welcome)
	http.ListenAndServe("localhost:8081", nil)
}

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

单控制器

在Golang的net/http包下有ServeMux实现了Front设计模式的Front窗口(主入口),ServeMux负责接收请求并把请求(拆分请求)分发给处理器(Handler控制单元)
net/http包中server.go

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry 
	hosts bool     
}
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

自定义结构体,实现Handler接口后,该结构体就属于一个处理器,可以处理全部请求
任何资源地址都可以访问ServeHTTP
一个结构体处理所有指定请求路径下的请求

type MyHandler struct {
}

func (mh *MyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("返回的数据"))
}
func main() {
	myhandler := MyHandler{}
	// 在这里面进行绑定只能绑定一个处理器
	server := http.Server{
		Addr: "localhost:8090",
		// 绑定一个处理器,所有请求都会执行ServeHTTP
		Handler: &myhandler,
	}
	server.ListenAndServe()
}

多控制器

不同的请求通过不同的处理单元处理

  • 多处理器(Handler)
    使用http.Handle把不同的URL绑定到不同的处理器
type MyHandler struct {
}
type MyOtherHandler struct {
}
func (mh *MyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("返回的数据"))
}
func (mh *MyOtherHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("返回的数据"))
}
func main() {
	myhandler := MyHandler{}
	myother := MyOtherHandler{}
	server := http.Server{
		Addr: "localhost:8090",
	}
	// 将不同的请求绑定到对应的处理器上
	// 当调用ServeHTTP时使用的是指针类型,所以不能传递值类型
	http.Handle("/myhandler", &myhandler)
	http.Handle("/myother", &myother)
	server.ListenAndServe()
}
  • 多处理函数(HandleFunc)
    直接将资源路径和函数进行绑定
func first(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("第一个"))
}
func second(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("第二个"))
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/first", first)
	http.HandleFunc("/second", second)
	server.ListenAndServe()
}
  • fmt.Fprintln:
    用于格式化字符串并将其写入到一个 io.Writer 接口类型的对象中
    通常用于向 HTTP 响应写入格式化的文本内容
    自动在字符串末尾添加换行符
  • w.Write:
    直接将字节切片写入到 HTTP 响应中
    更底层,适合直接操作字节数据
    不会自动添加换行符

获取请求头

在这里插入图片描述

func param(w http.ResponseWriter, r *http.Request) {
	// 获取请求头所有内容
	// map[string][]string类型
	h := r.Header
	fmt.Fprintln(w, h)
	fmt.Fprintln(w, h["Accept-Encoding"])
	fmt.Fprintln(w, len(h["Accept-Encoding"]))
	// 通过for循环获取切片中的值
	for _, s := range strings.Split(h["Accept-Encoding"][0], ",") {
		fmt.Fprintln(w, strings.TrimSpace(s))
	}
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/param", param)
	server.ListenAndServe()
}

在这里插入图片描述

获取请求参数

func param(w http.ResponseWriter, r *http.Request) {
	// map[string][]string类型
	// 存放请求的参数
	// 必须先将参数解析为form
	r.ParseForm()
	fmt.Fprintln(w, r.Form)
	// 常用于当多个值的参数名相同时
	fmt.Fprintln(w, strings.TrimSpace(r.Form["name"][0]))
	// 会将后面的值写入响应体中
	fmt.Fprintln(w, r.FormValue("age"))
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/param", param)
	server.ListenAndServe()
}

在这里插入图片描述

获取JSON数据

type getJs struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func getJson(w http.ResponseWriter, r *http.Request) {
	// 获取请求体中的数据
	res := r.Body
	file, _ := io.ReadAll(res)
	var js getJs
	json.Unmarshal(file, &js)
	fmt.Fprintln(w, js)
	//{zhangsan 18}
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/json", getJson)
	server.ListenAndServe()
}

HTML模板显示

template.ParseFiles()
创建一个新的模板,并从指定名称的文件中解析模板定义。返回的模板将使用第一个文件的基本名称和解析后的内容。必须至少指定一个文件。如果发生错误,解析会停止,并且返回的 *Template 将为 nil。
当解析多个位于不同目录但具有相同名称的文件时,最后提到的那个文件将是最终的结果。例如,ParseFiles(“a/foo”, “b/foo”) 会将 “b/foo” 存储为名为 “foo” 的模板,而 “a/foo” 将不可用

func ParseFiles(filenames ...string) (*Template, error) {
	return parseFiles(nil, readFileOS, filenames...)
}

把模板信息响应写入到输出流中
将已解析的模板应用到指定的数据对象上,并将输出写入到 wr(输出写入器)中。如果在执行模板或写入输出时发生错误,执行会停止,但部分结果可能已经写入到输出写入器中。一个模板可以安全地在多个 goroutine 中并发执行,但如果多个并发执行共享同一个写入器,输出可能会交错

func (t *Template) Execute(wr io.Writer, data any) error {
	if err := t.escape(); err != nil {
		return err
	}
	return t.text.Execute(wr, data)
}

通过服务器获取index和login资源

func welcome(w http.ResponseWriter, r *http.Request) {
	// 解析当前项目下的指定文件
	t, _ := template.ParseFiles("view/index.html")
	// 参数二:传入的数值
	t.Execute(w, nil)
}
func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/login", login)
	server.ListenAndServe()
}

在这里插入图片描述
加载静态资源并使其生效

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/login", login)
	// 加载static下的静态资源文件,不需要被服务端解析的内容
	// 让文件服务生效
	// 当发现请求路径以/static开头,就把请求转发给指定的路径
	// http://localhost:8090/static/js/index.js
	// 当检索到/static/开头的请求,就交由指定的控制器处理
	// http.StripPrefix控制器:
	// 判断请求路径是否由严格的指定路径开头
	// 是,就将前缀后的路径转发到指定的路径
	// 否,则认为该请求的是一个handleFunc
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	server.ListenAndServe()
}

在这里插入图片描述

向模版传递数据

在HTML中通过{{}}获取template.Execute()第二个参数传递的值(interface{})
html中常用{{.}},“.“是指针,指向当前变量(第二个参数),称为"dot”
在{{}}中可以有:
在这里插入图片描述
无类型,可以通过”."获取到,可以多次获取

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	t.Execute(w, "欢迎来到本系统")
}

在这里插入图片描述
在这里插入图片描述
获取结构体类型
结构体字段名必须大写,否则无法正确获取

type user struct {
	name string
	age  int
}

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

type user struct {
	Name string
	Age  int
}
func welcome(w http.ResponseWriter, r *http.Request) {
	// 解析当前项目下的指定文件
	t, _ := template.ParseFiles("view/index.html")
	// 参数二:传入的数值
	t.Execute(w, nil)
}
func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	t.Execute(w, user{"周六", 18})
}

在这里插入图片描述
在这里插入图片描述
传递map类型数据

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	m := make(map[string]interface{})
	m["user"] = user{"周六", 18}
	m["money"] = 9999
	t.Execute(w, m)
}

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

调用方法

在模版中调用函数时,如果是无参函数直接调用函数名,没有括号
有参函数时参数和函数名之间有空格,参数与参数之间也有空格

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	// 创建时间
	time1 := time.Date(2024, 4, 18, 13, 14, 52, 0, time.Local)
	// 获取到年
	//time1.Year()
	// 指定时间的格式
	//time1.Format()
	t.Execute(w, time1)
}

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

调用自定义函数

模版想要调用函数,前提是模版中已经存在该函数
当调用自定义函数,需要借助html/template包下的FuncMap进行映射
FuncMap 本质是map的别名map[string]interface{}
函数被添加映射后,只能通过函数在FuncMap中的key调用函数(而不是函数名调用)

func myTransfer(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}
func login1(w http.ResponseWriter, r *http.Request) {
	// 把自定义函数绑定到FuncMap上
	fm := template.FuncMap{"mt": myTransfer}
	// 绑定FuncMap和指定模版
	// 先新建一个空模板,进行绑定
	t := template.New("login.html").Funcs(fm)
	// 再将指定模版解析到空模板中
	t, _ = t.ParseFiles("view/login.html")
	time1 := time.Date(2024, 4, 18, 13, 14, 52, 0, time.Local)
	t.Execute(w, time1)
}

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

Action

Arguments和pipelines代表数据执行的结果
主要完成流程控制,循环,模版等操作,通过使用action可以在模版中完成简单逻辑处理(复杂逻辑处理在go中实现,传递给模版是数据是加工完的)
在这里插入图片描述

if使用

if在模版和go中的功能相同,语法不同
布尔函数会将任何类型的零值视为假,其余为真
if后面的表达式若包含逻辑控制符在模版中实际上是全局函数
全局函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
if用法

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	// false
	t.Execute(w, "")
}

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

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	// true
	t.Execute(w, "123")
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
if…else if…else用法
在这里插入图片描述
模版之间可以相互嵌套
在这里插入图片描述
range用法
range 遍历数组或切片或map或channel时,在range内容中{{.}}表示获取迭代变量

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	s := []string{"第一个", "第二个"}
	t.Execute(w, s)
}

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

func login(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/login.html")
	m := map[string]string{
		"k1": "v1",
		"k2": "v2",
	}
	// 直接输出里面的值
	t.Execute(w, m)
}

在这里插入图片描述

模版嵌套

在实际项目中经常出现页面复用的情况
可以使用动作{{template “模版名称”}}引用模版
引用的模版必须在HTML中定义这个模版
执行主模版时也要给主模版一个名称,执行时调用的是ExecuteTemplate()方法

func welcome(w http.ResponseWriter, r *http.Request) {
	// 加载所有需要的文件
	t, _ := template.ParseFiles("view/layout.html", "view/head.html", "view/foot.html")
	// 执行主模版
	t.ExecuteTemplate(w, "layout", nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	server.ListenAndServe()
}

主模版

{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{template "head"}}<br/>
中间内容<br/>
{{template "foot"}}
</body>
</html>
{{end}}

被嵌套模版

{{define "head"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  head.html
</body>
</html>
{{end}}
{{define "foot"}}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
foot.html
</body>
</html>
{{end}}

http://localhost:8090/login
在这里插入图片描述

调用模版同时传递参数

直接引用HTML可以直接使用HTML标签的<iframe>,但要实现动态效果,可以在调用模版给模版传递参数

{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{template "head" "传递head"}}<br/>
中间内容<br/>
{{template "foot" "传递head"}}
</body>
</html>
{{end}}
{{define "head"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  head.html
  {{.}}
</body>
</html>
{{end}}
{{define "foot"}}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  foot.html
  {{.}}
</body>
</html>
{{end}}

在这里插入图片描述

文件上传

客户端把上传的文件转换为二进制流后发送给服务器,服务器对二进制流进行解析,然后把数据保存到服务器端
HTML表单(form)enctype(Encode Type)属性控制表单在提交数据到服务器时数据的编码类型
在这里插入图片描述
file类型需要通过该方法进行获取,其他类型还是普通方法
可以使用FormFile获取上传的文件
FormFile 方法返回指定表单键对应的第一个文件
如果需要,它会调用 ParseMultipartForm 和 ParseForm 方法

// 参数一:文件结构体
// 参数二:文件头,包含文件的相关信息
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
	if r.MultipartForm == multipartByReader {
		return nil, nil, errors.New("http: multipart handled by MultipartReader")
	}
	if r.MultipartForm == nil {
		err := r.ParseMultipartForm(defaultMaxMemory)
		if err != nil {
			return nil, nil, err
		}
	}
	if r.MultipartForm != nil && r.MultipartForm.File != nil {
		if fhs := r.MultipartForm.File[key]; len(fhs) > 0 {
			f, err := fhs[0].Open()
			return f, fhs[0], err
		}
	}
	return nil, nil, ErrMissingFile
}

multipart.File文件对象

type File interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

封装文件的基本信息

type FileHeader struct {
	Filename string //文件名
	Header   textproto.MIMEHeader //MIME信息
	Size     int64 // 文件大小,单位bit

	content   []byte // 文件内容,类型[]byte
	tmpfile   string // 临时文件
	tmpoff    int64
	tmpshared bool
}

在这里插入图片描述

在这里插入图片描述

func upload(w http.ResponseWriter, r *http.Request) {
	// 获取普通表单数据
	username := r.FormValue("username")
	fmt.Println(username)
	// 获取文件流
	file, header, _ := r.FormFile("photo")
	// 读取文件流为[]byte
	b, _ := ioutil.ReadAll(file)
	// 获取文件的后缀,先获取到.最后出现的索引
	index := strings.LastIndex(header.Filename, ".")
	// 把文件保存在指定位置
	// 0777文件具有读写和执行的权限
	ioutil.WriteFile("G:/goProject/src/web/document/"+username+header.Filename[index:], b, 0777)
	fmt.Println("上传文件名:", header.Filename)
}
func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/test.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)

	http.HandleFunc("/upload", upload)
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
</head>
<body>
    <!--application/x-www-form-urlencoded 不会将文件进行解析并上传
    multipart/form-data 只有可以将文件解析并上传
    text/plain 常用于邮箱-->
    <form action="upload" enctype="multipart/form-data" method="post">
        用户名:<input type="text" name="username"/> <br/>
        密码:<input type="file" name="photo"/> <br/>
        <input type="submit" value="注册"/>
    </form>
</body>
</html>

在这里插入图片描述

文件下载

服务器端把数据返回给客户端,客户端对文件进行解析
客户端向服务端发送请求,请求参数包含要下载文件的名称
服务器接收到客户端请求后把文件设置到响应对象中,响应给客户端浏览器
下载时需要设置响应头信息

  • Content-Type:内容MIME类型,判断返回文件的类型
    • application/octet-stream 任意类型
  • Content-Disposition:客户端对内容的操作方式
    • inline 默认值,表示浏览器能解析就解析,不能解析下载
    • attachment;filename=下载时显示的文件名,客户端浏览器恒下载
func download(w http.ResponseWriter, r *http.Request) {
	// 获取普通表单数据
	filename := r.FormValue("filename")
	fmt.Println(filename)
	// 获取到指定名称的文件
	f, err := ioutil.ReadFile("G:/goProject/src/web/document/" + filename)
	if err != nil {
		fmt.Println("文件不存在,下载失败")
	}
	// 下载文件,将文件转换为二进制流的形式
	// f是字节切片类型
	w.Write(f)
}
func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/download.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)

	http.HandleFunc("/download", download)
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件下载</title>
</head>
<body>
    <a href="download?filename=实验1.docx">下载</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件下载</title>
</head>
<body>
    <a href="download?filename=abc.png">下载</a>
</body>
</html>

在这里插入图片描述
浏览器可以将图片识别为对应格式进行解析,并显示在浏览器上
在这里插入图片描述
对于文件,下载一个download.zip包
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
只调整返回文件的类型为application/octet-stream

func download(w http.ResponseWriter, r *http.Request) {
	// 获取普通表单数据
	filename := r.FormValue("filename")
	fmt.Println(filename)
	// 获取到指定名称的文件
	f, err := ioutil.ReadFile("G:/goProject/src/web/document/" + filename)
	if err != nil {
		fmt.Println("文件不存在,下载失败")
	}
	// 获取到响应头
	h := w.Header()
	// 设置返回的文件类型,当前浏览器首字母大小写都可以识别
	h.Set("Content-Type", "application/octet-stream")
	// 下载文件,将文件转换为二进制流的形式
	// f是字节切片类型
	w.Write(f)
}

下载为download文件,内容乱码,图片也是
在这里插入图片描述
设置操作方式

func download(w http.ResponseWriter, r *http.Request) {
	// 获取普通表单数据
	filename := r.FormValue("filename")
	fmt.Println(filename)
	// 获取到指定名称的文件
	f, err := ioutil.ReadFile("G:/goProject/src/web/document/" + filename)
	if err != nil {
		fmt.Println("文件不存在,下载失败")
	}
	// 获取到响应头
	h := w.Header()
	// 设置返回的文件类型,当前浏览器首字母大小写都可以识别
	h.Set("Content-Type", "application/octet-stream")
	// 设置浏览器对内容的操作方式
	h.Set("Content-Disposition", "attachment;filename="+filename)
	
	// 下载文件,将文件转换为二进制流的形式
	// f是字节切片类型
	w.Write(f)
}

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

ajax请求返回json数据

type user struct {
	Name string
	Age  int
}
func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/table.html")
	t.Execute(w, nil)
}
func showUser(w http.ResponseWriter, r *http.Request) {
	// 输出一个json字符串
	us := make([]user, 0)
	us = append(us, user{"周六", 18},
		user{"钱五", 20},
		user{"李四", 22})
	b, _ := json.Marshal(us)
	fmt.Fprintln(w, string(b))
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/showUser", showUser)
	server.ListenAndServe()
}

在这里插入图片描述
在这里插入图片描述
设置为json字符串

func showUser(w http.ResponseWriter, r *http.Request) {
	// 输出一个json字符串
	us := make([]user, 0)
	us = append(us, user{"周六", 18},
		user{"钱五", 20},
		user{"李四", 22})
	b, _ := json.Marshal(us)
	// 默认识别是普通字符串
	// 指定是json字符串
	w.Header().Set("content-type", "application/json; charset=utf-8")
	fmt.Fprintln(w, string(b))
}

在这里插入图片描述
在这里插入图片描述
将返回的数据插入指定位置

type user struct {
	Name string
	Age  int
}

func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/json.html")
	t.Execute(w, nil)
}
func showUser(w http.ResponseWriter, r *http.Request) {
	// 输出一个json字符串
	us := make([]user, 0)
	us = append(us, user{"周六", 18},
		user{"钱五", 20},
		user{"李四", 22})
	b, _ := json.Marshal(us)
	// 默认识别是普通字符串
	// 指定是json字符串
	w.Header().Set("content-type", "application/json;charset=utf-8")
	fmt.Fprintln(w, string(b))
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/showUser", showUser)
	// 加载静态数据
	// http.FileServer文件系统
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="../static/js/jquery-3.7.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $("button").click(function () {
                // ajax请求,data是返回值
                $.post("/showUser",function(data){
                    // 解析数据,直接将json数值当作普通数组使用
                    var result = ""
                    for (var i = 0; i < data.length; i++) {
                        //将数据进行拼接后放入表格
                        result += "<tr>"
                        result += "<td>"+data[i].Name+"</td>"
                        result += "<td>"+data[i].Age+"</td>"
                        result += "</tr>"     
                    }
                    $("#mytbody").html(result)
                })
            })
        })
    </script>
</head>
<body>
    <!-- 点击按钮后把数据加载到表格中显示 -->
    <button>加载数据</button>
    <!-- 表格 -->
    <table border="1">
        <!-- 表头 -->
        <tr>
            <td>姓名</td>
            <td>年龄</td>
        </tr>
        <!-- 表体 -->
        <tbody id="mytbody">
        </tbody>
    </table>
</body>
</html>

在这里插入图片描述

正则表达式

常用正则:

  • \+特殊字符:代表某个取值范围
  • [内容]:代表一个字符
  • {n,m}个数,大于等于n小于等于m个
  • . 一个任意内容的字符
  • ^ 开始
  • $ 结束
  • + 至少一个
  • * 任意个
  • ? 最多一个
func main() {
	// 正则表达式规范长度,内容,和每一位的取值范围
	// 出现数字,大小写字母,下划线
	r, _ := regexp.MatchString(`\w`, "abc123")
	fmt.Println(r) // true
	r, _ = regexp.MatchString(`\w`, "[]';.,")
	fmt.Println(r) // false
	// 出现数字就匹配
	r, _ = regexp.MatchString(`\d`, "abcdefg")
	fmt.Println(r) // false
	r, _ = regexp.MatchString(`\d`, "123e45")
	fmt.Println(r) // true
	// 出现非数字就匹配
	r, _ = regexp.MatchString(`\D`, "123e45")
	fmt.Println(r) // true
	// 从开头,第一个数字必须满足匹配
	r, _ = regexp.MatchString(`^\D`, "123e45")
	fmt.Println(r) // false
	r, _ = regexp.MatchString(`^\D`, "t23e45")
	fmt.Println(r) // true
	// 从开头到结束只有一个字符,且这个字符要满足匹配条件,验证字符串长度为1
	// 一个正则字符代表一个字符
	r, _ = regexp.MatchString(`^\D$`, "123e45")
	fmt.Println(r) // false
	r, _ = regexp.MatchString(`^\D$`, "1")
	fmt.Println(r) // false
	r, _ = regexp.MatchString(`^\D$`, "e")
	fmt.Println(r) // true
	// 前面多个字母,后面多个数字
	r, _ = regexp.MatchString(`^\D+\d+$`, "adtfg1567")
	fmt.Println(r) // true
	r, _ = regexp.MatchString(`^\D+\d+$`, "ad66g1567")
	fmt.Println(r) // false
	r, _ = regexp.MatchString(`^\D+\d+$`, "1567adtfg")
	fmt.Println(r) // false
// 建立正则表达式的对象
	r := regexp.MustCompile(`\d[a-z0-9]`)
	// 通过该对象进行检验
	// 当字符串中有满足一个数字+一个数字或字母的子串就匹配
	fmt.Println(r.MatchString("e3!"))  // false
	fmt.Println(r.MatchString("4e3!")) // true

	// 字符串中满足要求的子串片段,返回[]string
	// 第二个参数是[]string的长度,-1表示不限制长度
	fmt.Println(r.FindAllString("66t7ttt!888u", -1))
	// [66 7t 88 8u]
	// 取出切片内指定数量的元素
	fmt.Println(r.FindAllString("66t7ttt!888u", 2))
	// [66 7t]

	// 把正则表达式匹配的结果当作拆分符,拆分字符串
	// n>0 返回最多n个字符串,最后一个子字符串是剩余未进行分割的部分
	// n==0 返回nil
	// n<0 返回所有子字符串
	fmt.Println(r.Split("66t7ttt!888u", -1)) // [ t tt!  ]
	fmt.Println(r.Split("66t7ttt!888u", 2))  // [ t7ttt!888u]
	fmt.Println(r.Split("66t7ttt!888u", 0))  // []

	// 将匹配的子串进行指定替换
	fmt.Println(r.ReplaceAllString("66t7ttt!888u", "替换"))
	// 替换t替换tt!替换替换
}

双重验证保证数据安全性,防止非法途径访问服务器

  • 通过客户端js脚本验证
  • 服务器再次验证
func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/register.html")
	t.Execute(w, nil)
}
func register(w http.ResponseWriter, r *http.Request) {
	us := r.FormValue("username")
	// 确保每一位都满足要求
	rg, _ := regexp.MatchString(`^[0-9a-zA-Z]{6,12}$`, us)
	if rg {
		fmt.Fprintln(w, "注册成功,数据保存在数据库中")
	} else {
		fmt.Fprintln(w, "格式不正确")
	}
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/register", register)
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="register" method="post">
        用户名:<input type="text" name="username"/>用户名必须6-12位,只能包含字符或数字<br/>
        <input type="submit" value="注册"> 
    </form>
</body>
</html>

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

Cookie

net/http 包下

type Cookie struct {
	Name  string // 设置Cookie的名称
	Value string // 表示Cookie的值
	Path       string    // 有效范围
	Domain     string    // 可访问Cookie的域
	Expires    time.Time // 过期时间
	RawExpires string    
	MaxAge   int // 最大存活时间,单位秒
	Secure   bool
	HttpOnly bool // 是否可以通过脚本访问
	SameSite SameSite
	Raw      string
	Unparsed []string 
}

服务端没有向客户端传递Cookie
在这里插入图片描述
设置Cookie

func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/cookie.html")
	t.Execute(w, nil)
}
func setCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:  "mykey",
		Value: "myvalue",
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/cookie.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/setCookie", setCookie)
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <a href="setCookie">产生Cookie</a>
</body>
</html>

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

常用设置

HttpOnly
控制Cookie的内容是否可以被JavaScript访问,通过设置该值为true防止XSS攻击
默认设置为false,表示客户端可以通过js获取
在项目中导入jquery.cookie.js库,使用jquery获取客户端Cookie内容
通过脚本获取到Cookie

func welcome(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}
func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:  "mykey",
		Value: "myvalue",
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/doCookie", doCookie)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="/static/js/jquery-3.7.1.js"></script>
    <script src="/static/js/jquery.cookie.js"></script>
    <script type="text/javascript">
        $(function(){
            $("button").click(function(){
                var $value = $.cookie("mykey")
                alert($value)
            })
        })
    </script>
</head>
<body>
    <a href="doCookie">产生Cookie</a>
    <button>获取Cookie</button>
</body>
</html>

在这里插入图片描述
设置HttpOnly

func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:     "mykey",
		Value:    "myvalue",
		HttpOnly: true,
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}

在这里插入图片描述
Path
设置Cookie的访问范围
默认为"/"表示当前项目下所有都可以访问
Path设置路径及子路径内容都可以访问
设置/abc/路径及其子路径可以访问Cookie

func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:  "mykey",
		Value: "myvalue",
		Path:  "/abc/",
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}
func abc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, r.Cookies())
}
func main() {
	server := http.Server{
		Addr: "localhost:8090",
	}
	http.HandleFunc("/", welcome)
	http.HandleFunc("/doCookie", doCookie)
	http.HandleFunc("/abc/jqk", abc)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	server.ListenAndServe()
}

http://localhost:8090/doCookie
在这里插入图片描述
http://localhost:8090/abc/jqk
在这里插入图片描述
在这里插入图片描述
Expires
Cookie默认存活时间是浏览器不关闭,当浏览器关闭后,Cookie失效
可以通过Expires设置具体过期时间
也可以通过MaxAge设置Cookie多长时间后实现
Expires是time.Time类型,所以设置时需要明确设置过期时间

func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:   "mykey",
		Value:  "myvalue",
		MaxAge: 10, // 10秒后过期
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}

http://localhost:8090/
10s内
在这里插入图片描述
10s后
在这里插入图片描述

func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:    "mykey",
		Value:   "myvalue",
		Expires: time.Date(2024, 4, 18, 13, 14, 52, 0, time.Local),
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}

Domain

func doCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:  "mykey",
		Value: "myvalue",
		// 只有在指定的域名下可以访问
		// .baidu.com 只要包含该字段的域名都可以访问
		Domain: "www.baidu.com",
	}
	http.SetCookie(w, &c)
	t, _ := template.ParseFiles("view/setting.html")
	t.Execute(w, nil)
}

多路复用

将满足特定格式的url映射到同一个func
对url进行解析,然后重定向到正确的处理器上
在这里插入图片描述

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

平时使用http.Server不指定Handler属性时默认就是DefaultServeMux

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

使用第三方实现Restful风格

go get github.com/gorilla/mux
func hello(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	// vars["key"] 通过名称取出值,填写到对应的url
	fmt.Fprintln(w, "dayinle", vars["url"])
}
func abc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "abc")
}
func main() {
	// 创建路由
	r := mux.NewRouter()
	// {key} 变量
	// 只能是/hello/...的格式其他的都无法访问
	r.HandleFunc("/hello/{url}", hello)
	r.HandleFunc("/abc", abc)
	http.ListenAndServe(":8090", r)
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值