zoukankan      html  css  js  c++  java
  • HTTP代理实现请求报文的拦截与篡改6从目标服务器接收响应报文并封装

    返回目录

      从目标服务器读取响应并封装      

      至此,请求已经重新转发到目标服务器了,那么下一步,自然就是要接收从服务器返回的响应信息了。 还记得请求是怎么发送的吗

     

    this.Response.ResendRequest() // 将请求报文重新包装后转发给目标服务器  

      那么接收呢  

    this.Response.ReadResponse () // 读取从目标服务器返回的信息 

      进入ReadResponse(ServerChatter.cs里)方法看一看后,我们会发现,他其实和读取请求报文时(ClientChatter的ReadRequest方法)基本上是一样的,都是不停的使用Receive方法接收数据,直到已经接收完成为止(要判断三种情况,不记得的,可以翻回去看一看请求报文的读取那部分)。然后再将接收到的响应信息映射到一个HTTPResponseHeaders 类型的对象里,这个对象名叫_inHeaders。同样的也有一个public的Headers属性来访问它。和读取请求报文时一样,这个方法里,也会记录<entity-body>的偏移位置,以备 TakeEntity方法调用时,用来读取entito-body 部分  。      

      响应信息是以响应报文的方式返回回来的, 响应报文的格式如下: 

    <version><status><reason-phrase>
    <header>
    <entity-body>

    <version> :  HTTP的版本号,例如 HTTP/1.1 or HTTP/1.0

    <status> : 状态码 常见的  200 成功  404 没有找到文件 500 服务器错误等 

    <reason-phrase> : 对于状态码的简短解释,这个解析不同的服务器内容可能不尽相同。例如 当 <status>为200时,这里可以是 OK

    <head> : 首部,可以有0个或者多个,每个首部都是key:value的形式,然后以CRLF(回车换行)结束  例如: content-type:text/html   

    <entity-body> 主体内容,返回的HTML,或者二进制的图片,视频等数据都在这一部分   

      这里不作详细说明,其它的可参考HTTP协议的报文部分说明,只举个简单例子  

    HTTP/1.1 200 OK                     <version><status><reason-phrase>CRLF      
    content-type:text/html              <head>CRLF
    content-length:1196                 CRLF
                                        CRLF
    <html>                              <entity-body>
    <head>...(省略N个字)</head>
    <body>...(省略N个字)</body>
    </html>

      其它具体的代码就不分析了,因为和读取请求报文区别不是很大,对照着理解和阅读就可以了,不过有个小技巧要在这里讲一讲。 

      我们知道,在读取请求报文的时候,我们没办法确定客户端的连接是否关闭,但在读取服务端响应的时候就完全不同了,因为我们可以控制让服务器发送完数据后自动关闭,那么如何控制呢,其实很简单,还记得前面提到的connection首部吗,在讲连接管理的时候主要讲的就是它,还记得他的用法吗,不记得的,可以翻回去再看一看,不过上面只讲了请求报文中connection:keep-alive的情况,还少讲了一种情况,那就是请求报文中,connection:close的情况(http1.0版本,默认就是这种情况)  

      当我们的请求报文里有connection:close首部时,服务端收到这个报文的时候,就知道,我们并不想建立持久连接,那么他也会在他的响应报文里加一个connection:close首部,然后等发送完所有数据后关闭他的连接。   

      所以如果你嫌判断接收完毕要三种情况太麻烦了。这里就有一个小技巧了,就是在ResendRequest的时候,简单的把connection:keep-alive,改成connection:close就行了。这样就可以只判断 iMaxByteCount = 0 (iMaxByteCount=Socket.Receive()) 来判断接收 是否完毕了   。  

      到这里,从服务器接收响应数据应该就算讲完了,但前面还遗留了一个transfer-encoding:chuncked的问题,transfer-encoding首部和connection首部一样,都是请求报文和响应报文中都有的首部,这种首部叫做通用首部。前面也讲过了,在请求的时候,如果不上传文件的情况下,报文是非常小的,基本上用到chunked的机率很小,而接收的时候相对的chunked出现的频率就要高一些了。

      他主要用于结合jsp,.net,asp等动态技术进行输出的时候。当然大部分情况下这些程序输出的时候标识长度使用的仍然是content-length。但是当输出内容需要压缩输出的时候,例如gzip。因为需要额外的在服务器申请一个很大的内存来存完整的压缩内容,然后再计算长度,这样是很耗空间和时间的,这时候,就需要使用chuncked了。 

      Chunck 从字面意思翻译就是“块”的意思。 简单说起来就是数据发送方把一个大的包分割成多个小块,便于在网络上传输。使用chunked机制的时候,需要在报文里加一个transder-encoding:chunked首部。然后在<entity-body>部分遵循一定的格式

      这个格式如下所示:  

    chunked-body = *chunk
    "0" CRLF
    footer
    CRLF
    chunk = chunk-size [ chunk-ext ] CRLF
    chunk-data CRLF
    hex-no-zero = <HEX excluding "0">
    chunk-size = hex-no-zero *HEX
    chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-value ] )
    chunk-ext-name = token
    chunk-ext-val = token | quoted-string
    chunk-data = chunk-size(OCTET)
    footer = *entity-header 

      这个是正则文法的范式表示,基本上属于普通人类看着比较郁闷的范围, 所以我们给他加上语法图,让其更容易理解一点(antlr不支持中间杠,所以我把中间杠全变成下划杠了)

    chunked-body = *chunk
    "0" CRLF
    Footer
    CRLF

    chunk = chunk-size [ chunk-ext ] CRLF
    chunk-data CRLF

    hex-no-zero = <HEX excluding "0"> 

      

           

    chunk-size = hex-no-zero *HEX    

    chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-value ] )

       

    chunk-ext-name = token
    chunk-ext-val = token | quoted-string
    chunk-data = chunk-size(OCTET)     
    footer = *entity-header 

      Chunked-Body表示经过chunked编码后的报文体。报文体可以分为chunk, ‘0’CRLF,footer和结束符(CRLF)四部分。chunk的数量在报文体中最少可以为0,无上限;每个chunk的长度是自指定的,即,起始的数据必然是16进制数字的字符串,代表后面chunk-data的长度(字节数)。这个16进制的字符串第一个字符如果是“0”,则表示chunk-size为0,该chunk为最后一个chunck,无chunk-data部分。可选的chunk-ext由通信双方自行确定,如果接收者不理解它的意义,可以忽略。

      footer是附加的在尾部的额外头域,通常包含一些元数据,这些头域可以在解码后附加在现有头域之,这个额外头域可以为空。

      Footer之后紧跟一个结束符CRLF表示整个文件已经传输完毕了 。    

      好的,chuncked编码的格式介绍完了,至于如何判定其结束的代码,各位可以自行参考代码,或者自己去实现 。 所谓十年修得同船渡,百年修得共枕眠,自已动手,才能丰衣足食的。    

  • 相关阅读:
    67. @Transactional的类注入失败【从零开始学Spring Boot】
    66. No EntityManager with actual transaction available for current thread【从零开始学】
    Android ShapeDrawable之OvalShape、RectShape、PaintDrawable、ArcShape
    Android渲染器Shader:环状放射渐变渲染器RadialGradient(三)
    Android渲染器Shader:梯度渐变扫描渲染器SweepGradient(二)
    Android弹幕编程设计实现的解决方案(一)
    65.什么是IOC?【从零开始学Spring Boot】
    64.JPA命名策略【从零开始学Spring Boot】
    事务、视图和索引
    存储过程
  • 原文地址:https://www.cnblogs.com/jivi/p/2953621.html
Copyright © 2011-2022 走看看