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设置的很大,还是每次只发送很小的数据,和上述一样,当该种连接过多时候,会导致服务器连接数满,从而不能接收新的请求。


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











相关文章
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
162 45
|
23天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
23天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
35 2
|
25天前
|
中间件 Go API
Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架
本文概述了Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架。
66 1
|
1月前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP是一种流行的服务器端脚本语言,自诞生以来在Web开发领域占据重要地位。从简单的网页脚本到支持面向对象编程的现代语言,PHP经历了多次重大更新。本文探讨PHP的现代演进历程,重点介绍其在Web开发中的应用及框架创新,如Laravel、Symfony等。这些框架不仅简化了开发流程,还提高了开发效率和安全性。
30 3
|
1月前
|
前端开发 JavaScript 开发工具
从框架到现代Web开发实践
从框架到现代Web开发实践
42 1
|
1月前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP 自发布以来一直在 Web 开发领域占据重要地位,历经多次重大更新,从简单的脚本语言进化为支持面向对象编程的现代语言。本文探讨 PHP 的演进历程,重点介绍其在 Web 开发中的应用及框架创新。自 PHP 5.3 引入命名空间后,PHP 迈向了面向对象编程时代;PHP 7 通过优化内核大幅提升性能;PHP 8 更是带来了属性、刚性类型等新特性。
30 3
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
136 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
71 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
111 3
Golang语言之gRPC程序设计示例