Flutter客户端HTTP请求200状态码触发时机及重复问题咨询
我正在开发一个Flutter应用,它会向Google App Engine(GAE)后端发送HTTP请求,提交用户对二选一问题的投票并存储到MySQL数据库。用户提交投票后会看到新的未投票过的问题,循环往复。设计逻辑是通过MySQL查询获取“用户未投过票的问题”,但偶尔会出现用户连续4-5次看到同一问题的情况,我推测这可能是因为应用在投票记录存入数据库前就发起了下一个问题的请求。
为了排查这个问题,我需要明确投票请求发送后客户端收到200状态码的触发时机。以下是处理投票的后端代码:
else if (operation.equals("voteFav")){ out.println("vote received"); // get userID and favID Integer userID = Integer.valueOf(request.getParameter("userID")); String creatorID = request.getParameter("creatorID"); Integer favID = Integer.valueOf(request.getParameter("favID")); String voter = request.getParameter("voterName"); String winpic = request.getParameter("winpic"); try{ Connection conn = DriverManager.getConnection(mysqlUrl); PreparedStatement ps; ResultSet rs; String query; // prepare Statement if(winpic.equals("1")) query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,1)"; else if(winpic.equals("2")) query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,2)"; else query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,-1)"; ps = conn.prepareStatement(query); ps.setInt(1, favID); ps.setInt(2, userID); // send vote to database ps.execute(); ps.close(); // close connection out.flush(); conn.close(); } catch (Exception e) { out.print("Fehler voting: " + e); out.flush(); } }
我的问题是:客户端的200状态码何时触发?是在使用PrintWriter写入“vote received”时立即触发,还是仅在关闭连接时触发?
解答
在GAE使用的Java Servlet容器(比如Jetty)环境中,HTTP 200状态码的发送时机和你对输出流的操作直接相关,具体细节如下:
状态码不会在写入内容时立即触发
当你调用out.println("vote received");时,内容只是被写入到Servlet的输出缓冲区中,并没有立刻发送给客户端,这时候客户端还不会收到任何响应(包括200状态码)。状态码会在第一次刷新输出流时发送
当你调用out.flush()(比如代码中在数据库操作后或者catch块里的flush),Servlet容器会把缓冲区中的内容(包括你之前写的"vote received")连同响应状态行(包含200状态码)、响应头一起发送给客户端。这时候客户端就会收到200响应,可能立刻发起下一个问题的请求。如果没有手动flush,容器会在请求处理完成后自动发送
要是你没手动调用flush(),当这段voteFav的处理代码执行完毕(也就是Servlet的service方法结束),容器会自动刷新输出流并关闭连接,这时候才会发送200状态码和响应内容。但你的代码里明确调用了out.flush(),所以实际触发时机是第一次flush的时刻。
结合你的问题场景分析
你现在的代码在数据库插入操作执行之前就写入了"vote received",之后在数据库操作完成后调用了out.flush()——这意味着客户端会在投票记录成功存入数据库的瞬间(甚至可能因为网络或数据库写入的延迟,记录还没完全落盘)就收到200响应,然后立刻去请求下一个问题。这时候MySQL可能还没完成投票记录的写入,导致查询“未投票问题”时找不到这条刚提交的记录,于是重复返回了同一个问题,这正好解释了你遇到的偶发重复问题。
修复建议
把响应内容的写入和flush操作放到数据库操作成功完成并关闭连接之后,这样客户端只有在投票记录确实存入数据库后才会收到200响应,再去请求下一个问题时就能正确过滤掉已投票的问题了。修改后的代码大致如下:
else if (operation.equals("voteFav")){ // get userID and favID Integer userID = Integer.valueOf(request.getParameter("userID")); String creatorID = request.getParameter("creatorID"); Integer favID = Integer.valueOf(request.getParameter("favID")); String voter = request.getParameter("voterName"); String winpic = request.getParameter("winpic"); try{ Connection conn = DriverManager.getConnection(mysqlUrl); PreparedStatement ps; ResultSet rs; String query; // prepare Statement if(winpic.equals("1")) query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,1)"; else if(winpic.equals("2")) query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,2)"; else query = "INSERT INTO faved (favID, userID, voting) VALUES (?,?,-1)"; ps = conn.prepareStatement(query); ps.setInt(1, favID); ps.setInt(2, userID); // send vote to database ps.execute(); ps.close(); // close connection conn.close(); // 确认数据库操作完成后再发送响应 out.println("vote received"); out.flush(); } catch (Exception e) { out.print("Fehler voting: " + e); out.flush(); } }
内容的提问来源于stack exchange,提问作者Chris

