第一次接触HTTP协议是在2013年做校园论坛项目时,当时为了调试一个表单提交问题,不得不打开Chrome开发者工具查看网络请求。那个瞬间突然明白:原来浏览器和服务器之间的对话,都是通过这种明文的文本协议完成的。
HTTP(HyperText Transfer Protocol)本质上是一种应用层通信协议,它定义了客户端和服务器之间交换信息的格式和规则。就像两个说同一种语言的人才能顺畅交流,浏览器和Web服务器也必须遵循HTTP协议才能正确理解彼此的意图。
关键认知:HTTP是无状态的协议,这意味着服务器不会记住之前的请求信息。这种设计既简化了服务器实现,也为后续的会话管理机制(如Cookie)埋下了伏笔。
在JavaWeb开发中,我们最常接触的是HTTP/1.1版本。一个典型的HTTP交互包含四个阶段:
用Wireshark抓取一个访问百度首页的请求,原始报文如下:
code复制GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0
Accept: text/html
这个简单报文包含三个关键部分:
在Java中获取这些信息非常直观:
java复制// 获取请求方法
String method = request.getMethod();
// 获取请求头
String userAgent = request.getHeader("User-Agent");
// 获取查询参数
String name = request.getParameter("name");
除了常见的GET/POST,HTTP协议实际定义了8种方法:
| 方法 | 安全性 | 幂等性 | 典型场景 |
|---|---|---|---|
| GET | 是 | 是 | 获取资源 |
| POST | 否 | 否 | 创建资源/提交数据 |
| PUT | 否 | 是 | 完整更新资源 |
| PATCH | 否 | 否 | 部分更新资源 |
| DELETE | 否 | 是 | 删除资源 |
| HEAD | 是 | 是 | 获取响应头(不返回body) |
| OPTIONS | 是 | 是 | 查询服务器支持的方法 |
| TRACE | 是 | 是 | 回显请求(用于调试) |
开发陷阱:曾经在RESTful接口设计中误用POST替代PUT,导致前端无法正确实现乐观锁控制。方法语义的准确使用对API设计至关重要。
这些年在处理跨域问题时,深刻体会到请求头的重要性。几个关键头字段:
调试技巧:在Chrome开发者工具的Network面板中,勾选"Preserve log"可以保留重定向过程中的请求头信息,这对调试登录跳转等问题特别有用。
记得刚工作时把404和500搞混的尴尬经历。HTTP状态码实际上分为五个类别:
在JavaWeb中设置状态码:
java复制response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 404
response.sendError(500, "服务器内部错误");
几个必须掌握的响应头控制技巧:
java复制response.setHeader("Cache-Control", "no-cache");
java复制response.setHeader("Access-Control-Allow-Origin", "*");
java复制response.setContentType("application/json;charset=UTF-8");
根据不同的场景,响应体生成方式也不同:
java复制PrintWriter out = response.getWriter();
out.println("<html><body>Hello</body></html>");
java复制response.setContentType("application/json");
String json = "{\"name\":\"张三\"}";
response.getWriter().write(json);
java复制response.setHeader("Content-Disposition", "attachment; filename=test.txt");
Files.copy(Paths.get("/path/to/file"), response.getOutputStream());
Tomcat处理HTTP请求的完整流程:
性能提示:避免在Servlet中创建大量临时对象,因为每个请求都会创建新的Servlet实例(除非标记@WebServlet(urlPatterns="/", loadOnStartup=1))
过滤器(Filter)是处理HTTP报文的瑞士军刀:
java复制public class LogFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(req, res); // 继续执行过滤器链
long cost = System.currentTimeMillis() - start;
System.out.println("请求耗时:" + cost + "ms");
}
}
常见过滤器用途:
对于耗时操作,使用异步处理避免阻塞线程:
java复制@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext ctx = req.startAsync();
CompletableFuture.runAsync(() -> {
// 模拟耗时操作
try { Thread.sleep(1000); }
catch (InterruptedException e) {}
ctx.getResponse().getWriter().write("Done");
ctx.complete();
});
}
}
HTTP/1.1默认开启持久连接(Connection: keep-alive),但需要注意:
配置建议:
xml复制<!-- server.xml中的Connector配置 -->
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="500"
maxConnections="1000"
connectionTimeout="30000"/>
java复制// 生成Token并存入session
String token = UUID.randomUUID().toString();
request.getSession().setAttribute("csrfToken", token);
// 在表单中携带
<input type="hidden" name="csrfToken" value="${csrfToken}">
java复制// 使用ESAPI过滤
String safeInput = ESAPI.encoder().encodeForHTML(rawInput);
// 或者设置响应头
response.setHeader("X-XSS-Protection", "1; mode=block");
java复制// 在Filter中检查
if (!request.isSecure()) {
response.sendRedirect("https://" + request.getServerName() + request.getRequestURI());
}
关键监控点:
示例监控代码:
java复制public class MonitorFilter implements Filter {
private AtomicLong counter = new AtomicLong();
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
long start = System.nanoTime();
try {
chain.doFilter(req, res);
recordSuccess(System.nanoTime() - start);
} catch (Exception e) {
recordError();
}
}
private void recordSuccess(long nanos) {
// 记录到监控系统
}
}
虽然Tomcat 9+支持HTTP/2,但需要注意:
xml复制<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
sslImplementationName="org.apache.tomcat.util.net.openssl.OpenSSLImplementation">
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
</Connector>
Spring WebFlux的异步处理示例:
java复制@RestController
public class UserController {
@GetMapping("/users")
public Flux<User> listUsers() {
return userRepository.findAll();
}
}
与传统Servlet模型的对比:
在Kubernetes环境中,Istio等服务网格对HTTP协议的处理:
配置示例(DestinationRule):
yaml复制trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
outlierDetection:
consecutiveErrors: 5
interval: 10s
baseEjectionTime: 30s