接上一篇文章,我们已经写好了如何去获取请求报文
如果你还从没有接触过
http
请求报文协议,请参考上一篇文章Golang之我想写个"web框架"-1: 获取请求报文
本篇文章,我们来看下如何构建响应报文
http响应报文协议
http响应报文概述
具体http
响应报文大致是这样的
我们一般将http
响应报文,称之为状态行,而后紧跟若干个首部行,上述我们统称为首部行,我们判断报文首部是否结束,其响应报文和请求报文一样,当响应报文结束后,会在其后加入2个CRLF
作为分割,而后则根据首部行的Content-Length
来记录响应报文主体的长度。
我们分别来解释一下
状态行
状态行除了最后的CRLF
以外,中间不允许有CR
或者LF
。
http
响应报文的第一行,称之为状态行,是用于记录http
状态的。其值分别为 协议版本 、 状态码 以及 短语 各个部分分别用空格隔开,状态行使用CRLF
标致结束。
协议版本
这里指的是服务器所使用的http
协议版本,注意这里的版本和客户端版本没有关系,例如: 客户端使用1.0
版本进行访问,而服务器使用1.1
版本进行回复,一般而言,服务器http
版本是要等于或高于客户端http
版本的。
这里我们可以简单使用curl
命令进行测试一下
curl --http1.0 www.juejin.cn -v -I
使用--http1.0
是用1.0
协议发送请求,-v
是指debug
请求过程 -I
是指请求方法使用HEAD
通过上述案例可以验证上述观点。
状态码
当客户端向服务器发送请求时,服务器处理时,可能出现多种情况,状态码的作用就是通知客户端响应情况,客户端拿到状态码后,在进行其他判断以及处理。
这里举个简单的例子: 若服务器返回状态码为301
,客户端在拿到后,会去取首部行key
为Location
的地址,进行再次访问,实际上,我们浏览器也是这么做的。
状态码分为5
类,其分类以及含义如下
类别 | 范围 | 含义 |
---|---|---|
1XX | 100~199 | 信息提示 |
2XX | 200~299 | 成功类 |
3XX | 300~399 | 重定向 |
4XX | 400~499 | 客户端错误 |
5XX | 500~599 | 服务器错误 |
短语
短语的作用是对状态码进行简单的文本描述,这里你可能会好奇,既然有了状态码,为什么还需要短语呢?
其实是这样的,状态码是用于客户端便于控制其流程,而短语是为了让用户方便阅读。
常见的http状态码及其含义
这里简单列举常见的http
状态码、及其含义
状态码 | 短语 | 含义 |
---|---|---|
100 | Continue | 服务器收到客户端请求 |
101 | Switching Protocols | 协议切换 |
200 | OK | 成功 |
301 | Moved Permanently | 重定向 |
400 | Bad Request | 客户端请求错误 |
401 | Unauthorized | 未授权 |
404 | Not Found | 找不到资源 |
500 | Internal Server Error | 服务器错误 |
504 | Gateway Time-out | 网关错误 |
本节目的是为了编写响应报文,所以这里简单叙述即可,不会再讨论。
首部行
这里首部行格式和http
请求报文首部行格式是一样的,都是由key: value\r\n
的格式,这里就不啰嗦了。
响应主体
这里应当和请求报文一样,在首部应当添加key
为Content-Length
,值为响应主体的长度,而后在首部结束后,我们进行相应长度的主体信息。
生成响应报文
我们想要生成响应报文,我们需要什么信息呢?
通过上述信息,我们知晓了,我们生成带响应主体的报文,我们至少需要 协议版本、状态码 以及 短语 , 而后是首部,我们想要包含响应主体,我们至少需要Content-Length
来指定我们的长度。
设计响应报文
假设我们想返回给客户端一个<h1>hello world</h1>
,我们报文应当如何构建呢?
根据上述概述,我们可以用图示生成一下如下报文。
其描述可以写为
HTTP/1.0 200 OK
Content-Length: 20
<h1>hello world</h1>
利用go
返回其响应报文
我们使用go
来返回相应的报文,单单返回意义不大,我们可以承接上一篇文章,来写个题目。
题目: 仅对请求URL
为/pdudo
返回<h1>hello world</h1>
,其他则跳转到https://www.juejin.cn
。
我们可以分析下,客户端连接上来后,要不返回hello world
,要不跳转到https://www.juejin.cn
,判断的点为请求的URL
是否为/pdudo
。
此伪代码可以写为
if url == /pdudo
return "<h1>hello world</h1>"
else
return "https://www.juejin.cn"
如上可知,我们需要返回2种结果,第一种,我们在上面已经写过报文了,关键是第二种,还记得我们前面讲述协议版本所介绍的案例么,我们可以返回301
让客户端跳转到https://www.juejin.cn
不就可以了么。
代码编写
代码我放到gitee
上,如下只是关于本篇的核心代码
构建报文核心代码
type responsePackages struct {
HttpVersion string
StatusCode uint
StatusMsg string
responseHeader map[string]string
Body []byte
}
func buildResponsePack(rp responsePackages)([]byte) {
responseByte := make([]byte,0)
// 构建状态行
// 版本
responseByte = append(responseByte, []byte(rp.HttpVersion+" ")...)
// 状态码
responseByte = append(responseByte, []byte(strconv.Itoa(int(rp.StatusCode))+" ")...)
// 短语
responseByte = append(responseByte, []byte(rp.StatusMsg+CRLF)...)
// 首部信息
for k,v := range rp.responseHeader {
responseByte = append(responseByte, []byte(k+": ")...)
responseByte = append(responseByte, []byte(v+CRLF)...)
}
// 添加响应主体
if 0 < len(rp.Body) {
// 新增 Content-Length
responseByte = append(responseByte, []byte("Content-Length: "+strconv.Itoa(len(rp.Body))+"\r\n")...)
}
responseByte = append(responseByte, []byte(CRLF2)...)
responseByte = append(responseByte, rp.Body...)
fmt.Println(string(responseByte))
return responseByte
}
我们可以根据请求,生成响应的报文,且将数据返回值客户端(部分代码)
// 分析URL
if httpHeader.Url == "/pdudo" {
// 返回 hello world
rp.HttpVersion = "HTTP/1.1"
rp.StatusCode = 200
rp.StatusMsg = "OK"
rp.responseHeader["User-Agent"] = "pdudo_golang"
rp.Body = []byte("<h1>hello world</h1>")
repBuf = buildResponsePack(rp)
} else {
// 返回 跳转报文
rp.HttpVersion = "HTTP/1.1"
rp.StatusCode = 301
rp.StatusMsg = "Moved Permanently"
rp.responseHeader["Location"] = "https://juejin.cn/"
repBuf = buildResponsePack(rp)
}
// 将repBuf返回给客户端
sendLen := 0
for sendLen < len(repBuf) {
n ,err := conn.Write(repBuf[sendLen:])
if err != nil {
fmt.Println(err)
return
}
sendLen += n
}
测试其正确性
结果真的如此么,我们尝试下
测试链接: http://127.0.0.1:8081/pdudo
和http://127.0.0.1:8081/hdasdadwdq
我们先使用curl
进行获取一下
再使用浏览器进行访问
总结
响应报文状态码至关重要
我们注意到了上面的案例,有一个报文是状态码为301
,首部信息Location
为https://juejin.cn/
,这这个跳转和服务器没有关系,是客户端(浏览器)拿到状态码之后,发现是301
,哦,要进行跳转,然后去拿首部信息的Location
进行跳转的。
案例代码已经上传gitee
: ResponseMessages.go
怎么样,好玩吧,快动动你的小手指,试试吧。
本文正在参加技术专题18期-聊聊Go语言框架