【Java与网络6】实现一个自己的HTTP浏览器

前面我们讨论了HTTP协议的基本结构和Socket编程的基本原理,本文我们来整个大活:自己实现一个简单的浏览器。

目录

1.主线程循环体

2.readHostAndPort()方法的实现

3.readHttpRequest()方法的实现

4.sendHttpRequest()方法的实现

5.readHttpResponse(...)方法的实现


在讨论HTTP协议的具体请求和响应头字段之前,让我们先来利用以前所学的知识来实现一个HTTP模拟器。所谓HTTP模拟器就是可以在用户输入HTTP的请求消息后,由这个模拟器将HTTP请求发送给相应的服务器,再接收服务器的响应消息。这个HTTP模拟器有几下特点:
1.  可以手工输入HTTP请求,并向服务器发送。
2.  接收服务器的响应消息。
3.  消息头和实体内容分段显示,也就是说,并不是象Telnet等客户端一样将HTTP响
应消息全部显示,而是先显示消息头,然后由用户决定是否显示实体内容。
4.  集中发送请求。这个HTTP模拟器和Telnet不同的是,并不是一开始就连接服务器,
而是将域名、端口以及HTTP请求消息都输完后,才连接服务器,并将这些请求发送给服务器。这样做的可以预防服务器提前关闭网络连接的现象。
    5. 可以循环做上述的操作。
从以上的描述看,要实现这个HTTP模拟器需要以下五步:

  1. 主线程里建立一个死循环的while,在循环内部是一个请求/响应对。这样就可以向服务器发送多次请求/响应以了。下面的四步都是被包括在循环内部的。
  2. 从控制台读取域名和端口,这个功能可以由readHostAndPort(...)来完成。
  3. 从控制台读取HTTP请求消息,这个功能由readHttpRequest(...)来完成。
  4. 向服务器发送HTTP请求消息,这个功能由sendHttpRequest()来完成。
  5. 读取服务器回送的HTTP响应消息,这个功能由readHttpResponse(...)来完成。

下面我们就来逐步实现这五步:

1.主线程循环体

在建立这个循环之前,先建立一个中叫HttpSimulator的类,并在这个类中定义一个run方法用来运行这个程序。实现代码如下:

public class HttpSimulator {private Socket socket;private int port = 80;private String host = "localhost";private String request = ""; // HTTP请求消息 012private boolean isPost, isHead;public void run() throws Exception {BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));while (true)  // 开始大循环{try {if (!readHostAndPort(reader)) break;readHttpRequest(reader);sendHttpRequest();readHttpResponse(reader);} catch (Exception e) {System.out.println("err:" + e.getMessage());}}}public static void main(String[] args) throws Exception {new HttpSimulator().run();}

从上面的代码可以看出,我们分别调用了上述的四个方法。这些方法的具体实现将在后面讨论。上面的代码除了调用这四个核心方法外,还做了一些准备工作。在008至012行定义了一些以后要用到的变量。在016和017行使用控制台的输入流建立了BufferedReader对象,通过这个对象,可以直接从控制台读取字符串,而不是一个个地字节。

2.readHostAndPort()方法的实现

 这个方法的主要功能是从控制台读取域名和端口。域名和端口通过":"隔开,":"和域名以及端口之间不能有空格。当从控制台读取一个"q"时,这个函数返回false,表示程序可以退出了,否则返回true,表示输入的域名和端口是正确的。这个方法的实现代码如下:

    private boolean readHostAndPort(BufferedReader consoleReader) throws Exception {System.out.print("host:port>");String[] ss = null;String s = consoleReader.readLine();if (s.equals("q")) return false;else {ss = s.split("[:]");if (!ss[0].equals("")) host = ss[0];if (ss.length > 1) port = Integer.parseInt(ss[1]);System.out.println(host + ":" + String.valueOf(port));return true;}}

上面的代码,我们做一个分析:

  1. 这个方法有一个BufferedReader类型的参数,这个参数的值就是在HttpSimulator.java中的第016和017行根据控制台输入流建立的BufferedReader对象。
  2. 这输出HTTP模拟器的控制符,就象Windows的控制台的"C:">"一样。
  3. consoleReader.readLine()从控制台读取一行字符串。
  4. ss = s.split("[:]") 通过字符串的split方法和响应的正则表示式("[:]")将域名和端口分开。域名的默认值是localhost,端口的默认值是80。 

3.readHttpRequest()方法的实现

 这个方法的主要功能是从控制台读取HTTP请求消息,如果输入一个空行,表示请求消息头已经输完;如果使用的是POST方法,还要输入POST请求的实体内容。这个方法的实现代码如下:

    private void readHttpRequest(BufferedReader consoleReader) throws Exception {System.out.println("请输入HTTP请求:");String s = consoleReader.readLine();request = s + "\r\n";boolean isPost = s.substring(0, 4).equals("POST");boolean isHead = s.substring(0, 4).equals("HEAD");while (!(s = consoleReader.readLine()).equals("")) {request = request + s + "\r\n";}request = request + "\r\n";if (isPost) {System.out.println("请输入POST方法的内容:");s = consoleReader.readLine();request = request + s;}}

上面的代码,我们简单解释一下:

  1. consoleReader.readLine() 读入HTTP请求消息的第一行。
  2. isPost 和isHead 用于确定所输入的请求方法是不是POST和HEAD。之后的代码读入HTTP请求消息的其余行。
  3. if (isPost) 代码段的功能是:如果HTTP请求使用的是POST方法,要求用户继续输入HTTP请求的实体内容。

4.sendHttpRequest()方法的实现

这个方法的功能是将request变量中的HTTP请求消息发送到服务器。下面是这个方法的实现代码:

   private void sendHttpRequest() throws Exception {socket = new Socket();socket.setSoTimeout(10 * 1000); //设置读取数据超时为10秒。System.out.println("正在连接服务器");socket.connect(new InetSocketAddress(host, port), 10 * 1000); //超时时间System.out.println("服务器连接成功!");OutputStream out = socket.getOutputStream();OutputStreamWriter writer = new OutputStreamWriter(out);writer.write(request);writer.flush();}

5.readHttpResponse(...)方法的实现

这个方法的主要功能是从服务器读取返回的响应消息。首先读取了响应消息头,然后要求用户输入Y或N以确定是否显示响应消息的实体内容。这个程序之所以这样做,主要有两个原因:
(1) 为了研究HTTP协议。
(2) 由于本程序是以字符串形式显示响应消息的,因此,如果用户请求了一个二进制Web资源,如一个rar文件,那么实体内容将会显示乱码。所以在显示完响应消息头后由用户决定是否显示实体内容。
这个方法的实现代码如下: 

 private void readHttpResponse(BufferedReader consoleReader) {String s = "";try {InputStream in = socket.getInputStream();InputStreamReader inReader = new InputStreamReader(in);BufferedReader socketReader = new BufferedReader(inReader);System.out.println("---------HTTP头---------");boolean b = true; // true: 未读取消息头 false: 已经读取消息头 011while ((s = socketReader.readLine()) != null) {if (s.equals("") && b == true && !isHead) {System.out.println("------------------------");b = false;System.out.print("是否显示HTTP的内容(Y/N):");String choice = consoleReader.readLine();if (choice.equals("Y") || choice.equals("y"))   {System.out.println("---------HTTP内容---------");continue;}                    else break;} else System.out.println(s);}} catch (Exception e) {System.out.println("err:" + e.getMessage());} finally {try {socket.close();} catch (Exception e) {}} System.out.println("------------------------");}

在上面的代码中013行是最值得注意的。其中s.equals("")表示读入一个空行(表明消息头已经结束);由于在实体内容中也可以存在空行,因此,b == true来标记消息头是否已经被读过,当读完消息头后,将b设为false,如果以后再遇到空行,就不会当成消息头来处理了。当HTTP请求使用HEAD方法时,服务器只返回响应消息头;因此,使用!isHead来保证使用HEAD发送请求时不显示响应消息的内容实体。
现在我们已经实现了这个HTTP模拟器,下面让我们来运行并测试它。 

我们将上面的代码执行起来,然后会有个输入的提示:

我们输入www.csdn.net

然后继续分行输入如下的HTTP请求消息:

GET / HTTP/1.1
Host: www.csdn.net

之后根据提示输入Y,运行的结果如下所示:

------------------------
是否显示HTTP的内容(Y/N):Y
---------HTTP内容---------
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>WAF</center>
</body>
</html>

到此我们就实现了一个简单的HTTP浏览器了。

参考:

本文参考了李宁老师(蒙娜丽宁)的文章和介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650185.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度强化学习(王树森)笔记03

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

STM32——创建HAL库工程

第一步 新建工程文件夹 所有文件夹存放的内容&#xff1a; 第二步 拷贝/新建工程相关文件 1、Drivers&#xff08;创建如下文件夹&#xff0c;除readme.txt&#xff09; 文件夹存放的内容&#xff1a; 2、Middlewares和output暂时不需要创建&#xff08;目前用不到&#x…

自动驾驶的决策层逻辑

作者 / 阿宝 编辑 / 阿宝 出品 / 阿宝1990 自动驾驶意味着决策责任方的转移 我国2020至2025年将会是向高级自动驾驶跨越的关键5年。自动驾驶等级提高意味着对驾驶员参与度的需求降低&#xff0c;以L3级别为界&#xff0c;低级别自动驾驶环境监测主体和决策责任方仍保留于驾驶…

华为配置在用户物理位置变化时部署业务随行示例(V200R006C00、V200R007C00、V200R008C00)

配置在用户物理位置变化时部署业务随行示例&#xff08;V200R006C00、V200R007C00、V200R008C00&#xff09; 业务随行简介配置注意事项组网需求需求分析数据规划配置思路操作步骤配置文件 组网图形 图1 组网图 业务随行简介配置注意事项组网需求需求分析数据规划配置思路操作步…

新建一个基于标准库的工程(STM32)

目录 1.新建存放工程的文件夹 2.打开KEIL5软件 3.新建一个本次工程的文件夹 4.添加工程的必要文件 4.1打开STM32的启动文件 ​编辑 4.2&#xff1a; 4.3添加内核寄存器文件 ​编辑 5.回到keil5软件&#xff0c;将刚才复制的那些文件添加到工程中 5.1添加一个启动文件&am…

浅谈WPF之样式与资源

WPF通过样式&#xff0c;不仅可以方便的设置控件元素的展示方式&#xff0c;给用户呈现多样化的体验&#xff0c;还简化配置&#xff0c;避免重复设置元素的属性&#xff0c;以达到节约成本&#xff0c;提高工作效率的目的&#xff0c;样式也是资源的一种表现形式。本文以一个简…

k8s-基础知识(Service,NodePort,CusterIP,无头服务,NameSpace,资源限制)

Node Node 是 Pod 真正运行的主机&#xff0c;可以是物理机&#xff0c;也可以是虚拟机。 Annotations 原文链接 Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象&#xff0c;Annotations 则是用来记录一些附加信息&#xff0c;用来辅助应…

x-cmd pkg | httpx - 为 Python 设计的下一代 HTTP 客户端库

目录 简介首次用户功能特点进一步探索 简介 HTTPX 是一个为 Python 设计的下一代 HTTP 客户端库&#xff0c;由 Tom Christie 创建。它提供了同步和异步的 API&#xff0c;并支持 HTTP/1.1 和 HTTP/2 协议。与 Requests 库类似&#xff0c;但增加了对异步请求的支持和 HTTP/2 …

MySql8的简单使用(1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践)

MySql8的简单使用&#xff08;1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践&#xff09; 一.like模糊查询、group by 分组 having 过滤 建表语句 create table student(id int PRIMARY KEY,name char(10),age int,sex char(5)); alter table student add height…

【TOP解刊】IEEE(trans)顶刊,国人绝对优势,同领域2个月录用,5天见刊!

工程技术类 • 顶刊解读 今天带来IEEE旗下工程技术领域顶刊&#xff0c;究竟这本高分期刊审稿情况如何呢&#xff1f;好投吗&#xff1f;一起来看看下文解析。如有投稿意向可重点关注&#xff0c;具体详情见下文&#xff1a; 01 期刊简介 IEEE Transactions on Power Electr…

FlashInternImage实战:使用 FlashInternImage实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

QDockWidget : 想要 top -> left -> rigt -> bottom 的布局实现

上图红圈中的实现&#xff0c;第一次想要实现&#xff0c;总会和想的不一样。 第一种情况 第二种情况 第三种情况 有时候为了达到一种效果&#xff0c;也算是煞费苦心了&#xff0c;且不说这个demo还是找的其他CSDN博主的&#xff0c;但是功夫不负有心人。 解决办法 先让 Doc…

[UI5 常用控件] 03.Icon, Avatar,Image

文章目录 前言1. Icon2. Avatar2.1 displayShape2.2 initials2.3 backgroundColor2.4 Size2.5 fallbackIcon2.6 badgeIcon2.7 badgeValueState2.8 active 3. Image 前言 本章节记录常用控件Title,Link,Label。 其路径分别是&#xff1a; sap.m.Iconsap.m.Avatarsap.m.Image 1…

01_ESP32 MicroPython开发环境搭建

一、工作原理 Python源代码->Python解释器(MicroPython)-->二进制代码(01010)-->硬件电路(ESP32)-->高低电平输出-->其他设备 二、准备工作&#xff1a; 硬件&#xff1a;ESP32开发版&#xff0c;有很多个版本可选&#xff0c;我这里用的是ESP-32开发板&…

K8s 安装部署-Master和Minion(Node)文档

K8s 安装部署-Master和Minion(Node)文档 操作系统版本&#xff1a;CentOS 7.4 Master &#xff1a;172.20.26.167 Minion-1&#xff1a;172.20.26.198 Minion-2&#xff1a;172.20.26.210&#xff08;后增加节点&#xff09; ETCD&#xff1a;172.20.27.218 先安装部署ETC…

navicat 可以直接往 mysql导入excel表格

妈呀 还好 提前问了一下&#xff0c;不然哼哧哼哧在那里写&#xff0c;导入接口。。

05-TiDB 之 HTAP 快速上手

混合型在线事务与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP) 功能 HTAP 存储引擎&#xff1a;行存 与列存 同时存在&#xff0c;自动同步&#xff0c;保持强一致性。行存 OLTP &#xff0c;列存 OLAPHTAP 数据一致性&#xff1a;作为一个分布式事务…

【王道数据结构】【chapter2线性表】【P43t15】

单链表有环&#xff0c;是指单链表的最后一个节点的指针指向了链表中的某个结点&#xff08;通常单链表的最后一个节点的指针域是空的&#xff09;。试编写算法判断单链表是否存在环。 #include <iostream>typedef struct node{int data;node* next; }node,*list;list I…

递归方法猴子吃桃问题

public class A {public static void main(String[] args) {System.out.println("第一天有&#xff1a;"f(1)"个");System.out.println("第二天有&#xff1a;"f(2)"个");System.out.println(".....");System.out.println(&…

代码随想录算法训练营第32天(贪心算法02● 122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

贪心算法 part02 122.买卖股票的最佳时机II解题思路 55. 跳跃游戏解题思路 45.跳跃游戏II &#xff08;来不及了 明天刷解题思路 122.买卖股票的最佳时机II 动态规划章节会重点讲买卖股票问题 本题解法很巧妙&#xff0c;大家可以看题思考一下&#xff0c;在看题解。 题目链接&…