Java中的HTTP
Pawn Lv2

少年自有凌云志,不废江河万古流

发送HTTP请求

1 HTTP请求

全称—— HyperText Transfer Protocol,是一套计算机网络进行通信的规则,遵循Request/Response应答模型。由客户端发起请求消息,即Request,这个时候客户端和服务器之间建立连接;然后服务器返回响应到客户端,即Reponse。

1.1 建立HTTP请求的步骤

一次完整的HTTP通信过程包括下列7个步骤:

  1. 建立TCP连接

    在HTTP工作开始之前,客户端首先要通过网络与服务器端建立连接,该连接是通过TCP来完成的。HTTP是比TCP/IP更高层——应用层的协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接。

  2. 客户端向服务器发送请求命令

    一旦建立了TCP连接,客户端就会向服务器发送请求命令

  3. 客户端发送请求头消息

    客户端发送其请求命令之后,还要以信息的形式向服务器发送一些别的信息,之后客户端发送了一空白行来通知服务器,它已结束了该头信息的发送。

  4. 服务器应答

    客户端向服务器发送请求之后,服务器端会向客户端回送应答,应答的第一部分是协议的版本号和应答状态码(HTTP/1.1 200 OK

  5. 服务器发送应答头消息

    服务器会随同应答向用户发送关于它自己的数据以及被请求的文档

  6. 服务器向客户端发送消息

    服务器向客户端发送头信息后,它会发送一个空白行来表示头信息的发送到此结束,接着它以Content-Type应答头信息所描述格式发送用户所请求的实际数据。

  7. 服务器关闭TCP连接

    一般情况下,一旦服务器向客户端发送了请求数据,它就要关闭TCP连接,然后如果客户端或者服务器再其头信息假如了Connection:keep-alive,TCP连接在发送完后仍然保持打开的状态,于是客户端可以继续通过相同的链接发送请求。保持连接节省了为每个请求建立新连接所需要的时间,还节约了网络带宽。

1.2 HTTP报文

HTTP报文本身是由多行(用CR+LF作换行符,即回车符+换行符=空行)数据构成的字符串文本。

HTTP协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  • 起始行 —— 描述请求或响应的基本信息
  • 头部字段集合 —— 使用key-value的形式更详细的说明报文
  • 消息正文 —— 实际传输数据,它不一定是纯文本,可以是图片、视频等二进制数据

其中前两部分起始行和头部字段经常合称为请求头/响应头,消息正文又称为实体,HTTP协议规定报文必须有Header,但可以没有body;并且header后面必须有一个空行。(CRLF就是空行,表示回车符号+换行符号,十六进制表示为0x0D0A

请求报文

以下就是一个请求报文的结构:

GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi

以下就是一个响应报文的结构:

HTTP/1.1 200 OK
Date: Mon, 27 Jul 2021 15:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain

请求行

请求行是指请求报文中的起始行,请求行由三部分构成:

  • 请求方法 —— 是一个动词,如GET/POST,表示对资源的操作类型
  • 请求目标 —— URL,标记了请求方法要操作的资源
  • 版本号 —— 表示报文用的HTTP协议版本

例如请求报文第一行,GET /hello.txt HTTP/1.1 其中GET表示请求方法,/hello.txt表示URL,HTTP/1.1表示的协议的版本

状态行

状态行是指响应报文中的起始行,状态行也由三部分组成:

  • 版本号 —— 表示报文使用的HTTP协议版本
  • 状态码 —— 一个三位数,用代码的形式表示处理的结果
  • 原因 —— 作为数字状态码的补充,更详细的解释文字

例如响应报文第一行,HTTP/1.1 200 OK 其中HTTP/1.1表示协议的版本,200表示状态码,OK表示响应成功

请求头

请求头指的是请求报文中,请求头下面的内容。请求头是由一系列的key-value键值对来表示的。

常用请求头字段的含义:

请求头字段 说明 示例
User-Agent 客户端的版本 User-Agent: Mozilla/5.0 (Linux; X11)
Accept 客户端能接受的MIME数据类型 Accept:text/plain,text/html
Accept-Charset 客户端采用的编码格式 Accept-Charset:iso-8859-5
Authentication HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cookie 客户端可以向服务器端带数据 Cookie: $Version=1; Skin=new;
From 发出请求的用户的Email From:user@email.com
Host 客户端想连接的目标主机和端口号 Host: http://www.hao123.com:8080
Date 客户端当前请求服务器的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT

响应头

响应头指的是响应报文中,响应头下面的内容。

常用响应头字段的含义:

响应字段 说明 示例
Allow 对某网络资源的有效请求方法,不允许返回状态码405 Allow:GET,HEAD
Content-Encoding 服务器采用的数据压缩格式 Content-Encoding:gzip
Content-Length 服务器返回给客户端的数据长度 Content-Length:348
Server 服务器的类型 Server:Apache
Content-Type 服务返回的数据类型以及编码格式 Content-Type:text/html;charset=utf-8
ETag 与缓存相关的头 ETag:”34aa387-d-1568eb00”
WWW-Authenticate 表明客户端请求实体应该使用的授权方案 WWW-Authenticate:Basic

状态码

  1. 100-199:表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程
  2. 200-299:表示成功接收请求并已完成整个处理过程,常用200
  3. 300-399:未完成请求,客户端需要进一步细化,常用302、307、304
  4. 400-499:客户端的请求有错误,常用404
  5. 500-599:服务器端出现错误,常用500

1.3 HTTP协议版本之间的区别

HTTP协议一共经过了四个版本的迭代,从最初的HTTP/0.9=>HTTP/1.0=>HTTP/1.1=>HTTP/2.0

  • HTTP/0.9

    该版本出自1991年,只有一种请求方法GET,并且服务器只能影响HTML格式的数据。

  • HTTP/1.0

    该版本出自1996年,相比于HTTP/0.9

    1. 可以发送任意格式的数据,不仅可以传输文字,还可以传输图像,视频,二进制文件等
    2. 除了GET请求方式,还加入了POST、HEAD请求方法
    3. 请求和响应格式改变,除了数据部分,每次通信必须包含头信息
    4. 新增状态码,多字符集支持,多部分发送,权限,缓存,内容编码等

    HTTP/1.0版本的缺点:

    每个TCP连接只能发送一个请求,发送数据完毕,连接就关闭了,如果要请求其他资源,就必须重新建立连接。(使用Connection:keep-alive字段来维持长连接

  • HTTP/1.1

    HTTP/1.1版本出自1997年,它进一步完善了HTTP协议,直到现在都是最流行的版本

    相比于1.0版本:

    1. 默认支持场链接,即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection:keep-alive
    2. 管道机制,即在同一个TCP连接中,客户端可以同时发送多个请求,进一步改善了HTTP协议的效率
    3. 增加了PUT、PATCH、OPTION、DELET等请求方式
    4. 客户端请求的头信息增加Host字段,用来指定服务器的域名

    HTTP/1.1版本的缺点:

    虽然在HTTP/1.1版本中允许复用TCP连接,但是在同一个TCP连接里面,所有的数据通信都是同步进行的。服务器只有处理完一个回应,才会进行下一个回应,容易造成响应的阻塞。

  • HTTP/2.0

    HTTP/2.0版本对于HTTP/1.x版本来说是有巨大提升的:

    1. 二进制格式 —— HTTP/1.x是文本协议,而HTTP/2.0是以二进制帧为单位,是一个二进制协议。一帧中除了包含数据外同时还包含该帧的标识,即标识该帧属于哪个Request,即使得网络传输变得十分灵活
    2. 多路复用 —— 多个请求共用一个TCP连接,多个请求可以同时在这个TCP连接上并发
    3. header头部压缩 —— 对head使用HPACK算法对header数据进行压缩,减少请求的大小,减少流量消耗,提高效率
    4. 支持服务端推送 —— 2.0版本允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。

1.4 HTTP与HTTPS之间的区别

HTTP协议的传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能够加密传输,于是设计SSL协议用于对HTTP协议传输数据进行加密,从而就诞生了HTTPS。简单来说HTTPS协议就是SSL+HTTP协议构建的可进行加密传输、身份认证的网络传输协议

HTTPS工作原理

  1. 客户端使用HTTPS的URL访问Web服务器,要求与Web服务器建立SSL连接
  2. Web服务器接收到客户端的请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端
  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级
  4. 客户端的浏览器根据双方同意的安全等级,建立会话秘匙,然后利用网站的公匙将会话密匙加密,并传送给网站
  5. Web服务器利用自己的私匙解密出会话密匙
  6. web服务器利用会话密匙机密与客户端之间的通信

其与HTTP协议的区别:

  1. HTTPS协议需要到CA申请证书,一般免费证书较少,因此需要一定的费用。
  2. HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议
  3. HTTP和HTTPS使用的完全不同的连接方式,用的端口也不一样,前者是80端口,后者是443端口
  4. HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可加密传输、身份认证的网络协议,比HTTP协议更安全

1.5 HTTP的参数

HTTP不管使用什么方法和参数放的位置都没有关系的,只是会有一些约定俗称的做法。

在URL里面放参数是最简单的,就是利用来分隔键值对即可。例如:http://localhost:8443//hello?username=panhao

其次是在Body里面放参数,在Body中存放参数的时候,就需要在Header中指明Conten-Type,告诉服务器以什么样的格式来解析Body中的参数

Body参数方式 Content-type
Text text/plain
Form application/x-www-form-urlencoded
JSON application/json
File 不确定
Multipart multipart/form-data;boundary=X_PAW_BOUNDARY

2 JAVA的HTTP工具类

2.1 Java原生发送HTTP请求

Java原生的API可以发送HTTP请求,即java.net.URL、java.net.URLConnection,JDK自带的类。其具体步骤为:

  1. 通过统一资源定位器获取连接器(URL=> URLConnection
  2. 设置请求的参数
  3. 发送请求
  4. 以输入流的形式获取返回内容
  5. 关闭输入流

发送GET请求

public String sendGet(String httpURL){

        HttpURLConnection connection=null;
        InputStream is=null;
        BufferedReader br=null;
        String result=null;

        try {
            //创建URL连接对象
            URL url=new URL(httpURL);
            //通过远程URL连接对象创建一个连接,强转成HTTPURLConnection
            connection= (HttpURLConnection) url.openConnection();
            //设置连接方式
            connection.setRequestMethod("GET");
            //设置连接超时时间
            connection.setConnectTimeout(15000);
            //设置读取远程返回的数据时间
            connection.setReadTimeout(60000);
            //在请求头中设置字段的值
            connection.setRequestProperty("Authorization","xxxx");
            //发送连接
            connection.connect();
            if(connection.getResponseCode()==200){
                is=connection.getInputStream();
                br=new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
                StringBuilder builder=new StringBuilder();
                String temp=null;
                while ((temp=br.readLine())!=null){
                    builder.append(temp);
                    builder.append("\r\n");
                }
                result=builder.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //关闭远程连接
            connection.disconnect();
        }
        return result;
    }

发送Post请求

public String sendPost(String httpUrl){
        OutputStreamWriter out=null;
        BufferedReader in =null;
        StringBuilder result=new StringBuilder();
        HttpURLConnection connection=null;

        try {
            URL url=new URL(httpUrl);
            connection= (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            //发送POST请求的时候必须设置为true
            connection.setDoInput(true);
            connection.setDoOutput(true);
            //设置连接超时时间和读取超时时间
            connection.setConnectTimeout(30000);
            connection.setReadTimeout(10000);
            //设置请求体中的内容格式
            connection.setRequestProperty("Content-Type","application/json");
            connection.setRequestProperty("Accept","application/json");
            //向POST请求中装配参数
            out=new OutputStreamWriter(connection.getOutputStream());
            Map<String,String> params=new HashMap<>();
            params.put("u","xxxx");
            params.put("p","xxxx");
            String jsonStr= JSON.toJSONString(params);
            out.write(jsonStr);
            out.flush();
            out.close();
            //读取返回结果
            if(connection.getResponseCode()==200){
                in=new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line=in.readLine())!=null){
                    result.append(line);
                }
            }else {
                System.out.println("ResponseCode is an error code:"+connection.getResponseCode());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result.toString();
    }

2.2 使用HttpClient

HttpClient最基本的功能就是执行http方法,执行http方法包括了一次或者几次HTTP请求和相应的变化,通常也是通过HTTPClient来处理的。只要用户提供一个Request的对象,HTTPCLient就会将用户的请求发送到目标服务器上,并且返回一个Respone对象,如果没有执行成功将抛出一个异常。

常用步骤:

  1. 创建一个CloseableHttpClient对象,通过HttPClients的静态方法createDefault()来获取
  2. 生成一个对应方法的请求
  3. 执行这个请求
  4. 处理返回结果

无论是什么请求都需要先添加依赖:

首先添加HTTPClient的依赖:

     <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

GET请求:

 public String sendGet(String url){
        //1.获得一个HttpClient对象
        CloseableHttpClient httpClient= HttpClients.createDefault();
        CloseableHttpResponse response=null;
        String result=null;
        try {
            URIBuilder builder=new URIBuilder(url);
            //1.第一种添加参数的方式
            builder.addParameter("u","bridge");
            builder.addParameter("p","bridge");
            //2.第二种添加参数的方式
//            List<NameValuePair> list=new LinkedList<>();
//            BasicNameValuePair param1=new BasicNameValuePair("u","bridge");
//            BasicNameValuePair param2=new BasicNameValuePair("p","bridge");
//            list.add(param1);
//            list.add(param2);
//            builder.setParameters(list);
            //2.创建一个GET请求
            HttpGet httpGet=new HttpGet(builder.build());
            //设置一个Header的属性
            //httpGet.setHeader("Authorization","JWT ");
            //3.执行GET请求并返回结果
            response=httpClient.execute(httpGet);
            //4.处理返回结果
            HttpEntity entity=response.getEntity();
            if(entity!=null){
                result= EntityUtils.toString(entity);
            }
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

POST请求

  public String sendPost(String url, Map<String, String> map) {
        CloseableHttpResponse response = null;
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost post = new HttpPost(url);
        JSONObject result = null;
        try {
            String jsonString = JSON.toJSONString(map);
            System.out.println(jsonString);
            StringEntity encodedFormEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON);
            post.setEntity(encodedFormEntity);
            post.setHeader("Content-Type", "application/json;charset=UTF-8");
            post.setHeader("Accept","application/json");
            response = client.execute(post);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                result = JSON.parseObject(EntityUtils.toString(entity));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (String) result.get("data");
    }

2.3 使用OkHttp

使用OkHttp的时候其实与HttpClient是大致类似的

  1. 实例化一个OkHttpClient实例来实现的
  2. 实例化一个Request对象,将Headers和Body装配完成
  3. 实例化一个Response对象,执行Request
  4. 最后从Response对象的ResponseBody对象中获取到返回后数据

GET请求

public String sendGET(String token){
        OkHttpClient client=new OkHttpClient();
        //添加参数
//        HttpUrl url=HttpUrl.parse("http://www.cyjsyjy.com:7416/unifiedapi/sites")
//                .newBuilder()
//                .addQueryParameter("name","Http")
//                .build();
        Request request=new Request.Builder()
                .url("xxxx")
                .addHeader("Authorization","JWT "+token)
                .addHeader("Accept","application/json")
                .build();
        Response response=null;
        JSONObject result=null;
        try {
            response=client.newCall(request).execute();
            ResponseBody body=response.body();
            if(response.isSuccessful()){
                result= JSON.parseObject(body.string());
            }else {
                result=new JSONObject();
                result.put("code",500);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return JSON.toJSONString(result);
    }

POST请求

 public String sendPOST(){
        OkHttpClient okHttpClient=new OkHttpClient();
        Map<String, String> params = new HashMap<>();
        params.put("u", "xxxx");
        params.put("p", "xxxx");
        String result=null;
        Request request=new Request.Builder()
                .url("xxxx")
                .addHeader("Accept","application/json")
                .post(RequestBody.create(MediaType.get("application/json"),JSON.toJSONBytes(params)))
                .build();

        try {
            Response response=okHttpClient.newCall(request).execute();
            ResponseBody body=response.body();
            if(body!=null&&response.isSuccessful()){
                result=body.string();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (String) JSON.parseObject(result).get("data");
    }

2.4 使用RestTemplate

作为Java中的扛鼎者,Spring家族怎么会在这种地方沉默呢。所有RestTemplate就是Spring的杰作。

GET请求

  public String login() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("u", "xxxx");
        map.add("p", "xxxx");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(url + "/login", request, String.class);
        JSONObject data = JSON.parseObject(response.getBody());
        return String.valueOf(data.get("data"));
    }

POST请求

 public void site(String token) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("Authorization","JWT "+token);
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(httpHeaders);
        ResponseEntity<String> result = restTemplate.exchange(url+"/sites", HttpMethod.GET,request, String.class);
        JSONObject data=JSON.parseObject(result.getBody());
        System.out.println(request);
        System.out.println(data);
    }
  • Post title:Java中的HTTP
  • Post author:Pawn
  • Create time:2021-05-08 16:26:34
  • Post link:https://panhao.work/2021/05/08/Java中的HTTP/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.