http1.x VS http2.x 协议
目录
http1.x & http2.x
关键区别
详解
总结
java代码样例
1. 添加依赖 (pom.xml)
2. 生成 SSL 证书 (测试用)
3. 配置文件 (application.yml)
4. 服务端主类
5. HTTP/2 服务配置类
6. 客户端 (Java 11+ HttpClient)
7. 运行步骤
8. 关键点说明
http1.x & http2.x
关键区别
HTTP/1.1 和 HTTP/2 是 Web 通信协议的两个主要版本,HTTP/2 旨在解决 HTTP/1.1 的许多性能限制,以提供更快的网页加载体验。下面是它们的主要区别:
特性 | HTTP/1.1 (1997年至今) | HTTP/2 (2015年至今) | 对性能和效率的影响 |
数据传输模型 | 基于文本的协议(可读性好)。 | 二进制分帧层协议(效率更高)。 | 二进制协议解析更快、更紧凑、更健壮。 |
连接复用 | 需要多个TCP连接(通常6-8个/域名)并行请求。 | 单一持久连接 + 多路复用。 | 消除队头阻塞 → 减少延迟、提升并发能力。 |
头部压缩 | 冗余且未压缩。 | 使用HPACK压缩算法。 | 大幅减少头部开销(高达90%)→ 加快页面加载,对移动端友好。 |
服务器推送 | 无此功能。 | 服务端可主动推送资源到客户端缓存。 | 减少额外请求往返 → 提升页面渲染速度。需谨慎配置避免浪费带宽。 |
优先级 | 无显式优先级控制。 | 流可分配优先级和权重。 | 优化关键资源加载(如优先发送HTML/CSS)→ 提高感知性能。 |
安全加密 | 不强制HTTPS(可单独使用HTTP)。 | 虽未强制,但所有主流浏览器要求HTTPS(基于TLS)。 | 事实上的加密强制 → 提高安全性,但略微增加连接建立开销。 |
详解
- 二进制分帧层:
◦ HTTP/1.1:消息是纯文本格式(请求行、头部、正文)。人类可读,但解析效率低且易出错。◦ HTTP/2:在应用层(HTTP)和传输层(TCP)之间引入了一个二进制分帧层。HTTP消息(请求和响应)被分解为更小的、格式化的二进制帧(HEADERS
帧、DATA
帧等),然后发送。接收端再将帧重组为完整的消息。这使得解析更高效、更健壮,并为多路复用等特性奠定了基础。 - 多路复用:
◦ HTTP/1.1:
▪ 队头阻塞:在单个TCP连接上,请求必须按发出顺序得到响应。如果一个请求处理缓慢(如大文件下载),后面的所有请求都会被阻塞,即使它们处理的资源已经准备好了。▪ 连接数限制:浏览器为了解决队头阻塞和提升并行度,会为同一个域名打开多个TCP连接(通常是6-8个)。但这增加了服务器和网络资源消耗(内存、CPU、端口、慢启动、竞争带宽),并且在达到限制后仍可能排队。
◦ HTTP/2:
▪ 单一连接:仅需一个TCP连接。▪ 流与帧:HTTP/2引入了“流”的概念。每个请求/响应对在一个独立的流中进行。流被赋予唯一的ID。消息被拆分为帧后,不同流的帧可以在同一个TCP连接上交错发送和接收。▪ 消除应用层队头阻塞:即使一个流的响应被延迟(如等待数据库查询),该阻塞只影响这一个流,其他流的帧可以继续传输。因此,慢响应不会阻塞整个连接。这大大提高了连接的利用率和并发效率。
- 头部压缩:
◦ HTTP/1.1:头部(包含 cookie、User-Agent、Accept-* 等字段)在每个请求中重复发送,且未经压缩。头部大小可能超过几百字节甚至几千字节,造成显著开销,尤其对大量小文件请求影响很大。◦ HTTP/2:使用 HPACK 压缩算法。它利用:
▪ 静态哈夫曼编码:对头部字段值进行高效压缩。▪ 维护客户端和服务端共享的头部字段表:常用的头部字段(如`:method: GET`, `:path: /index.html`)可以只发送一个索引号代替完整字符串。即使是动态变化的头部(如 Cookie),也通过记录之前的头部来避免重复发送。▪ 这大大减少了头部开销(通常压缩率在 60%-90%),对降低延迟(尤其是高延迟网络)和提高带宽利用率非常有效。
- 服务器推送:
◦ HTTP/1.1:服务器只能被动响应客户端的请求。客户端必须解析 HTML,发现所需的资源(如 CSS、JS、图像),然后再发送新的请求去获取它们。这增加了额外的网络往返(RTT)。◦ HTTP/2:服务器可以在客户端明确请求之前,主动将资源“推送”给客户端。例如,当服务器收到对index.html
的请求时,它可以立即把styles.css
和script.js
一起推送过来(前提是服务器知道这些资源是后续渲染index.html
所必需的)。客户端可以将这些推送的资源存储到缓存中。当它后续需要这些资源时,缓存中已经有了,节省了请求时间。推送是提前获取资源的一种方法。 - 流优先级:
◦ HTTP/1.1:浏览器会尝试猜测哪些资源(如 CSS, JS)更重要并优先请求它们,但协议本身没有提供优先级机制。◦ HTTP/2:允许客户端在发起请求时为流指定优先级和权重(依赖关系树)。服务器可以利用这些信息,优先处理和传输优先级高的流的帧(如关键 CSS),然后再传输低优先级的流(如图像)。这有助于优化内容的渲染顺序,提升用户体验(感知性能)。 - 加密要求:
◦ HTTP/1.1:可以在 HTTP(明文)或 HTTPS(HTTP over TLS)上运行。◦ HTTP/2:规范本身并不强制要求使用 TLS。然而,在实践当中,所有主要的浏览器都只支持在 HTTPS(TLS)上运行 HTTP/2。这意味着部署 HTTP/2 几乎必然同时启用了 HTTPS。这既提高了安全性,也简化了协议部署(避免了中间代理修改流量带来的问题)。
总结
• HTTP/1.1 是一个成熟的、广泛部署的基础协议,但它的文本格式、队头阻塞和缺乏压缩等问题限制了其性能。
• HTTP/2 通过引入二进制分帧、强制多路复用、高效头部压缩、服务器推送和优先级控制,从根本上解决了 HTTP/1.1 的性能瓶颈。它在单一连接上实现高效并行,显著减少了延迟,提高了网络带宽的利用率,从而大大加快了现代复杂网页的加载速度。同时,主流浏览器要求在 HTTPS 上使用 HTTP/2,也提升了整体安全性。
迁移建议: 对绝大多数现代网站,使用 HTTPS 并提供 HTTP/2 支持是推荐的性能和安全最佳实践。不过需要确保你的服务器和 CDN 已正确配置支持 HTTP/2。
java代码样例
下面是一个完整的 HTTP/2 实现示例,包含 Spring Boot 2.x 服务端和使用 Java HttpClient 的客户端:
服务端 (Spring Boot 2.7.x)
1. 添加依赖 (pom.xml)
<dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.18</version></dependency><!-- HTTP/2 支持 --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.85</version></dependency><dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-coyote</artifactId><version>9.0.85</version></dependency>
</dependencies>
2. 生成 SSL 证书 (测试用)
keytool -genkeypair -alias http2-demo -keyalg RSA -keysize 2048 \-storetype PKCS12 -keystore http2-demo.p12 -validity 3650
# 密码: changeit
3. 配置文件 (application.yml)
server:port: 8443ssl:key-store: classpath:http2-demo.p12key-store-password: changeitkey-store-type: PKCS12key-alias: http2-demohttp2:enabled: truetomcat:max-threads: 200
4. 服务端主类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@SpringBootApplication
public class Http2ServerApplication {public static void main(String[] args) {SpringApplication.run(Http2ServerApplication.class, args);System.out.println("HTTP/2 Server running on https://localhost:8443");}@RestControllerstatic class Http2Controller {@GetMapping("/")public String home() {return "Welcome to HTTP/2 Server!";}@GetMapping("/data")public String getData() {return "HTTP/2 Response: " + System.currentTimeMillis();}@GetMapping("/push-resource")public String getPushResource() {return "Pushed Resource Content";}@GetMapping("/stream")public Flux<String> getStream() {return Flux.interval(Duration.ofMillis(500)).map(tick -> "Stream data #" + tick + " @ " + Instant.now());}}
}
5. HTTP/2 服务配置类
import org.apache.coyote.http2.Http2Protocol;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class Http2Config {@Beanpublic WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {return factory -> factory.addConnectorCustomizers(connector -> {connector.addUpgradeProtocol(new Http2Protocol());});}
}
6. 客户端 (Java 11+ HttpClient)
- HTTP/2 客户端代码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.net.ssl.SSLContext;public class Http2Client {private static final String SERVER_URL = "https://localhost:8443/data";public static void main(String[] args) throws Exception {// 1. 演示HTTP/2请求singleRequest();// 2. 演示HTTP/2并发请求concurrentRequests();// 3. 演示HTTP/2服务端推送testServerPush();// 4. 演示HTTP/2流式响应streamResponse();}private static void singleRequest() throws Exception {HttpClient client = createHttpClient();HttpRequest request = HttpRequest.newBuilder().uri(URI.create(SERVER_URL)).GET().timeout(Duration.ofSeconds(10)).build();HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println("\n=== 单次请求结果 ===");printResponse(response);}private static void concurrentRequests() {HttpClient client = createHttpClient();List<CompletableFuture<Void>> futures = new ArrayList<>();System.out.println("\n=== 并发请求 (10次) ===");for (int i = 0; i < 10; i++) {final int requestId = i + 1;CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {HttpRequest request = HttpRequest.newBuilder().uri(URI.create(SERVER_URL + "?req=" + requestId)).GET().build();HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.printf("[%s] 请求 %d 完成: %s%n", response.version(), requestId, response.body());} catch (Exception e) {e.printStackTrace();}});futures.add(future);}// 等待所有请求完成CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();System.out.println("所有并发请求完成");}private static void testServerPush() throws Exception {HttpClient client = createHttpClient();HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://localhost:8443")).GET().build();// 处理服务端推送CompletableFuture<Void> pushFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString(),// 推送处理器(initialRequest, pushRequest) -> {System.out.println("\n服务端推送资源: " + pushRequest.uri());return CompletableFuture.completedFuture(HttpResponse.BodyHandlers.ofString());}).thenApply(pushResponse -> {System.out.println("\n=== 服务端推送测试 ===");printResponse(pushResponse);return null;});pushFuture.join();}private static void streamResponse() throws Exception {HttpClient client = createHttpClient();HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://localhost:8443/stream")).GET().build();System.out.println("\n=== 流式响应 ===");client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()).thenAccept(response -> {response.body().limit(5).forEach(line -> System.out.println("收到流数据: " + line));}).join();}private static HttpClient createHttpClient() {return HttpClient.newBuilder().version(HttpClient.Version.HTTP_2) // 强制使用HTTP/2.sslContext(insecureContext()) // 忽略证书验证 (测试用).followRedirects(HttpClient.Redirect.NORMAL).build();}private static void printResponse(HttpResponse<?> response) {System.out.println("协议版本: " + response.version());System.out.println("状态码: " + response.statusCode());System.out.println("响应头: ");response.headers().map().forEach((k, v) -> System.out.println(" " + k + ": " + v));System.out.println("响应体: " + response.body());}// 创建不安全的SSL上下文 (仅用于测试!)private static SSLContext insecureContext() {try {java.security.KeyStore ks = java.security.KeyStore.getInstance("PKCS12");ks.load(null, null);javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("PKIX");tmf.init(ks);javax.net.ssl.TrustManager[] trustManagers = tmf.getTrustManagers();SSLContext sc = SSLContext.getInstance("TLS");sc.init(null, trustManagers, null);return sc;} catch (Exception e) {throw new RuntimeException("SSL上下文创建失败", e);}}
}
功能说明
服务端功能
- 基础端点:
/
,/data
返回简单响应 - 流式端点:
/stream
返回服务器推送事件流 - 支持HTTP/2服务端推送
- 使用Tomcat HTTP/2实现
客户端功能
singleRequest()
: 发送单个HTTP/2请求concurrentRequests()
: 发送10个并发请求(展示HTTP/2多路复用)testServerPush()
: 接收服务端推送资源streamResponse()
: 处理流式响应
7. 运行步骤
- 启动服务端
# 将生成的 http2-demo.p12 放入 resources 目录
mvn spring-boot:run
- 运行客户端
java Http2Client
预期输出
=== 单次请求结果 ===
协议版本: HTTP_2
状态码: 200
响应头: content-type: [text/plain;charset=UTF-8]date: [Thu, 01 Jan 2024 12:00:00 GMT]
响应体: HTTP/2 Response: 1700000000000=== 并发请求 (10次) ===
[HTTP_2] 请求 1 完成: HTTP/2 Response: 1700000000001
[HTTP_2] 请求 3 完成: HTTP/2 Response: 1700000000002
[HTTP_2] 请求 2 完成: HTTP/2 Response: 1700000000001
...
所有并发请求完成服务端推送资源: https://localhost:8443/push-resource=== 服务端推送测试 ===
协议版本: HTTP_2
状态码: 200
响应头: content-type: [text/plain;charset=UTF-8]
响应体: Welcome to HTTP/2 Server!=== 流式响应 ===
收到流数据: Stream data #0 @ 2024-01-01T12:00:00.123Z
收到流数据: Stream data #1 @ 2024-01-01T12:00:00.623Z
收到流数据: Stream data #2 @ 2024-01-01T12:00:01.123Z
8. 关键点说明
- 服务端配置
• 使用 Tomcat 9+ 内置 HTTP/2 支持• 必须启用 SSL/TLS 加密• 添加Http2Protocol
协议升级处理器 - 客户端特性
• 使用 Java 11+ 的HttpClient
• 处理服务端推送 (pushPromiseHandler
)• 接收流式响应 (BodyHandlers.ofLines()
)• 多路复用演示(10个并发请求共享单一连接) - 安全注意
• 生产环境应使用有效证书•insecureContext()
仅用于开发和测试• 正式环境应配置信任库和证书校验
此示例完整实现了 HTTP/2 的核心特性,包括二进制分帧、多路复用、服务端推送和流式响应,展示了与 HTTP/1.1 相比的性能优势。