当前位置: 首页 > article >正文

JavaFX -- chapter07(HTTP程序设计)

chapter07(HTTP程序设计)

使用Java的Socket类时,你只需要知道服务器的域名(或IP地址)和端口号就可以建立连接。Java的网络库会处理域名解析的过程,即将域名转换为IP地址。

import java.io.*;
import java.net.*;

public class SocketExample {
    public static void main(String[] args) {
        String hostname = "www.example.com"; // 服务器域名
        int port = 80; // 端口号

        try {
            // 创建Socket连接
            Socket socket = new Socket(hostname, port);
            System.out.println("连接到服务器: " + hostname + " 在端口: " + port);

            // 发送请求
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("GET / HTTP/1.1");
            out.println("Host: " + hostname);
            out.println("Connection: Close");
            out.println();

            // 接收响应
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String responseLine;
            while ((responseLine = in.readLine()) != null) {
                System.out.println(responseLine);
            }

            // 关闭连接
            in.close();
            out.close();
            socket.close();
        } catch (IOException e) {
            System.err.println("连接失败: " + e.getMessage());
        }
    }
}

关于HTTP协议的基本信息

你可以自行查阅相关资料,也可以查看我的博客(十分基础)python网络编程 | 0zxm

一、概述

HTTP 系统包括客户端软件(浏览器)和服务器软件(HTTP服务器)。早期的客户端软件,其主要工作可理解为文件下载和文件显示。

实际上现代的HTTP客户端比文件下载要复杂得多,它包括网页文件的下载、跨平台的本地显示,参数的传递,动态网页的实现,以及交互等功能。

HTTP系统程序设计包括:

  1. 客户端软件(web浏览器软件如Edge浏览器、360浏览器);
  2. 服务器软件(web服务器软件如IIS、Nginx、Tomcat等)。

HTTP系统客户端的工作过程是:

  1. 客户端软件和服务器建立连接(TCP的三次握手);
  2. 发送HTTP头格式协议;
  3. 接收网页文件;
  4. 显示网页。

HTTP系统服务端的工作过程:

  1. 服务器软件开启80端口;
  2. 响应客户的要求、完成TCP连接;
  3. 检查客户端的HTTP头格式发送客户请求的网页文件(含动态网页)。

在这里插入图片描述

二、第一步:基于TCP套接字的网页下载程序设计

利用TCP客户套接字Socket和HTTP服务器进行信息交互,将网页的原始内容下载显示在图形界面中,具体工作如下:

  1. 新建一个包chapter08,将第3讲的TCPClient.java复制到此包下, 重命名为 HTTPClient.java

  2. 创建 HTTPClientFX.java 程序,界面如图8.2所示,网页地址输入 www.baidu.com 进行测试,端口为 80,在“连接”按钮

    类似以往的编码方式, 放置连接服务器的代码和启动输入流 “读线程”;在“网页请求”按钮中发送 HTTP 请求头标准格式(关于

    HTTP请求头的更多信息可查阅互联网);在“退出”按钮中,相对之前章节的代码,不是发送 “bye” 表示结束,而是

    send("Connection:close" +"\r\n") 表示结束连接。

在这里插入图片描述

具体来说,“网页请求”按钮中,我们的程序可以按顺序发送以下HTTP请求头:

GET / HTTP/1.1  //访问默认网页,注意GET后的 / 前后要留有空格 
HOST: address  //address 指服务器的 IP 或域名 
Accept: */* 
Accept-Language: zh-cn 
User-Agent: User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64)  
Connection: Keep-Alive

将上述的HTTP头格式构成一整个字符串,一致发送到HTTP服务器。

(注意:Windows系统下各个请求头字符串用\r\n分隔区分,不同操作系统的 User-Agent 也要相应修改;通常用StringBuffer类的append方法来拼接这些字符串,其toString()方法可将内容转换为字符串)。

HTTP状态码

如果网页信息显示区返回的第一条信息是“HTTP/1.1 200 OK”,则说明访问正常。现在将网页地址改为www.gdufs.edu.cn,其它不变,再次连接并点击网页请求,结果如图8.3所示:

在这里插入图片描述

HTTPClient
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class HTTPClient {
    private final Socket socket; // 定义套接字
    private final PrintWriter pw; // 定义字符输出流
    private final BufferedReader br; // 定义字符输入流

    public HTTPClient(String ip, String port) throws IOException {
        // 主动向服务器发起连接,实现TCP的三次握手过程
        // 如果不成功,则抛出错误信息,其错误信息交由调用者处理
        socket = new Socket(ip, Integer.parseInt(port));

        // 得到网络输出字节流地址,并封装成网络输出字符流
        // 设置最后一个参数为true,表示自动flush数据
        OutputStream socketOut = socket.getOutputStream();
        pw = new PrintWriter(new OutputStreamWriter(socketOut, StandardCharsets.UTF_8), true);

        // 得到网络输入字节流地址,并封装成网络输入字符流
        InputStream socketIn = socket.getInputStream();
        br = new BufferedReader(new InputStreamReader(socketIn, StandardCharsets.UTF_8));
    }

    public void send(String msg) {
        // 输出字符流,由Socket调用系统底层函数,经网卡发送字节流
        pw.println(msg);
    }

    public String receive() {
        String msg = null;
        try {
            // 从网络输入字符流中读信息,每次只能接收一行信息
            // 如果不够一行(无行结束符),则该语句阻塞等待
            msg = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return msg;
    }
    public boolean isConnected(){
        return socket.isConnected();
    }

    // 实现close方法以关闭socket连接及相关的输入输出流
    public void close() {
        try {
            if (pw != null) {
                pw.close(); // 关闭PrintWriter会先flush再关闭底层流
            }
            if (br != null) {
                br.close(); // 关闭BufferedReader
            }
            if (socket != null) {
                socket.close(); // 关闭Socket连接
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
HTTPClientFx
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;

import java.io.IOException;

public class HTTPClientFx extends Application {
    private final Button connectBtn = new Button("连接");
    private final Button requestBtn = new Button("网页请求");
    private final Button clearBtn = new Button("清空");
    private final Button quitBtn = new Button("退出");
    private final TextField urlField = new TextField();
    private final TextField portField = new TextField();
    private final TextArea responseArea = new TextArea();

    private HTTPClient httpc;

    private Thread receiveThread;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("HTTP Client");
        BorderPane mainPane = new BorderPane();

        HBox topBox = new HBox();
        topBox.setSpacing(10);
        topBox.setPadding(new Insets(10));
        topBox.getChildren().addAll(new Label("网页地址: "), urlField, new Label("端口: "), portField, connectBtn);
        topBox.setAlignment(Pos.CENTER);

        VBox centerBox = new VBox(10);
        centerBox.setPadding(new Insets(10, 20, 10, 20));
        centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
        centerBox.setAlignment(Pos.CENTER);

        HBox bottomBox = new HBox(10);
        bottomBox.setPadding(new Insets(10, 10, 10, 10));
        bottomBox.getChildren().addAll(requestBtn, clearBtn, quitBtn);
        bottomBox.setAlignment(Pos.CENTER_RIGHT);

        VBox mainBox = new VBox();
        mainBox.getChildren().addAll(topBox, centerBox, bottomBox);
        mainBox.setAlignment(Pos.CENTER);


        VBox.setVgrow(responseArea, Priority.ALWAYS);
        VBox.setVgrow(centerBox, Priority.ALWAYS);

        mainPane.setCenter(mainBox);

        Scene scene = new Scene(mainPane, 720, 450);

        // 按钮事件
        quitBtn.setOnAction(event -> {
            this.httpc.close();
            receiveThread.interrupt();
            httpc.send("Connection:close" + "\r\n");
            System.exit(0);
        });

        // 连接按钮事件
        connectBtn.setOnAction(event -> {
            try {
                String url = urlField.getText().trim();
                httpc = new HTTPClient(url, portField.getText().trim());
                if (httpc.isConnected()) {
                    this.responseArea.appendText("连接成功!\n");
                }
                receiveThread = new Thread(() -> {
                    while (true) {
                        String response = httpc.receive();
                        if (response == null) {
                            break;
                        }
                        Platform.runLater(() -> {
                            this.responseArea.appendText(response);
                        });
                    }
                }, "ReceiveThread");
                receiveThread.start();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        clearBtn.setOnAction(event -> {
            this.responseArea.clear();
        });

        requestBtn.setOnAction(event -> {
            httpc.send("GET / HTTP/1.1");
            httpc.send("Host: " + this.urlField.getText().trim());
            httpc.send("Connection: Close");
            httpc.send("\r\n");
        });


        urlField.setText("www.baidu.com");
        portField.setText("80");

        this.responseArea.setEditable(false);
        this.responseArea.setWrapText(true);

        // 添加滚轮事件
        responseArea.setOnScroll(event -> {
            if (event.isControlDown()) {
                if (event.getDeltaY() > 0) {
                    responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() + 1) + "px;");
                } else {
                    responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() - 1) + "px;");
                }
            }
        });

        // 界面显示
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

第二步: 基于SSLSocket的网页下载程序设计

HTTP协议是一种基于普通套接字的不安全的协议,要访问HTTPS站点,就不能用普通的客户端套接字,而是使用SSL套接字。Java 安全套接字扩展(Java Secure Socket Extension,JSSE)为基于 SSL 和 TLS 协议的Java网络应用程序提供了Java API以及参考实现,相关的类都定义在 javax.net.ssl 包下面,我们这里只使用其客户端的SSLSocket套接字。

SSL/TLS协议是在TCP连接的基础上实现的,通常用于提高数据传输的安全性。这意味着SSL套接字实际上是建立在TCP套接字之上的。其工作流程如下:

  1. TCP连接建立:首先,客户端和服务器通过TCP建立一个可靠的连接。这个过程包括TCP的三次握手。
  2. SSL握手:一旦TCP连接建立,接下来进行SSL/TLS握手。握手过程包括以下几个步骤:
    • 客户端发送一个“客户端问候”消息,包含支持的TLS版本、加密算法等信息。
    • 服务器响应“服务器问候”消息,选择TLS版本和加密算法,并发送其数字证书
    • 客户端验证服务器的证书,确保服务器的身份。
    • 双方生成共享密钥,用于加密后续的通信。
  3. 安全数据传输:握手完成后,客户端和服务器就可以使用生成的密钥进行加密数据的传输,确保数据在传输过程中是安全的。
  4. 连接关闭:通信完成后,双方可以按照TLS协议关闭连接,确保安全地结束会话。

SSLSocket相对之前学习的客户端套接字,只是创建方法不同,SSLSocket对象 由SSLSocketFactory创建

HTTPSClient
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class HTTPSClient {
    // 定义SSL套接字
    private final SSLSocket sslSocket;
    // 定义SSL套接字工厂
    private final SSLSocketFactory sslSocketFactory;
    private final PrintWriter pw; // 定义字符输出流
    private final BufferedReader br; // 定义字符输入流

    public HTTPSClient(String ip, String port) throws IOException {
        // 创建工厂对象()使用静态方法getDefault()获取默认的SSL套接字工厂
        sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        // 创造SSL套接字对象
        sslSocket = (SSLSocket) sslSocketFactory.createSocket(ip, Integer.parseInt(port));
        pw = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream(), StandardCharsets.UTF_8),true);
        br = new BufferedReader(new InputStreamReader(sslSocket.getInputStream(), StandardCharsets.UTF_8));
        // 开始SSL握手
        sslSocket.startHandshake();
    }

    public void send(String message) {
        pw.println(message);
    }

    public String receive() {
        String msg = null;
        try {
            // 从网络输入字符流中读信息,每次只能接收一行信息
            // 如果不够一行(无行结束符),则该语句阻塞等待
            msg = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return msg;
    }

    public boolean isConnected() {
        return sslSocket.isConnected();
    }

    // 实现close方法以关闭socket连接及相关的输入输出流
    public void close() {
        try {
            if (pw != null) {
                pw.close(); // 关闭PrintWriter会先flush再关闭底层流
            }
            if (br != null) {
                br.close(); // 关闭BufferedReader
            }
            if (sslSocket != null) {
                sslSocket.close(); // 关闭Socket连接
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
HTTPSClientFx
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;

import java.io.IOException;

public class HTTPSClientFx extends Application {
    private final Button connectBtn = new Button("连接");
    private final Button requestBtn = new Button("网页请求");
    private final Button clearBtn = new Button("清空");
    private final Button quitBtn = new Button("退出");
    private final TextField urlField = new TextField();
    private final TextField portField = new TextField();
    private final TextArea responseArea = new TextArea();

    private HTTPSClient httpsc;

    private Thread receiveThread;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("HTTPS Client");
        BorderPane mainPane = new BorderPane();

        HBox topBox = new HBox();
        topBox.setSpacing(10);
        topBox.setPadding(new Insets(10));
        topBox.getChildren().addAll(new Label("网页地址: "), urlField, new Label("端口: "), portField, connectBtn);
        topBox.setAlignment(Pos.CENTER);

        VBox centerBox = new VBox(10);
        centerBox.setPadding(new Insets(10, 20, 10, 20));
        centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
        centerBox.setAlignment(Pos.CENTER);

        HBox bottomBox = new HBox(10);
        bottomBox.setPadding(new Insets(10, 10, 10, 10));
        bottomBox.getChildren().addAll(requestBtn, clearBtn, quitBtn);
        bottomBox.setAlignment(Pos.CENTER_RIGHT);

        VBox mainBox = new VBox();
        mainBox.getChildren().addAll(topBox, centerBox, bottomBox);
        mainBox.setAlignment(Pos.CENTER);


        VBox.setVgrow(responseArea, Priority.ALWAYS);
        VBox.setVgrow(centerBox, Priority.ALWAYS);

        mainPane.setCenter(mainBox);

        Scene scene = new Scene(mainPane, 720, 450);

        // 按钮事件
        quitBtn.setOnAction(event -> {
            httpsc.send("Connection:close" + "\r\n");
            this.httpsc.close();
            receiveThread.interrupt();
            System.exit(0);
        });

        // 连接按钮事件
        connectBtn.setOnAction(event -> {
            try {
                String url = urlField.getText().trim();
                httpsc = new HTTPSClient(url, portField.getText().trim());
                if (httpsc.isConnected()) {
                    this.responseArea.appendText("连接成功!\n");
                }
                receiveThread = new Thread(() -> {
                    while (true) {
                        String response = httpsc.receive();
                        if (response == null) {
                            break;
                        }
                        Platform.runLater(() -> {
                            this.responseArea.appendText(response);
                        });
                    }
                }, "ReceiveThread");
                receiveThread.start();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        clearBtn.setOnAction(event -> {
            this.responseArea.clear();
        });

        requestBtn.setOnAction(event -> {
            httpsc.send("GET / HTTP/1.1");
            httpsc.send("Host: " + this.urlField.getText().trim());
            httpsc.send("Accept: */*");
            httpsc.send("Accept-Language: zh-cn");
            httpsc.send("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            httpsc.send("Connection: Keep-Alive");
            httpsc.send("\r\n");
        });


        urlField.setText("www.gdufs.edu.cn");
        portField.setText("443");

        this.responseArea.setEditable(false);
        this.responseArea.setWrapText(true);

        // 添加滚轮事件
        responseArea.setOnScroll(event -> {
            if (event.isControlDown()) {
                if (event.getDeltaY() > 0) {
                    responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() + 1) + "px;");
                } else {
                    responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() - 1) + "px;");
                }
            }
        });

        // 界面显示
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

第三步:基于URL类的网页下载程序设计

前面直接发送http请求的程序,对于复杂的地址,例如指向具体页面的地址,无法完成网页下载任务,我们可以使用基于URL类的解决方案。

URL(Uniform Resource Locator)中文名为统一资源定位符,用于表示资源地址,资源如网页或者FTP地址等。

URL 的格式为 protocol://资源地址,protocol可以是HTTP、HTTPS、FTP 和 File,资源地址中可以带有端口号及查询参数,具体可以自行搜索URL的知识。

java.net包中定义了URL类,该类用来处理有关URL的内容。并且其封装有一个InputStream返回类型的openStream()方法,我们的程序就可以读取 这个字节输入流来获得对应内容。

URLClientFx
  1. 创建程序URLClientFX.java,界面布局可参见如图8.5所示:

在这里插入图片描述

URLClient
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class URLClient {
    private URL url;
    private BufferedReader br;

    public URLClient(String urlString) throws IOException {
        url = new URL(urlString);
        //获得url的字节流输入
        InputStream in = url.openStream();
        br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));

    }
    public String readLine() throws IOException {
        return br.readLine();
    }

    public void close() throws Exception {
        br.close();
    }
}
URLClientFx
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;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

public class URLClientFx extends Application {
    private final Button sendBtn = new Button("发送");
    private final Button quitBtn = new Button("退出");
    private final TextField urlField = new TextField();
    private final TextArea responseArea = new TextArea();
    private URLClient urlClient;

    private Thread receiveThread;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("URL Downloader");
        BorderPane mainPane = new BorderPane();

        VBox centerBox = new VBox(10);
        centerBox.setPadding(new Insets(10, 20, 10, 20));
        centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
        centerBox.setAlignment(Pos.CENTER);

        VBox urlBox = new VBox(10);
        urlBox.setPadding(new Insets(10, 20, 10, 20));
        urlBox.getChildren().addAll(new Label("输入URL地址: "), urlField);
        urlBox.setAlignment(Pos.CENTER_LEFT);

        HBox bottomBox = new HBox(10);
        bottomBox.setPadding(new Insets(10, 20, 10, 20));
        bottomBox.getChildren().addAll(sendBtn, quitBtn);
        bottomBox.setAlignment(Pos.CENTER_RIGHT);

        VBox mainBox = new VBox();
        mainBox.getChildren().addAll(centerBox, urlBox, bottomBox);
        mainBox.setAlignment(Pos.CENTER);


        VBox.setVgrow(responseArea, Priority.ALWAYS);
        VBox.setVgrow(centerBox, Priority.ALWAYS);

        mainPane.setCenter(mainBox);

        Scene scene = new Scene(mainPane, 720, 450);

        // 按钮事件处理
        quitBtn.setOnAction(event -> System.exit(0));
        sendBtn.setOnAction(
                event -> {
                    try {
                        String url = this.urlField.getText().trim();
                        try {
                            this.urlClient = new URLClient(url);
                        } catch (MalformedURLException e) {
                            responseArea.appendText("URL格式错误!\n");
                            return;
                        }
                        Thread receiveThread = new Thread(() -> {
                            while (true) {
                                try {
                                    String line = this.urlClient.readLine();
                                    if (line == null) {
                                        break;
                                    }
                                    Platform.runLater(
                                            () -> {
                                                this.responseArea.appendText(line);
                                            }
                                    );
                                } catch (IOException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                        }, "receiveThread");
                        receiveThread.start();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                }
        );
        urlField.setText("http://www.baidu.com");
        responseArea.setEditable(false);
        responseArea.setWrapText(true);
        // 界面显示
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

http://www.kler.cn/a/384161.html

相关文章:

  • 将自己的项目打包成一个docker发布
  • Flutter自定义矩形进度条实现详解
  • CLIP论文CLIP 改进工作串讲
  • Python OpenCV 傅里叶变换
  • win11电脑无法找到声音输出设备怎么办?查看解决方法
  • vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
  • Hive 操作基础(进阶篇✌️)
  • 基于Python的自然语言处理系列(54):Neo4j DB QA Chain 实战
  • android gradle list
  • 基于MATLAB的人体行为检测与识别
  • 微服务架构面试内容整理-服务拆分的原则
  • 【React】默认导出和具名导出
  • 机器学习与数据挖掘_使用梯度下降法训练线性回归模型
  • 有什么办法换网络ip动态
  • 算法每日双题精讲——双指针(移动零,复写零)
  • Windows系统服务器怎么设置远程连接?详细步骤
  • Windows下QT调用MinGW编译的OpenCV
  • SIwave:释放 EMI 扫描仪/探测器的强大功能
  • 【CSS】“flex: 1“有什么用?
  • 如何在Linux环境中的Qt项目中使用ActiveMQ-CPP
  • 简单又便宜的实现电脑远程开机唤醒方法
  • 前端 | MYTED单篇TED词汇学习功能优化
  • leetcode 622.设计循环队列
  • DeBiFormer实战:使用DeBiFormer实现图像分类任务(二)
  • 高级 SQL 技巧详解
  • MDC(重要)