如需转载,请根据 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 许可,附上本文作者及链接。
本文作者: 执笔成念
作者昵称: zbcn
本文链接: https://1363653611.github.io/zbcn.github.io/2021/02/19/socket_04HTTP/
什么是HTTP?
HTTP就是目前使用最广泛的Web应用程序使用的基础协议,例如,浏览器访问网站,手机App访问后台服务器,都是通过HTTP协议实现的。
HTTP(HyperText Transfer Protocol).超文本传输协议,它是基于TCP协议之上的一种请求-响应协议
浏览器请求访问某个网站时
当浏览器希望访问某个网站时,浏览器和网站服务器之间首先建立TCP连接,且服务器总是使用80
端口和加密端口443
,然后,浏览器向服务器发送一个HTTP请求,服务器收到后,返回一个HTTP响应,并且在响应中包含了HTML的网页内容,这样,浏览器解析HTML后就可以给用户显示网页了。
一个完整的http请求
HTTP 请求
HTTP请求的格式是固定的,它由HTTP Header和HTTP Body两部分构成。
第一行总是
请求方法 路径 HTTP版本
,例如,GET / HTTP/1.1
表示使用GET
请求,路径是/
,版本是HTTP/1.1
。后续的每一行都是固定的
Header: Value
格式,我们称为HTTP Header,服务器依靠某些特定的Header来识别客户端请求,例如:
- Host:表示请求的域名,因为一台服务器上可能有多个网站,因此有必要依靠Host来识别用于请求;
- User-Agent:表示客户端自身标识信息,不同的浏览器有不同的标识,服务器依靠User-Agent判断客户端类型;
- Accept:表示客户端能处理的HTTP响应格式,
*/*
表示任意格式,text/*
表示任意文本,image/png
表示PNG格式的图片;- Accept-Language:表示客户端接收的语言,多种语言按优先级排序,服务器依靠该字段给用户返回特定语言的网页版本。
- 如果是
GET
请求,那么该HTTP请求只有HTTP Header,没有HTTP Body。 - 如果是
POST
请求,那么该HTTP请求带有Body,以一个空行分隔。一个典型的带Body的HTTP请求如下:
1
POST /login HTTP/1.1
2
Host: www.example.com
3
Content-Type: application/x-www-form-urlencoded
4
Content-Length: 30
5
6
username=hello&password=123456
POST
请求通常要设置Content-Type
表示Body的类型,Content-Length
表示Body的长度,这样服务器就可以根据请求的Header和Body做出正确的响应。GET
请求的参数必须附加在URL上,并以URLEncode方式编码,例如:http://www.example.com/?a=1&b=K%26R
,参数分别是a=1
和b=K&R
。- 因为URL的长度限制,
GET
请求的参数不能太多,而POST
请求的参数就没有长度限制,因为POST
请求的参数必须放到Body中。 POST
请求的参数不一定是URL编码,可以按任意格式编码,只需要在Content-Type
中正确设置即可。常见的发送JSON的POST
请求如下:
1
POST /login HTTP/1.1
2
Content-Type: application/json
3
Content-Length: 38
4
5
{"username":"bob","password":"123456"}
HTTP 响应
- HTTP响应也是由Header和Body两部分组成,一个典型的HTTP响应如下:
1
HTTP/1.1 200 OK
2
Content-Type: text/html
3
Content-Length: 133251
4
5
<!DOCTYPE html>
6
<html><body>
7
<h1>Hello</h1>
8
...
- 响应的第一行总是
HTTP版本 响应代码 响应说明
,例如,HTTP/1.1 200 OK
表示版本是HTTP/1.1
,响应代码是200
,响应说明是OK
。 - 客户端只依赖响应代码判断HTTP响应是否成功。HTTP有固定的响应代码:
- 1xx:表示一个提示性响应,例如101表示将切换协议,常见于WebSocket连接;
- 2xx:表示一个成功的响应,例如200表示成功,206表示只发送了部分内容;
- 3xx:表示一个重定向的响应,例如301表示永久重定向,303表示客户端应该按指定路径重新发送请求;
- 4xx:表示一个因为客户端问题导致的错误响应,例如400表示因为Content-Type等各种原因导致的无效请求,404表示指定的路径不存在;
- 5xx:表示一个因为服务器问题导致的错误响应,例如500表示服务器内部故障,503表示服务器暂时无法响应
- 当浏览器收到第一个HTTP响应后,它解析HTML后,又会发送一系列HTTP请求,例如,
GET /logo.jpg HTTP/1.1
请求一个图片,服务器响应图片请求后,会直接把二进制内容的图片发送给浏览器:
1
HTTP/1.1 200 OK
2
Content-Type: image/jpeg
3
Content-Length: 18391
4
5
????JFIFHH??XExifMM?i&??X?...(二进制的JPEG图片)
- 因此,服务器总是被动地接收客户端的一个HTTP请求,然后响应它。客户端则根据需要发送若干个HTTP请求。
- 对于最早期的HTTP/1.0协议,每次发送一个HTTP请求,客户端都需要先创建一个新的TCP连接,然后,收到服务器响应后,关闭这个TCP连接。由于建立TCP连接就比较耗时,因此,为了提高效率,HTTP/1.1协议允许在一个TCP连接中反复发送-响应,这样就能大大提高效率:
因为HTTP协议是一个请求-响应协议,客户端在发送了一个HTTP请求后,必须等待服务器响应后,才能发送下一个请求,这样一来,如果某个响应太慢,它就会堵住后面的请求。
为了进一步提速,HTTP/2.0允许客户端在没有收到响应的时候,发送多个HTTP请求,服务器返回响应的时候,不一定按顺序返回,只要双方能识别出哪个响应对应哪个请求,就可以做到并行发送和接收:
可见,HTTP/2.0进一步提高了效率。
HTTP 编程
- 既然HTTP涉及到客户端和服务器端,和TCP类似,我们也需要针对客户端编程和针对服务器端编程。
- 本节我们不讨论服务器端的HTTP编程,因为服务器端的HTTP编程本质上就是编写Web服务器,这是一个非常复杂的体系,也是JavaEE开发的核心内容.
客户端的HTTP编程
因为浏览器也是一种HTTP客户端,所以,客户端的HTTP编程,它的行为本质上和浏览器是一样的,即发送一个HTTP请求,接收服务器响应后,获得响应内容。只不过浏览器进一步把响应内容解析后渲染并展示给了用户,而我们使用Java进行HTTP客户端编程仅限于获得响应内容。
java 实现 http 客户端编程
早期
Java标准库提供了基于HTTP的包,但是要注意,早期的JDK版本是通过HttpURLConnection
访问HTTP,典型代码如下:
1 | URL url = new URL("http://www.example.com/path/to/target?a=1&b=2"); |
2 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
3 | conn.setRequestMethod("GET"); |
4 | conn.setUseCaches(false); |
5 | conn.setConnectTimeout(5000); // 请求超时5秒 |
6 | // 设置HTTP头: |
7 | conn.setRequestProperty("Accept", "*/*"); |
8 | conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)"); |
9 | // 连接并发送HTTP请求: |
10 | conn.connect(); |
11 | // 判断HTTP响应是否200: |
12 | if (conn.getResponseCode() != 200) { |
13 | throw new RuntimeException("bad response"); |
14 | } |
15 | // 获取所有响应Header: |
16 | Map<String, List<String>> map = conn.getHeaderFields(); |
17 | for (String key : map.keySet()) { |
18 | System.out.println(key + ": " + map.get(key)); |
19 | } |
20 | // 获取响应内容: |
21 | InputStream input = conn.getInputStream(); |
22 | ... |
上述代码编写比较繁琐,并且需要手动处理InputStream
,所以用起来很麻烦。
晚期
JDK 8
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HttpClient的主要功能:
- 实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
- 支持 HTTPS 协议
- 支持代理服务器(Nginx等)等
- 支持自动(跳转)转向
- ……
- java 8 则需要引入依赖
1 | <dependency> |
2 | <groupId>org.apache.httpcomponents</groupId> |
3 | <artifactId>httpclient</artifactId> |
4 | <version>4.5.12</version> |
5 | </dependency> |
get 请求
1 | /** |
2 | * get 请求: 无参 |
3 | */ |
4 | private static void get() { |
5 | HttpGet httpGet = new HttpGet("https://www.baidu.com"); |
6 | // 响应模型 |
7 | CloseableHttpResponse response = null; |
8 | try { |
9 | // 由客户端执行(发送)Get请求 |
10 | response = httpClient.execute(httpGet); |
11 | // 从响应模型中获取响应实体 |
12 | handleResponse(response); |
13 | |
14 | } catch (IOException e) { |
15 | e.printStackTrace(); |
16 | } |
17 | } |
18 | |
19 | /** |
20 | * get请求: 带参数:手动在url后面加上参数 |
21 | */ |
22 | private static void getWithParam() { |
23 | // 参数 |
24 | StringBuffer params = new StringBuffer(); |
25 | try { |
26 | // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去) |
27 | params.append("name=" + URLEncoder.encode("&", "utf-8")); |
28 | params.append("&"); |
29 | params.append("age=24"); |
30 | } catch (UnsupportedEncodingException e1) { |
31 | e1.printStackTrace(); |
32 | } |
33 | |
34 | HttpGet httpGet = new HttpGet("https://www.baidu.com" + "?" + params); |
35 | // 响应模型 |
36 | CloseableHttpResponse response = null; |
37 | try { |
38 | // 配置信息 |
39 | RequestConfig requestConfig = getBuilder() |
40 | // 设置是否允许重定向(默认为true) |
41 | .setRedirectsEnabled(true).build(); |
42 | |
43 | // 将上面的配置信息 运用到这个Get请求里 |
44 | httpGet.setConfig(requestConfig); |
45 | |
46 | // 由客户端执行(发送)Get请求 |
47 | response = httpClient.execute(httpGet); |
48 | // 从响应模型中获取响应实体 |
49 | handleResponse(response); |
50 | |
51 | } catch (IOException e) { |
52 | e.printStackTrace(); |
53 | } |
54 | } |
55 | |
56 | private static RequestConfig.Builder getBuilder() { |
57 | return RequestConfig.custom() |
58 | // 设置连接超时时间(单位毫秒) |
59 | .setConnectTimeout(5000) |
60 | // 设置请求超时时间(单位毫秒) |
61 | .setConnectionRequestTimeout(5000) |
62 | // socket读写超时时间(单位毫秒) |
63 | .setSocketTimeout(5000); |
64 | } |
65 | |
66 | //有参测试 (方式二:将参数放入键值对类中,再放入URI中,从而通过URI得到HttpGet实例) |
67 | private static void getWithParam2(){ |
68 | // 参数 |
69 | URI uri = null; |
70 | try { |
71 | // 将参数放入键值对类NameValuePair中,再放入集合中 |
72 | List<NameValuePair> params = new ArrayList<>(); |
73 | params.add(new BasicNameValuePair("name", "&")); |
74 | params.add(new BasicNameValuePair("age", "18")); |
75 | // 设置uri信息,并将参数集合放入uri; |
76 | // 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value) |
77 | uri = new URIBuilder().setScheme("http").setHost("localhost") |
78 | .setPort(12345).setPath("/doGetControllerTwo") |
79 | .setParameters(params).build(); |
80 | } catch (URISyntaxException e1) { |
81 | e1.printStackTrace(); |
82 | } |
83 | |
84 | // 创建Get请求 |
85 | HttpGet httpGet = new HttpGet(uri); |
86 | |
87 | // 响应模型 |
88 | CloseableHttpResponse response = null; |
89 | try { |
90 | // 配置信息 |
91 | setGetConfig(httpGet); |
92 | |
93 | // 由客户端执行(发送)Get请求 |
94 | response = httpClient.execute(httpGet); |
95 | handleResponse(response); |
96 | |
97 | } catch (ClientProtocolException e) { |
98 | e.printStackTrace(); |
99 | } catch (IOException e) { |
100 | e.printStackTrace(); |
101 | } finally { |
102 | try { |
103 | // 释放资源 |
104 | if (httpClient != null) { |
105 | httpClient.close(); |
106 | } |
107 | if (response != null) { |
108 | response.close(); |
109 | } |
110 | } catch (IOException e) { |
111 | e.printStackTrace(); |
112 | } |
113 | } |
114 | |
115 | |
116 | } |
117 | |
118 | /** |
119 | * 处理响应信息 |
120 | * @param response |
121 | * @throws IOException |
122 | */ |
123 | private static void handleResponse(CloseableHttpResponse response) throws IOException { |
124 | // 从响应模型中获取响应实体 |
125 | HttpEntity responseEntity = response.getEntity(); |
126 | System.out.println("响应状态为:" + response.getStatusLine()); |
127 | if (responseEntity != null) { |
128 | System.out.println("响应内容长度为:" + responseEntity.getContentLength()); |
129 | System.out.println("响应内容为:" + EntityUtils.toString(responseEntity)); |
130 | } |
131 | } |
POST 请求
1 | /** |
2 | * 无参post 请求 |
3 | */ |
4 | public static void post(){ |
5 | // 创建Post请求 |
6 | HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerOne"); |
7 | // 响应模型 |
8 | CloseableHttpResponse response = null; |
9 | try { |
10 | // 由客户端执行(发送)Post请求 |
11 | response = httpClient.execute(httpPost); |
12 | // 从响应模型中获取响应实体 |
13 | HttpEntity responseEntity = response.getEntity(); |
14 | |
15 | System.out.println("响应状态为:" + response.getStatusLine()); |
16 | if (responseEntity != null) { |
17 | System.out.println("响应内容长度为:" + responseEntity.getContentLength()); |
18 | System.out.println("响应内容为:" + EntityUtils.toString(responseEntity)); |
19 | } |
20 | } catch (ClientProtocolException e) { |
21 | e.printStackTrace(); |
22 | } catch (IOException e) { |
23 | e.printStackTrace(); |
24 | } finally { |
25 | try { |
26 | // 释放资源 |
27 | if (httpClient != null) { |
28 | httpClient.close(); |
29 | } |
30 | if (response != null) { |
31 | response.close(); |
32 | } |
33 | } catch (IOException e) { |
34 | e.printStackTrace(); |
35 | } |
36 | } |
37 | |
38 | } |
39 | |
40 | /** |
41 | * POST传递普通参数时,方式与GET一样即可,这里以直接在url后缀上参数的方式示例。 |
42 | */ |
43 | public static void postWithParam(){ |
44 | // 参数 |
45 | StringBuffer params = new StringBuffer(); |
46 | try { |
47 | // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去) |
48 | params.append("name=" + URLEncoder.encode("&", "utf-8")); |
49 | params.append("&"); |
50 | params.append("age=24"); |
51 | } catch (UnsupportedEncodingException e1) { |
52 | e1.printStackTrace(); |
53 | } |
54 | |
55 | // 创建Post请求 |
56 | HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerFour" + "?" + params); |
57 | |
58 | // 设置ContentType(注:如果只是传普通参数的话,ContentType不一定非要用application/json) |
59 | httpPost.setHeader("Content-Type", "application/json;charset=utf8"); |
60 | |
61 | // 响应模型 |
62 | CloseableHttpResponse response = null; |
63 | try { |
64 | // 由客户端执行(发送)Post请求 |
65 | response = httpClient.execute(httpPost); |
66 | handleResponse(response); |
67 | |
68 | } catch (ClientProtocolException e) { |
69 | e.printStackTrace(); |
70 | } catch (IOException e) { |
71 | e.printStackTrace(); |
72 | } finally { |
73 | try { |
74 | // 释放资源 |
75 | if (httpClient != null) { |
76 | httpClient.close(); |
77 | } |
78 | if (response != null) { |
79 | response.close(); |
80 | } |
81 | } catch (IOException e) { |
82 | e.printStackTrace(); |
83 | } |
84 | } |
85 | } |
86 | //POST有参(对象参数) |
87 | public static void postWithParam2(){ |
88 | // 创建Post请求 |
89 | HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerTwo"); |
90 | User user = new User(); |
91 | user.setPwd("123"); |
92 | user.setUsername("张三"); |
93 | // 我这里利用阿里的fastjson,将Object转换为json字符串; |
94 | // (需要导入com.alibaba.fastjson.JSON包) |
95 | String jsonString = JSON.toJSONString(user); |
96 | |
97 | StringEntity entity = new StringEntity(jsonString, "UTF-8"); |
98 | |
99 | // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中 |
100 | httpPost.setEntity(entity); |
101 | |
102 | httpPost.setHeader("Content-Type", "application/json;charset=utf8"); |
103 | |
104 | // 响应模型 |
105 | CloseableHttpResponse response = null; |
106 | try { |
107 | // 由客户端执行(发送)Post请求 |
108 | response = httpClient.execute(httpPost); |
109 | handleResponse(response); |
110 | } catch (ClientProtocolException e) { |
111 | e.printStackTrace(); |
112 | } catch (IOException e) { |
113 | e.printStackTrace(); |
114 | } finally { |
115 | try { |
116 | // 释放资源 |
117 | if (httpClient != null) { |
118 | httpClient.close(); |
119 | } |
120 | if (response != null) { |
121 | response.close(); |
122 | } |
123 | } catch (IOException e) { |
124 | e.printStackTrace(); |
125 | } |
126 | } |
127 | |
128 | } |
129 | |
130 | //POST有参(普通参数 + 对象参数): |
131 | public static void postWithParam3(){ |
132 | // 创建Post请求 |
133 | // 参数 |
134 | URI uri = null; |
135 | try { |
136 | // 将参数放入键值对类NameValuePair中,再放入集合中 |
137 | List<NameValuePair> params = new ArrayList<>(); |
138 | params.add(new BasicNameValuePair("flag", "4")); |
139 | params.add(new BasicNameValuePair("meaning", "这是什么鬼?")); |
140 | // 设置uri信息,并将参数集合放入uri; |
141 | // 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value) |
142 | uri = new URIBuilder().setScheme("http").setHost("localhost").setPort(12345) |
143 | .setPath("/doPostControllerThree").setParameters(params).build(); |
144 | } catch (URISyntaxException e1) { |
145 | e1.printStackTrace(); |
146 | } |
147 | HttpPost httpPost = new HttpPost(uri); |
148 | // HttpPost httpPost = new |
149 | // HttpPost("http://localhost:12345/doPostControllerThree1"); |
150 | StringEntity entity = buildUser(); |
151 | |
152 | // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中 |
153 | httpPost.setEntity(entity); |
154 | |
155 | httpPost.setHeader("Content-Type", "application/json;charset=utf8"); |
156 | |
157 | // 响应模型 |
158 | CloseableHttpResponse response = null; |
159 | try { |
160 | // 由客户端执行(发送)Post请求 |
161 | response = httpClient.execute(httpPost); |
162 | handleResponse(response); |
163 | } catch (ClientProtocolException e) { |
164 | e.printStackTrace(); |
165 | } catch (IOException e) { |
166 | e.printStackTrace(); |
167 | } finally { |
168 | try { |
169 | // 释放资源 |
170 | if (httpClient != null) { |
171 | httpClient.close(); |
172 | } |
173 | if (response != null) { |
174 | response.close(); |
175 | } |
176 | } catch (IOException e) { |
177 | e.printStackTrace(); |
178 | } |
179 | } |
180 | |
181 | } |
182 | |
183 | private static StringEntity buildUser() { |
184 | // 创建user参数 |
185 | User user = new User(); |
186 | user.setPwd("123"); |
187 | user.setUsername("张三"); |
188 | |
189 | // 将user对象转换为json字符串,并放入entity中 |
190 | return new StringEntity(JSON.toJSONString(user), "UTF-8"); |
191 | } |
发送文件
1 | //发送文件 |
2 | public static void sendFile(){ |
3 | HttpPost httpPost = new HttpPost("http://localhost:12345/file"); |
4 | CloseableHttpResponse response = null; |
5 | try { |
6 | MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); |
7 | // 第一个文件 |
8 | String filesKey = "files"; |
9 | File file1 = new File("C:\\Users\\JustryDeng\\Desktop\\back.jpg"); |
10 | multipartEntityBuilder.addBinaryBody(filesKey, file1); |
11 | // 第二个文件(多个文件的话,使用同一个key就行,后端用数组或集合进行接收即可) |
12 | File file2 = new File("C:\\Users\\JustryDeng\\Desktop\\头像.jpg"); |
13 | // 防止服务端收到的文件名乱码。 我们这里可以先将文件名URLEncode,然后服务端拿到文件名时在URLDecode。就能避免乱码问题。 |
14 | // 文件名其实是放在请求头的Content-Disposition里面进行传输的,如其值为form-data; name="files"; filename="头像.jpg" |
15 | multipartEntityBuilder.addBinaryBody(filesKey, file2, ContentType.DEFAULT_BINARY, URLEncoder.encode(file2.getName(), "utf-8")); |
16 | // 其它参数(注:自定义contentType,设置UTF-8是为了防止服务端拿到的参数出现乱码) |
17 | ContentType contentType = ContentType.create("text/plain", Charset.forName("UTF-8")); |
18 | multipartEntityBuilder.addTextBody("name", "邓沙利文", contentType); |
19 | multipartEntityBuilder.addTextBody("age", "25", contentType); |
20 | |
21 | HttpEntity httpEntity = multipartEntityBuilder.build(); |
22 | httpPost.setEntity(httpEntity); |
23 | |
24 | response = httpClient.execute(httpPost); |
25 | HttpEntity responseEntity = response.getEntity(); |
26 | System.out.println("HTTPS响应状态为:" + response.getStatusLine()); |
27 | if (responseEntity != null) { |
28 | System.out.println("HTTPS响应内容长度为:" + responseEntity.getContentLength()); |
29 | // 主动设置编码,来防止响应乱码 |
30 | String responseStr = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); |
31 | System.out.println("HTTPS响应内容为:" + responseStr); |
32 | } |
33 | } catch (IOException e) { |
34 | e.printStackTrace(); |
35 | } finally { |
36 | try { |
37 | // 释放资源 |
38 | if (httpClient != null) { |
39 | httpClient.close(); |
40 | } |
41 | if (response != null) { |
42 | response.close(); |
43 | } |
44 | } catch (IOException e) { |
45 | e.printStackTrace(); |
46 | } |
47 | } |
48 | |
49 | } |
发送流
1 | //发送流(示例) |
2 | public static void sendStream(){ |
3 | HttpPost httpPost = new HttpPost("http://localhost:12345/is?name=邓沙利文"); |
4 | CloseableHttpResponse response = null; |
5 | try { |
6 | InputStream is = new ByteArrayInputStream("流啊流~".getBytes()); |
7 | InputStreamEntity ise = new InputStreamEntity(is); |
8 | httpPost.setEntity(ise); |
9 | |
10 | response = httpClient.execute(httpPost); |
11 | HttpEntity responseEntity = response.getEntity(); |
12 | System.out.println("HTTPS响应状态为:" + response.getStatusLine()); |
13 | if (responseEntity != null) { |
14 | System.out.println("HTTPS响应内容长度为:" + responseEntity.getContentLength()); |
15 | // 主动设置编码,来防止响应乱码 |
16 | String responseStr = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); |
17 | System.out.println("HTTPS响应内容为:" + responseStr); |
18 | } |
19 | } catch (IOException e) { |
20 | e.printStackTrace(); |
21 | } finally { |
22 | try { |
23 | // 释放资源 |
24 | if (httpClient != null) { |
25 | httpClient.close(); |
26 | } |
27 | if (response != null) { |
28 | response.close(); |
29 | } |
30 | } catch (IOException e) { |
31 | e.printStackTrace(); |
32 | } |
33 | } |
34 | |
35 | } |
JDK 11
从Java 11开始,引入了新的HttpClient
,它使用链式调用的API,能大大简化HTTP的处理。
我们来看一下如何使用新版的HttpClient
。首先需要创建一个全局HttpClient
实例,因为HttpClient
内部使用线程池优化多个HTTP连接,可以复用:
1 | static HttpClient httpClient = HttpClient.newBuilder().build(); |
使用GET
请求获取文本内容代码如下:
1 | import java.net.URI; |
2 | import java.net.http.*; |
3 | import java.net.http.HttpClient.Version; |
4 | import java.time.Duration; |
5 | import java.util.*; |
6 | |
7 | public class Main { |
8 | // 全局HttpClient: |
9 | static HttpClient httpClient = HttpClient.newBuilder().build(); |
10 | |
11 | public static void main(String[] args) throws Exception { |
12 | String url = "https://www.sina.com.cn/"; |
13 | HttpRequest request = HttpRequest.newBuilder(new URI(url)) |
14 | // 设置Header: |
15 | .header("User-Agent", "Java HttpClient").header("Accept", "*/*") |
16 | // 设置超时: |
17 | .timeout(Duration.ofSeconds(5)) |
18 | // 设置版本: |
19 | .version(Version.HTTP_2).build(); |
20 | HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); |
21 | // HTTP允许重复的Header,因此一个Header可对应多个Value: |
22 | Map<String, List<String>> headers = response.headers().map(); |
23 | for (String header : headers.keySet()) { |
24 | System.out.println(header + ": " + headers.get(header).get(0)); |
25 | } |
26 | System.out.println(response.body().substring(0, 1024) + "..."); |
27 | } |
28 | } |
如果我们要获取图片这样的二进制内容,只需要把HttpResponse.BodyHandlers.ofString()
换成HttpResponse.BodyHandlers.ofByteArray()
,就可以获得一个HttpResponse<byte[]>
对象。如果响应的内容很大,不希望一次性全部加载到内存,可以使用HttpResponse.BodyHandlers.ofInputStream()
获取一个InputStream
流。
要使用POST
请求,我们要准备好发送的Body数据并正确设置Content-Type
:
1 | String url = "http://www.example.com/login"; |
2 | String body = "username=bob&password=123456"; |
3 | HttpRequest request = HttpRequest.newBuilder(new URI(url)) |
4 | // 设置Header: |
5 | .header("Accept", "*/*") |
6 | .header("Content-Type", "application/x-www-form-urlencoded") |
7 | // 设置超时: |
8 | .timeout(Duration.ofSeconds(5)) |
9 | // 设置版本: |
10 | .version(Version.HTTP_2) |
11 | // 使用POST并设置Body: |
12 | .POST(BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build(); |
13 | HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); |
14 | String s = response.body(); |
小结
- Java提供了
HttpClient
作为新的HTTP客户端编程接口用于取代老的HttpURLConnection
接口; - HttpClient
使用链式调用并通过内置的
BodyPublishers和
BodyHandlers`来更方便地处理数据。
参考
-