目录
一、简介:HTTP程序设计
期末复习之HTTP网络编程,主要学习记录HTTP(s)协议的网络编程,包括使用TCP Socket进行三次握手的HTTP网页下载,和使用SSL Socket的安全传输的HTTPs网页下载,通过案例实践自行完成编程,认识http(s)的实际工作机制!
现在的HTTP客户端比早期的复杂得多,不仅包括了网页文件下载和显示,还有许多新的功能:跨平台的显示、参数的传递、动态网页的实现和用户交互等。
1、HTTP系统设计
- 客户端软件(web浏览器:Chrome、360浏览器等)
- 服务端软件(web服务器:微软的IIS、Apache Tomcat)
2、HTTP客户端工作过程
- 客户端软件和服务器建立连接(TCP的三次握手);
- 发送HTTP头格式协议;
- 接收网页文件;
- 显示网页。
3、HTTP服务端工作过程
- 服务器软件开启80端口;
- 响应客户的要求、完成TCP连接;
- 检查客户端的HTTP头格式发送客户请求的网页文件(含动态网页)。
图1 HTTP请求-响应完整过程
网页下载技术是搜索引擎、网络爬虫、网页采集器或网络推送服务等相关应用领域内的基础技术,下面会介绍日常使用到的两种协议(http和https)的网页访问下载。
二、基于TCP Socket的HTTP网页下载
对于TCP套接字的连接过程已经有很深刻的认识了,在本地测试通信也使用过TCP的Socket建立连接,同理,与HTTP服务器建立连接,也是利用TCP进行信息交互的。
建立连接之后,需要发送HTTP请求头,服务器确认请求者,开启两端的通信,客户端可以接收网页文件信息,进而经过渲染后显示网页页面。这里我们先实现接收网页文件信息,在下一篇实现浏览器对网页渲染之后的功能。
以www.baidu.com为例,与HTTP服务器建立连接之后,需要我们发送网页请求,也就是HTTP请求头。构造请求头如下:
需要严格按照格式发送,并且通常用StringBuffer类的toString()方法可将完整的HTTP请求头转换为字符串,一致发送到HTTP服务器。
StringBuffer msg = new StringBuffer(); msg.append("GET / HTTP/1.1\r\n"+ "HOST: "+domainName+"\r\n"+ "Accept: */*\r\n"+ "Accept-Language: zh-CN\r\n"+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"+ "Connection: Keep-Alive\r\n" );
换行符使用\r\n是为了避免由于编码问题出错。
发送请求之后如果网页信息显示区返回的第一条信息是“HTTP/1.1 200 OK”,则说明访问正常。
可以看到HTTP服务器返回许多信息,这也是响应头,包含了许多关键信息内容。
三、基于SSL Socket的HTTPS网页下载
以上面设计的基于TCP通信传输的HTTP,我们尝试访问www.sina.com.cn,结果发现响应头信息第一行是HTTP/1.1 302 Moved Temporarily(站点被移除),出于安全考虑,现在绝大部分的web站点都将放弃HTTP而启用HTTPS,都使用了安全加密传输的HTTPS协议,而关闭了HTTP,只允许启用了SSL/TLS的HTTPS安全连接,这种连接默认是使用443端口。所以TCP Socket建立连接的方式无正常访问网页。
那只是端口改为443能正常吗,答案如下。
原因在前面也能看出,需要使用SSL/TLS的HTTPS安全连接,来建立与HTTPS服务器的通信,因此需要修改Socket类型。
这里使用到了Java安全套接字扩展(Java Secure Socket Extension,JSSE),基于SSL和TLS协议的Java网络应用程序提供了Java API以及参考实现,这里使用其客户端的SSLSocket套接字。SSLSocket相对之前学习的客户端套接字,只是创建方法不同,SSLSocket对象由SSLSocketFactory创建。
在类中声明成员变量以及创建Socket连接:
private SSLSocket socket; private SSLSocketFactory factory; factory=(SSLSocketFactory)SSLSocketFactory.getDefault(); socket=(SSLSocket)factory.createSocket(ip,Integer.parseInt(port));
对SSL Socket的使用与TCP相同,只是创建方法不同,经过稍微修改之后,可以成功请求HTTPS网站的网页信息。
四、HTTP客户端完整代码
这里给出HTTP客户端的完整代码,HTTPS只需改改上述讲到的SSL Socket。
/* * HTTPClient.java * Copyright (c) 2020-12-21 * author : Charzous * All right reserved. */ package chapter08; import java.io.*; import java.net.Socket; public class HTTPClient { private Socket socket; private PrintWriter pw; private BufferedReader br; /** * @param ip * @param port * @return * @author Charzous * @date 2020/12/21 14:52 * */ public HTTPClient(String ip, String port) throws IOException{ //主动向服务器发起连接,实现TCP三次握手 //不成功则抛出错误,由调用者处理错误 socket =new Socket(ip,Integer.parseInt(port)); //得到网络流输出字节流地址,并封装成网络输出字符流 OutputStream socketOut=socket.getOutputStream(); //参数true表示自动flush数据 pw=new PrintWriter(new OutputStreamWriter(socketOut,"utf-8"),true); //得到网络输入字节流地址,并封装成网络输入字符流 InputStream socketIn=socket.getInputStream(); br=new BufferedReader(new InputStreamReader(socketIn,"utf-8")); } public void send(String msg) throws InterruptedException { //输出字符流,由socket调用系统底层函数,经网卡发送字节流 try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } pw.println(msg); } public String receive(){ String msg=null; try { //从网络输入字符流中读取信息,每次只能接受一行信息 //不够一行时(无行结束符),该语句阻塞 //直到条件满足,程序往下运行 msg=br.readLine(); }catch (IOException e){ e.printStackTrace(); } return msg; } public void close(){ try { if (socket!=null) socket.close(); }catch (IOException e){ e.printStackTrace(); } } }
五、界面完整代码
我直接用一个图形界面来访问http和https,融合以上两个图形客户端的功能,使得该图形客户端既能访问443的https内容,也可以访问非443端口(一般是80)的http内容。
/* * HTTPAllClientFX.java * Copyright (c) 2020-12-21 * author : Charzous * All right reserved. */ package chapter08; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class HTTPAllClientFX extends Application { private Button btnExit=new Button("退出"); private Button btnSend = new Button("网页请求"); // private TextField tfSend=new TextField();//输入信息区域 private TextArea taDisplay=new TextArea();//显示区域 private TextField ipAddress=new TextField();//填写ip地址 private TextField tfport=new TextField();//填写端口 private Button btConn=new Button("连接"); private HTTPSClient httpsClient; private HTTPClient httpClient; private Thread readThread; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { BorderPane mainPane=new BorderPane(); //连接服务器区域 HBox hBox1=new HBox(); hBox1.setSpacing(10); hBox1.setPadding(new Insets(10,20,10,20)); hBox1.setAlignment(Pos.CENTER); hBox1.getChildren().addAll(new Label("网页地址:"),ipAddress,new Label("端口:"),tfport,btConn); mainPane.setTop(hBox1); VBox vBox=new VBox(); vBox.setSpacing(10); vBox.setPadding(new Insets(10,20,10,20)); vBox.getChildren().addAll(new Label("网页信息显示区"),taDisplay); VBox.setVgrow(taDisplay, Priority.ALWAYS); mainPane.setCenter(vBox); HBox hBox=new HBox(); hBox.setSpacing(10); hBox.setPadding(new Insets(10,20,10,20)); hBox.setAlignment(Pos.CENTER_RIGHT); hBox.getChildren().addAll(btnSend,btnExit); mainPane.setBottom(hBox); Scene scene =new Scene(mainPane,700,500); primaryStage.setScene(scene); primaryStage.show(); //连接按钮 btConn.setOnAction(event -> { String ip=ipAddress.getText().trim(); String port=tfport.getText().trim(); taDisplay.clear(); try { if (port.equals("443")){ httpsClient = new HTTPSClient(ip, port); //成功连接服务器,接受服务器发来的第一条欢迎信息 taDisplay.appendText("服务器连接成功。\n"); readThread = new Thread(()->{ String receiveMsg=null;//从服务器接收一串字符 if (port.equals("443")){ while ((receiveMsg=httpsClient.receive())!=null){ //lambda表达式不能直接访问外部非final类型局部变量,需要定义一个临时变量 //若将receiveMsg定义为类成员变量,则无需临时变量 String msgTemp = receiveMsg; Platform.runLater(()->{ taDisplay.appendText(msgTemp+"\n"); }); } } }); readThread.start(); } else if (port.equals("80")){ httpClient = new HTTPClient(ip, port); taDisplay.appendText("服务器连接成功。\n"); readThread = new Thread(()-> { String receiveMsg = null; while ((receiveMsg = httpClient.receive()) != null) { String msgTemp = receiveMsg; Platform.runLater(() -> { taDisplay.appendText(msgTemp + "\n"); }); } }); readThread.start(); } }catch (Exception e){ taDisplay.appendText("服务器连接失败!"+e.getMessage()+"\n"); } }); //网页请求按钮事件 btnSend.setOnAction(event -> { String ip=ipAddress.getText().trim(); String port=tfport.getText().trim(); String domainName=ipAddress.getText().trim(); try { StringBuffer msg = new StringBuffer(); msg.append("GET / HTTP/1.1\r\n"+ "HOST: "+domainName+"\r\n"+ "Accept: */*\r\n"+ "Accept-Language: zh-CN\r\n"+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"+ "Connection: Keep-Alive\r\n" ); if (port.equals("443")) httpsClient.send(msg.toString()); else if (port.equals("80")) httpClient.send(msg.toString()); } catch (InterruptedException e) { e.printStackTrace(); } }); btnExit.setOnAction(event -> { try { exit(); } catch (InterruptedException e) { e.printStackTrace(); } }); //窗体关闭响应的事件,点击右上角的×关闭,客户端也关闭 primaryStage.setOnCloseRequest(event -> { try { exit(); } catch (InterruptedException e) { e.printStackTrace(); } }); } private void exit() throws InterruptedException { if (httpsClient!=null||httpClient!=null){ readThread.sleep(1000);//多线程等待,关闭窗口时还有线程等待IO,设置1s间隔保证所有线程已关闭 httpsClient.close(); httpClient.close(); } System.exit(0); } }
六、最后+演示
HTTP连接www.baidu.com,成功
HTTP连接www.sina.com.cn,失败
HTTPS连接www.sina.com.cn,成功
期末复习,顺便写博客记录下来,这篇为上篇,介绍HTTP网页请求下载,主要是HTTP(s)协议的网络编程,包括使用TCP Socket进行三次握手的HTTP网页下载,和使用SSL Socket的安全传输的HTTPs网页下载,通过案例实践自行完成编程,认识http(s)的实际工作机制!
期待:Java之HTTP网络编程(下篇:网页浏览器程序设计),将看到网页的HTML源代码,以及经过浏览器功能渲染之后的网页!
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!
我的博客园:https://www.cnblogs.com/chenzhenhong/p/14435762.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/111470556