HTTP学习(3)--解析报文
上一篇博客介绍了,如何简单的组装报文,现在来处理比较麻烦的另一部分了–解析报文。组装报文实际上是偷了一个懒,把解析工作交给了浏览器。但是,如果直接解析一下实际的报文的话,还是会对报文的结构认识更加深刻一些,下面就来开始吧!
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)--解析报文相关推荐
- Virtual-Network--—DHCP协议学习以及对应报文分析
文章目录 Virtual-Network---DHCP协议学习以及对应报文分析 转载 1. DHCP 简介 1.1 DHCP 作用 1.2 DHCP 工作机制 2. DHCP 交互流程 2.1 正常交 ...
- 分布式深度学习DDL解析
分布式深度学习DDL解析 一.概述 给一个庞大的GPU集群,在实际的应用中,现有的大数据调度器会导致长队列延迟和低的性能,该文章提出了Tiresias,即一个GPU集群的调度器,专门适应分布式深度学习 ...
- 深度学习常见问题解析
深度学习常见问题解析 计算机视觉与自动驾驶 今天 一.为什么深层神经网络难以训练? 1.梯度消失.梯度消失是指通过隐藏层从后向前看,梯度会变得越来越小,说明前面层的学习会显著慢于后面层的学习,所以学习 ...
- 【深度学习】解析神经网络中的数值稳定性、模型初始化和分布偏移(Pytorch)
[深度学习]解析神经网络中的数值稳定性.模型初始化和分布偏移 文章目录 1 概述1.1 梯度消失和梯度爆炸1.2 打破对称性 2 参数初始化 3 环境和分布偏移3.1 协变量偏移3.2 标签偏移3.3 ...
- 【深度学习】解析深度学习的集成方法
[深度学习]解析深度学习的集成方法 文章目录 1 前言 2 使用集成模型降低方差 3 如何集成神经网络模型3.1 Varying Training Data3.2 Varying Combinatio ...
- 用深度学习来解析梦境中出现的物体
这篇文章主要的工作算是机器学习和神经科学的结合工作,需要读者在这两个方向有一定的基础. 另有科普版本,结构更加清晰,不过删减了很多内容.科普版本如下: 用深度学习来解析梦境中出现的物体 - 行为与认知 ...
- ISO8583报文工具类(组装和解析报文)
很久没来csdn了.现在的工作地点在银行,妈的,不让上网啊. 写写博客其实是对自己知识的总结,这么长时间了,也该写点东西了.接触银行的项目,避免不了 遇上8583报文.具体介绍就不细讲了,这个网上一大 ...
- 使用axis发送xml报文,返回并解析报文实例
前段时间刚好学了一点webservice,因此想和大家分享一下我的成果,因为能力原因,只能做个小实例,望大家见谅! 此实例的思路是:(1)用String类型构造好需要发送的报文;(2)使用axis调用 ...
- HarmonyOS(一) 快速开始学习鸿蒙开发,官方文档学习路线解析
系列文章目录 HarmonyOS(一):快速开始学习鸿蒙开发,官方文档学习路线解析 HarmonyOS(二):应用开发环境搭建准备 HarmonyOS(三):创建你的第一个HelloWorld应用 文 ...
- 安卓学习之解析json数据
安卓学习之解析json数据 文章目录 安卓学习之解析json数据 前言 一.Json数据 二.使用原始的JsonObject来解析 1.请求数据 2.解析数据 2.1JsonObject解析数据 2. ...
最新文章
- Minimum Cost-最小费用最大流
- 第三届全国县域经济基本竞争力百强县(市)
- 【C++】43.使用【类对象】与 【类指针】的区别
- 线性表C语言locate和ETget,线性表(数据结构重难点讲解)
- 无监督和有监督的区别_干货|全面理解无监督学习基础知识
- 逻辑漏洞之任意用户登陆漏洞
- oracle视图view看不出来主键,oracle - 使用主键创建视图
- python stdout stderr 一起输出_python – 使用subprocess.Popen()时,stderr和stdout没有输出
- mysql创建文章表_创建表--文章表article
- Hadoop MapReduce概念学习系列之MPI和MapReduce(十三)
- dataframe 空值替换为0_dataframe取元素方法总结
- 在ASP.NET 2.0中操作数据:使用FormView 的模板
- playbook管理配置文件
- Atlassian发布Bamboo 6.0和Bitbucket Server 5.0
- jsp遍历List map
- Java Cryptography
- MathType无法加载mathpage.wll文件
- Python | List和Deque的速度对比
- 基于python的情感分析案例-基于情感词典的python情感分析
- vm眼影真的好用吗_vm拜金主义眼影盘值得买吗?vm拜金主义眼影如何配色