1. 请求与响应基础概念解析
在JavaWeb开发中,HTTP请求与响应构成了客户端与服务器交互的基础范式。理解这个核心机制,是掌握Web开发的关键第一步。
HTTP请求(Request)是指客户端(通常是浏览器)向服务器发送的数据包,包含了用户想要执行的操作信息。典型的HTTP请求由三部分组成:
- 请求行:包含请求方法(GET/POST等)、URI和HTTP版本
- 请求头:包含客户端环境信息、缓存策略等元数据
- 请求体:可选部分,包含提交的表单数据等
HTTP响应(Response)则是服务器对请求的处理结果,同样由三部分构成:
- 状态行:包含HTTP版本、状态码和状态描述
- 响应头:包含服务器信息、内容类型等元数据
- 响应体:实际的返回内容(HTML、JSON等)
java复制// 一个简单的Servlet请求处理示例
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html");
// 获取请求参数
String username = request.getParameter("user");
// 构造响应内容
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>Hello " + username + "</h1>");
out.println("</body></html>");
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 请求对象(HttpServletRequest)深度解析
2.1 请求参数的获取方式
HttpServletRequest对象提供了多种获取请求参数的方法,每种方法适用于不同场景:
-
getParameter():获取单个参数值
- 适用于表单字段、URL查询参数
- 同名参数只返回第一个值
- 示例:
String name = request.getParameter("username");
-
getParameterValues():获取多值参数
- 适用于复选框等多选场景
- 返回String数组
- 示例:
String[] hobbies = request.getParameterValues("hobby");
-
getParameterMap():获取全部参数
- 返回Map<String, String[]>
- 适合需要批量处理参数的场景
重要提示:获取参数前应考虑字符编码问题。对于POST请求,应在第一次调用getParameter()前设置:
request.setCharacterEncoding("UTF-8");
2.2 请求头信息的处理
请求头包含了客户端环境的重要信息,常用方法包括:
- getHeader(String name):获取指定头信息
- getHeaderNames():获取所有头名称枚举
- getIntHeader(String name):获取整型头信息
典型应用场景:
java复制// 检查AJAX请求
boolean isAjax = "XMLHttpRequest".equals(
request.getHeader("X-Requested-With"));
// 获取客户端IP(考虑代理情况)
String ip = request.getHeader("X-Forwarded-For");
if(ip == null || ip.length() == 0) {
ip = request.getRemoteAddr();
}
2.3 请求作用域与属性操作
请求对象提供了属性存储机制,适用于请求转发场景:
- setAttribute(String name, Object value):设置属性
- getAttribute(String name):获取属性
- removeAttribute(String name):移除属性
典型应用模式:
java复制// 在ServletA中
User user = userService.findById(userId);
request.setAttribute("userInfo", user);
request.getRequestDispatcher("/servletB").forward(request, response);
// 在ServletB中
User user = (User)request.getAttribute("userInfo");
3. 响应对象(HttpServletResponse)高级特性
3.1 响应状态码控制
HTTP状态码是服务器向客户端表明请求处理结果的重要方式:
- setStatus(int sc):设置任意状态码
- sendError(int sc, String msg):发送错误状态及描述
- sendRedirect(String location):302重定向
正确使用状态码的实践:
java复制// 资源不存在
if(user == null) {
response.sendError(404, "User not found");
return;
}
// 操作成功但无内容返回
response.setStatus(204);
3.2 响应头的高级应用
响应头可以控制缓存、内容协商等高级特性:
java复制// 禁用缓存
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// 文件下载设置
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=\"report.pdf\"");
3.3 响应输出流的选择
根据响应内容类型,可选择不同的输出流:
-
字符流(PrintWriter):
- 适合文本内容(HTML/JSON/XML)
- 需先设置contentType
- 示例:
java复制response.setContentType("application/json"); PrintWriter out = response.getWriter(); out.print("{\"status\":\"success\"}");
-
字节流(ServletOutputStream):
- 适合二进制数据(图片/文件)
- 示例:
java复制response.setContentType("image/png"); ServletOutputStream out = response.getOutputStream(); out.write(imageBytes);
注意:同一个响应中不能同时使用两种输出流,否则会抛出IllegalStateException
4. 请求转发与重定向的深度对比
4.1 请求转发(Forward)
特点:
- 服务器内部跳转
- 客户端URL不变
- 共享同一个request对象
- 只能跳转到同一应用内的资源
实现方式:
java复制request.getRequestDispatcher("/target.jsp").forward(request, response);
适用场景:
- MVC模式中的视图渲染
- 需要共享请求属性的处理链
4.2 重定向(Redirect)
特点:
- 客户端二次请求
- URL变为目标地址
- 不共享request对象
- 可以跳转到任意URL
实现方式:
java复制response.sendRedirect("http://example.com/newpage");
底层原理:
- 服务器返回302状态码和Location头
- 浏览器自动发起新请求到Location指定地址
适用场景:
- 表单提交后的防重复提交
- 登录后跳转回原页面
- 跨应用的页面跳转
4.3 选择策略对比表
| 特性 | 请求转发 | 重定向 |
|---|---|---|
| 请求次数 | 1次 | 2次 |
| URL变化 | 不变 | 变化 |
| 数据共享 | 共享request | 不共享 |
| 目标限制 | 只能应用内 | 可以跨域 |
| 浏览器感知 | 不可见 | 可见 |
| 性能 | 更高 | 较低 |
| 典型场景 | MVC视图渲染 | 登录后跳转 |
5. 中文乱码问题全解决方案
5.1 GET请求乱码处理
问题根源:Tomcat默认使用ISO-8859-1解码URL参数
解决方案:
- 修改Tomcat配置(server.xml):
xml复制<Connector URIEncoding="UTF-8" .../> - 代码层面转换:
java复制String name = new String(request.getParameter("name") .getBytes("ISO-8859-1"), "UTF-8");
5.2 POST请求乱码处理
解决方案:
java复制// 必须在第一次调用getParameter前设置
request.setCharacterEncoding("UTF-8");
5.3 响应乱码处理
完整解决方案:
java复制// 设置响应编码(必须在getWriter之前)
response.setContentType("text/html;charset=UTF-8");
// 或者分别设置
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
5.4 数据库乱码预防
确保数据库连接指定编码:
java复制// JDBC URL示例
String url = "jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8";
6. 文件上传下载实战
6.1 文件上传实现
使用Apache Commons FileUpload组件:
-
添加依赖:
xml复制<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> -
处理代码:
java复制// 检查是否为multipart请求 if(ServletFileUpload.isMultipartContent(request)) { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); for(FileItem item : items) { if(!item.isFormField()) { // 文件字段 String fileName = item.getName(); InputStream fileContent = item.getInputStream(); // 保存文件... } } }
6.2 文件下载实现
关键点:
- 设置正确的Content-Type
- 添加Content-Disposition头
- 使用缓冲流提高性能
示例代码:
java复制response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + fileName + "\"");
try(InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
7. 请求响应性能优化技巧
7.1 响应压缩
启用GZIP压缩减少传输量:
java复制String acceptEncoding = request.getHeader("Accept-Encoding");
if(acceptEncoding != null && acceptEncoding.contains("gzip")) {
response.setHeader("Content-Encoding", "gzip");
GZIPOutputStream gzipOut = new GZIPOutputStream(response.getOutputStream());
// 使用gzipOut输出内容...
}
7.2 合理使用缓存
静态资源缓存策略:
java复制// 设置1小时缓存
response.setHeader("Cache-Control", "public, max-age=3600");
response.setDateHeader("Expires",
System.currentTimeMillis() + 3600 * 1000);
7.3 批量处理优化
减少IO操作的技巧:
java复制// 避免多次调用getWriter()
PrintWriter out = response.getWriter();
out.write("大量内容...");
// 而不是多次out.print()
// 二进制数据使用缓冲
byte[] buffer = new byte[8192];
int count;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
8. 安全防护最佳实践
8.1 XSS防护
输出编码防御跨站脚本:
java复制import org.apache.commons.text.StringEscapeUtils;
String userInput = request.getParameter("comment");
// HTML转义
String safeOutput = StringEscapeUtils.escapeHtml4(userInput);
response.getWriter().write(safeOutput);
8.2 CSRF防护
添加CSRF Token:
java复制// 生成Token
String csrfToken = UUID.randomUUID().toString();
request.getSession().setAttribute("csrfToken", csrfToken);
// 在表单中添加
<input type="hidden" name="csrfToken" value="${csrfToken}">
// 验证Token
String sessionToken = (String)request.getSession()
.getAttribute("csrfToken");
String requestToken = request.getParameter("csrfToken");
if(!sessionToken.equals(requestToken)) {
response.sendError(403, "Invalid CSRF Token");
return;
}
8.3 点击劫持防护
设置X-Frame-Options头:
java复制response.setHeader("X-Frame-Options", "SAMEORIGIN");
