每天我们都会使用浏览器去访问一些网站页面,但是每次访问时,你是否会想知道,浏览器和服务器到底做了些什么,才让你看到浏览器呈现给你的这些具有样式排版,乃至动画的页面?
本文需要知识前提:HTTP协议了解,TCP/IP协议了解,Socket编程了解。
1.浏览器收发HTTP报文
你可能会回答,浏览器向服务器端发送HTTP请求,服务器回复HTTP请求,再经过浏览器内核的渲染和javascript引擎的解释执行,展现了这一切

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

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

最后浏览器渲染给我们看到的就是一个美观的页面。
对!但并不彻底。
2.JAVA实现HTTP请求(爬虫)
我们知道HTTP协议是基于TCP协议的上层协议,那么HTTP请求说到底,也就是一次TCP请求。
Java中Socket编程可以进行TCP连接,那么是否使用Java可以模拟HTTP请求的过程呢?
答案是肯定的:熟悉的同学可能会想到,Java中内置的java.net.HttpURLConnection和java.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());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream, "utf-8"));
String tem;
while((tem = bufferedReader.readLine())!=null) {
System.out.println(tem);
}
bufferedReader.close();
这段代码会打印出CSDN首页的HTTP源码,也就是说这就是一个最简单的网页爬虫。

但是这还不够,我们想知道,如果直接使用TCP连接去访问对端服务器的80端口会怎么样?
3.Socket编程实现HTTP请求发送(模拟浏览器)
这里我们首先在虚拟机上搭建了HTTP服务器环境,使用了Nginx + Ubuntu Server版本。
Socket client = new Socket();
InetSocketAddress inetSocketAddress = new InetSocketAddress("192.168.194.129", 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);
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

其实这些信息,就是我们通过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);
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;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream(), "utf-8"));
while((tem = bufferedReader.readLine())!=null) {
System.out.println(tem);
}
client.close();

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


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