http.ResponseWriter接口

前一篇中的例子已经“庞大”,我们这篇将编写新的例子来学习服务器的响应和cookie。在看例子代码之前,我们先来看handler  func 的函数签名

func process(w http.ResponseWriter, r *http.Request) {
...................
}

代表“请求”的 r 是一个 http.Request 类型的指针,而代表响应的 w  却是一个值类型 http.ResponseWriter ?在标准库http中,存在 Response 这一结构体,但创建响应时,并非直接生成一个 Response 对象,填充数据并返回。 理解这一点的要点在于,http/server.go文件中定义的 ResponseWriter 是一个 interface 而非具体化的结构体,该接口约定了3个方法:Write、WriteHeader、Header,而 Response 类型在实现接口方法时,接收者使用的是指针式接收者,从而在调用接口方法时,可以用“值”形式的语法,而作为其他函数的参数或者赋值时的右值时,必须使用地址。(参考 golang学习随便记8-接口_sjg20010414的博客-CSDN博客 中 方法接收者T和*T的差别)。不过,在 handler func 中,并未使用 *http.Response 作为类型,而是使用了“父类型”接口类型,是变相引用传递(即本质上方法接收者是*http.Response类型)。

因为w和r本质上都是引用,故一旦 handler  func 中对请求或响应有所修改,那么main函数中的server是可以感知这种变化的。

接口方法Write(..)

http.ResponseWriter的Write接口方法接受一个字节数组作为入参,这个字节数组会写入HTTP响应的body主体中。如果调用Write写入body前,没有设定内容相应的类型,那么使用的默认头部将通过检测字节数组前512字节来决定内容类型。

编写下面的代码 server.go 并运行:

package mainimport "net/http"func writeTest1(w http.ResponseWriter, r *http.Request) {str := `<html>
<head><title>Go Web 响应测试</title></head>
<body><h1>Hello, Go Response 1</h1></body>
</html>`w.Write([]byte(str))
}func main() {server := http.Server{Addr: "127.0.0.1:8088",}http.HandleFunc("/response1", writeTest1)server.ListenAndServe()
}

用 curl 测试如下(观察头部信息):(也可以用浏览器,但需要打开开发者工具,观察“网络” - response1 - “标头”部分)

sjg@sjg-PC:~$ curl -i 127.0.0.1:8088/response1
HTTP/1.1 200 OK
Date: Mon, 08 May 2023 02:42:37 GMT
Content-Length: 105
Content-Type: text/html; charset=utf-8<html>
<head><title>Go Web 响应测试</title></head>
<body><h1>Hello, Go Response 1</h1></body>
</html>

我们路由中添加2条,handler func 相应添加2个:

// ...........................func writeTest2(w http.ResponseWriter, r *http.Request) {str := `<?xml version="1.0" encoding="UTF-8"?><html>
<head><title>Go Web 响应测试</title></head>
<body><h1>Hello, Go Response 2</h1></body>
</html>`w.Write([]byte(str))
}func writeTest3(w http.ResponseWriter, r *http.Request) {str := `<root>
<head><title>Go Web 响应测试</title></head>
<body><h1>Hello, Go Response 3</h1></body>
</root>`w.Write([]byte(str))
}func writeTest4(w http.ResponseWriter, r *http.Request) {str := `{
head: {title:"Go Web 响应测试"},
body: "Hello, Go Response 3"
}`w.Write([]byte(str))
}// .........................http.HandleFunc("/response2", writeTest2)http.HandleFunc("/response3", writeTest3)http.HandleFunc("/response4", writeTest4)
// .........................

可以发现,访问 http://127.0.0.1:8088/response2 响应的内容类型为 text/xml,而访问 http://127.0.0.1:8088/response3 和 /response4响应的内容类型为 text/plain,也就是自动识别内容时,xml必须在头部有xml文档声明,而html必须符合html格式,否则会识别为普通文本(上述JSON字符串也被识别为text/plain)。

接口方法 Header()、WriteHeader(..)

接口函数Header()和WriteHeader()都和头部有关,正确的顺序是:Header()返回关于头部的map,如果需要修改默认头部,就是修改这个map有关键值对(对于需要取消的头部,应该将值设成nil)。WriteHeader(statusCode)接受一个状态码作为入参,调用该函数将发送指定的状态码和HTTP响应头部(也就是前述map中信息)。如果不显式调用WriteHeader()而直接调用Write()接口函数输出主体body,那么WriteHeader()将隐式调用,状态码默认为 200 OK —— 换句话说,要响应非 200 OK的状态码时,才必须调用 WriteHeader()

下面两个例子,分别表示服务尚未实现(状态501)和重定向(状态302):

// ........................................................func headerTest1(w http.ResponseWriter, r *http.Request) {w.WriteHeader(501)fmt.Fprintln(w, "本店无此服务,请出门右转")
}func headerTest2(w http.ResponseWriter, r *http.Request) {w.Header().Set("Location", "http://10.5.10.200")w.WriteHeader(302)
}// ...............................................http.HandleFunc("/header1", headerTest1)http.HandleFunc("/redirect", headerTest2)
// ...............................................

测试结果:

sjg@sjg-PC:~$ curl -i 127.0.0.1:8088/header1
HTTP/1.1 501 Not Implemented
Date: Mon, 08 May 2023 04:33:40 GMT
Content-Length: 37
Content-Type: text/plain; charset=utf-8本店无此服务,请出门右转
sjg@sjg-PC:~$ curl -i 127.0.0.1:8088/redirect
HTTP/1.1 302 Found
Location: http://10.5.10.200
Date: Mon, 08 May 2023 04:33:44 GMT
Content-Length: 0

设置响应的Header头部

下面是在头部指定类型为JSON,然后响应json数据的例子:

// ..............................................................type Post struct {User    stringThreads []string
}func jsonTest(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "application/json")post := &Post{User:    "张三",Threads: []string{"娱乐事件1", "八卦事件2", "国家大事1"},}json, _ := json.Marshal(post)w.Write(json)
}// .......................................http.HandleFunc("/json", jsonTest)
// .......................................

上面的代码中,post是指针,去掉&改成值传递,结果不变。测试:

sjg@sjg-PC:~$ curl -i 127.0.0.1:8088/json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 08 May 2023 04:46:51 GMT
Content-Length: 77{"User":"张三","Threads":["娱乐事件1","八卦事件2","国家大事1"]}

使用Cookie

Cookie也是一种服务器响应的信息,只是它生成时更像是服务器给浏览器客户端下命令,而不是回答客户请求。之后,cookie会成为浏览器和服务器沟通的“桥梁”,它常见的用途包括作为会话跟踪的识别信息。

Cookie是 http/cookie.go 中定义的 struct,必需的字段是 Name、Value。一般,没有设置 Expires 字段信息的cookie是临时cookie(或叫会话cookie),浏览器关闭后它就消失了(被删除)。设置了 Expires 字段的称为持久 cookie,在指定的过期时间来临或者被手动删除前,这类cookie一直存在。另外,cookie是支持US ASCII(同时排除了一些特殊字符)的,对于中文之类的值只能编码或者转义,例如,用url.QueryEscape(..) 转义编码,url.QueryUnescape(..) 解码,或者base64.URLEncoding.EncodeToString(..)编码,base64.URLEncoding.DecodeString(..)解码。

发送cookie给客户

下面的代码,演示服务器将cookie发送给浏览器(可以理解为命令浏览器记录cookie),分别使用直接发送头部的方式和调用http.SetCookie函数的方式(后者更方便,不过前者控制自由度更高,且一些时候可以跳过不必要的判断)

// .............................................................func cookieTest1(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name:     "cookie_name1",Value:    "Go Web Programming",HttpOnly: true,}c2 := http.Cookie{Name:  "cookie_name2",Value: url.QueryEscape("Go Web 编程"), // 只能US-ASCII https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1}w.Header().Set("Set-Cookie", c1.String())w.Header().Add("Set-Cookie", c2.String())
}func cookieTest2(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name:     "cookie_name1",Value:    "Go Web Programming",HttpOnly: true,}c2 := http.Cookie{Name:  "cookie_name2",Value: url.QueryEscape("Go Web 编程"), // 只能US-ASCII https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1}http.SetCookie(w, &c1)http.SetCookie(w, &c2)
}// ...........................................http.HandleFunc("/cookie1", cookieTest1)http.HandleFunc("/cookie2", cookieTest2)
// ...........................................

运行之后,浏览器访问 http://localhost:8088/cookie1和 http://localhost:8088/cookie2,在开发者工具:应用程序 - 存储 - Cookie 里面观察浏览器记录的cookie。

读取客户发送的cookie

接下来,我们来看服务器如何读取浏览器发送过来的cookie信息:浏览器向服务器发送请求时,会把它记录的还在生命周期内的对应domain的cookie都发送过去(作为请求头的一部分)

// ..........................................................func cookieTest3(w http.ResponseWriter, r *http.Request) {cs := r.Header["Cookie"]fmt.Fprintln(w, cs)c1, err := r.Cookie("cookie_name1")if err != nil {fmt.Fprintln(w, "未能获取 cookie_name1 的值")} else {fmt.Fprintln(w, c1)}c3, err := r.Cookie("cookie_name3")if err != nil {fmt.Fprintln(w, "未能获得 cookie_name3 的值")} else {fmt.Fprintln(w, c3)}
}// ...........................................http.HandleFunc("/cookie3", cookieTest3)
// ...........................................

运行并浏览器访问结果如下:

r.Header这个map包含了请求包含的所有头部信息,键Cookie对应的值就是浏览器发送的cookie,这是一个字符串切片,所有cookie混在一起。我们当然可以自己解析这个切片,不过,使用r.Cookie(..) 函数更容易获取指定字段的cookie信息(r.Cookie(..)函数解析时可能因为对应cookie不存在而失败,所以,需要判断)。

下面我们看一下cookie在flash message闪现消息中的使用:向浏览器发送cookie,让浏览器记住消息;浏览器发送回来消息,取出该消息,向浏览器发送cookie命令该cookie失效,显示已经取出的消息

// .........................................................
func setMessage(w http.ResponseWriter, r *http.Request) {msg := []byte("这是一条 Flash Message 消息")c := http.Cookie{Name:  "flash-message",Value: base64.URLEncoding.EncodeToString(msg),}http.SetCookie(w, &c)
}func showMessage(w http.ResponseWriter, r *http.Request) {c, err := r.Cookie("flash-message")if err != nil {if err == http.ErrNoCookie {fmt.Fprintln(w, "未找到消息")}} else {rc := http.Cookie{Name:    "flash-message",MaxAge:  -1,Expires: time.Unix(1, 0),}http.SetCookie(w, &rc) // 清除cookie就是让它失效v, _ := base64.URLEncoding.DecodeString(c.Value)fmt.Fprintln(w, string(v))}
}// .................................................http.HandleFunc("/set-message", setMessage)http.HandleFunc("/show-message", showMessage)
// .................................................

运行,测试时(可以同时打开开发者工具观察cookie的产生和消失),浏览器分别访问 http://localhost:8088/set-message和http://localhost:8088/show-message,应该能看到消息被记录到浏览器cookie和从cookie消失的过程,第二个网址第一次访问时能看到消息,当刷新后一个网址时,就不能看到消息了

golang web学习随便记3-响应有关相关推荐

  1. golang web服务器_使用Go制作自己的Web服务器:快速指南

    golang web服务器 The Go programming language is well-known for having a built-in web server. In this ar ...

  2. Go语言编程从入门到精通,WEB服务,路由、响应、报头、GET、POST

    Go标准库中的net/http提供了http服务器的方法. 从写一个最基本的HTTP服务器示例开始学习. 代码解析: 3行:import "net/http",导入net/http ...

  3. Web学习之跨域问题及解决方案

    Web学习之跨域问题及解决方案 javascript/jquery 浏览数:161 2017-5-8 在做前端开发时,我们时常使用ajax与服务器通信获取资源,享受ajax便利的同时,也知道它有限制: ...

  4. golang web php,golang 适合做web开发吗

    使用go语言来做web开发,是非常方便的.如果不使用框架,仅仅使用net/http包,也能快速开发一个web应用.但是,官方包不支持RESTful风格的API,所以我们依然还是需要选择一个框架来帮助我 ...

  5. linux为用户指定资源大小,Linux_Linux系统下生成一个指定大小的文件,在装系统,因此在这里随便记 - phpStudy...

    Linux系统下生成一个指定大小的文件 在装系统,因此在这里随便记点东西. 从前与经理讨论NVR项目的时候,他提到要预先将用户指定好的磁盘空间预留出来,这是比较容易实现的功能.不容易实现的是自己写一个 ...

  6. Web前端--HTML+CSS+JavaScript响应式网络科技网页设计

    临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?网页要求的总数量太多?HTML网页作业无从下手?没有合适的模板?等等一系列问题.你想要解决的问题,在这里常见网页设计作业题材有 个 ...

  7. WEB学习路线2020完整版+附视频教程

    WEB学习路线2020完整版+附视频教程,适合初学者的最新WEB前端学习路线汇总! 在当下来说web前端开发工程师可谓是高福利.高薪水的职业了.所以现在学习web前端开发的技术人员也是日益增多了,但是 ...

  8. Web学习(二)CSS

    Web学习(二)CSS 小技巧:ctrl+shift+c:快速查看网页元素信息 qq截图取色:ctrl+alt+a截图,然后鼠标在所选颜色区域按下ctrl+c即可取得色号的16进制色号 1.样式定义方 ...

  9. Web前端期末大作业---响应式美女健身教练瑜伽馆网页设计(HTML+CSS+JavaScript+)实现

    临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?网页要求的总数量太多?HTML网页作业无从下手?没有合适的模板?等等一系列问题.你想要解决的问题,在这里常见网页设计作业题材有 个 ...

最新文章

  1. 《Linux内核设计与实现》内存管理札记
  2. poj 1041(欧拉回路+输出字典序最小路径)
  3. java inputtools_Java后台开发常用工具类
  4. Linux中相关知识(atexit(),fork(),粘滞位)
  5. 判断dll是版本(Debug Or Release)[测试通过]
  6. “不翻身,就要翻船”!帆软独家:制造业数字化转型解决方案
  7. Grafana密码重置为admin
  8. English - even though和even if用法解析
  9. Python修改Mp3文件名称
  10. mysql handbook_MySQL 8 Administrator’s Guide
  11. java nio 多路复用_JAVA NIO 一步步构建I/O多路复用的请求模型
  12. 软件测试 — 面试题
  13. 传奇服务器怎么修改升级武器成功,四次升级武器成功
  14. 软件测试输入准则,单元测试准则
  15. http://blog.csdn.net/wrp920227/article/details/54588238
  16. php商品浏览页面,jquery仿京东商品放大浏览页面_实例分享
  17. easyexcel 列头合并_2020-05-19:EasyExcel自定义合并单元格
  18. MeanTeacher文章解读+算法流程+核心代码详解
  19. 阴阳师服务器维护3月14,3.14日服维护内容非常感谢您一直使用‘阴阳师’。以下日程将...
  20. 3g安卓市场_安卓离场,鸿蒙能否破局?

热门文章

  1. QT 加载外部CAN dll库
  2. js html引入外部css文件,js中如何引入css文件?
  3. 【小白向】让电脑成为热点WIFI
  4. outline:none || hidefocus=true || onfocus=this.blur();
  5. “李妈妈—嘉利敦” 十四天成长计划挑战赛
  6. 无尽之海:从手机到万物
  7. 小程序input组件获得焦点时placeholder内容有重影
  8. Linux电子书下载
  9. C#大小写验证 RegexOptions.IgnoreCase
  10. 一个人适合做什么早餐摊