ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb开发必懂的HTTP协议:从请求报文到实战排查

JavaWeb开发必懂的HTTP协议:从请求报文到实战排查 开头部分直接用Java Web开发者的视角切入讲清楚HTTP协议在Web开发里到底处于什么位置再引出这篇博文能帮你解决什么、适合谁读。下面就是正文。1. 先把HTTP协议在JavaWeb里的位置摆正做JavaWeb开发也快十年了带过的新人少说也有几十个。我发现一个特别普遍的现象很多人一上来就急着学Spring、学MyBatis写出来的东西也能跑、页面也能出来但一遇到异常就懵了。比如请求参数明明传了怎么后端拿不到、乱码从哪来的、明明跳转了URL却不对、Session怎么老失效。这些问题扒开一看全是对HTTP协议理解不够透。HTTP协议在整个JavaWeb里就像地基。你盖楼用的Spring MVC、Spring Boot、JSP、Servlet统统是建在这一层之上的框架。框架帮你屏蔽了底层细节但不代表你可以不懂——恰恰相反框架报错时抛出的异常信息往往就是在描述HTTP层的某个环节出了问题。你要是听不懂两眼一抹黑。这篇博文我就打算把HTTP协议这条线完整捋一遍从协议本身的原理到它在JavaWeb具体项目里怎么落地再到实战案例和踩坑记录一路写到进阶技巧。适合三类读者刚学完JavaSE准备进入Web阶段的新手需要系统复习HTTP相关知识的在校生以及工作中遇到HTTP相关问题想要查漏补缺的开发人员。2. HTTP协议最容易被忽视的核心逻辑2.1 一句话说清HTTP在干什么HTTP的全称是HyperText Transfer Protocol超文本传输协议。它在整个网络模型中属于应用层想象一下两个人对话一个说中文一个说外语谁也听不懂谁。HTTP就是浏览器和服务器之间约定好的“通用语言”双方都按这套语法来说话对方才能明白。这套对话基本是同步的、你来我往的。浏览器先开口发出一段“请求”服务器收到后根据请求内容做处理再回一段“响应”。说完这轮这轮对话就结束了。注意HTTP协议本身是无状态的——服务器不会天然记住上一次和这个浏览器聊过什么。你登录了一次刷新页面后服务器并不知道你是谁所以才有了后来的Cookie、Session这些补丁方案。理解了这个底层设计才能明白为什么JavaWeb里要花那么大功夫做会话管理。2.2 请求报文和响应报文的完整结构初学者最该练的基本功就是能手写出请求和响应报文的完整结构。你不需要背但看到要认识知道每一行代表什么。我直接展示一个典型的HTTP请求报文POST /login HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept: text/html,application/xhtmlxml Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 38 usernameadminpassword123456rememberon它分三个段落第一行是请求行包含请求方法、请求URI和协议版本接下来是请求头用“键: 值”的形式一组一组排开空行之后是请求体存放页面提交的数据。POST方法的数据放在请求体里GET方法的参数则是拼接在URL的?后面请求体为空。响应报文的结构也差不多但第一行换成了状态行格式是“HTTP/1.1 200 OK”后面的200是状态码OK是短语。接着是响应头再到空行最后是响应体——就是服务器返回给浏览器的实际内容可能是HTML页面、图片、JSON字符串等。平时用浏览器开发者工具F12看到的Network面板已经把请求和响应都解析成人类可读的表格了。但建议初学者做一件事在Network里找到任意一个请求点开“View source”选项亲眼看一下原始的请求报文长什么样。看完几次你对HTTP的理解会直接上一个台阶。2.3 请求方法选不对代码写再多也白费HTTP协议定义了一组请求方法JavaWeb开发中接触最多的就是GET和POST。很多人对这两个方法的理解停留在“GET把参数放URL里POST把参数放请求体里”这确实是最直观的区别但远远不够。GET是幂等的、安全的意思是它应该只用于获取资源不应该对服务器数据产生副作用。你刷新一个GET请求一百次结果应该是一样。POST则相反它专门用于提交数据、创建资源——注册用户、发帖、下单这些操作都有副作用必须用POST。不遵守这个约定后果不只是语义错乱。比如一个删除用户的接口用GET实现搜索引擎的爬虫路过这个URL时顺手就把数据删了——这类事故在真实生产环境里发生过不止一次。从入门到进阶常用的方法就这几个GET查询资源参数拼URL可缓存可收藏。POST新增资源或提交表单参数放请求体。PUT整体更新资源幂等。DELETE删除资源。PATCH局部更新资源。真正到了写接口的时候你会发现用POST还是PUT用PUT还是PATCH很多团队内部都有争议。我的习惯很简单拿不准就选POST正确性永远是第一位的语义规范可以等团队形成共识后再统一——但GET做修改、DELETE还是别用它去查数据。2.4 状态码服务器想告诉你的事全在这三位数字里状态码是响应报文里最核心的信息服务端处理结果如何全藏在123这三位数字里。JavaWeb入门阶段只要你见过下面这些基本就够覆盖90%的场景。状态码含义JavaWeb常见触发场景200 OK请求成功页面正常访问、AJAX正常返回302 Found临时重定向sendRedirect()触发跳转304 Not Modified资源未修改走缓存浏览器缓存机制生效400 Bad Request请求参数格式错误参数缺失或类型不匹配401 Unauthorized未认证未登录访问需要权限的资源403 Forbidden被禁止访问权限不足404 Not Found资源不存在访问了不存在的Servlet或页面405 Method Not Allowed请求方法不符只写了doPost却用GET访问500 Internal Server Error服务器内部异常Java代码抛出异常未处理502 Bad Gateway网关错误Nginx代理的后端挂了这里我特别提一下302和404这两个容易迷惑的。302是重定向服务器返回这个状态码时同时会在响应头里的Location字段写上目标地址浏览器看到后自动发起第二次请求。404不见得是文件真的不存在更常见的是你请求的Servlet路径配置错了或者URL写错了一个字母。排查这类问题第一件事永远是打开Network面板看状态码。3. HTTP协议在JavaWeb项目里的落地细节3.1 Servlet容器与HTTP协议的关系JavaWeb里头真正和HTTP协议直接打交道的是Servlet容器比如Tomcat。我们自己写的Servlet类本质上就是在处理HTTP请求和响应。容器做了这么一件事把浏览器发过来的原始HTTP报文解析成一个Java对象——HttpServletRequest然后创建一个HttpServletResponse对象来代表响应接着调用你的Servlet方法。市面上的Servlet类大多继承自HttpServlet这个父类已经帮你按请求方法做了分发。你只需要重写doGet()方法处理GET请求重写doPost()方法处理POST请求。如果某个方法没重写父类默认返回405错误——这就是前面表格里405的来历。有个小细节值得留意HttpServlet的service()方法里会根据请求方法再去调用对应的doXxx()方法但这个分发的逻辑是在service方法内完成的。你如果重写了service()方法却忘了调super.service()那么doGet、doPost就全部失效了。这个坑我见过不止一个新手踩过排错半天最后发现是service方法没放行。3.2 请求对象里到底藏了哪些可用信息HttpServletRequest里能拿到的信息相当丰富但入门阶段大多数人只知道getParameter()这一个方法。实际上你可以通过这些方法获取完整的HTTP请求信息// 获取请求行信息 String method request.getMethod(); // 请求方法 GET/POST String uri request.getRequestURI(); // /login StringBuffer url request.getRequestURL(); // http://localhost:8080/login String protocol request.getProtocol(); // HTTP/1.1 // 获取请求头 String userAgent request.getHeader(User-Agent); String referer request.getHeader(Referer); String host request.getHeader(Host); // 获取参数 String username request.getParameter(username); String[] hobbies request.getParameterValues(hobby); MapString, String[] paramMap request.getParameterMap();这里有几个关键点。第一getParameter()返回String类型前端传的number类型数字拿到后端也是字符串需要自己转型这是新手最容易忽略的处理。第二getParameterValues()用于获取多个同名参数——比如复选框提交的多个兴趣标签用getParameter()只会拿到第一个。第三getParameterMap()能拿到全部参数的Map集合调试时打印一下比猜前端到底传了什么高效十倍。还有两个重定向相关的细节经常考到request.getRequestDispatcher(/index.jsp).forward(...) 这是转发浏览器地址栏不变服务器内部把请求转到另一个资源response.sendRedirect(/index.jsp) 这是重定向服务器返回302浏览器重新发一个新的请求。前者是一次请求后者是两次请求。这个区别后面Session、域对象的学习里会不断遇到。3.3 响应对象如何控制浏览器行为HttpServletResponse控制的是服务器返回给浏览器的内容和行为核心操作就三类设置响应头、设置状态码、输出响应体。// 设置内容类型和编码这是乱码问题的关键 response.setContentType(text/html;charsetUTF-8); // 自定义响应头 response.setHeader(Refresh, 3;url/index.html); // 3秒后跳转 // 重定向等价于状态码302 Location头 response.sendRedirect(/login.jsp); // 设置状态码 response.setStatus(404); // 获取输出流写响应体 PrintWriter writer response.getWriter(); writer.write(h1Hello HTTP/h1);重点提一下字符编码。setContentType(text/html;charsetUTF-8)必须在getWriter()之前调用否则响应头已经生成了再设置也没用。这个顺序问题一直是期末作业和面试里最高频的考点。另一个是getWriter()和getOutputStream()不能同时调用只能用其中一个同时调用会抛IllegalStateException。3.4 Cookie和Session给无状态的HTTP补上记忆HTTP是无状态的但业务有状态。你登录购物网站加入购物车的商品不能因为刷新就消失。解决办法就是Cookie和Session。这俩名字谁都会说但原理要说清楚。Cookie是一段由服务器生成、存储在浏览器本地的小文本。服务器通过响应头Set-Cookie把数据发给浏览器浏览器按规则存下来之后每次请求同一个域名时自动在请求头里带上Cookie字段。Session则是服务器端的内存对象。服务器首次收到请求时会创建一个HttpSession对象并用一个唯一的ID标识它然后把这个ID通过Cookie下发给浏览器——默认这个Cookie的名字叫JSESSIONID。之后浏览器再请求时服务器通过JSESSIONID找到对应的Session对象从而实现“记住你是谁”。实际操作中实现登录状态的代码无非就是两件事// 登录成功后把用户信息塞进Session HttpSession session request.getSession(); session.setAttribute(loginUser, user); // 后续请求里取出来校验 HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(/login.jsp); return; }有一个细节很多人不理解request.getSession()参数不传时如果服务器上没有对应的Session会新创建一个。如果你要校验用户是否已登录一定要用getSession(false)这样已登录就返回原有Session没登录就返回null方便你判断并跳转登录页。否则你每次校验都会新建一个啥内容都没有的Session永远走不到登录逻辑里去。3.5 JSP里和HTTP直接相关的几个对象很多学校教JavaWeb都会走到JSP这一步。JSP内置了九个对象其中有几个和HTTP协议直接绑定。request、response这两个不用多说本质上就是当前请求和响应的对象只要有请求发生它们在JSP里就已经存在了。session也是JSP内置对象前提是当前请求开启了Session管理默认是开启的。JSP页面里最常见的HTTP相关操作就是通过request拿参数、通过session拿登录信息然后拼接成动态页面% String username (String) session.getAttribute(loginUser); if (username null) { response.sendRedirect(/login.jsp); } % h1欢迎, % username ! null ? username : 游客 %!/h1不过说句实在话这种直接在JSP里写Java代码的写法在现如今的商业项目里已经很少见了。JSP更适合作为入学阶段理解“动态网页”概念的入门载体真正到了项目实战前端模板引擎、Vue/React 后端API的分离开发才是主流。但不管前端技术怎么换后端和前端之间通信走的一定还是HTTP协议这一点永远躲不开。4. 完整项目案例手写一个带MySQL的登录注册模块4.1 用IDEA 2026创建JavaWeb项目的前期准备这里我以IDEA 2026版本为例说一下当前环境下创建JavaWeb项目的流程。第一步是检查本机环境JDK建议装17或21Maven用3.8以上版本Tomcat用10.x版本。注意Tomcat 10之后Servlet包名从javax.servlet迁移到了jakarta.servlet导入包时的区别就能让不少老教程失效新项目直接选Tomcat 10以上版本就好。IDEA里创建项目推荐直接创建Maven项目然后在pom.xml里打开war打包插件这样后续加依赖会方便很多。网上很多教程让你创建Java Enterprise项目那个IDEA会帮你生成一堆配置新手反而容易看不懂。Maven项目一切配置都显式写在pom.xml和web.xml里出了问题看得见、好查。pom.xml的最小配置如下project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdjavaweb-http-demo/artifactId version1.0-SNAPSHOT/version packagingwar/packaging dependencies !-- Tomcat 10 使用 jakarta.servlet API -- dependency groupIdjakarta.servlet/groupId artifactIdjakarta.servlet-api/artifactId version6.0.0/version scopeprovided/scope /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency !-- JSTL 标签库JSP里用 -- dependency groupIdjakarta.servlet.jsp.jstl/groupId artifactIdjakarta.servlet.jsp.jstl-api/artifactId version3.0.0/version /dependency /dependencies build finalNamehttp-demo/finalName plugins plugin groupIdorg.apache.tomcat.maven/groupId artifactIdtomcat7-maven-plugin/artifactId version2.2/version configuration port8080/port path//path /configuration /plugin /plugins /build /projectpom.xml配置好之后还需要在src/main/webapp/WEB-INF下面放一个web.xmlServlet 4.0以后的web.xml版本号写4.0或直接配成metadata-completetrue都行目的是让容器能加载web应用的部署描述符。实际上Tomcat 10里如果你用注解方式配置Servletweb.xml甚至可以只留一个空壳子。4.2 数据库设计一张最简单的用户表登录注册模块不需要复杂表设计一张用户表就够。这里给一个建表SQL字段不贪多够用就好CREATE DATABASE javaweb_http_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE javaweb_http_demo; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码(密文存储), email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;建表时唯一的看点在于字符集。utf8mb4和utf8的区别就是utf8mb4支持存emoji表情和四字节中文生僻字。密码字段无论是明文还是密文长度都直接给100方便以后接加盐哈希。这张表后续所有HTTP请求的处理都以它为依据。4.3 JDBC连接MySQL手动实现数据访问先来一个最基础的JDBC工具类用这个工具类组装连接信息后续操作都基于它import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/javaweb_http_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL上那三个参数务必保留useUnicodetrue表示启用Unicode字符支持characterEncodingutf8强制使用UTF-8编码传输中文serverTimezone指定服务器时区不然8.0版本的驱动会报时区错误。这三个参数是JavaWeb连接MySQL中文乱码的直接对策之一。下面的注册逻辑演示了完整的数据校验流程注意看每个环节怎么和HTTP请求关联WebServlet(/register) public class RegisterServlet extends HttpServlet { private UserDAO userDAO new UserDAO(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取前端通过HTTP POST请求体提交的数据 String username request.getParameter(username); String password request.getParameter(password); String confirmPassword request.getParameter(confirmPassword); String email request.getParameter(email); // 2. 响应统一设置为UTF-8避免中文乱码 response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); // 3. 基础校验 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { out.write(scriptalert(用户名和密码不能为空);history.back();/script); return; } if (!password.equals(confirmPassword)) { out.write(scriptalert(两次密码不一致);history.back();/script); return; } // 4. 调用DAO层访问数据库 boolean success userDAO.insertUser(username, password, email); if (success) { // 注册成功后重定向到登录页 response.sendRedirect(/login.jsp); } else { out.write(scriptalert(用户名已存在);history.back();/script); } } }这一步的操作流程完整走了一遍“接收HTTP请求参数 → 做业务校验 → 访问MySQL → 返回HTTP跳转或提示”的闭环。注意注册成功后用的是sendRedirect而不是转发——重定向到登录页后地址栏会变刷新也不会重复提交表单防止产生重复注册数据。4.4 登录逻辑从校验到Session的完整闭环登录的流程涉及查询数据库 校验密码 记录登录状态 跳转四个环节核心逻辑如下WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDAO userDAO new UserDAO(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); // 查询数据库 User user userDAO.findByUsername(username); if (user null || !user.getPassword().equals(password)) { out.write(scriptalert(用户名或密码错误);history.back();/script); return; } // 登录成功把用户信息存入Session HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟无操作自动失效 // 重定向到主页 response.sendRedirect(/index.jsp); } }这里把登录状态放进Session是JavaWeb项目最经典的做法。setMaxInactiveInterval(30 * 60)设置的是Session的空闲过期时间单位秒。注意不是“总时长30分钟”而是“30分钟内没有请求就销毁”有请求访问就重新倒计时。这是面试里容易被绕进去的点。front登录后访问受限资源的校验前面3.4里已经给过一段代码它可以封装成一个Filter统一处理避免每个页面都写一遍判断。Filter在HTTP层面拦截请求、做统一处理正好契合HTTP协议的请求-响应模型。4.5 用户列表接口后端向前端返回JSON数据纯JSP页面时代已经过去了现在前后端分离的项目里后端返回的往往是JSON而不是HTML。用Servlet写一个返回JSON格式用户列表的接口非常容易WebServlet(/api/users) public class UserListApiServlet extends HttpServlet { private UserDAO userDAO new UserDAO(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应类型为JSON response.setContentType(application/json;charsetUTF-8); ListUser users userDAO.findAll(); // 手动拼JSON字符串小型demo够用 StringBuilder json new StringBuilder(); json.append([); for (int i 0; i users.size(); i) { if (i 0) json.append(,); User u users.get(i); json.append({\id\:).append(u.getId()) .append(,\username\:\).append(u.getUsername()) .append(\,\email\:\).append(u.getEmail() null ? : u.getEmail()) .append(\}); } json.append(]); response.getWriter().write(json.toString()); } }这里最关键的一行代码是response.setContentType(application/json;charsetUTF-8)它告诉浏览器“我回给你的内容不是HTML是JSON”。浏览器和AJAX拿到响应后才知道该怎么解析。之前用text/html时后端返回JSON字符串给前端前端拿responseText解析也会报错原因就在响应头的内容类型不匹配。这类问题在生产环境里非常常见排查思路永远是先去Network面板看响应头Content-Type是什么、响应体长什么样子。配套的前端AJAX调用也一并给出来用原生的fetch不需要引库fetch(/api/users) .then(response { if (!response.ok) { throw new Error(HTTP状态码: response.status); } return response.json(); }) .then(data { console.log(获取到用户列表:, data); }) .catch(error { console.error(请求失败:, error); });你没看错前端fetch的.then()里第一个拿到的是Response对象必须手动调用.json()方法才能拿到数据。这个Response对象就是HTTP响应报文在JavaScript里的映射它有status、statusText、headers、ok这些属性对应着HTTP状态码和响应头。学到这里前端和后端在你的认知里就打通了——它们之间没有魔法就是你来我往的HTTP报文。4.6 配置Tomcat启动与运行参数项目写好后在IDEA里运行起来的配置也很关键。打开Run/Debug Configurations点左上角加号选“Tomcat Server → Local”。有几个参数每次新建都要检查一遍URL设置成http://localhost:8080/和Deployment里的Application context保持一致。Deployment页面点加号Artifact选“http-demo:war exploded”。选exploded模式是为了支持IDEA热部署改完代码后不用重启Tomcat刷新页面就能看到效果。Server页面勾选“After launch”和“Open browser”启动后自动打开浏览器。On frame deactivation选“Update classes and resources”或“Redeploy”这样从IDEA切回浏览器时改动过的内容能自动同步到Tomcat。上面的配置都设置好以后直接点运行IDEA会启动Tomcat并且部署这个war包。如果看到控制台输出“Artifact is deployed successfully”这样的日志就说明部署成功了。浏览器访问就能看到登录注册页面。5. HTTP相关高频坑位与排查方案5.1 中文乱码的三处战场逐一击破中文乱码是JavaWeb排在最前面的头号问题。网上讲乱码的文章一堆但真正能说清楚哪里要设置的没几个。我总结一下乱码只有一个根本原因数据在传输过程中发送方和接收方用的字符编码不一致。HTTP传输的是字节流浏览器用UTF-8编码的字节服务器却按ISO-8859-1解码出来的就是乱码。针对不同环节解决方法也不同。第一是POST请求体乱码Tomcat处理POST请求体时默认用ISO-8859-1解码所以要在取参数前设置request.setCharacterEncoding(UTF-8);这段代码必须放在第一次调用request.getParameter()之前否则已经解析过了再设置就没用。第二是响应输出乱码前面写过的response.setContentType(text/html;charsetUTF-8)必须在getWriter()之前调用。第三是GET请求URL参数乱码Tomcat对URL里的参数编码处理是另外一套配置9.0及以后版本需要在server.xml的Connector节点上加一个参数Connector port8080 URIEncodingUTF-8 /这一步改完要重启Tomcat才生效。IDE自带的Tomcat实例配置文件在tomcat安装目录的conf/server.xml里。5.2 随手就能用的HTTP调试利器排查HTTP问题光看代码是不够的必须学会用好HTTP调试工具。这里按上手难度说三款。第一款是浏览器开发者工具那是人人都有的。Network面板按F5刷新后能看到所有请求点开任意一条就能看请求头、响应头、状态码、耗时。页面乱码了、接口报404了、状态码不对了这里全能看到。第二款是Postman适合单独测后端接口。不用打开浏览器、不用登录页面直接构造GET、POST、PUT、DELETE请求各种状态码、响应时间、返回数据一目了然后端接口调试强烈推荐。第三款是IDEA自带HTTP Client如果你不想额外装软件在IDEA里写一个简易的请求配置文件直接点按钮发请求即可。我建议所有JavaWeb入门者养成一个习惯拿到任何报错先打开Network面板定位这个请求到底发生了什么再去看代码。这比在代码里Println然后瞎猜效率高得多。十年前的开发环境没有这些工具排错全靠看日志现在工具这么方便再傻呼呼地盯着错误提示看半天太浪费了。5.3 常见HTTP状态码问题的定位速查把经典的高频问题整理成一张速查表按状态码维度来排查比从头看日志快得多。表现可能原因排查步骤页面显示404Servlet路径变了、web.xml没配置好、项目没部署成功检查注解WebServlet的路径、检查URL拼写、看IDEA部署日志请求返回405用POST访问了只重写doGet的Servlet、反之亦然检查HttpServlet的子类重写了哪个doXxx方法500错误Java代码运行期异常、空指针、类型转换失败去IDEA控制台看堆栈信息报错行号直接定位302死循环登录拦截逻辑有漏洞未登录就重定向登录页登录后又重定向回去检查Filter或拦截器里有没有放行登录请求、静态资源200但数据不对响应正常返回但内容不是预期数据对比请求参数和数据库数据确认是不是缓存问题刷新页面重复提交表单提交用了转发而不是重定向提交成功后改用response.sendRedirect()刷新页面导致重复提交这个问题在开发中太常见了。POST请求提交表单后直接转发到成功页面浏览器地址栏还是刚才那个POST的URL按F5就会重新发一遍POST导致重复插入数据。改成重定向之后浏览器地址栏变成新的GET地址刷新就安全了——这就是Post/Redirect/Get模式虽然名字洋气原理说白了就这一句。5.4 GET和POST的编码差异要当心POST请求的中文参数在服务端用request.setCharacterEncoding(UTF-8)之后就解决了。但GET请求的参数放在URL里Tomcat解析URL用的是URIEncoding这个很多人不知道。前面给过server.xml配置URIEncodingUTF-8的办法。Tomcat 9.0版本以后默认改了UTF-8但如果是旧版本或者改过配置还是要检查一下。更隐蔽的是Ajax里的GET请求中文编码问题。浏览器对URL里的中文会自动做percent-encoding百分号编码也就是把汉字编码成%XX这样的形式。后端如果没有正确解码拿到的就是你根本看不懂的乱码。遇到这种情况除了检查Tomcat的URIEncoding还可以用JavaScript的encodeURIComponent()手动对参数编码后端用URLDecoder.decode(param, UTF-8)解码双保险。不过现代框架都自动处理了实际遇到的情况是旧的servlet项目。还有一个细节HTTP头里不能写中文。Cookie里存中文值、自定义响应头里放中文都会直接抛异常——这就是HTTP协议头部必须是ASCII字符的规定。解决办法是对中文做一次URL编码再放入Cookie或请求头读取时再解码。这个规定让很多初学JavaWeb的同学一脸懵但知道了以后就再也不会踩了。6. 从入门走向进阶的学习路线建议最后一部分聊点学习方法论。HTTP协议在大学课程里可能就是三五节课的内容但它在整个JavaWeb知识体系里的地位怎么高估都不为过。讲一个我自己的体会真正理解HTTP协议的标志是你给一个新手讲问题时能够按照“请求是从哪发起的、经过了谁、最终怎么被处理的”这条链路去描述。而不是一上来就说这是Spring的bug、是Tomcat的问题。进阶方向可以按下面几条线去拓展。第一深入学习Servlet生命周期与Listener、Filter的原理理解容器是怎么加载Servlet、请求怎么被Filter链过滤的。第二学习HTTP缓存机制Cache-Control、ETag、Last-Modified这是优化网站性能的重要一环同一份静态资源怎么让浏览器走缓存、不带请求到服务端全看这些响应头。第三学习RESTful API设计规范把前面学过的请求方法、状态码合理用到实际接口设计里。第四了解HTTPS协议理解HTTP明文传输的不安全性对称加密和非对称加密在HTTPS里是怎么配合的。第五走框架路线时带上HTTP的视角去看Spring MVCDispatcherServlet的本质就是Servlet接口的实现它做的事是把“URL映射 → 方法调用 → 视图解析”这一套流程封装得更容易用。如果你想系统练一遍可以做的第一个综合性项目就是用Servlet JSP MySQL写一个带完整登录注册、用户管理、权限控制、日志记录的小系统。练这个项目的过程里你会把所有HTTP知识都用一遍——表单提交、Cookie/Session维持状态、Filter做权限拦截、重定向与转发、响应JSON数据、中文乱码处理。做完这个再进Spring、Spring Boot你会比那些直接跳框架的人扎实非常多。最后再分享一个实战小技巧在你写的Servlet里临时加一行代码把请求的所有头信息打印出来java.util.EnumerationString names request.getHeaderNames(); while (names.hasMoreElements()) { String name names.nextElement(); System.out.println(name : request.getHeader(name)); }然后打开浏览器访问这个接口把输出内容截图存下来。之后你看到任何“浏览器到底发了什么”的疑问翻出这张图看一遍很多困惑当场就能解开。HTTP协议看着内容少但它是整个JavaWeb最稳固的地基值得你多花时间。
返回列表