JAVA--Socket编程实现HTTP报文收发(模拟浏览器)

每天我们都会使用浏览器去访问一些网站页面,但是每次访问时,你是否会想知道,浏览器和服务器到底做了些什么,才让你看到浏览器呈现给你的这些具有样式排版,乃至动画的页面?

本文需要知识前提:HTTP协议了解,TCP/IP协议了解,Socket编程了解。

1.浏览器收发HTTP报文

你可能会回答,浏览器向服务器端发送HTTP请求,服务器回复HTTP请求,再经过浏览器内核的渲染和javascript引擎的解释执行,展现了这一切

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第1张图片

可以看到浏览器进行了一次HTTP请求。

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第2张图片

服务器返回的HTTP报文其实就是一些标签和javascript代码。

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第3张图片

最后浏览器渲染给我们看到的就是一个美观的页面。

对!但并不彻底。

2.JAVA实现HTTP请求(爬虫)

我们知道HTTP协议是基于TCP协议的上层协议,那么HTTP请求说到底,也就是一次TCP请求。

Java中Socket编程可以进行TCP连接,那么是否使用Java可以模拟HTTP请求的过程呢?

答案是肯定的:熟悉的同学可能会想到,Java中内置的java.net.HttpURLConnectionjava.net.URL类就可以实现对基于HTTP协议的URL进行访问。

比如:

            URL url = new URL("https://www.csdn.net/");
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            /**
             * URL对象调用openConnection()方法以后并不是直接去创建Socket进行通信
             * 而是调用了getContent()方法以后(以及其他的各种获得Header方法等等)
             * 所以要在真正建立Socket通信访问服务器80端口以前setRequestMethod
             */
            httpURLConnection.setRequestMethod("GET");
            BufferedInputStream bufferedInputStream = new BufferedInputStream(httpURLConnection.getInputStream());
            // 使用字符流类读取内容,注意编码从二进制转为字符时使用 UTF-8
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream, "utf-8"));
            String tem;
            while((tem = bufferedReader.readLine())!=null) {
                System.out.println(tem);
            }

            bufferedReader.close();         

这段代码会打印出CSDN首页的HTTP源码,也就是说这就是一个最简单的网页爬虫。

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第4张图片

但是这还不够,我们想知道,如果直接使用TCP连接去访问对端服务器的80端口会怎么样?

3.Socket编程实现HTTP请求发送(模拟浏览器)

这里我们首先在虚拟机上搭建了HTTP服务器环境,使用了Nginx + Ubuntu Server版本。

            Socket client = new Socket();
            InetSocketAddress inetSocketAddress = new InetSocketAddress("192.168.194.129", 8080);
            // 建立TCP连接,虚拟机的地址为192.168.194.129
            // Nginx监听的端口设置为8080
            client.connect(inetSocketAddress, 1000);
            String request = "GET /index.html HTTP/1.1\r\n"+
                    "Host: 192.168.194.129:8080\r\n";
            PrintWriter pWriter = new PrintWriter(client.getOutputStream(),true);
            // 这里使用println()方法是因为 HTTP 协议在报头和报文之间,有一行空行
            // 如果少了这一行空行,服务器是无法解析报文的,会出现 400 错误
            pWriter.println(request);
            client.close();

然后使用Socket编程对服务器发起一次TCP请求。

这里写图片描述

这时我们查看Nginx的访问日志,会发现有一个GET请求,请求页面是/index.html。也就是说,我们模拟浏览器发送了一个HTTP请求。

此时我们需要关注的是

            String request = "GET /index.html HTTP/1.1\r\n"+
                    "Host: 192.168.194.129:8080\r\n";

这是一段HTTP request报文,可以看到HTTP请求的方法是GET方法,请求的路径是/index.html,使用的协议是HTTP1.1。然后我们可以自己添加Header

一般浏览器发送HTTP请求的时候,会自动构造报文,而且会添加Header

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第5张图片

其实这些信息,就是我们通过TCP连接发送给服务器的信息,无论是HTTP报文,还是HTTP Request Header,还是发送的消息,最终都是通过Sokcet传输的TCP连接。

了解清楚了这些以后,我们就可以自己构造HTTP报文,然后通过TCP连接的形式发送。

4.Socket编程实现HTTP请求的接收(模拟浏览器)

那么我们再模拟一下浏览器接收HTTP报文的过程。

            Socket client = new Socket();
            InetSocketAddress inetSocketAddress = new InetSocketAddress("192.168.194.129", 8080);
            // 建立TCP连接,虚拟机的地址为192.168.194.129
            // Nginx监听的端口设置为8080
            client.connect(inetSocketAddress, 1000);
            String request = "GET /index.html HTTP/1.1\r\n"+
                    "Host: 192.168.194.129:8080\r\n";
            PrintWriter pWriter = new PrintWriter(client.getOutputStream(),true);
            pWriter.println(request);

            String tem;
            // 这里要注意二进制字节流转换为字符流编码要使用UTF-8
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream(), "utf-8"));
            while((tem = bufferedReader.readLine())!=null) {
                System.out.println(tem);
            }

            client.close();

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第6张图片

可以看到控制台打印了以上内容。

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第7张图片

JAVA--Socket编程实现HTTP报文收发(模拟浏览器)_第8张图片

我们在回头来看浏览器访问得到的HTTP报文,是不是一模一样呢(除了时间)

看到现在,再回头想想我们一开始使用的HttpURLConnection类,其实这个类和其依赖的关联类,最终还是需要使用Socket编程实现HTTP报文的收发,只不过他内置了一些简单地HTTP Header和一些方便的方法(比如自动使用UTF-8解码流)。

你可能感兴趣的:(socket编程)