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)
}