TCP/IP协议
目前大规模使用的是TCP/IP协议
应用层:
合并osi中的5,6,7层 (会话层,表示层,应用层)
常用协议:HTTP,FTP,SMTP,POP3,RPC
传输层:
OSI中第四层
常用协议TCP,UDP
网络层:
OSI中第3层
常用协议:IP,IPV4,IPV6
网络接口层
OSI中第1,2层
UDP:
User Datagram Protocol 用户 数据报协议
是一种无连接的协议
基于UDP协议主机把数据包发送给网络后就不管了,是一种不可靠协议
TCP和UDP的主要区别:
TCP是安全可靠的,UDP是不安全的,不可靠的。
UDP的速度高于TCP
go语言对socket的支持:
socket分类:
按照连接时间:
- 短连接
- 长连接
按照客户端和服务器数量:
- 点对点
- 点对多
- 多对多
TCPAddr结构体表示服务器IP和端口:
// TCPAddr represents the address of a TCP end point.
type TCPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone
}
TCPConn结构体表示连接,封装了数据读写操作:
// TCPConn is an implementation of the [Conn] interface for TCP network
// connections.
type TCPConn struct {
conn
}
TCPListener负责监听服务器特定端口:
// TCPListener is a TCP network listener. Clients should typically
// use variables of type [Listener] instead of assuming TCP.
type TCPListener struct {
fd *netFD
lc ListenConfig
}
接口,切片,map,channel都是引用类型,不用传指针
HTTP请求包
我们先来看看 Request 包的结构,Request 包分为 3 部分,第一部分叫 Request line(请求行), 第二部分叫 Request header(请求头), 第三部分是 body(主体)。header 和 body 之间有个空行,请求包的例子所示:
GET /domains/example/ HTTP/1.1 // 请求行: 请求方法 请求 URI HTTP 协议/协议版本
Host:www.iana.org // 服务端的主机名
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 // 浏览器信息
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 // 客户端能接收的 mine
Accept-Encoding:gzip,deflate,sdch // 是否支持流压缩
Accept-Charset:UTF-8,*;q=0.5 // 客户端字符编码集
// 空行,用于分割请求头和消息体
// 消息体,请求资源参数,例如 POST 传递的参数
HTTP响应包
HTTP/1.1 200 OK // 状态行
Server: nginx/1.0.8 // 服务器使用的 WEB 软件名及版本
Date: Tue, 30 Oct 2012 04:14:25 GMT // 发送时间
Content-Type: text/html // 服务器发送信息的类型
Transfer-Encoding: chunked // 表示发送 HTTP 包是分段发的
Connection: keep-alive // 保持连接状态
Content-Length: 90 // 主体内容长度
// 空行 用来分割消息头和主体
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... // 消息体
软件模型分类:
- B/S结构,客户端浏览器/服务器,客户端是运行在浏览器中
- C/S结构,客户端/服务器,客户端是独立软件
控制器:
单控制器:
package main
import (
"fmt"
"net/http"
)
//单控制器
type MyHandler struct {
}
func (mh *MyHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
fmt.Println(res, "输入内容。")
res.Write([]byte("我叫xxx"))
}
func main() {
myhandler := MyHandler{
}
server := http.Server{
Addr: "localhost:8090",
Handler: &myhandler,
}
server.ListenAndServe()
}
多控制器:
在实际开发中大部分情况是不应该只有一个控制器的,不同的请求应该交给不同的处理单元,在Golang中支持两中处理方式
- 多个处理器(Handler)
- 多个处理函数(HandleFunc)(推荐)
使用多处理器
- 使用http.handler把不同的URL绑定到不同的处理器
- 在浏览器输入不同路径,对应不同处理器方法,但是其他URL会出现404资源未找到页面
多处理器:
package main
import (
"fmt"
"net/http"
)
//多控制器 多处理器
type MyHandler struct {
}
func (mh *MyHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
fmt.Println(res, "输入内容。")
res.Write([]byte("我叫魏正想"))
}
func main() {
myhandler := MyHandler{
}
myhandler2 := MyHandler{
}
server := http.Server{
Addr: "localhost:8090",
}
http.Handle("/fir", &myhandler)
http.Handle("/sec", &myhandler2)
err := server.ListenAndServe()
if err != nil {
return
}
}
多处理函数:
package main
import (
"fmt"
"net/http"
"time"
)
//多控制器 多处理器
func first(res http.ResponseWriter, req *http.Request) {
fmt.Println(res, "输入内容。")
res.Write([]byte("first"))
}
func second(res http.ResponseWriter, req *http.Request) {
fmt.Println(res, "输入内容。")
res.Write([]byte("second"))
}
func main() {
// 1. 创建独立的路由表
mux := http.NewServeMux()
mux.HandleFunc("/fir", first)
mux.HandleFunc("/sec", second)
// 2. 创建服务器,并显式绑定这个独立路由表
server := &http.Server{
Addr: "localhost:8090",
Handler: mux, // 显式指定!这样就不会受全局路由干扰
ReadTimeout: 5 * time.Second, // 可以配置超时,这是用 struct 的最大优势
WriteTimeout: 10 * time.Second,
}
// 3. 启动
server.ListenAndServe()
//http.ListenAndServe("123",mux)
}
向前端html模板传递数据
- 可以在HTML中使用{ {}}获取template.Execte()第二个参数传递
- 最常用的{ {.}}中的“.”是指针,指向当前变量,成为“dot”
- 在{ {}}可以有的Argument,官方给定如下
- 如果是字符串直接**{ {.}}**
- 如果结构体类型数据就是**{ {.field}}**
- 传递map类型数据**{ {.key}}** 深度不限,如果value是一个对象还可以 { {.field1.key1.field2.key2}}
type User struct {
Name string
Age int
Tim time.Time
}
// 模板中调用字符串
func fir(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("./view/index.html")
t.Execute(w, "wzx")
}
// 模板中调用结构体
//
// func sec(w http.ResponseWriter, r *http.Request) {
// t, _ := template.ParseFiles("./view/index.html")
// t.Execute(w, User{Name: "张三", Age: 18})
// }
//
// 模板中调用结构体
func sec(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("./view/index.html")
if err != nil {
http.Error(w, "模板解析失败: "+err.Error(), http.StatusInternalServerError)
log.Printf("sec 函数模板解析错误: %v", err)
return
}
if err := t.Execute(w, User{
Name: "张三", Age: 18}); err != nil {
http.Error(w, "模板执行失败: "+err.Error(), http.StatusInternalServerError)
log.Printf("sec 函数模板执行错误: %v", err)
}
}
// 在模板中调用函数
func thr(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("./view/index.html")
time1 := time.Date(2026, 2, 26, 8, 47, 34, 0, time.Local)
//time1.Year()
//time1.Format()
t.Execute(w, User{
Name: "张三",
Age: 18,
Tim: time1,
})
}
func MyTransfer(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
func fou(w http.ResponseWriter, r *http.Request) {
fm := template.FuncMap{
"mt": MyTransfer}
t := template.New("index.html").Funcs(fm)
t, _ = t.ParseFiles("./view/index.html")
time1 := time.Date(2026, 2, 26, 8, 47, 34, 0, time.Local)
t.Execute(w, time1)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.HandleFunc("/fir", fir)
mux.HandleFunc("/sec", sec)
mux.HandleFunc("/thr", thr)
mux.HandleFunc("/fou", fou)
server := http.Server{
Addr: ":8090",
Handler: mux,
}
server.ListenAndServe()
}
处理表单输入
login.gtpl
<html>
<head>
<title></title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="登录">
</form>
</body>
</html>
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) // 获取请求的方法
if r.Method == "GET" {
t, _ := template.ParseFiles("./view/login.gtpl")
log.Println(t.Execute(w, nil))
} else {
err := r.ParseForm() // 解析 url 传递的参数,对于 POST 则解析响应包的主体(request body)
if err != nil {
// handle error http.Error() for example
log.Fatal("ParseForm: ", err)
}
// 请求的是登录数据,那么执行登录的逻辑判断
fmt.Println("username:", r.Form["username"])
fmt.Println("password:", r.Form["password"])
}
}
func main() {
http.HandleFunc("/", sayhelloName) // 设置访问的路由
http.HandleFunc("/login", login) // 设置访问的路由
err := http.ListenAndServe(":9090", nil) // 设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
防止表单多次递交
解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该唯一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了 Ajax 模式递交表单的话,当表单递交后,通过 javascript 来禁用表单的递交按钮。
预防跨站脚本
Go 里面是怎么做这个有效防护的呢?Go 的 html/template 里面带有下面几个函数可以帮你转义
func HTMLEscape (w io.Writer, b [] byte) // 把 b 进行转义之后写到 w
func HTMLEscapeString (s string) string // 转义 s 之后返回结果字符串
func HTMLEscaper (args …interface {
}) string // 支持多个参数一起转义,返回结果字符串
文件上传
文件上传:客户端把上传文件转换为二进制后发送给服务器,服务器对二进制流进行解析
HTML表单(form)enctype(Encode Type)属性控制表单在提交数据到服务器时对数据的编码类型
<form action="upload" method="post" enctype="multipart/form-data"> //编码成消息,每个控件对应消息的一部分,请求方式必须是post
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
头像:<input type="file" name="avatar"><br>
<input type="submit" value="提交">
</form>
<form action="upload" method="post" enctype="text/plain"> //纯文本形式编码的
</form>
<form action="upload" method="post" enctype="application/x-www-form-urlencoded"> //默认值,表单数据会被编码为名称/值形式
</form>
文件方面两个关键的结构体
File
- File是一个接口,实现了对一个multipart信息中文件记录的访问。它的内容可以保持在内存或者硬盘中,如果保持在硬盘中,底层类型就会是*os.File。
type File interface {
io.Reader
io.ReaderAt
io.Seeker
io.Closer
}
FlieHeader
- FileHeader描述一个multipart请求的(一个)文件记录的信息。
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// 内含隐藏或非导出字段
}
表单类型 & 对应解析方式
1. 普通键值对表单
(application/x-www-form-urlencoded)
- 适用场景:最常见的表单类型(HTML 表单默认类型),仅包含文本类键值对(无文件),比如登录表单、搜索表单。
- 请求特征:请求体是
key1=value1&key2=value2格式的字符串,数据会被 URL 编码(比如空格转+,特殊字符转%xx)。 - Go 解析方式:
- 自动解析:
r.FormValue("key")/r.PostFormValue("key")会自动触发r.ParseForm(),无需手动调用; - 手动解析(如需批量处理):
err := r.ParseForm(),解析后可通过r.Form(GET+POST)/r.PostForm(仅 POST)获取所有键值对。
- 自动解析:
2. 文件上传表单
(multipart/form-data)
-
适用场景:包含文件的表单(如图片 / 文档上传),也可同时包含普通文本字段。
-
请求特征:请求体按 “边界符” 分割,分为多个部分(每个部分对应一个字段 / 文件),支持二进制数据(文件流)。
-
Go 解析方式:
- 必须手动调用
r.ParseMultipartForm(maxSize)(maxSize为请求体最大字节数,如10<<20=10MB); - 普通字段:
r.PostFormValue("key")(解析后才能取到); - 文件字段:
file, fileHeader, err := r.FormFile("fileKey")(获取文件流和文件信息)。
- 必须手动调用
-
关键注意点:
- 不能用
r.ParseForm()解析,否则拿不到文件和普通字段; - 解析后
r.MultipartForm可获取所有字段(包括文件),但日常用r.PostFormValue/r.FormFile更便捷; - 用完文件流后需
defer file.Close(),避免句柄泄漏。
- 不能用
3. JSON 格式表单
(application/json)
- 适用场景:前后端分离项目(如 Vue/React+Go),前端以 JSON 格式提交数据(无文件)。
- 请求特征:请求体是纯 JSON 字符串(如
{"username":"test","age":18}),Content-Type 为application/json。 - Go 解析方式:
- 无内置的 “自动解析” 方法,需手动读取请求体,再反序列化为结构体 / Map;
- 步骤:① 读取请求体字节流 ② json.Unmarshal 解析到目标变量。
func handleJSON(w http.ResponseWriter, r *http.Request) {
// 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求体失败", http.StatusBadRequest)
return
}
defer r.Body.Close()
// 反序列化JSON到结构体
var user User
err = json.Unmarshal(body, &user)
if err != nil {
http.Error(w, "JSON解析失败", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "用户名:%s,年龄:%d", user.Username, user.Age)
}
| 表单类型 | 核心解析方法 | 取值方式 | 核心区别 |
|---|---|---|---|
| application/x-www-form-urlencoded | r.ParseForm ()(可自动触发) | r.FormValue()/r.PostFormValue() | 仅文本,URL 编码,自动解析 |
| multipart/form-data | r.ParseMultipartForm(maxSize) | r.PostFormValue()/r.FormFile() | 支持文件,需手动解析,二进制 |
| application/json | 手动读 body + json.Unmarshal | 解析到结构体 / Map | 无键值对结构,纯 JSON 字符串 |
总结
- 普通文本表单:用
r.FormValue()/r.PostFormValue()即可,无需手动调用r.ParseForm()(方法内部会自动触发); - 文件上传表单:必须先手动调用
r.ParseMultipartForm(maxSize),再用r.PostFormValue()取文本、r.FormFile()取文件; - JSON 表单:没有内置解析方法,需手动读取请求体后用
json.Unmarshal反序列化,是前后端分离的主流方式。
核心原则:表单类型和解析方法必须匹配,比如文件上传用r.ParseForm()会导致字段解析失败,JSON 表单用r.FormValue()也拿不到数据。
4.文件上传代码:
index3.html
<form action="upload" method="post" enctype="multipart/form-data">
文件名:<input type="text" name="name"><br>
文件:<input type="file" name="file"><br>
<input type="submit" value="上传">
</form>
main.go
func file(w http.ResponseWriter, r *http.Request) {
files, _ := template.ParseFiles("./view/index3.html")
files.Execute(w, nil)
}
func upload(w http.ResponseWriter, r *http.Request) {
//fmt.Println(r.Header)
//const maxSize = 10 << 20 // 10MB
//r.ParseMultipartForm(maxSize)
//获取普通表单数据
fileName := r.FormValue("name")
fmt.Println(fileName)
//获取文件流,第三个返回值是错误对象
file, fileHeader, _ := r.FormFile("file")
//取出文件扩展名
//split := strings.Split(fileHeader.Filename, ".")
extensionName := fileHeader.Filename[strings.LastIndex(fileHeader.Filename, "."):]
//读取文件流[]byte
b, _ := ioutil.ReadAll(file)
//把文件保存到指定位置
ioutil.WriteFile("d:/"+fileName+extensionName, b, 0777)
//输出上传时文件名
fmt.Println("上传文件名", fileHeader.Filename)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/file", file)
mux.HandleFunc("/upload", upload)
server := http.Server{
Addr: ":8090",
Handler: mux,
}
server.ListenAndServe()
}
文件下载
文件下载总体步骤:
客户端向服务端发起请求,请求参数包含要下载文件的名称
服务器接收到客户端请求后把文件设置到响应对象中,响应给客户端浏览器
下载时需要设置的响应头信息
-
content-Type:内容MIME类型
- application/octet-stream任意类型
-
Content-Disposition:客户端对内容的操作方式
- inline默认值,表示浏览器能解析就解析,不能解析以下载形式展示给客户端
- attachment;filename=下载时显示的文件名,客户端浏览器恒下载
index3.html
<a href="download?filename=abc.png">下载</a> <form action="upload" method="post" enctype="multipart/form-data"> 文件名:<input type="text" name="name"><br> 文件:<input type="file" name="file"><br> <input type="submit" value="上传"> </form>main.go
func file(w http.ResponseWriter, r *http.Request) { files, _ := template.ParseFiles("./view/index3.html") files.Execute(w, nil) } func download(w http.ResponseWriter, r *http.Request) { //获取请求参数 filename := r.FormValue("filename") //设置响应头 header := w.Header() header.Add("Content-Type", "application/octet-stream") header.Add("Content-Disposition", "attachment;filename="+filename) //使用ioutil包读取文件 readFile, err := ioutil.ReadFile("d:/" + filename) if err != nil { fmt.Fprintln(w, "文件下载失败", err) return } //写入到响应中 w.Write(readFile) }
http请求参数分类
很多人会把URL 查询参数(?key=value)和路径参数、请求头参数弄混,这里顺带区分:
- 查询参数:
r.URL.Query().Get("key")或r.FormValue("key")(比如/user?id=123里的123); - 路径参数:
/user/123里的123(无key,靠位置识别); - 请求头参数:
Authorization: Bearer token里的token(在 Header 里)。
| 方法 | 是否能获取查询参数 | 是否会被 POST 参数干扰 | 适用场景 |
|---|---|---|---|
r.URL.Query().Get("key") |
✅ 是 | ❌ 不会 | 只想获取 URL 中的查询参数 |
r.FormValue("key") |
✅ 是 | ✅ 可能(优先级低) | 兼容 GET/POST 参数的场景 |
r.PostFormValue("key") |
❌ 否 | - | 只获取 POST 请求体中的参数 |
json简介
轻量级数据传输格式
总体上分为两种
- 一种是JSONObject(json对象)
- 一种是JSONArray(json数组),包含多个JSONObject
属性tag(键key)的配置
type User struct {
Name string `json:"Name"` //字段 在json里的键为“Name”
Age int `json:"-"` //字段被本包忽略
Tim time.Time `json:",omitempty"` //字段在json里的键默认为原来的属性“Tim”(通称Field),但如果字段为空值,就会跳过,注意前面的逗号
Sex int `json:"Sex,omitempty"` //字段在json中的键为“Sex”,且如果字段为空值将在对象中省略掉
}
处理json
func showUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
//返回405错误,方法不允许
http.Error(w, "只支持GET请求", http.StatusMethodNotAllowed)
}
var users = make([]User, 0)
users = append(users, User{
Name: "wzx", Age: 12, Tim: time.Now(), Sex: "0"})
users = append(users, User{
Name: "wzx1", Age: 18, Tim: time.Now(), Sex: "1"})
users = append(users, User{
Name: "wzx2", Age: 16, Tim: time.Now(), Sex: "0"})
header := w.Header()
header.Add("Content-Type", "application/json;charset=utf-8")
marshal, _ := json.Marshal(users)
//用js的index3_2函数
//fmt.Fprintln(w, string(marshal))
w.WriteHeader(200)
//用js的index3函数
w.Write(marshal)
}
Cookie
cookie简介
- cookie就是客户端存储技术,以键值对的形式存在
- 在B/S架构中,服务端产生Cookie响应给客户端,浏览器接收后把Cookie存在在特定的文件夹中,以后每次请求浏览器会把Cookie内容放入请求中
Go语言对Cookie的支持
在net/http包下提供了Cookie结构体
net/http包下的Cookie结构体
type Cookie struct {
Name string //设置cookie的名称
Value string //表示cookie的值
Path string //有效范围
Domain string //可访问Cookie的域
Expires time.Time //Expires过期时间
RawExpires string //最大存活时间,单位秒
// MaxAge=0表示未设置Max-Age属性
// MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
// MaxAge>0表示存在Max-Age属性,单位是秒
MaxAge int //最大存活时间,单位秒
Secure bool
HttpOnly bool //是否可以通过脚本访问
Raw string
Unparsed []string // 未解析的“属性-值”对的原始文本
}
Cookie代表一个出现在HTTP回复的头域中Set-Cookie头的值里或者HTTP请求的头域中Cookie头的值里的HTTP cookie。
注意:
响应头:可传 Set-Cookie,也可以不传
请求头:只有存过才自动带 Cookie
<body>
<a href="setCookie">产生Cookie</a>
<a href="getCookie">获取Cookie</a>
<br/>
{
{range $key, $value := .}}
键:{
{$key}},值:{
{$value}}
{
{end}}
</body>
func Cookie(w http.ResponseWriter, r *http.Request) {
files, _ := template.ParseFiles("./view/index4.html")
files.Execute(w, nil)
}
// setCookie
func setCookie(w http.ResponseWriter, r *http.Request) {
cookie := http.Cookie{
Name: "myKey", Value: "myValue"}
http.SetCookie(w, &cookie)
files, _ := template.ParseFiles("./view/index4.html")
files.Execute(w, nil)
}
// getCookie
func getCookie(w http.ResponseWriter, r *http.Request) {
//根据key来取cookie
//cookie, _ := r.Cookie("mykey")
//取出全部Cookie内容
cookies := r.Cookies()
m := make(map[string]string)
for _, n := range cookies {
m[n.Name] = n.Value
}
files, _ := template.ParseFiles("./view/index4.html")
files.Execute(w, m)
}
cookie常用设置
前文中讲的cookie结构体
type Cookie struct {
Name string //设置cookie的名称
Value string //表示cookie的值
Path string //有效范围
Domain string //可访问Cookie的域 例如(www.baidu.com)只能这个url可以访问
Expires time.Time //Expires过期时间
RawExpires string //最大存活时间,单位秒
// MaxAge=0表示未设置Max-Age属性
// MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
// MaxAge>0表示存在Max-Age属性,单位是秒
MaxAge int //最大存活时间,单位秒
Secure bool
HttpOnly bool //是否可以通过脚本访问
Raw string
Unparsed []string // 未解析的“属性-值”对的原始文本
}
HttpOnly
- 控制Cookie的内容是否可以被JavaScript访问到。通过设置HttpOnly为true时防止XSS攻击防御手段之一
- 默认HttpOnly为false,表示客户端可以通过js获取
- 在项目中导入jquery.cookie.js库,使用jquery获取客户端Cookie内容
Expires
- Cookie默认存活时间是浏览器不关闭,当浏览器关闭后,Cookie失效
- 可以通过Expires设置具体什么时候过期,Cookie失效,也可以通过MaxAge设置Cookie多长时间后实现
- IE6,7,8和很多浏览器不支持MaxAge,建议使用Expires(谷歌浏览器不影响)
- Expires是time.Time类型,所以设置时需要明确设置过期时间
cookie分类
cookie 是有时间限制的,根据生命期不同分成两种:会话 cookie 和持久 cookie;
会话Cookie
如果不设置过期时间,则表示这个 cookie 的生命周期为从创建到浏览器关闭为止,只要关闭浏览器窗口,cookie 就消失了。这种生命期为浏览会话期的 cookie 被称为会话 cookie。会话 cookie 一般不保存在硬盘上而是保存在内存里。
持久Cookie
如果设置了过期时间 (setMaxAge (606024)),浏览器就会把 cookie 保存到硬盘上,关闭后再次打开浏览器,这些 cookie 依然有效直到超过设定的过期时间。存储在硬盘上的 cookie 可以在不同的浏览器进程间共享,比如两个 IE 窗口。而对于保存在内存的 cookie,不同的浏览器有不同的处理方式。
Session
session与cookie关系
session 和 cookie 的目的相同,都是为了克服 http 协议无状态的缺陷,但完成的方法不同。session 通过 cookie,在客户端保存 session id,而将用户的其他会话消息保存在服务端的 session 对象中,与此相对的,cookie 需要将所有信息都保存在客户端。
Go如何使用session
如何发送session唯一标识符:
存入cookie和URL重写
生产环境最佳实践
-
优先用 Cookie 存储:
- 必须配置
HttpOnly: true(防 XSS)、Secure: true(仅 HTTPS 传输)、SameSite: Lax/Strict(防 CSRF); - 用 Redis 替代内存存储 session(避免服务重启丢失,支持分布式)。
- 必须配置
-
URL 重写仅作为降级:
| 维度 | Cookie 存储 | URL 重写 |
|---|---|---|
| 安全性 | 高(HttpOnly/Secure/SameSite 防护) | 低(URL 易泄露、被劫持) |
| 开发成本 | 低(浏览器自动管理,库封装) | 高(手动拼接所有 URL,前端配合) |
| 用户体验 | 无感知(自动携带) | 有感知(URL 带额外参数) |
| 适用场景 | 99% 的主流场景(登录、用户态管理) | 仅兼容用户禁用 Cookie 的降级场景 |
| Go 实现 | 用 gorilla/sessions 自动处理 | 手动拼接 URL + 解析查询 / 路径参数 |
Restful风格
go语言多路复用技术
在http包中提供了ServeMux实现多路复用器,它会对URL进行解析,然后重定向到正确的处理器上
// 1. 定义用户结构体(模拟数据库模型)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
// 2. 模拟内存数据库(实际项目替换为MySQL/PostgreSQL)
var users = []User{
{
ID: 1, Name: "张三", Age: 20},
{
ID: 2, Name: "李四", Age: 22},
}
// 3. 统一响应结构体(RESTful 规范:返回结构化JSON)
type Response struct {
Code int `json:"code"` // 业务状态码
Msg string `json:"msg"` // 提示信息
Data interface{
} `json:"data"` // 数据体
}
// 辅助函数:统一返回JSON响应(简化代码)
func sendJSON(w http.ResponseWriter, statusCode int, resp Response) {
w.Header().Set("Content-Type", "application/json") // 设置响应头为JSON
w.WriteHeader(statusCode) // 设置HTTP状态码
json.NewEncoder(w).Encode(resp) // 序列化JSON并返回
}
// **************************
// RESTful 接口处理函数
// **************************
// listUsers 获取所有用户(GET /users)
func listUsers(w http.ResponseWriter, r *http.Request) {
// RESTful:查询成功返回200 + 所有用户数据
sendJSON(w, http.StatusOK, Response{
Code: 200,
Msg: "查询成功",
Data: users,
})
}
// getUser 获取单个用户(GET /users/{id})
func getUser(w http.ResponseWriter, r *http.Request) {
// 1. 提取mux路由参数(你代码里的 vars["key"] 核心逻辑)
vars := mux.Vars(r)
idStr := vars["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
// RESTful:参数错误返回400
sendJSON(w, http.StatusBadRequest, Response{
Code: 400,
Msg: "无效的用户ID:" + idStr,
Data: nil,
})
return
}
// 2. 查找用户
for _, user := range users {
if user.ID == id {
sendJSON(w, http.StatusOK, Response{
Code: 200,
Msg: "查询成功",
Data: user,
})
return
}
}
// 3. 资源不存在返回404
sendJSON(w, http.StatusNotFound, Response{
Code: 404,
Msg: "用户不存在",
Data: nil,
})
}
// createUser 创建用户(POST /users)
func createUser(w http.ResponseWriter, r *http.Request) {
// 1. 解析请求体(JSON格式)
var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
sendJSON(w, http.StatusBadRequest, Response{
Code: 400,
Msg: "参数解析失败:" + err.Error(),
Data: nil,
})
return
}
// 2. 模拟生成ID(实际用数据库自增ID)
newUser.ID = len(users) + 1
users = append(users, newUser)
// 3. RESTful:创建成功返回201
sendJSON(w, http.StatusCreated, Response{
Code: 201,
Msg: "创建用户成功",
Data: newUser,
})
}
// **************************
// 主函数:路由注册 + 启动服务
// **************************
func main() {
// 1. 创建mux路由器(你代码里的核心对象)
router := mux.NewRouter()
// 2. 静态文件服务(保留你代码里的逻辑)
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// 3. RESTful 核心:按「资源+HTTP方法」注册路由
// 注意:gorilla/mux 用 Methods() 绑定HTTP方法,这是实现RESTful的关键
router.HandleFunc("/users", listUsers).Methods("GET") // 获取所有用户
router.HandleFunc("/users", createUser).Methods("POST") // 创建用户
router.HandleFunc("/users/{id}", getUser).Methods("GET") // 获取单个用户
err := http.ListenAndServe(":80", router)
if err != nil {
fmt.Printf("服务启动失败:%v\n", err)
}
}