HTTP编程
基础知识
什么是HTTP?HTTP就是目前使用最广泛的Web应用程序使用的基础协议,例如,浏览器访问网站,手机App访问后台服务器,都是通过HTTP协议实现的。
一个完整的HTTP请求-响应如下:
GET / HTTP/1.1
Host: www.sina.com.cn
User-Agent: Mozilla/5 MSIE
Accept: */* ┌────────┐
┌─────────┐ Accept-Language: zh-CN,en │░░░░░░░░│
│O ░░░░░░░│───────────────────────────>├────────┤
├─────────┤<───────────────────────────│░░░░░░░░│
│ │ HTTP/1.1 200 OK ├────────┤
│ │ Content-Type: text/html │░░░░░░░░│
└─────────┘ Content-Length: 133251 └────────┘
Browser <!DOCTYPE html> Server
<html><body>
<h1>Hello</h1>
...
- 如果是
GET
请求,那么该HTTP请求只有HTTP Header,没有HTTP Body。如果是POST
请求,那么该HTTP请求带有Body,以一个空行分隔。一个典型的带Body的HTTP请求如下:
POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
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
请求如下:
POST /login HTTP/1.1
Content-Type: application/json
Content-Length: 38
{"username":"bob","password":"123456"}
- HTTP/1.0协议
对于最早期的HTTP/1.0协议,每次发送一个HTTP请求,客户端都需要先创建一个新的TCP连接,然后,收到服务器响应后,关闭这个TCP连接。由于建立TCP连接就比较耗时,因此,为了提高效率,HTTP/1.1协议允许在一个TCP连接中反复发送-响应,这样就能大大提高效率:
┌─────────┐
┌─────────┐ │░░░░░░░░░│
│O ░░░░░░░│ ├─────────┤
├─────────┤ │░░░░░░░░░│
│ │ ├─────────┤
│ │ │░░░░░░░░░│
└─────────┘ └─────────┘
│ request 1 │
│─────────────────────>│
│ response 1 │
│<─────────────────────│
│ request 2 │
│─────────────────────>│
│ response 2 │
│<─────────────────────│
│ request 3 │
│─────────────────────>│
│ response 3 │
│<─────────────────────│
▼ ▼
- HTTP/2.0
HTTP/2.0允许客户端在没有收到响应的时候,发送多个HTTP请求,服务器返回响应的时候,不一定按顺序返回,只要双方能识别出哪个响应对应哪个请求,就可以做到并行发送和接收:
┌─────────┐
┌─────────┐ │░░░░░░░░░│
│O ░░░░░░░│ ├─────────┤
├─────────┤ │░░░░░░░░░│
│ │ ├─────────┤
│ │ │░░░░░░░░░│
└─────────┘ └─────────┘
│ request 1 │
│─────────────────────>│
│ request 2 │
│─────────────────────>│
│ response 1 │
│<─────────────────────│
│ request 3 │
│─────────────────────>│
│ response 3 │
│<─────────────────────│
│ response 2 │
│<─────────────────────│
▼ ▼
关于客户端的HTTP编程(和爬虫一个道理)
Java标准库提供了基于HTTP的包,但是要注意,早期的JDK版本是通过HttpURLConnection
访问HTTP,典型代码如下:
URL url = new URL("http://www.example.com/path/to/target?a=1&b=2");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setUseCaches(false);
conn.setConnectTimeout(5000); // 请求超时5秒
// 设置HTTP头:
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)");
// 连接并发送HTTP请求:
conn.connect();
// 判断HTTP响应是否200:
if (conn.getResponseCode() != 200) {
throw new RuntimeException("bad response");
}
// 获取所有响应Header:
Map<String, List<String>> map = conn.getHeaderFields();
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
// 获取响应内容:
InputStream input = conn.getInputStream();
...
上述代码编写比较繁琐,并且需要手动处理InputStream
,所以用起来很麻烦。
从Java 11开始,引入了新的HttpClient
,它使用链式调用的API,能大大简化HTTP的处理。
我们来看一下如何使用新版的HttpClient
。首先需要创建一个全局HttpClient
实例,因为HttpClient
内部使用线程池优化多个HTTP连接,可以复用:
static HttpClient httpClient = HttpClient.newBuilder().build();
使用GET
请求获取文本内容代码如下:
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Version;
import java.time.Duration;
import java.util.*;
public class Main {
// 全局HttpClient:
static HttpClient httpClient = HttpClient.newBuilder().build();
public static void main(String[] args) throws Exception {
String url = "https://www.sina.com.cn/";
HttpRequest request = HttpRequest.newBuilder(new URI(url))
// 设置Header:
.header("User-Agent", "Java HttpClient").header("Accept", "*/*")
// 设置超时:
.timeout(Duration.ofSeconds(5))
// 设置版本:
.version(Version.HTTP_2).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// HTTP允许重复的Header,因此一个Header可对应多个Value:
Map<String, List<String>> headers = response.headers().map();
for (String header : headers.keySet()) {
System.out.println(header + ": " + headers.get(header).get(0));
}
System.out.println(response.body().substring(0, 1024) + "...");
}
}
如果我们要获取图片这样的二进制内容,只需要把HttpResponse.BodyHandlers.ofString()
换成HttpResponse.BodyHandlers.ofByteArray()
,就可以获得一个HttpResponse<byte[]>
对象。如果响应的内容很大,不希望一次性全部加载到内存,可以使用HttpResponse.BodyHandlers.ofInputStream()
获取一个InputStream
流。
要使用POST
请求,我们要准备好发送的Body数据并正确设置Content-Type
:
String url = "http://www.example.com/login";
String body = "username=bob&password=123456";
HttpRequest request = HttpRequest.newBuilder(new URI(url))
// 设置Header:
.header("Accept", "*/*")
.header("Content-Type", "application/x-www-form-urlencoded")
// 设置超时:
.timeout(Duration.ofSeconds(5))
// 设置版本:
.version(Version.HTTP_2)
// 使用POST并设置Body:
.POST(BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String s = response.body();
可见发送POST
数据也十分简单。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/java/287667.html