百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Go 语言 Web 编程系列——设置、读取和删除 Cookie

cac55 2024-09-20 12:44 24 浏览 0 评论

1、Cookie 简介

介绍完了 Go 语言的 HTTP 请求和响应处理,接下来,我们来看看 Go 语言中 Cookie 技术的实现,由于 HTTP 协议本身是无状态的,所以引入了 Cookie 来实现客户端用户识别和状态管理,关于 Cookie 本身这里不多做介绍,你可以在维基百科或者阅读 HTTP 报文首部字段(五):扩展字段篇(Cookie)这篇教程了解更多细节。

我们可以在 HTTP 响应头中通过 Set-Cookie 字段设置 Cookie,然后在下次请求时就会在请求头 Cookie 中自动包含新增的 Cookie。你可以在 Chrome 浏览器的控制台中通过 Application->Storage->Cookies 查看指定域名下的所有 Cookie:

可以在 Cookie 列表顶部抬头看到 Cookie 的所有属性。

2、Cookie 结构体

Go 专门提供了一个 http.Cookie 结构体来表示 Cookie,其中的字段对应着 Cookie 的所有属性:

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
// See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct {
    Name  string
    Value string
    Path       string    // optional
    Domain     string    // optional
    Expires    time.Time // optional
    RawExpires string    // for reading cookies only
    // MaxAge=0 means no 'Max-Age' attribute specified.
    // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
    // MaxAge>0 means Max-Age attribute present and given in seconds
    MaxAge   int
    Secure   bool
    HttpOnly bool
    SameSite SameSite
    Raw      string
    Unparsed []string // Raw text of unparsed attribute-value pairs
}

我们可以将其与上面截图中的 Cookie 属性一一对应,如果你了解 Cookie 的基本结构的话,很容易知道每个字段的含义。如果你不了解的话,可以通过点击上面 Cookie 简介中给出的链接去了解。

这里,我们重点介绍下 Expires 字段:

  • 如果 Expires 字段为空,则 Cookie 就是 Session Cookie 或者叫临时 Cookie,这个 Cookie 会随着浏览器的关闭而销毁。
  • 如果设置了 Expires,那么这个 Cookie 就是持久 Cookie,直到过期时间后才会销毁。

有两种方法来设置过期时间:一种是直接设置 Expires 字段,一种是设置 MaxAge 字段。前者表示到期的具体时间点,后者表示 Cookie 的有效时长(单位是秒)。这并不是 Go 语言的设计,而是不同浏览器的混乱标准使然,比如虽然 HTTP/1.1 有意废弃 Expires,不过 IE 6、7、8 却不支持 MaxAge 字段。通常,考虑到默认时区问题,本地时间不可靠,推荐通过 MaxAge 字段设置 Cookie 过期时间,不过对于 Web 应用而言,通常不设置过期时间,让 Cookie 随着浏览器关闭而失效即可。

3、发送 Cookie 到客户端

了解了 Cookie 的基本结构,以及如何在 Go 语言中表示后,我们尝试在 HTTP 响应中通过设置 Set-Cookie 头新增 Cookie 并将其发送给客户端浏览器,在此之前的在线论坛项目中,已经在用户认证时用到了 Cookie 设置:

cookie := http.Cookie{
    Name:     "_cookie",
    Value:    session.Uuid,
    HttpOnly: true,
    Path: "/",
}
http.SetCookie(writer, &cookie)
http.Redirect(writer, request, "/", 302)

上述代码是在用户认证通过后将 Session ID 通过 Cookie 存储到客户端,以便后续记住用户登录状态,直到用户关闭浏览器(没有设置过期时间)。通过这段代码,可以看出要在响应中发送 Cookie,需要先通过 http.Cookie 初始化一个 Cookie 对象,再通过 http.SetCookie 方法将这个 Cookie 写入到 HTTP 响应中,这样发送响应给客户端的时候就会带上这个 Cookie 了。

接下来,我们在 goblog 项目中演示发送 Cookie 到客户端,这一次,我们不通过写入 Cookie 到 HTTP 响应,而是直接通过 HTTP 响应头 Set-Cookie 来设置 Cookie,在 goblog/handlers/common.go 中新增 SetCookie 处理器:

func SetCookie(w http.ResponseWriter, r *http.Request)  {
    c1 := http.Cookie{
        Name: "username",
        Value: url.QueryEscape("学院君"),
        HttpOnly: true,
    }
    c2 := http.Cookie{
        Name: "website",
        Value: "https://xueyuanjun.com",
        HttpOnly: true,
    }
    w.Header().Add("Set-Cookie", c1.String())
    w.Header().Add("Set-Cookie", c2.String())
    fmt.Fprintln(w, "通过 HTTP 响应头发送 Cookie 信息")
}

这里我们新增了两个 Cookie,所以使用了 w.Header().Add 方法,如果用 Set 方法,后面的 Set-Cookie 头会覆盖前面的,另外,由于 Cookie 值包含了中文字符,需要通过 url.QueryEscape 方法进行 URL 编码,否则无法正常显示。

在 routes/web.go 中注册对应的路由:

WebRoute{
    "SetCookie",
    "GET",
    "/setcookies",
    handlers.SetCookie,
},

重启 HTTP 服务器,在浏览器中访问 http://localhost:8080/setcookies,就可以在响应结果中看到对应的 Cookie 信息了:

当然也可以通过 http.SetCookie 方法写入 Cookie 到 HTTP 响应来实现,对应的代码如下,这样做更便捷:

http.SetCookie(w, &c1)
http.SetCookie(w, &c2)

设置 Cookie 有效期

还可以通过 Expires/MaxAge 设置 Cookie 的有效期:

c1 := http.Cookie{
    Name: "username",
    ...
    Expires: time.Now().AddDate(0, 0, 1),  // Cookie 有效期设置为1天
}
c2 := http.Cookie{
    Name: "website",
    ...
    MaxAge: 1000,    // Cookie 有效期设置为 1000s
}

这样,就可以在响应中看到对应的 Cookie 有效期了:

4、从请求中读取 Cookie

一旦通过 Set-Cookie 响应头将 Cookie 信息发送到客户端浏览器,那么在 Cookie 有效期内,下次同域名下的用户请求将自动在请求头中包含对应的 Cookie 信息,比如我们访问 http://localhost:8080,就可以在请求头 Cookie 中看到上次响应返回的 Cookie:

要在服务端获取这些 Cookie 信息,可以通过读取请求头的方式:

cookie := r.Header.Get("Cookie")

但是这种方式读取的 Cookie 字符串值还需要进行解析,才能得到每个 Cookie 的值,为此可以通过更加便捷的专门用于读取每个 Cookie 的 r.Cookie 方法(r 表示 HTTP 请求对象实例),我们在 handlers/common.go 中新增一个 GetCookie 方法:

func GetCookie(w http.ResponseWriter, r *http.Request)  {
    c1, err := r.Cookie("username")
    if err != nil {
        fmt.Fprintln(w, "名为 username 的 Cookie 不存在")
        return
    }
    username, _ := url.QueryUnescape(c1.Value)
    c2, err := r.Cookie("website")
    if err != nil {
        fmt.Fprintln(w, "名为 website 的 Cookie 不存在")
        return
    }
    website := c2.Value
    fmt.Fprintf(w, "从用户请求中读取的 Cookie: {username: %s, website: %s}\n", username, website)
}

需要注意的是 r.Cookie 方法返回的是指针类型的 Cookie 对象和一个错误信息,需要通过调用 Cookie 对象上的 Value 属性返回对应的 Cookie 值,对于 username 而言,还需要通过 url.QueryUnescape 对编码值进行解码。

在 routes/web.go 中注册一个对应的路由:

WebRoute{
    "GetCookie",
    "GET",
    "/getcookies",
    handlers.GetCookie,
},

然后重启 HTTP 服务器,在浏览器中访问 http://localhost:8080/getcookies,website Cookie 如果已经过期,则会打印错误消息:

重新访问 http://localhost:8080/setcookies,再访问 http://localhost:8080/getcookies,就可以成功获取并打印出所有的 Cookie 信息:

如果想要一次性获取所有 Cookie,还可以通过 r.Cookies() 方法,该方法会返回的是 Cookie 对象指针类型切片:

cookies := r.Cookies()
c1 := cookies[0]  // username=%E5%AD%A6%E9%99%A2%E5%90%9B
c2 := cookies[1]  // website=https://xueyuanjun.com

5、删除 Cookie

如果想要在 Cookie 过期之前提前删除 Cookie,可以将 MaxAge 设置为小于 0 的值即可:

c2 := http.Cookie{
    Name: "website",
    Value: "https://xueyuanjun.com",
    HttpOnly: true,
    MaxAge: -1,    // Cookie 有效期设置为 -1,就会在当前响应发送给客户端后销毁该 Cookie
}

如果用 Expires 字段来设置的话,可以设置 Unix 时间戳的值为 1(对应的绝对时间是 1970-01-01 08:00:01 +0800 CST,也就是一个过去的时间):

c2 := http.Cookie{
    Name: "website",
    Value: "https://xueyuanjun.com",
    HttpOnly: true,
    Expires: time.Unix(1, 0),    // Cookie 有效期设置为过去的时间
}

使用 Cookie 设置一次性消息

所谓一次性消息,指的是页面重新加载后消息就不存在了,也就是该消息只能被读取一次,不管你用不用它都不复存在了。我们可以结合上面的删除 Cookie 功能来实现这个一次性消息功能,首先在 common.go 中新增一个 SetWelcomeMessage 处理器:

func SetWelcomeMessage(w http.ResponseWriter, r *http.Request)  {
    msg := "欢迎访问学院君网站"
    cookie := http.Cookie{
        Name: "welcome_message",
        Value: base64.URLEncoding.EncodeToString([]byte(msg)),
    }
    http.SetCookie(w, &cookie)
    http.Redirect(w, r, "/get_welcome_message", 302)
}

这里发送 Cookie 到客户端和前面的新增 Cookie 逻辑完全一样,最后通过 http.Redirect 将请求重定向到 /get_welcome_message 路由,另外这一次我们通过 Base64 对 Cookie 值进行编码,因为其中包含中文和特殊字符。接下来我们继续在 common.go 中编写 /get_welcome_message 路由对应的处理器 GetWelcomeMessage:

func GetWelcomeMessage(w http.ResponseWriter, r *http.Request)  {
    cookie, err := r.Cookie("welcome_message")
    if err != nil {
        fmt.Fprintln(w, "没有在 Cookie 中找到欢迎消息")
    } else {
        delCookie := http.Cookie{
            Name: "welcome_message",
            MaxAge: -1,
        }
        http.SetCookie(w, &delCookie)
        msg, _ := base64.URLEncoding.DecodeString(cookie.Value)
        fmt.Fprintln(w, string(msg))
    }
}

在这段代码中,首先通过 r.Cookie 方法从 Cookie 中读取欢迎消息,读取之后,通过 Base64 对其进行解码,然后作为响应实体返回给客户端,这一块和之前读取 Cookie 逻辑一样,只是新增了读取成功之后,删除这个 Cookie 的功能。

最后,在 routes/web.go 中注册上述处理器方法对应的路由:

WebRoute{
    "SetMessage",
    "GET",
    "/set_welcome_message",
    handlers.SetWelcomeMessage,
},
WebRoute{
    "GetMessage",
    "GET",
    "/get_welcome_message",
    handlers.GetWelcomeMessage,
},

重启 HTTP 服务器,在浏览器中访问 http://localhost:8080/set_welcome_message,页面会重定向到 http://localhost:8080/get_welcome_message,然后打印出欢迎消息,说明 Cookie 读取成功:

如果查看 Chrome 控制台的话,可以在 Network 标签页看到对应的 Cookie 响应头和请求头:

在 get_welcome_message 请求头中可以看到对应的 Cookie,此外这个请求的响应头还包含了删除 Cookie 的逻辑:

如果在「Application」标签页查看 Cookie 的话,会发现已经没有 welcome_message 了(website 到了过期时间自动销毁,所以也看不到了):

好了,关于 HTTP Cookie 功能我们简单介绍到这里,下篇教程,我们来探讨在 Go 语言中如何基于 Cookie 实现 Session 技术。

(全文完)

相关推荐

让组策略保护Windows XP的安全

默认安装完WindowsXP之后,我们的WindowsXP并不很安全。因此,我们有必要对系统进行一些修修补补,一般情况下我们都要动用到注册表。诚然,修改注册表是一种非常有效的方法,但是它需要一定的...

你造吗?十种方式保护你免受&quot;零日攻击&quot;

|责编:王迪WindowsXP的寿终正寝,数据安全问题又再一次成为人们关注的焦点。近日,微软透漏,一个基于InternetExplorer的“零日攻击”给用户带来了严重破坏。“零日攻击”一种利用...

特立独行——打造游戏专用独立系统

大部分人的电脑是为了学习和工作用的,所以,如果你是一个游戏迷,那么推荐你安装一个独立系统专用于游戏,做到工作娱乐两不相扰。方案1:游戏专用移动WindowsXP目的:解决游戏兼容性问题喜欢玩游戏的都...

驰为VX8 3G Win8入门教程篇

距离Win8.1的正式发布也将近1年了,凭借着Win8.1在移动便携以及娱乐办公上的优势,现在的Win8平板越来越受到消费者的追捧,而驰为VX83G就是其中一款,搭载了卓越的英特尔Z3735G四核芯,...

易淘收银软件说明

易淘收银系统,简称易淘收银,专为小型及连锁零售、餐饮行业打造。基于SaaS模式,智能便捷,无需维护,轻量级设计却功能强大,简约而不失专业,助力门店高效管理收银。1、前台系统:收银客户端;2、后台系...

CAD打不开怎么办?原因可能是电脑中毒了,6步就能完美解决问题

一、问题描述我的CAD安装后无法打开,安装过程中没有出现任何问题,但是安装后打开就出现一个对话框“DBXCAS0”点击后又出现“FATALERROR:UnhandledAccessViola...

腾讯QQ6.1正式版发布更新

2014-07-2405:12:00作者:张林【中关村在线软件资讯】7月24日消息:腾讯QQ官网小幅更新了QQ6.1正式版,最新版本号升级至11905,继续主打扁平化、炫酷登录窗口、支持同步最近一...

Win10等网页版OneDrive无法登陆怎么办?

IT之家(www.ithome.com):Win10等网页版OneDrive无法登陆怎么办?Win10之家报道,微软OneDrive云网盘是跨平台的数据同步和存储服务,支持WindowsPC(如Wi...

经典回顾:折戟沉沙的Windows Longhorn有着惊艳的登录屏幕

尽管微软原先计划让WindowsLonghorn继承WindowsXP操作系统的衣钵,但这个充满雄心壮志的操作系统项目最终还是未能迎来曙光,而是被微软用WindowsVista取而代...

电脑怎么优化

电脑配置和宽带流量也是硬件,但这些要求其实并不需要很高,关键还是怎么去安全使用电脑并进行有效的优化。电脑的应用和优化处理一、电脑的应用和优化处理二、目前,大家使用的个人电脑,配置方面均没多大问题,比如...

怎么安装usb驱动

USB驱动主要是针对WIN98时代的说法,如今WINXP已集成大部分USB驱动,通常都能识别。只有极少数情况下,例如手机、打印机或扫描仪等办公设备的USB驱动可能无法自动识别。1、USB驱动偶尔无法...

普通话考试多名考生信息被泄露,接投诉后涉事网站被限制访问

“陕西普通话成绩查询网(sxpth.cn)”泄露个人信息网站截图网传图片显示,407名普通话考试考生的姓名、身份证号码等个人信息疑似被泄露。9月26日下午,涉事网站sxpth.cn的域名注册商——成...

电脑伪技巧——个人电脑无需设置登录密码

默认情况下,我们每次登录系统都要输入登录账户对应的密码才能进入桌面。有些朋友觉得这样很麻烦,由于电脑只是自己使用,还不如不要设置密码,这样每次可以自动登录。大家知道,账户密码是系统验证用户合法性的唯一...

Windows 10/11 自带远程桌面:实用技巧与操作指南

Windows10/11自带远程桌面:实用技巧与操作指南在当今快节奏的数字时代,远程访问和控制计算机的需求日益增长。微软在Windows10和Windows11中内置了远程桌面功能,为用户提供...

不升级系统的5大原因造吗?

2015-01-2405:54:00作者:陈占伟Windows10系统的发布,让人们重新将目光聚焦到生命力长久的Windows系统之上。如今操作系统越来越多,似乎Windows升级的获得的关注度...

取消回复欢迎 发表评论: