0
点赞
收藏
分享

微信扫一扫

C++后端开发(2.1.3)——http协议解析与http服务器实现

胡桑_b06e 2022-03-19 阅读 73

C++后端开发(2.1.3)——http协议解析与http服务器实现


小节提纲

1.htttp 简介

http协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,用于从万维网(www)传输超文本到本地浏览器的传送协议。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。

http协议是一个应用层协议,由请求和响应构成,是一个标准的c/s(客户端/服务器)模型。

http协议一般承载于传输层的tcp协议上,有时也承载于传输层的tls/ssl协议之上,这时就是https。

在这里插入图片描述
在这里插入图片描述

1.1 特点

  1. 简单快速:客户端向服务器发起请求时,只需要传请求方法和路径。请求方法包括get、post、put、delete、head,每种方法规定了客户端与服务器联系的类型。由于http协议简单,使得http服务器的程序规模小,因而通信速度很快。
  2. 灵活:http允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记。
  3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  4. 无状态:http协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
  5. 支持C/S(Client/Server)模式。

1.2 版本差别

第一个http协议诞生于1989年3月,到现在为止总共经历了3个版本的演化。

1.2.1 http 0.9

http 0.9是第一个版本的http协议,已经过时。它的组成极其简单,只允许客户端发送get这一种请求,且不支持请求头。由于没有协议头,造成了http 0.9协议只支持一种内容,即纯文本。不过网页仍然支持用html语言格式化,同时无法插入图片。

http 0.9具有典型的无状态性,每个事务独立进行处理,事务结束时就释放这个连接。由此可见,http协议的无状态特点在其第一个版本0.9中已经成型。

1.2.2 http 1.0

http协议的第二个版本,第一个在通讯中指定版本号的http协议版本,至今仍被广泛采用。相对于http 0.9 增加了如下主要特性:

  1. 请求与响应支持头域。
  2. 响应对象以一个响应状态行开始。
  3. 响应对象不只限于超文本。
  4. 开始支持客户端通过post方法向Web服务器提交数据,支持get、head、post方法。
  5. 支持长连接(但默认还是使用短连接),缓存机制,以及身份认证。

1.2.3 http 1.1

http协议的第三个版本是http 1.1,是目前使用最广泛的协议版本。http 1.1引入了许多关键性能优化:keepalive连接,chunked编码传输,字节范围请求,请求流水线等。

  1. Persistent Connection(keepalive连接)
    允许http设备在事务处理结束之后将tcp连接保持在打开的状态,以便未来的http请求重用现在的连接,直到客户端或服务器端决定将其关闭为止。在http 1.0中使用长连接需要添加请求头Connection: Keep-Alive,而在http 1.1 所有的连接默认都是长连接,除非特殊声明不支持(http请求报文首部加上Connection: close)。
  2. chunked编码传输
    该编码将实体分块传送并逐块标明长度,直到长度为0块表示传输结束,,这在实体长度未知时特别有用(比如由数据库动态产生的数据)。
  3. 字节范围请求
    http 1.1支持传送内容的一部分。比方说,当客户端已经有内容的一部分,为了节省带宽,可以只向服务器请求一部分。该功能通过在请求消息中引入了range头域来实现,它允许只请求资源的某个部分。在响应消息中Content-Range头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码206(Partial Content)。
  4. Pipelining(请求流水线)
    A client that supports persistent connections MAY “pipeline” its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received.

1.2.4 http 2.0

http 2.0是下一代http协议,目前应用还非常少,主要特点有:

  1. 多路复用(二进制分帧)
    http 2.0最大的特点:不会改动http的语义,http方法、状态码、URI及首部字段等等,这些核心概念上一如往常。却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫2.0,是在于新增的二进制分帧层。在二进制分帧层上,http 2.0会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中http.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。http 2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
  2. 头部压缩
    当一个客户端向相同服务器请求许多资源时,像来自同一个网页的图像,将会有大量的请求看上去几乎同样的,这就需要压缩技术对付这种几乎相同的信息。
  3. 随时复位
    http 1.1一个缺点是当http信息有一定长度大小数据传输时,你不能方便地随时停止它,中断tcp连接的代价是昂贵的。使用http 2.0的RST_STREAM将能方便停止一个信息传输,启动新的信息,在不中断连接的情况下提高带宽利用效率。
  4. 服务器端推流: Server Push
    客户端请求一个资源X,服务器端判断也许客户端还需要资源Z,在无需事先询问客户端情况下将资源Z推送到客户端,客户端接受到后,可以缓存起来以备后用。
  5. 优先权和依赖
    每个流都有自己的优先级别,会表明哪个流是最重要的,客户端会指定哪个流是最重要的,有一些依赖参数,这样一个流可以依赖另外一个流。优先级别可以在运行时动态改变,当用户滚动页面时,可以告诉浏览器哪个图像是最重要的,你也可以在一组流中进行优先筛选,能够突然抓住重点流。

1.3 URL和 URI

1.3.1 简介

1.3.2 URL组成

以下面这个URL为例,介绍下普通URL的各部分组成:

https://blog.csdn.net/weixin_43687811/article/details/123437322

从上面的URL可以看出,一个完整的URL包括以下几部分:

  1. 协议部分:该URL的协议部分为"https:",这代表网页使用的是https协议,在"https"后面的"//"为分隔符。
  2. 域名部分:该URL的域名部分为"blog.csdn.net"。一个URL中,也可以使用IP地址作为域名使用。
  3. 端口部分:跟在域名后面的是端口,域名和端口之间使用":"作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口。默认http的端口号为80,https的端口号为443。
  4. 虚拟目录部分:从域名后的第一个"/“开始到最后一个”/“为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分,上述URL的虚拟目录为"weixin_43687811/article/details/”。
  5. 文件名部分:从域名后的最后一个"/“开始到”?“为止,是文件名部分,如果没有”?",则是从域名后的最后一个"/“开始到”#“为止,是文件部分,如果没有”?“和”#",那么从域名后的最后一个"/“开始到结束,都是文件名部分。本例中的文件名是"123437322”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名。
  6. 锚部分:从"#"开始到最后,都是锚部分,锚部分也不是一个URL必须的部分。
  7. 参数部分:从"?“开始到”#“为止之间的部分为参数部分,又称搜索部分、查询部分。参数可以允许有多个参数,参数与参数之间用”&"作为分隔符。

1.3.3 URL 和 URI 区别

1.4 request请求消息

1.4.1 请求消息

客户端发送一个http请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据。
在这里插入图片描述
看上图,访问百度主页的request:

  • 第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的http版本。get说明请求类型为get,该行的最后一部分说明使用的是http 1.1版本
  • 第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息。从第二行起为请求头部,host将指出请求的目的地。User-Agent,服务器端和客户端脚本都能访问它,它是客户端类型检测逻辑的重要基础。该信息由你的客户端来定义,并且在每个请求中自动发送等。
  • 第三部分:空行,请求头部后面的空行是必须的。即使第四部分的请求数据为空,也必须有空行。
  • 第四部分:请求数据也叫主体,可以添加任意的其他数据,上面的请求数据为空。

1.4.2 请求头部

http通用头:通用头域包含请求和响应消息都支持的头域,通用头域包含缓存头部Cache-Control、Pragma及信息性头部Connection、Date、Transfer-Encoding、Update、Via。

  1. Cache-Control
    Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、 max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age。
  2. Pragma
    Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在http 1.1协议中,它的含义和Cache- Control:no-cache相同。
  3. Connection
    Connection表示是否需要持久连接。如果Servlet看到这里的值为"Keep-Alive",或者看到请求使用的是http 1.1(http 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。Close:告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了。Keepalive:告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求。Keep-Alive:如果客户端请求保持连接,则该头部表明希望 WEB 服务器保持连接多长时间(秒),如Keep-Alive:300。
  4. Date
    Date头域表示消息发送的时间,服务器响应中要包含这个头部,因为缓存在评估响应的新鲜度时要用到,其时间的描述格式由RFC822定义。例如,Date:Mon, 31 Dec 2001 04:25:57 GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。
  5. Transfer-Encoding
    WEB 服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码,比如是否分块(chunked),例如:Transfer-Encoding: chunked。
  6. Upgrade
    它可以指定另一种可能完全不同的协议,如http 1.1客户端可以向服务器发送一条http 1.0请求,其中包含值为"HTTP/1.1"的Upgrade头部,这样客户端就可以测试一下服务器是否也使用http 1.1了。
  7. Via
    列出从客户端到OCS 或者相反方向的响应经过了哪些代理服务器,它们用什么协议和版本发送的请求。

http请求头:请求头用于说明是谁或什么在发送请求、请求源于何处,或者客户端的喜好及能力。服务器可以根据请求头部给出的客户端信息,试着为客户端提供更好的响应。请求头域可能包含下列字段Accept、Accept-Charset、Accept- Encoding、Accept-Language、Authorization、From、Host、If-Modified-Since、If-Match、If-None-Match、If-Range、If-Range、If-Unmodified-Since、Max-Forwards、Proxy-Authorization、Range、Referer、User-Agent。对请求头域的扩展要求通讯双方都支持,如果存在不支持的请求头域,一般将会作为实体头域处理。

  1. Accept
    告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type。
  2. Accept-Charset
    客户端告诉服务器自己能接收的字符集。
  3. Accept-Encoding
    客户端声明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate)。
  4. Accept-Language
    客户端声明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等。
  5. Authorization
    当客户端接收到来自WEB服务器的 WWW-Authenticate响应时,用该头部来回应自己的身份验证信息给WEB服务器。
  6. If-Match
    如果对象的ETag没有改变,其实也就意味著对象没有改变,才执行请求的动作,获取文档。
  7. If-None-Match
    如果对象的ETag改变了,其实也就意味著对象也改变了,才执行请求的动作,获取文档。
  8. If-Modified-Since
    如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告诉客户端该对象没有修改。例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT。
  9. If-Unmodified-Since
    如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。
  10. If-Range
    客户端告诉WE 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。客户端通过发送请求对象的ETag或者自己所知道的最后修改时间给WEB服务器,让其判断对象是否改变了。总是跟Range头部一起使用。
  11. Range
    客户端(比如 Flashget 多线程下载时)告诉WEB服务器自己想取对象的哪部分。例如:Range: bytes=1173546。
  12. Proxy-Authenticate
    代理服务器响应客户端,要求其提供代理身份验证信息。
  13. Proxy-Authorization
    客户端响应代理服务器的身份验证请求,提供自己的身份信息。
  14. Host
    客户端指定自己想访问的WEB服务器的域名/IP 地址和端口号。
  15. Referer
    客户端向WEB服务器表明自己是从哪个网页URL获得点击当前请求中的网址/URL。
  16. User-Agent
    客户端表明自己的身份。例如:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36。

1.5 response响应消息

1.5.1响应消息

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个http的响应消息。http响应也由四个部分组成:状态行、消息报头、空行和响应正文。

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
 
<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>
  • 第一部分:状态行,由http协议版本号, 状态码, 状态消息三部分组成。上面http版本号为http/1.1,状态码为200,状态消息为"OK"。
  • 第二部分:消息报头,用来说明客户端要使用的一些附加信息。Date:生成响应的日期和时间;Content-Type:指定了MIME类型的html(text/html),编码类型是UTF-8。
  • 第三部分:空行,消息报头后面的空行是必须的。
  • 第四部分:响应正文,服务器返回给客户端的文本信息,空行后面的html部分为响应正文。

1.5.2 响应头部

上面说了第二部分为消息报头,那么消息报头有哪些呢?

http响应头:响应头向客户端提供一些额外信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令。这些头部有助于客户端处理响应,并在将来发起更好的请求。响应头域包含Age、Location、Proxy-Authenticate、Public、Retry- After、Server、Vary、Warning、WWW-Authenticate。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。

  1. Age
    当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
  2. Server
    web服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61 (Unix)。
  3. Accept-Ranges
    web服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
  4. Vary
    web服务器用该头部的内容告Cache服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源web服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding: gzip; Vary: Content-Encoding,那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary:Accept-Encoding。

HTTP实体头:实体头部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。总之,实体头部可以告知接收者它在对什么进行处理。请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括信息性头部Allow、Location,内容头部Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type,缓存头部Etag、Expires、Last-Modified、extension-header。

  1. Allow
    服务器支持哪些请求方法(如get、post等)。
  2. Location
    表示客户应当到哪里去提取文档,用于将接收端定位到资源的位置(URL)上。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
  3. Content-Base
    解析主体中的相对URL时使用的基础URL。
  4. Content-Encoding
    web服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip。
  5. Content-Language
    web服务器告诉浏览器理解主体时最适宜使用的自然语言。
  6. Content-Length
    web服务器告诉浏览器自己响应的对象的长度或尺寸,例如:Content-Length: 26012。
  7. Content-Location
    资源实际所处的位置。
  8. Content-MD5
    主体的MD5校验。
  9. Content-Range
    实体头用于指定整个实体中的一部分的插入位置,它也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式: Content-Range:bytes-unitSPfirst-byte-pos-last-byte-pos/entity-legth。例如,传送头500个字节次字段的形式:Content-Range:bytes0- 499/1234如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。
  10. Content-Type
    web服务器告诉浏览器自己响应的对象的类型。例如:Content-Type:application/xml。
  11. Etag
    就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。
  12. Expires
    web服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟web服务器验证了其有效性后,才能用来响应客户请求。是 http 1.0 的头部。例如:Expires:Sat, 23 May 2009 10:02:12 GMT。
  13. Last-Modified
    web服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT。

1.6 状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

  • 1xx:指示信息 - 表示请求已接收,继续处理。
  • 2xx:成功 - 表示请求已被成功接收、理解、接受。
  • 3xx:重定向 - 要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误 - 请求有语法错误或请求无法实现。
  • 5xx:服务器端错误 - 服务器未能实现合法的请求。

常见状态码:

200 OK                        //客户端请求成功
400 Bad Request               //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized              //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
403 Forbidden                 //服务器收到请求,但是拒绝提供服务
404 Not Found                 //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error     //服务器发生不可预期的错误
503 Server Unavailable        //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

1.7 请求方法

1.7.1 请求方法

根据http标准,http请求可以使用多种请求方法。
http 1.0定义了三种请求方法: get, post 和head方法。
http 1.1 新增了五种请求方法:options, put, delete, trace 和 connect 方法。

get           请求指定的页面信息,并返回实体主体。
head          类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
post          向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
put           从客户端向服务器传送的数据取代指定的文档的内容。
delete        请求服务器删除指定的页面。
connect       HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
options       允许客户端查看服务器的性能。
trace         回显服务器收到的请求,主要用于测试或诊断。

1.7.2 get和post 区别

  1. get是从服务器上获取数据,post是向服务器传送数据。get和 post只是一种传递数据的方式,get也可以把数据传到服务器,它们的本质都是发送请求和接收结果。只是组织格式和数据量上面有差别,http协议里面有介绍。
  2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过http post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。 因为get设计成传输小数据,而且最好是不修改服务器的数据,所以浏览器一般都在地址栏里面可以看到,但post一般都用来传递大数据,或比较隐私的数据,所以在地址栏看不到,能不能看到不是协议规定,是浏览器规定的。
  3. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。 post基本没有限制,我想大家都上传过文件,都是用post方式的,但是需要修改form里面的那个type参数。
  4. get安全性非常低,post安全性稍微高点。但是如果没有加密的话,它们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。

1.8 session和cookie

1.8.1 session

session在计算机中,尤其是在网络应用中,称为"会话控制",session对象存储特定用户会话所需的属性及配置信息。这样当用户在应用程序的web页之间跳转时,存储在session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的web页时,如果该用户还没有会话,则web服务器将自动创建一个session对象。当会话过期或被放弃后,服务器将终止该会话。session对象最常见的一个用法就是存储用户的首选项,例如,如果用户指明不喜欢查看图形,就可以将该信息存储在session对象中。实际上session和cookie是类似的一种维持客户端和服务会话状态的技术,不过session安全性要比cookie高,这是因为session的数据是存放在服务端上的,所以相对的也会增加服务器的压力,因此session被应用于储存一些比较隐私的数据,例如用户名密码和用户的资料等。

1.8.2 cookie

cookie与session最大的区别就是一个是将数据存放在客户端,一个是将数据存放在服务端。cookie是将信息都存放在客户端的浏览器内存或磁盘中,所以不是很安全,别人可以分析存放在本地的cookie数据来进行用户信息的盗窃或进行cookie欺骗。 所以在安全性上session要好一些,session通信的一般实现形式是通过cookie来实现,与cookie不同的是,session只会保存一个sessionID在客户端,不会像cookie那样将具体的数据保存在客户端,session具体的数据只会保存在服务端上,在Servlet中session数据是被封装在一个对象里,而这个对象会被保存在对象池中,客户端发生请求时会带上它的sessionID,服务端就会根据这个sessionID,来从对象池中获得相应的session对象,从对象中获得session的具体数据,服务端通过这个session数据来保持或改变与客户端会话的状态。

1.8.3 session和cookie 区别

  1. cookie数据存放在客户端上,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。
  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  • 所以建议:
    将登陆信息等重要信息存放为session。
    其他信息如果需要保留,可以放在cookie中。

1.9 一次完整的http请求

在这里插入图片描述

在一次完整的HTTP通信过程中, Web浏览器与Web服务器之间将完成下列6个步骤:

  1. 建立TCP连接
    在http工作开始之前,客户端首先要通过网络与服务器建立连接,该连接是通过tcp来完成的,该协议与ip协议共同构建Internet,即著名的tcp/ip协议族,因此Internet又被称作是tcp/ip网络。
    http是比tcp更高层次的应用层协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接,因此首先要建立tcp连接, 一般tcp连接的端口号是80,tcp连接中我们比较熟悉的就是三次握手。
    一旦建立了tcp连接, 客户端就会向服务器发送请求命令:
    例如:GET/sample/hello.jsp HTTP/1.1
  2. 客户端发送请求头信息
    客户端发送其请求命令之后,还要以头信息的形式向服务器发送一些别的信息,这些信息用来描述自己。之后客户端发送了一空白行来通知服务器,表示它已经结束了该头信息的发送。若是post请求,还会在发送完请求头信息之后发送请求体。
  3. 服务器应答
    客户端向服务器发出请求后, 服务器会向客户机回送应答。
    HTTP/1.1 200 OK
    应答的第一部分是协议的版本号和应答状态码。
  4. 服务器发送应答头信息
    正如客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档,最后以一个空白行来表示头信息发送到此结束。
  5. 服务器向客户端发送数据
    服务器向浏览器发送头信息后,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
  6. 服务器关闭tcp连接
    一般情况下,一旦服务器向浏览器发送了请求数据,它就要关闭tcp连接。如果请求头信息加入了这行代码
    Connection:keep-alive,tcp连接在发送后将仍然保持打开状态。客户端可以继续通过相同的连接发送请求,保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

2. http服务器实现

本文基于rector百万并发实现中的代码做出改进使其成为http服务器

2.1 结构体

在ntyevent 结构体中增加http相关的参数

//事务结构体
struct ntyevent
{
    int fd;                                         //事务的fd
    int events;                                     // epoll events类型
    void *arg;                                      //需要传给回调函数的参数,一般传的是reactor的指针
    int (*callback)(int fd, int events, void *arg); //对应的回调函数
    int status;                                     // 0:新建 1:已存在
    char buffer[BUFFER_LENGTH];                     //接收到的消息
    int length;
    long last_active;

    // http 参数
    int method;
    char resource[BUFFER_LENGTH];
    int ret_code;
};

2.2 http 接收请求处理

2.2.1 recv_cb

//接收回调函数
int recv_cb(int fd, int events, void *arg)
{
    //外界传参获得的ntyreactor指针
    struct ntyreactor *reactor = (struct ntyreactor *)arg;
    //从内核的链表中取出当前的event
    struct ntyevent *ev = ntyreactor_idx(reactor, fd);
    //此次收到数据的长度
    int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
    //收到就直接删除对应的fd,以免多次响应
    nty_event_del(reactor->epfd, ev);
    //正确接收,大于0
    if (len > 0)
    {
        ev->length = len;
        ev->buffer[len] = '\0';

        printf("C[%d]:%s\n", fd, ev->buffer);

        //接收http请求
        http_request(ev);

        //收到以后直接发送回去
        nty_event_set(ev, fd, send_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLOUT, ev);
    }
    else if (len == 0)
    {
        close(ev->fd);
        // printf("[fd=%d] pos[%ld], closed\n", fd, ev - reactor->events);
    }
    else
    {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
    }
    return len;
}

2.1.2 http_request

//读一行http请求,将有用的信息放进linebuf
int readline(char *allbuf, int idx, char *linebuf)
{
    int len = strlen(allbuf);

    for (; idx < len; idx++)
    {
        //遇到\r\n
        if (allbuf[idx] == '\r' && allbuf[idx + 1] == '\n')
        {
            return idx + 2;
        }
        else
        {
            *(linebuf++) = allbuf[idx];
        }
    }

    return -1;
}
//服务器接收请求
int http_request(struct ntyevent *ev)
{
    // GET,POST
    char linebuf[1024] = {0};
    int idx = readline(ev->buffer, 0, linebuf);

    if (strstr(linebuf, "GET"))
    {
        ev->method = HTTP_METHOD_GET;

        //获取GET前面和HTTP之前的资源位,并塞进linebuf里面
        int i = 0;
        while (linebuf[sizeof("GET ") + i] != ' ')
        {
            i++;
        }
        linebuf[sizeof("GET ") + i] = '\0';
        //将请求的资源路径放进去
        sprintf(ev->resource, "./%s/%s", HTTP_WEBSERVER_HTML_ROOT, linebuf + sizeof("GET "));
    }
    else if (strstr(linebuf, "POST"))
    {
    }
}

2.2 http 响应处理

2.2.1 http_response

// http响应
int http_response(struct ntyevent *ev)
{
    if (ev == NULL)
        return -1;
    memset(ev->buffer, 0, BUFFER_LENGTH);

#if 0
	const char *html = "<html><head><title>hello http</title></head><body><H1>King</H1></body></html>\r\n\r\n";
						  	   
	ev->length = sprintf(ev->buffer, 
		"HTTP/1.1 200 OK\r\n\
		 Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n\
		 Content-Type: text/html;charset=ISO-8859-1\r\n\
		 Content-Length: 83\r\n\r\n%s", 
		 html);

#else
    printf("resource: %s\n", ev->resource);
    int filefd = open(ev->resource, O_RDONLY);
    //如果找不到
    if (filefd == -1)
    {
        ev->ret_code = 404;
        ev->length = sprintf(ev->buffer,
                             "HTTP/1.1 404 Not Found\r\n"
                             "Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
                             "Content-Type: text/html;charset=ISO-8859-1\r\n"
                             "Content-Length: 85\r\n\r\n"
                             "<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n");
    }
    else
    {
        //获取文件状态
        struct stat stat_buf;
        fstat(filefd, &stat_buf);
        close(filefd);
        //文件处于保护状态
        if (S_ISDIR(stat_buf.st_mode))
        {
            ev->ret_code = 404;
            ev->length = sprintf(ev->buffer,
                                 "HTTP/1.1 404 Not Found\r\n"
                                 "Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
                                 "Content-Type: text/html;charset=ISO-8859-1\r\n"
                                 "Content-Length: 85\r\n\r\n"
                                 "<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n");
        }
        else if (S_ISREG(stat_buf.st_mode))
        {
            ev->ret_code = 200;
            ev->length = sprintf(ev->buffer,
                                 "HTTP/1.1 200 OK\r\n"
                                 "Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
                                 "Content-Type: text/html;charset=ISO-8859-1\r\n"
                                 "Content-Length: %ld\r\n\r\n",
                                 stat_buf.st_size);
        }
    }
#endif
    return ev->length;
}

2.2.2 send_cb

int send_cb(int fd, int events, void *arg)
{
    struct ntyreactor *reactor = (struct ntyreactor *)arg;
    struct ntyevent *ev = ntyreactor_idx(reactor, fd);

    http_response(ev);

    //返回的是发送的字节数
    int len = send(fd, ev->buffer, ev->length, 0);
    //正确发送,删除发送fd,注册接收的fd
    if (len > 0)
    {
        printf("send[fd=%d], [%d]%s\n", fd, len, ev->buffer);

        if (ev->ret_code == 200)
        {
            int filefd = open(ev->resource, O_RDONLY);
            struct stat stat_buf;
            fstat(filefd, &stat_buf);
            //通过内存映射实现,零拷贝
            sendfile(fd, filefd, NULL, stat_buf.st_size);
            close(filefd);
        }

        nty_event_del(reactor->epfd, ev);
        nty_event_set(ev, fd, recv_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLIN, ev);
    }
    else
    {
        close(ev->fd);
        nty_event_del(reactor->epfd, ev);
        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }
    return len;
}

处理分包和粘包

  1. 分隔符 \r\n\r\n
  2. 定义包的长度
举报

相关推荐

0 条评论