Golang之我想写个"web框架"-2: 构建响应报文

接上一篇文章,我们已经写好了如何去获取请求报文

如果你还从没有接触过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,客户端在拿到后,会去取首部行keyLocation的地址,进行再次访问,实际上,我们浏览器也是这么做的。

状态码分为5类,其分类以及含义如下

类别范围含义
1XX100~199信息提示
2XX200~299成功类
3XX300~399重定向
4XX400~499客户端错误
5XX500~599服务器错误
短语

短语的作用是对状态码进行简单的文本描述,这里你可能会好奇,既然有了状态码,为什么还需要短语呢?

其实是这样的,状态码是用于客户端便于控制其流程,而短语是为了让用户方便阅读。

常见的http状态码及其含义

这里简单列举常见的http状态码、及其含义

状态码短语含义
100Continue服务器收到客户端请求
101Switching Protocols协议切换
200OK成功
301Moved Permanently重定向
400Bad Request客户端请求错误
401Unauthorized未授权
404Not Found找不到资源
500Internal Server Error服务器错误
504Gateway Time-out网关错误

本节目的是为了编写响应报文,所以这里简单叙述即可,不会再讨论。

首部行

这里首部行格式和http请求报文首部行格式是一样的,都是由key: value\r\n的格式,这里就不啰嗦了。

响应主体

这里应当和请求报文一样,在首部应当添加keyContent-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/pdudohttp://127.0.0.1:8081/hdasdadwdq

我们先使用curl进行获取一下

再使用浏览器进行访问

总结

响应报文状态码至关重要

我们注意到了上面的案例,有一个报文是状态码301,首部信息Locationhttps://juejin.cn/,这这个跳转和服务器没有关系,是客户端(浏览器)拿到状态码之后,发现是301,哦,要进行跳转,然后去拿首部信息的Location进行跳转的。

案例代码已经上传gitee: ResponseMessages.go

怎么样,好玩吧,快动动你的小手指,试试吧。

本文正在参加技术专题18期-聊聊Go语言框架

posted @ 2022-07-15 23:18  pdudos  阅读(0)  评论(0编辑  收藏  举报  来源