Golang之我想写个"web框架"-1: 获取请求报文

简介: Golang之我想写个"web框架"-1: 获取请求报文

本来是想学习go web框架的,我跟着beego文档一顿猛操作,结果发现自己根本学不下去,beego框架太难了,朋友们,可能是因为我的基础知识太弱鸡了吧,于是乎就想从头开始学一下。



什么是http协议


要讲清楚这个点,我们得先回顾一下TCP/IP4层协议,图示如下

image.png


我们http协议是应用层协议,是基于传输层的TCP来传输报文的,所以说,想要进行http请求之前,需要首先先建立TCP链接,从而发送请求。 我们通常将基于http协议的应用,称之为是web应用,目前我们主流使用的是http 1.0http 1.1两个版本。由于本节我们将介绍如何获取报文,所以不展开讲协议内容。


通过上述对http协议的描述,我们知晓了想要进行http请求之前,首先需要建立TCP连接,从而发送请求报文,现在问题是,TCP建立之后,相当于建立了一根管道,我们怎么知道请求报文发送完毕了呢?这个就要先分析一下我们请求报文格式了。


http报文概述

具体的http格式大概是这样的

image.png



整个起始行 和 请求头 是依靠/r/n/r/n来判断的,当读取到这个字符的时候,就意味着整个起始行 和 请求头结束了,然后请求体的报文主体是依靠什么来结尾的呢? 我们判断是否有报文主体 以及 报文主体 如何判断结束的,若我们有报文主体需要发送的时候,需要在请求头中添加 Content-Length来记录报文主体的长度。


我们分别来解释一下含义



起始行


我们接着来看起始行,它由 请求方法 和 URL 以及 协议版本构成

请求方法有哪些呢?这里暂时简单列举一下

  • GET: 请求读取由URL所标注的信息
  • HEAD: 请求读取由URL所标识的首部信息
  • POST: 向服务器添加信息
  • PUT: 在指名的URL下存储一个文档

URL是指我们需要请求的资源,例如: /1.txt/1/2.txt

协议版本是指向服务器声明客户端所使用的版本,目前最流行的是 http 1.0http 1.1



请求头

http报文请求头主要是用于携带报文的附加信息,其格式为key: value(key后跟一个冒号:,然后是空格,接着是value,最后是\r\n)


首部结束符

我们使用\r\n\r\n来标注首行结束了,主要是用于分割首部行与报文主体


报文主体

报文主体的长度来源于请求头中keyContent-Length的记录值。


手写报文验证格式

我们可以通过telnet来验证下报文的正确性,我们可以先写一个简单的http服务器,然后通过telnet连接上服务器,然后键入报文,来获取数据。



我们http服务器定义路由: /pdudo,当访问后,返回字符 hello juejin\n


image.png


我们开启服务器后,使用telnet进行获取数据

image.png

以上足以验证,我们编写的报文可以获取服务器数据。



获取报文


建立TCP服务器


现在假设没有net.http包供我们调用,我们如何获取报文并且分析报文请求格式呢,我们回顾上面案例,应用层http协议,是基于传输层TCP协议的,所以,当前我们得先建立一个TCP服务器起来。


我们写出一个最简单的TCP程序

如上监听8081端口,获取TCP管道数据后,就输出出来

image.png


启动程序后

我们使用curl客户端来访问一下


image.png

当然,请求该连接,这个是不会有返回结果的。

服务器打印了输出

image.png

我们服务器接收报文成功

如上http协议所述,我们还可以请求带主体,我们尝试一下,查看请求报文长什么样子。

使用curl可以发送POST请求,并且携带请求主体

image.png


服务器输出

image.png


从结果来看,我们可以通过Content-Length来获取请求主体长度



获取报文详细数据


我们在获取报文之前,应当思考一个问题,如何确定报文结束 以及 请求主体长度 ,好在我们再介绍http协议的时候提及了,这里再重复一下。


我们使用\r\n\r\n来标注首行结束了,主要是用于分割首部行与报文主体

报文主体的长度来源于请求头中keyContent-Length的记录值。


那么在代码中,我们应当如何编写呢? 我们可以想一个笨办法,每次从TCP管道中读取一个字节,然后比对已经获取的后4个字节 是否 和 \r\n\r\n相等,若相等,则代表首行结束了,接着在首行中获取 起始行 和 请求头的数据。


我们拿到了请求头数据,就可以判断是否存在 Content-Length,若存在,我们就可以根据其长度去取主体数据了,这样,我们整个请求报文就可以拿出来了,我们尝试下。



我们编写代码如下(main方法省略了)

type httpHeaders struct {
  Method string
  Url string
  HttpVersion string
  RequestHeader map[string]string
  Body []byte
}
func worker(conn net.Conn) {
  defer conn.Close()
  maxLen := 8182
  headBuf := make([]byte,maxLen) // 申请内存用于接收报文
  CRLF := "\r\n" // 定义行结束
  CRLF2 := "\r\n\r\n" // 定义首部报文结束符
  readSize := 0
  // 每次读取一个字节数据
  for i:=1;i<=maxLen;i++ {
    n,err := conn.Read(headBuf[readSize:i])
    if err != nil {
      fmt.Println("error: " , err)
      return
    }
    readSize = readSize + n
    // 判断首部报文是否结束了
    if len(CRLF2) <= len(string(headBuf[:readSize])) {
      if string(headBuf[readSize-len(CRLF2):readSize]) == CRLF2 {
        break
      }
    }
  }
  // 判断首部报文是否超过允许接收长度
  if maxLen <= readSize {
    fmt.Println("超过允许接收的最大长度")
    return
  }
  // 声明 httpHeaders
  var httpHeader httpHeaders
  httpHeader.RequestHeader = make(map[string]string,1)
  // 将获取的首部数据打印出来
  for k,v := range strings.Split(string(headBuf[:readSize-len(CRLF)]),CRLF) {
    if v == "" {
      continue
    }
    if 0 == k {
      // 请求行
      header := strings.Split(v," ")
      httpHeader.Method = header[0]
      httpHeader.Url = header[1]
      httpHeader.HttpVersion = header[2]
    } else {
      // 请求头
      requestLine := strings.Split(v,": ")
      httpHeader.RequestHeader[requestLine[0]] = requestLine[1]
    }
  }
  // 获取报文主体数据
  bodyLen , ok := httpHeader.RequestHeader["Content-Length"];if ok {
    bodyLenInt , err := strconv.Atoi(bodyLen)
    if err != nil {
      fmt.Println("Content-Length值转化失败")
      return
    }
    // 从管道中取 Content-Length 长度的数据
    bodyBuf := make([]byte,bodyLenInt)
    recvLen := 0
    for recvLen < bodyLenInt {
      n , err := conn.Read(bodyBuf[recvLen:bodyLenInt])
      if err != nil {
        fmt.Println("read error " , err)
        return
      }
      recvLen += n
    }
    httpHeader.Body = bodyBuf[:]
  }
  // 暂时打印请求报文
  fmt.Printf("请求方法:%s\nURL:%s\n协议版本:%s\n请求头:%v\n报文主体:%s", httpHeader.Method , httpHeader.Url,httpHeader.HttpVersion , httpHeader.RequestHeader , httpHeader.Body)
}





我们访问一下

image.png


我们查看服务器打印的请求报文


image.png

这里解释一下,curl之所以报错: curl: (52) Empty reply from server,是因为函数结束后,服务器将该连接断开了,并没有返回任何数据给客户端。



总结


为什么将/r/n称之为CRLF

后期我们将/r/n统称为CRLF,为什么呢,我们不妨打开ascii看一下就明白了

linux中使用命令man ascii可以查看ascii编码,我们看10进制的1310其实CRLF就是这么来的。

image.png


http 报文攻击方式


这里有个有意思的事情,我在查询http报文资料的时候,发现有一种攻击方法,称之为slow headerslow post,这里来解释下是什么意思。


slow header:表示客户端连接到服务器后,通过慢速度发送数据,但是一直不发送\r\n\r\n,服务器一直在接收,所以始终占着服务器连接,当该种连接过多时,会导致服务器连接数满,从而不能接收新的请求。


slow post: 这里指的是,通过post发送数据,但是将Content-Length设置的很大,还是每次只发送很小的数据,和上述一样,当该种连接过多时候,会导致服务器连接数满,从而不能接收新的请求。


好了,动动你的手,来试试吧。











相关文章
|
17天前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【4月更文挑战第9天】本文对比了Python三大Web框架Django、Flask和Pyramid。Django功能全面,适合快速开发,但学习曲线较陡;Flask轻量灵活,易于入门,但默认配置简单,需自行添加功能;Pyramid兼顾灵活性和可扩展性,适合不同规模项目,但社区及资源相对较少。选择框架应考虑项目需求和开发者偏好。
|
1天前
|
存储 中间件 Go
探索Gin框架:快速构建高性能的Golang Web应用
探索Gin框架:快速构建高性能的Golang Web应用
|
2天前
|
安全 前端开发 JavaScript
在Python Web开发过程中:Web框架相关,如何在Web应用中防止CSRF攻击?
在Python Web开发中防范CSRF攻击的关键措施包括:验证HTTP Referer字段、使用CSRF token、自定义HTTP头验证、利用Web框架的防护机制(如Django的`{% csrf_token %}`)、Ajax请求时添加token、设置安全会话cookie及教育用户提高安全意识。定期进行安全审计和测试以应对新威胁。组合运用这些方法能有效提升应用安全性。
7 0
|
3天前
|
开发框架 前端开发 数据库
Python从入门到精通:3.3.2 深入学习Python库和框架:Web开发框架的探索与实践
Python从入门到精通:3.3.2 深入学习Python库和框架:Web开发框架的探索与实践
|
8天前
|
存储 监控 Go
Golang框架实战-KisFlow流式计算框架(1)-概述
KisFlow是针对缺乏数仓平台但又有实时计算需求的企业的解决方案,它提供分布式批量消费、有状态流式计算、数据流监控和分布式任务调度等功能。通过KisFunction实现业务逻辑复用,减轻对业务数据库的压力。系统包括流式计算层和任务调度层,支持多种数据源和中间件集成。KisConfig用于配置管理,KisFunction是基本计算单元。设计目标是使业务工程师能轻松进行流式计算。项目源码可在GitHub查看:https://github.com/aceld/kis-flow。
40 0
Golang框架实战-KisFlow流式计算框架(1)-概述
|
12天前
|
前端开发 数据挖掘 API
使用Python中的Flask框架进行Web应用开发
【4月更文挑战第15天】在Python的Web开发领域,Flask是一个备受欢迎的轻量级Web框架。它简洁、灵活且易于扩展,使得开发者能够快速地构建出高质量的Web应用。本文将深入探讨Flask框架的核心特性、使用方法以及在实际开发中的应用。
|
24天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
1月前
|
SQL 前端开发 Go
编程笔记 GOLANG基础 001 为什么要学习Go语言
编程笔记 GOLANG基础 001 为什么要学习Go语言
|
3月前
|
物联网 Go 网络性能优化
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式【1月更文挑战第21天】【1月更文挑战第104篇】
101 1
|
1天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
10 1