上一篇博客介绍了,如何简单的组装报文,现在来处理比较麻烦的另一部分了–解析报文。组装报文实际上是偷了一个懒,把解析工作交给了浏览器。但是,如果直接解析一下实际的报文的话,还是会对报文的结构认识更加深刻一些,下面就来开始吧!

HTTP学习(2)–组装报文

解析报文

模拟报文

我们根据前面博客知道了报文的具体结构,下面就来解析报文,可是哪里来的报文呢?相信,虽然每天都在经历各种报文的传递,但是真正见到过报文的人还是不多吧。让我们来想象一下,报文从产生到传递给客户(或者服务器)被消费掉,其实时间是很短的。所以说,一个报文存在的时间极短,可以报文说就是朝生夕死。这里我们先来提供一个简单的模拟报文,存放在一个文件里面(把流动的报文固定住),这样处理起来比较方便。如果解析成功了,我们再去处理实际的报文。相当于,开发过程中,先使用模拟的数据,等开发完成了,再使用真实的数据。

模拟请求报文

Host: localhost:8080
Content-Type: image/jpeg
Content-Length: 112008

如果只是这样看的话,其实是缺少了很多必要的信息的,比如不可见的回车换行符 CRLF。所以我们换一个方式来查看模拟的报文:

这样是不是就非常清晰了,模拟的报文头部每一行包括一个首部字段信息和CRLF,最后还有一个CRLF(这里把它作为结束读取请求头的标识),后面的就是报文体的部分了,这里只考虑报文头部分。因为只要获取了头部,那么剩下的也就见到那了。(对于GET方式,是没有报文体的!)

首先是构造一个模拟的报文头部

这里就是向文件写入头部的信息,构造一个包含简单报文头的文件。

//构造一个简易的 报文头部,比较简陋,但是应该是可以满足学习条件了。static void createMessage(File file) {try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {out.write("Host: localhost:8080\r\n".getBytes(Charset.forName("UTF-8")));out.write("Content-Type: image/jpeg\r\n".getBytes(Charset.forName("UTF-8")));out.write("Content-Length: 112008\r\n".getBytes(Charset.forName("UTF-8")));out.write("\r\n".getBytes(Charset.forName("UTF-8")));} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

解析报文头部

static void resolve(File file) {try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {/*** 当取出来的行为空时,意味着已经取出了所有的头部。* 下面就是数据部分了(这里考虑的是简单的报文,非Multip-part类型的报文)* */String header = null;do {header = getHeader(in);  //每次取一行,即一个首部字段if ("".equals(header)) break;System.out.println("header:-----> " + header);} while (true);System.out.println("报文解析完成!");} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

获取报文首部的方法

这个方法参考了 getLine 方法,但是它是读取的字节而非字符。每次取一个字节的数据,直到遇到 \n 符号,就表示取出一个完整的首部了,然后去除两端的空白字符(主要是 \r 符号)。

//封装一个 getHeader 方法,每次取一个头部行,参考 getLine 方法。static String getHeader(InputStream in) throws IOException {StringBuilder header = new StringBuilder();while (true) {int c = in.read();if (c == '\n' || c == -1) break;header.append((char)c);}return header.toString().trim();  //取出空格字符}

完整的代码

package com.dragon.resolve;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;public class HttpMessageResolver {public static void main(String... args) {File file = new File("./src/main/java/com/dragon/resolve/httpmessage.txt");createMessage(file);   //创建报文resolve(file);         //解析报文}//构造一个简易的 报文头部,比较简陋,但是应该是可以满足学习条件了。static void createMessage(File file) {try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {out.write("Host: localhost:8080\r\n".getBytes(Charset.forName("UTF-8")));out.write("Content-Type: image/jpeg\r\n".getBytes(Charset.forName("UTF-8")));out.write("Content-Length: 112008\r\n".getBytes(Charset.forName("UTF-8")));out.write("\r\n".getBytes(Charset.forName("UTF-8")));} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}static void resolve(File file) {try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {/*** 当取出来的行为空时,意味着已经取出了所有的头部。* 下面就是数据部分了(这里考虑的是简单的报文,非Multip-part类型的报文)* */String header = null;do {header = getHeader(in);if ("".equals(header)) break;System.out.println("header:-----> " + header);} while (true);System.out.println("报文解析完成!");} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}//封装一个 getHeader 方法,每次取一个头部行,参考 getLine 方法。static String getHeader(InputStream in) throws IOException {StringBuilder header = new StringBuilder();while (true) {int c = in.read();if (c == '\n' || c == -1) break;header.append((char)c);}return header.toString().trim();  //取出空格字符}
}

运行结果


可见首部已经成功获取出来了,然后应该使用一个 Map 来存储这些首部字段。当然了,这里主要是演示如何取出这些首部字段。这里是我思考的一种解析的方式,非常简陋,但是可以工作就行了。

真实的报文

获取真实的报文

这里还是使用上篇博客组装报文的那个程序,使用一个简单的请求信息来请求(虽然这个请求没有用处),注意网络流的获取和文件流的获取是有区别的。通常使用本地文件可以使用读取字节的返回值为-1来结束读取,但是读取网络流的话,如果读取为-1,那么就表示当前流已经关闭了。只有几个方法可以做到:Socket 的 close() 或者是 Socket 的 shutInputStream()shutOutputStream() 方法。所以,如果对方不关闭连接或者关闭对应的输入输出流,我们是无法根据-1来结束读取的。最好的办法,就是知道需要读取的长度。这也就是 Content-Length 的作用了。这个首部还是很必要的,虽然也有不使用指定长度的分块传输模式,但是这里只考虑使用 Content-Length的简单情况。
通过刚才的那个程序,我们其实已经可以获取到Content-Length的大小了,也就知道报文体部分需要获取多少字节了。但是这里我还是使用提前设置好的报文大小来读取了,这样更方便一点。不然这里我还是需要解析报文头部,才能获取到报文体的大小。注意这里的重点,只要是获取完整的报文头部就行了。

注意: 这里我使用的其实是响应报文,并不是请求报文,因为我当时获取响应报文比较方便,使用响应报文也是可以的,主要是因为请求报文和响应报文的区别不是很大(在我使用的例子里,我这里只是针对简单情况处理)。

完整的代码

package httpmsg;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;//这个类用于获取一个完整的可被浏览器解析的Http报文。
public class HttpMessageSpider {public static void main(String[] args) {//构造一个简单的不含请求体的请求报文String requestMsg = "GET / HTTP/1.0\r\n"+ "User-Agent: CrazyDragon\r\n"+ "Host: localhost: 10000\r\n"+ "\r\n";try (Socket client = new Socket("localhost", 10000)) {   //监听本地的10000端口//发送请求报文OutputStream out = new BufferedOutputStream(client.getOutputStream());out.write(requestMsg.getBytes(Charset.forName("UTF-8")));out.flush();//接收响应报文InputStream in = new BufferedInputStream(client.getInputStream());         //获取输入流int len = 0;byte[] data = new byte[1024];   //报文的总大小,这里一次性读取int size = 1222990;    //总共需要读取的字节大小int count = 0;         //读取字节计数器File file = new File("D:/DragonFile/httpmessage");try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) {while (count < size && (len = in.read(data)) != -1) {count += len;//将报文写入文件output.write(data, 0, len);}}System.out.println(count);System.out.println(file.length());System.out.println("报文获取完成!");} catch (IOException e) {e.printStackTrace();}}
}

运行结果

在指定的目录下可以找到完整的报文文件,我们把它打开,就可以看到完整的报文信息了。

完整的报文信息

说明: 前面是报文头部,后面是图片的信息。图片的信息也包括了图片的一些详细信息了,后面的是二进制数据,这里太长了,就不截取了。
原来这些报文都是流动的,从服务端到客户端,当你看到的时候,已经是解析好的了。所以说一个报文存在的时间是很短的,但是现在我把它固定下来了,哈哈。

解析真实的报文

package com.dragon.resolve;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;public class HttpMessageResolver {public static void main(String... args) {//真实报文的位置File file = new File("D:/DragonFile/httpmessage");resolve(file);         //解析报文}static void resolve(File file) {try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {/*** 当取出来的行为空时,意味着已经取出了所有的头部。* 下面就是数据部分了(这里考虑的是简单的报文,非Multip-part类型的报文)* */String header = null;do {header = getHeader(in);if ("".equals(header)) break;System.out.println("header:-----> " + header);} while (true);//报文头部取出来,剩下的就是报文体了,取出报文体,写入文件,应该就是一张图片了。try (OutputStream out = new BufferedOutputStream(new FileOutputStream(new File("D:/DragonFile/newImg.jpg")))) {int len = 0;byte[] data = new byte[1024];while ((len = in.read(data)) != -1) {out.write(data, 0, len);}}System.out.println("报文解析完成!");} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}//封装一个 getHeader 方法,每次取一个头部行,参考 getLine 方法。static String getHeader(InputStream in) throws IOException {StringBuilder header = new StringBuilder();while (true) {int c = in.read();if (c == '\n' || c == -1) break;header.append((char)c);}return header.toString().trim();  //取出空格字符}
}

说明:
在控制台可以看到报文头部的输出:

在指定目录下面,可以找到名为 newImg.jpg 的图片:

说明

其实上面这个解析还是不够真实,因为真实的报文是流动的网络流,并不是固定的文件流。所以有两点需要解决:
1.在从网络流中获取报文的时候,获取该报文的数据部分的长度。
注意:长度其实是很重要的,因为必须要知道报文什么时候结束,否则可能因为 1 字节,而造成读取阻塞。

2.在从网络流中获取报文的时候,获取该报文的数据部分的类型–Content-Type

但是这已经不是问题了,前面的那个读取首部功能已经解决了这两个问题了。下一步就是一个完整的整合过程了,下一篇博客介绍一个很有趣的错误。第5篇会使用前面博客的知识来编写一个完整的demo了。

HTTP学习(4)–违反协议的错误

HTTP学习(3)--解析报文相关推荐

  1. Virtual-Network--—DHCP协议学习以及对应报文分析

    文章目录 Virtual-Network---DHCP协议学习以及对应报文分析 转载 1. DHCP 简介 1.1 DHCP 作用 1.2 DHCP 工作机制 2. DHCP 交互流程 2.1 正常交 ...

  2. 分布式深度学习DDL解析

    分布式深度学习DDL解析 一.概述 给一个庞大的GPU集群,在实际的应用中,现有的大数据调度器会导致长队列延迟和低的性能,该文章提出了Tiresias,即一个GPU集群的调度器,专门适应分布式深度学习 ...

  3. 深度学习常见问题解析

    深度学习常见问题解析 计算机视觉与自动驾驶 今天 一.为什么深层神经网络难以训练? 1.梯度消失.梯度消失是指通过隐藏层从后向前看,梯度会变得越来越小,说明前面层的学习会显著慢于后面层的学习,所以学习 ...

  4. 【深度学习】解析神经网络中的数值稳定性、模型初始化和分布偏移(Pytorch)

    [深度学习]解析神经网络中的数值稳定性.模型初始化和分布偏移 文章目录 1 概述1.1 梯度消失和梯度爆炸1.2 打破对称性 2 参数初始化 3 环境和分布偏移3.1 协变量偏移3.2 标签偏移3.3 ...

  5. 【深度学习】解析深度学习的集成方法

    [深度学习]解析深度学习的集成方法 文章目录 1 前言 2 使用集成模型降低方差 3 如何集成神经网络模型3.1 Varying Training Data3.2 Varying Combinatio ...

  6. 用深度学习来解析梦境中出现的物体

    这篇文章主要的工作算是机器学习和神经科学的结合工作,需要读者在这两个方向有一定的基础. 另有科普版本,结构更加清晰,不过删减了很多内容.科普版本如下: 用深度学习来解析梦境中出现的物体 - 行为与认知 ...

  7. ISO8583报文工具类(组装和解析报文)

    很久没来csdn了.现在的工作地点在银行,妈的,不让上网啊. 写写博客其实是对自己知识的总结,这么长时间了,也该写点东西了.接触银行的项目,避免不了 遇上8583报文.具体介绍就不细讲了,这个网上一大 ...

  8. 使用axis发送xml报文,返回并解析报文实例

    前段时间刚好学了一点webservice,因此想和大家分享一下我的成果,因为能力原因,只能做个小实例,望大家见谅! 此实例的思路是:(1)用String类型构造好需要发送的报文;(2)使用axis调用 ...

  9. HarmonyOS(一) 快速开始学习鸿蒙开发,官方文档学习路线解析

    系列文章目录 HarmonyOS(一):快速开始学习鸿蒙开发,官方文档学习路线解析 HarmonyOS(二):应用开发环境搭建准备 HarmonyOS(三):创建你的第一个HelloWorld应用 文 ...

  10. 安卓学习之解析json数据

    安卓学习之解析json数据 文章目录 安卓学习之解析json数据 前言 一.Json数据 二.使用原始的JsonObject来解析 1.请求数据 2.解析数据 2.1JsonObject解析数据 2. ...

最新文章

  1. Minimum Cost-最小费用最大流
  2. 第三届全国县域经济基本竞争力百强县(市)
  3. 【C++】43.使用【类对象】与 【类指针】的区别
  4. 线性表C语言locate和ETget,线性表(数据结构重难点讲解)
  5. 无监督和有监督的区别_干货|全面理解无监督学习基础知识
  6. 逻辑漏洞之任意用户登陆漏洞
  7. oracle视图view看不出来主键,oracle - 使用主键创建视图
  8. python stdout stderr 一起输出_python – 使用subprocess.Popen()时,stderr和stdout没有输出
  9. mysql创建文章表_创建表--文章表article
  10. Hadoop MapReduce概念学习系列之MPI和MapReduce(十三)
  11. dataframe 空值替换为0_dataframe取元素方法总结
  12. 在ASP.NET 2.0中操作数据:使用FormView 的模板
  13. playbook管理配置文件
  14. Atlassian发布Bamboo 6.0和Bitbucket Server 5.0
  15. jsp遍历List map
  16. Java Cryptography
  17. MathType无法加载mathpage.wll文件
  18. Python | List和Deque的速度对比
  19. 基于python的情感分析案例-基于情感词典的python情感分析
  20. vm眼影真的好用吗_vm拜金主义眼影盘值得买吗?vm拜金主义眼影如何配色

热门文章

  1. 机器学习算法入门与编程实践
  2. 测试计划报告---5W1H
  3. 企业微信的好处和功能介绍 | 最新企业微信电脑版下载
  4. 健康管理软件 PHP,健康管理信息系统
  5. gis如何加入emf图片_ArcGIS教程:地图导出格式,教你如何选择
  6. java华氏度xhuan_华氏摄氏转换
  7. java nio和io的区别_Java NIO和IO的区别
  8. 百度地图,坐标反查,拾取坐标位置
  9. C# Socket/TCPClient断线重连/不断重连的简单思路+代码,海量注释
  10. 基于 Flink 的 PB 级数据即席查询实践