什么是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请求,客户端都需要先创建一个新的TCP连接,然后,收到服务器响应后,关闭这个TCP连接。由于建立TCP连接就比较耗时,因此,为了提高效率,HTTP/1.1协议允许在一个TCP连接中反复发送-响应,这样就能大大提高效率:
┌─────────┐ ┌─────────┐ │░░░░░░░░░│ │O ░░░░░░░│ ├─────────┤ ├─────────┤ │░░░░░░░░░│ │ │ ├─────────┤ │ │ │░░░░░░░░░│ └─────────┘ └─────────┘ │ request 1 │ │─────────────────────>│ │ response 1 │ │<─────────────────────│ │ request 2 │ │─────────────────────>│ │ response 2 │ │<─────────────────────│ │ request 3 │ │─────────────────────>│ │ response 3 │ │<─────────────────────│ ▼ ▼
HTTP/2.0允许客户端在没有收到响应的时候,发送多个HTTP请求,服务器返回响应的时候,不一定按顺序返回,只要双方能识别出哪个响应对应哪个请求,就可以做到并行发送和接收:
┌─────────┐ ┌─────────┐ │░░░░░░░░░│ │O ░░░░░░░│ ├─────────┤ ├─────────┤ │░░░░░░░░░│ │ │ ├─────────┤ │ │ │░░░░░░░░░│ └─────────┘ └─────────┘ │ request 1 │ │─────────────────────>│ │ request 2 │ │─────────────────────>│ │ response 1 │ │<─────────────────────│ │ request 3 │ │─────────────────────>│ │ response 3 │ │<─────────────────────│ │ response 2 │ │<─────────────────────│ ▼ ▼
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
数据也十分简单。