美文网首页
HttpURLConnection链接详解

HttpURLConnection链接详解

作者: 瞎胡扯1 | 来源:发表于2022-01-10 13:48 被阅读0次

    HttpURLConnection链接详解

    一、简介

    简单来说,HttpURLConnection 是 Java 提供的发起 HTTP 请求的基础类库,提供了 HTTP 请求的基本功能,不过封装的比较少,在使用时很多内容都需要自己设置,也需要自己处理请求流和响应流。

    二、获取连接

    获取 HttpURLConnection 对象的方法如下所示:

        // 定义 URL对象
        final URL url = new URL("http//ip:port/xxx");
        // 获取 URL 链接
        URLConnection urlConnection =  url.openConnection();
        // 因为 URL 是根据 url 中的协议(此处http)生成的 URLConnection 类的子类
        // HttpURLConnection, 故此处转换为 HttpURLConnection子类,方便使用子类
        // 中的更多的API
        HttpURLConnection connection = (HttpURLConnection)urlConnection;
    

    三、设置参数

    超时时间

    在 Http 请求时防止对方长时间无法连接等问题,一般会设置超时时间,可以通过如下方式设置

        // 设置连接超时时间, 值必须大于0,设置为0表示不超时 单位为“毫秒”
        connection.setConnectTimeout(30000);   
        // 设置读超时时间, 值必须大于0,设置为0表示不超时 单位毫秒
        connection.setReadTimeout(60000);
    

    设置请求方法

    在 Http 请求中包括 GET、POST、PUT等方法,可以通过如下方法设置 HttpURLConnection的请求方法

    // 设置为 GET 请求, 
    connection.setRequestMethod("GET");
    

    <span style="color: red;">注意:此处 方法必须设置为 大写,否则会报如下错误</span>

    java.net.ProtocolException: Invalid HTTP method: get
    

    可以通过定义枚举类型设置,如下所示:

    // 定义请求方法枚举类  
    public enum HttpMethod {
        GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
      }
    
    // 在使用枚举设置请求方法
    connection.setRequestMethod(HttpMethod.POST.name());
    

    请求头信息

    在请求时,经常会遇到设置自定义请求头,或者更改 Conent-Type 的值,可以通过如下方设置:

    // 设置请求类型为 application/json
    connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
    // 设置可接受的数据类型
    connection.setRequestProperty("Accept", "*/*");
    // 设置保持长链接
    connection.setRequestProperty("Connection", "Keep-Alive");
    // 设置自定义头 Token
    connection.setRequestProperty("Token", "123456");
    

    其他参数

    // 设置不使用缓存, 默认为 true 使用缓存
    connection.setUseCaches(false);
    // 设置单次请求是否支持重定向,默认为 setFollowRedirects 方法设置的值
    connection.setInstanceFollowRedirects(false);
    
    // 设置是否进行重定向,注意此处为 静态方法,表示所有的请求都不支持重定向,默认为true
    HttpURLConnection.setFollowRedirects(false);
    
    

    <span style="color:red;">注意:</span>

    所有的参数,必须在建立连接之前设置,否则会报如下错误

    java.lang.IllegalStateException: Already connected
    

    四、建立连接

    显式连接

    在设置完所有参数后,可以通过调用 connect 方法,进行显式建立连接。如下所示:

        // 调用打开连接, 调用此方法,只是建立一个连接,并不会发送数据。 
        connection.connect();
    

    隐式连接

    除了上面的调用 connect 显式建立连接外,在调用如下方法时,会隐式的调用此方法,建立连接。

        // 获取输出流
        connection.getOutputStream();
        // 获取输入流
        connection.getInputStream();
    

    由于,在网络请求时,一般都会获取请求结果,故在实际应用中,一般不调用 connect() 方法进行显式打开连接。

    五、发送数据

    POST请求

    众所周知,HTTP 中的 POST 请求的数据是包含在请求体中的。在 HttpURLConnection 中 POST 请求发送数据时,需要获取 连接的输出流对象,然后往输出流中写数据即可,如下所示:

        // 要发送的数据
        String connect = "我是一个POST请求数据";
    
        // 因为这个是post请求,参数要放在
        // http正文内,因此需要设为true, 默认情况下是false;
        connection.setDoOutput(true);
    
        // 从连接中获取 输出流对象
        OutputStream os = connection.getOutputStream();
        // 往输出流中写数据
        os.write(connect.getBytes(StandardCharsets.UTF_8));
        // 冲刷 并 关闭输出流
        os.flush();
        os.close();
    

    <span style="color: red;">注意:</span>

    1、 需要写数据时,必须调用 connection.setDoOutput(true); 方法,并且参数为 true, 且需要在调用 getOutputStream() 方法之前调用。

    2、此时写的数据,只是写到了缓冲区中,并不会真正的把数据发送给资源方。

    GET请求

    Http 中的 GET 请求的参数是拼接在 URL 后进行发送的,所以 发送 GET 请求时,在创建 连接时把参数拼接在后面即可。

    但是,有一点需要注意,如果在 GET 请求中 也调用了 getOutputStream() 方法,那么,自动就会把请求改为 POST 请求。如下源码所示:

    // HttpURLConnection 中的 方法,
    private synchronized OutputStream getOutputStream0() throws IOException {
            try {
                if (!this.doOutput) {
                   
                } else {
                    // 如果设置的 方法为 GET 则改为 POST
                    if (this.method.equals("GET")) {
                        this.method = "POST";
                    }
                }
            }catch(Exception e){
                
            }
    }
    
    

    六、响应数据

    请求结果

    在 HTTP 请求中一般是需要知道请求状态,在 HttpURLConnection 中可以通过如下方式获取请求状态

    // 获取请求状态,此状态即为 HTTP 请求的状态 200:成功,404:找不到资源 等
    int responseCode = connection.getResponseCode();
    
    // 获取请求描述信息
    String msg = connection.getResponseMessage();
    
    

    获取头信息

    获取响应头有如下几种方式:

    // 1、获取所有的响应头信息
     Map<String, List<String>> headerFields = connection.getHeaderFields();
    
    // 2、根据头信息名称获取响应头信息
    String connectionHeader =  connection.getHeaderField("Connection");
    
    // 3、根据头信息索引获取响应头信息, 此下标 必须大于 0。
    String secHeader = connection.getHeaderField(2);
    
    

    读取数据

    读取响应数据也是比较简单的,可以首先通过 HttpURLConnection 中的 getInputStream() 方法 获取输入流,然后,通过输入流获取数据即可,如下所示:

        // 获取输入流
        InputStream inputStream = connection.getInputStream();
        // 定义一个临时字节输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            // 开始读取数据
            byte[] buffer = new byte[256];
            int len = 0;
            while ((len = inputStream.read(buffer)) > 0){
                baos.write(buffer,0, len);
            }
            return new String(baos.toByteArray(), StandardCharsets.UTF_8);
        } finally {
            // 关闭输入、输出流
            inputStream.close();
            baos.close();
        }
    

    七、上传下载

    上传

    在普通 Web 页面中上传文件是很简单的,只需要把 from 标签中加上 enctype="multipart/form-data" 即可,剩下的都交给浏览器去完成发送数据的收集并发送 Http 请求即可。

    但是,在 HttpURLConnection 中脱离了浏览器,就需要我们自己去完成数据收集并发送请求了。那么我们首先看下浏览器是怎么收集上传数据,并发送请求的。

    先看下浏览器发送上传时间的请求正文格式:

    // 请求头中的 Content-Type 属性 其中定义了属性分割线  
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfdRf0g4cPSTVeLkJ
    
    // 请求数据正文信息
    ------WebKitFormBoundaryfdRf0g4cPSTVeLkJ
    Content-Disposition: form-data; name="images"; filename="20150703212056_Yxi4L.jpeg"
    Content-Type: image/jpeg
    
    
    ------WebKitFormBoundaryfdRf0g4cPSTVeLkJ
    Content-Disposition: form-data; name="checkRecord"
    
    {"describe":"","rectify":"立即整改"}
    ------WebKitFormBoundaryfdRf0g4cPSTVeLkJ--
    

    分析上面的的数据我们能够发下如下规则:

    1. 数据正文中的第一行 ------WebKitFormBoundaryfdRf0g4cPSTVeLkJ 作为分隔符,然后是 \r\n 回车换行符。
    2. 第二行 Content-Disposition: form-data; name="images"; filename="*****", 代表 form 表单数据域,其中 name 表示 接口属性值,filename 为文件名称。
    3. 第三行 Content-Type: image/jpeg 表示上传文件的类型。
    4. 第四行是一个 回车换行符。
    5. 第五行 是 数据内容,由于此处为 图片故没有显示出来。
    6. 后面的也是遵从上述规律。
    7. 最后一行表示结束行,注意后面多两个--

    根据以上规律,我们 在 使用 HttpURLConnection 进行上传时,就可以按照此规律拼接发送的数据流。实例如下所示:

    public void upload(File file) throws Exception {
        final URL url = new URL("http://localhost:10010/user/upload");
    
        // 获取 URL 链接
        URLConnection urlConnection =  url.openConnection();
        // 因为 URL 是根据 url 中的协议(此处http)生成的 URLConnection 类的子类
        // HttpURLConnection, 故此处转换为 HttpURLConnection子类,方便使用子类
        // 中的更多的API
        HttpURLConnection connection = (HttpURLConnection)urlConnection;
    
        // 自定义分割线,并设置请求头信息
        String boundary = "------------" + System.currentTimeMillis();
    
        connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
        // 设置请求为 POST 请求
        connection.setRequestMethod(METHOD.POST.name());
        // 打开输出流
        connection.setDoOutput(true);
        // 获取上传文件的类型
        MagicMatch magicMatch = Magic.getMagicMatch(file, false, true);
        String mimeType = magicMatch.getMimeType();
    
        // 获取输出流
        OutputStream outputStream = connection.getOutputStream();
    
        //拼接请求数据
        StringBuilder builder = new StringBuilder();
        // 第一行分割行
        builder.append("\r\n").append("--" + BOUNDARY).append( "\r\n");
        // 第二行form表单数据
        builder.append("Content-Disposition: form-data; name=\"file\"; filename=\"").append(file.getName() ).append("\"\r\n");
        // 第三行 上传数据类型
        builder.append( "Content-Type:").append(mimeType).append("\r\n");
        // 第四行一个空行
        builder.append("\r\n");
        outputStream.write(builder.toString().getBytes(StandardCharsets.UTF_8));
        // 开始写文件数据
        InputStream fileInput = new FileInputStream(file);
        byte[] buffer = new byte[512];
        int len = 0;
        while ((len = fileInput.read(buffer)) > 0){
            outputStream.write(buffer, 0, len);
        }
    
    
        // 开始写基本数据
        StringBuilder textBuffer = new StringBuilder();
        // 分隔符行
        textBuffer.append("\r\n").append("--" + BOUNDARY).append("\r\n");
        // form表单数据
        textBuffer.append("Content-Disposition: form-data; name=\"name\"\r\n");
        // 一个空行
        textBuffer.append("\r\n");
        // 数据值
        textBuffer.append("张三");
        outputStream.write(textBuffer.toString().getBytes(StandardCharsets.UTF_8));
    
        // 写入结束行
        outputStream.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
        fileInput.close();
        int responseCode = connection.getResponseCode();
        printHeaders(connection.getHeaderFields());
        if(responseCode != 200){
            LOGGER.error("请求失败, code: {}, message: {}", responseCode, connection.getResponseMessage());
        }else {
            InputStream inputStream = connection.getInputStream();
            String reader = reader(inputStream);
            LOGGER.info("服务端返回数据为: \n {}", reader);
        }
    }
    

    注意:

    基本数据比 file 缺少 Content-Type: image/jpeg 行

    下载

    文件的下载就比较简单了,获取输入流,然后读取输入流,并把读到的数据保存到本地即可,一下是下载网络上的图片为例。

    /**
         * 下载
         * @param url 下载文件路径
         * @param distDir 保存的文件路径
         */
       public void download(String url, String distDir) throws Exception {
           // 获取连接
           HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
    
           // 设置请求方法
           connection.setRequestMethod("GET");
           connection.setRequestProperty("Charset", "UTF-8");
    
           // 获取文件名
           String fileUrl = connection.getURL().getFile();
           String fileName = fileUrl.substring(fileUrl.lastIndexOf(File.separatorChar) + 1);
    
           LOGGER.info("文件名:{} -- {}", fileName,  File.separator);
           String filePath = distDir + File.separatorChar + fileName;
           File file = new File(filePath);
           if(!file.getParentFile().exists()){
               file.getParentFile().mkdirs();
           }
           // 获取输入流,并写入文件
           try (InputStream inputStream = connection.getInputStream();
                OutputStream os = new FileOutputStream(file)) {
               byte[] buffer = new byte[256];
               int len = 0;
               while ((len = inputStream.read(buffer)) > 0) {
                   os.write(buffer, 0, len);
               }
               os.flush();
           }
    
       }
    

    九、总结

    1. 通过 URL 类的 openConnection() 方法获取请求连接
    2. 如果需要写入数据需要调用 setDoOutput(true) 打开输出流
    3. 在调用connection()getOutputStream()getInputStream() 方法之前设置好请求参数。
    4. 如果调用 getOutputStream() 方法,则会把把请求方法改为 POST
    5. 建立连接和调用 getOutputStream() 方法写入数据并关闭连接后,也不会发送数据,只有调用 getInputStream()才会真正的发送数据。
    6. 在使用 HttpURLConnection 上传数据时,需要仿照浏览器上传,手动拼接数据格式

    相关文章

      网友评论

          本文标题:HttpURLConnection链接详解

          本文链接:https://www.haomeiwen.com/subject/hsvbcrtx.html