Java实现HTTP请求通信

参考:博客

前言

在校项目涉及到的一个技术,实现逻辑为:厂商提供第三方API,我这边通过HTTP请求调用其API获取Json数据进行处理,然后导入数据库。所以本文总结一下Http请求技术。

一、实现方式

目前JAVA实现HTTP请求的方法用的最多的有两种:

  • 通过HTTPClient这种第三方的开源框架去实现。
  • 通过HttpURLConnection去实现,HttpURLConnection是JAVA的标准类,是JAVA比较原生的一种实现方式。

HTTPClient对HTTP的封装性比较不错,通过它基本上能够满足我们大部分的需求,HttpClient3.1 是org.apache.commons.httpclient下操作远程 url的工具包,虽然已不再更新,但实现工作中使用httpClient3.1的代码还是很多,HttpClient4.5是org.apache.http.client下操作远程 url的工具包,最新的。

二、Post和Get请求定义、特点以及其区别

说到请求,这里必须先了解一下POST和GET请求

1.Get请求Post请求是啥?

  • 在客户机和服务器之间进行请求-响应时,两种最常用到的方法时Get和Post.
  • Get和Post请求都是HTTP的请求方法,除了这2个请求方法之外,HTTP还有 Head、Put、Delete、Trace、Connect、Option请求方法。
请求方法 描述
GET 请求指定的页面信息,并返回实体主体。
POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
DELETE 请求服务器删除指定的页面。
HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。
PUT 从客户端向服务器传送的数据取代指定的文档的内容。
TRACE 回显服务器收到的请求,主要用于测试或诊断
CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS 允许客户端查看服务器的性能

2.请求方法如何使用?
首先先了解HTTP的请求报文结构:
在这里插入图片描述
可见HTTP的请求报文由三部分构成:

  • 请求行: 由请求方法(Method)+URL字段+HTTP协议版本组成,注意其中的空格、回车符合换行符均不可省略,所以我们的请求方法实际上就是请求行中的了
  • 请求头部: 位于请求行之后,个数可以为0~若干个,每个请求头部都包含一个头部字段名和一个值,它们之间用冒号 “:” 分隔,在最后用回车符和换行符表示结束。
  • 请求数据: 如果请求方法为 GET,那么请求数据为空。它主要是在 POST 中进行使用,适用于需要填表单(FORM)的场景。

3.通过抓包我们得到的Get请求报文如下所示:

GET /search/users?q=JakeWharton HTTP/1.1
Host: api.github.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _octo=GH1.1.1623908978.1549006668; _ga=GA1.2.548087391.1549006688; logged_in=yes; dotcom_user=GoMarck; _gid=GA1.2.17634150.1554639136; _gat=1

重点看请求行:GET /search/users?q=JakeWharton HTTP/1.1

可以看到请求方法用的是GET请求,URL为/search/user?q=JakeWharton
协议为 HTTP1.1。

请求行下面部分全都是请求头部,我们可以看到 host (头部字段名)为 api.github.com,连接方式为长连接等信息。值得注意的是我们这个例子中是不存在请求数据的。

4.再来看一下 POST 请求的报文

POST / HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

可以看到请求行中请求方法为 POST,URL 为空,协议版本也是 HTTP1.1。

它和上面 GET 方法例子不一样的地方在于它的请求参数是位于请求数据中的。
可以看到 name=Professional%20Ajax&publisher=Wiley 就是它的请求数据。并且我们要注意到在请求数据和请求头之间是空出一行的,这是必不可少的。

4.GET 方法的特点

  • 前面的例子:https://api.github.com/search/users?q=JakeWharton 就是一个非常典型的 GET 请求的表现形式。即请求的数据会附在 URL 之后(放在请求行中),以 ? 分割 URL 和传输数据,多个参数用 & 连接。
  • 根据 HTTP 规范,GET 用于信息获取,而且应该是安全和幂等的 。

安全性:指的是非修改信息,即该操作用于获取信息而非修改信息。换句话说,GET请求一般不应产生副作用,也就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。

幂等性:则指的是无论调用这个URL 多少次,都不会有不同的结果的 HTTP
方法。而在实际过程中,这个规定没有那么严格。例如在一个新闻应用中,新闻站点的头版不断更新,虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等(获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作)的,因为它总是返回当前的新闻。

  • GET 是会被浏览器主动缓存的,如果下一次传输的数据相同,那么就会返回缓存中的内容,以求更快地展示数据
  • GET 方法的 URL 一般都具有长度限制,但是需要注意的是 HTTP 协议中并未规定 GET 请求的长度。这个长度限制主要是由浏览器和 Web 服务器所决定的,并且各个浏览器对长度的限制也各不相同。
  • GET 方法只产生一个 TCP 数据包(TCP/IP协议通信传输中的数据单位),浏览器会把请求头和请求数据一并发送出去,服务器响应 200 ok(返回数据)

5.Post方法的特点

  • 根据 HTTP 规范,POST 表示可能修改变服务器上的资源的请求。例如我们在刷知乎的时候对某篇文章进行点赞,就是提交的 POST 请求,因为它改变了服务器中的数据(该篇文章的点赞数)
  • POST 方法因为有可能修改服务器上的资源,所以它是不符合安全和幂等性
  • POST 是将请求信息放置在请求数据中的,这也是 POST 和 GET 的一点不那么重要的区别
  • 因为 POST 方法的请求信息是放置在请求数据中的,所以它的请求信息是没有长度限制的
  • POST 方法会产生两个 TCP 数据包,浏览器会先将请求头发送给服务器,待服务器响应100 continue,浏览器再发送请求数据,服务器响应200 ok(返回数据)。这么看起来 GET 请求的传输会比 POST 快上一些(因为GET 方法只发送一个 TCP 数据包),但是实际上在网络良好的情况下它们的传输速度基本相同

6.GET 和 POST 的区别 参考博客

  • 其都是HTTP协议中的两种发送请求的方法,它们的本质都是 TCP 链接,并无区别。但是由于 HTTP 的规定以及浏览器/服务器的限制,导致它们在应用过程中可能会有所不同
  • 其实可以想象一下,如果我们直接使用 TCP 进行数据的传输,那么无论是单纯获取资源的请求还是修改服务器资源的请求在外观上看起来都是 TCP 链接,这样就非常不利于进行管理。所以在 HTTP 协议中,就会对这些不同的请求设置不同的类别进行管理,例如单纯获取资源的请求就规定为 GET、修改服务器资源的请求就规定为 POST,并且也对它们的请求报文的格式做出了相应的要求(例如请求参数 GET 位于 URL 而 POST 则位于请求数据中)

附:

也就是说,GET 和 POST 所做的事其实是一样的,如果你给 GET 加上请求数据,给 POST 加上 URL 参数,这在技术上是完全可行的,事实上确实有一些人为了贪图方便在更新资源时用了GET,因为用POST必须要到FORM(表单),这样会麻烦一点(但是强烈不建议这样子做!!!)。

既然 GET 和 POST 的底层都是 TCP,那么为什么 HTTP 还要特别将它们区分出来呢?

其实可以想象一下,如果我们直接使用 TCP 进行数据的传输,那么无论是单纯获取资源的请求还是修改服务器资源的请求在外观上看起来都是 TCP 链接,这样就非常不利于进行管理。所以在 HTTP 协议中,就会对这些不同的请求设置不同的类别进行管理。
例如单纯获取资源的请求就规定为 GET、修改服务器资源的请求就规定为 POST,并且也对它们的请求报文的格式做出了相应的要求(例如请求参数 GET 位于 URL 而 POST 则位于请求数据中)

当然,如果我们想将 GET 的请求参数放置在请求数据中或者将 POST 的请求数据放置在 URL 中,这是完全可以的,虽然这样子做并不符合 HTTP 的规范。但是这样子做是否能得到我们期望的响应数据呢?答案是未必,这取决于服务器的行为。

以 GET 方法在请求数据中放置请求参数为例,有些服务器会将请求数据中的参数读出,在这种情况下我们依然能获得我们期望的响应数据;而有些服务器则会选择直接忽略,这种情况下我们就无法获取期望的响应数据了。

三、代码实例

  • 通过HTTPClient这种第三方的开源框架去实现。
  • 通过HttpURLConnection去实现,HttpURLConnection是JAVA的标准类,是JAVA比较原生的一种实现方式。

一、通过CloseableHttpClient实现

public static String postJson(String url,String jsonString){
    
    
        String result = null;
        //获取一个httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost post = new HttpPost(url); //用url生成一个请求
//        post.setHeader("Content-Type","application/x-www-form-urlencoded");
        CloseableHttpResponse response = null; 
        try {
    
    
            post.setEntity(new ByteArrayEntity(jsonString.getBytes("UTF-8")));
            
            用创建的httpclient执行这个请求,response获取数据
            response = httpClient.execute(post); 
            
            用来获取状态码
            System.out.println(response.getStatusLine().getStatusCode());
            
            状态码为200成功
            if(response != null && response.getStatusLine().getStatusCode() == 200)
            {
    
    
                获取响应实体
                HttpEntity entity = response.getEntity(); 
                result = entityToString(entity); 将他转为String类型
            }
            return result;
        } catch (UnsupportedEncodingException e) {
    
    
            e.printStackTrace();
        } catch (ClientProtocolException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            try {
    
    
                httpClient.close(); 一定要关闭通信
                if(response != null)
                {
    
    
                    response.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
        return null;
    }
    
    private static String entityToString(HttpEntity entity) throws IOException {
    
    
        String result = null;
        if(entity != null)
        {
    
    
            long lenth = entity.getContentLength();
            if(lenth != -1 && lenth < 2048)
            {
    
        
                采用EntityUtils对象(是org.apache.http.util下的一个工具类)将实体类内容转化为字符串
                result = EntityUtils.toString(entity,"UTF-8");
            }else {
    
    
            
                //InputStreamReader类是从字节流到字符流的桥接器
                
                InputStreamReader reader1 = new InputStreamReader(entity.getContent(), "UTF-8");
                CharArrayBuffer buffer = new CharArrayBuffer(2048); //采用buffer缓存区基于char数组存储 读取字符流
                char[] tmp = new char[1024];
                int l;
                while((l = reader1.read(tmp)) != -1) {
    
    
                    buffer.append(tmp, 0, l); //从0开始,每次读一个
                }
                result = buffer.toString();
            }
        }
        return result;
    }

上面代码大致思路:

  • 利用CloseableHttpClient的HttpClients.createDefault()创建一个httpClient对象用来通信
  • 然后利用new HttpPost(url)生成一个请求对象
  • 后面利用httpClient.execute(post) 执行这个请求
  • 利用CloseableHttpResponse 来接收服务端返回来的数据
  • 判断这个数据是否为空,还有response.getStatusLine().getStatusCode()的状态码是否为200的成功标记
  • HttpEntity entity = response.getEntity() 获取这个封装数据的对象的响应实体内容
  • entityToString(entity)转为String类型,下面讲解如何转
  • 先判断entity.getContentLength()=-1?&& <2048 如果=-1并且<2048存在压缩问题,则直接利用http旗下的EntityUtils.toString将其转为字符串返回
  • 如果没有则采用InputStreamReader将entity.getContent()获取的内容转为字符流
  • 单单是字符流的形式不法满足返回条件,则创建CharArrayBuffer的字符流缓存区基于数组的存储,自主进行读取流中的数据。
  • 再转为字符串返回

二、通过HttpURLConnection实现

 public static void dealPlan(String apiPath){
    
    
            BufferedReader in = null;
            StringBuffer result = null;
            try {
    
    
                URL url = new URL(apiPath);
                //打开和url之间的连接-
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                connection.setRequestProperty("Charset", "utf-8");
                connection.connect();
                result = new StringBuffer();
                //读取URL的响应
                in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
                String line;
                while ((line = in.readLine()) != null) {
    
    
                    result.append(line);
                }

            } catch (MalformedURLException e) {
    
    
                e.printStackTrace();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                try {
    
    
                    if (in != null) {
    
    
                        in.close();
                    }
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

上面代码实现逻辑:

  • 利用new URL(url)将url转变为URL类对象
  • HttpURLConnection connection = (HttpURLConnection) url.openConnection()打开URL连接
  • setRequestMethod(“GET”)指定请求方式然后利用connection.connect()进行连接
  • conn.getResponseCode()获取响应码进行判断
  • 后面进行读取字节流connection.getInputStream()、
  • new InputStreamReader 将其转为字符流
  • new BufferedReader(new InputStreamReader(connection.getInputStream(), “UTF-8”))再存入字符缓存区
  • 再利用readLine()读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

猜你喜欢

转载自blog.csdn.net/weixin_42754971/article/details/114434362