如何将带查询字符串的GET请求转为带表单数据的POST请求?
问题描述
技术栈
- Servlet 4.0(基于Tomcat 9运行)
- Java 8
我使用的第三方商业前端仅支持发送GET请求,但请求中的查询字符串频繁被用户意外篡改,已经出现多起相关问题。由于无法修改供应商的前端代码,此前只能用带查询字符串的GET请求规避限制——如果能完全控制前端,我们会直接改用带formdata的POST请求。
现在我希望通过Servlet实现以下流程:将用户发起的带参GET请求重定向到中间地址,移除查询字符串防止用户编辑,再转成带表单数据的POST请求发送到目标服务,同时避免创建复杂的落地页。
已尝试的方案均存在问题:
- 请求转发:无法修改URL
- 中间页面转发:同样无法修改URL
- JSP嵌入JS提交隐藏表单:高负载场景下可能出现异常
我希望在Servlet内部通过修改请求对象实现需求,但尚未找到可行方案。
当前请求示例
- 请求方法:GET
- URL:
http://www.your.domain.com/service-get-or-post?field1=foo&field2=bar&field3=42
期望流程
- 先跳转至中间地址:
- 请求方法:GET
- URL:
http://www.your.domain.com/service-trampoline?field1=foo&field2=bar&field3=42
- 再转换为POST请求发送至目标服务:
- 请求方法:POST
- 内容类型:
application/x-www-form-urlencoded - URL:
http://www.your.domain.com/service-get-or-post - 请求体:
field1=foo&field2=bar&field3=42
解决方案
方案1:服务器端直接转发POST请求
在service-trampoline Servlet中接收GET请求的参数,直接构造POST请求发送到目标服务,再将目标服务的响应返回给客户端。全程在服务器端处理,客户端不会暴露参数在URL中。
代码示例
@WebServlet("/service-trampoline") public class TrampolineServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取所有GET参数 Map<String, String[]> params = request.getParameterMap(); // 2. 构造URL编码的表单数据 StringBuilder formData = new StringBuilder(); for (Map.Entry<String, String[]> entry : params.entrySet()) { String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8"); for (String value : entry.getValue()) { if (formData.length() > 0) { formData.append("&"); } formData.append(encodedKey) .append("=") .append(URLEncoder.encode(value, "UTF-8")); } } // 3. 发起POST请求到目标服务 URL targetUrl = new URL("http://www.your.domain.com/service-get-or-post"); HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); // 写入表单数据 try (OutputStream os = conn.getOutputStream()) { os.write(formData.toString().getBytes("UTF-8")); os.flush(); } // 4. 将目标服务的响应透传给客户端 response.setStatus(conn.getResponseCode()); // 复制响应头 for (Map.Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) { if (header.getKey() != null) { header.getValue().forEach(value -> response.addHeader(header.getKey(), value)); } } // 复制响应体 try (InputStream is = conn.getInputStream(); OutputStream os = response.getOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } catch (IOException e) { // 处理错误响应 try (InputStream es = conn.getErrorStream(); OutputStream os = response.getOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = es.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } } } }
方案2:Session暂存参数+无参数重定向
如果需要彻底移除客户端URL中的查询字符串,可以结合Session暂存参数,再重定向到无参数的中间地址,最后发起POST请求:
代码示例
@WebServlet("/service-trampoline") public class TrampolineServlet extends HttpServlet { private static final String PARAMS_SESSION_KEY = "TRAMPOLINE_REQUEST_PARAMS"; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); Map<String, String[]> storedParams = (Map<String, String[]>) session.getAttribute(PARAMS_SESSION_KEY); if (storedParams != null) { // 从Session取出参数,发起POST请求 sendPostRequest(storedParams, response); session.removeAttribute(PARAMS_SESSION_KEY); // 用完清除参数 } else { // 第一次请求:暂存参数并重定向到无参数地址 Map<String, String[]> paramsCopy = new HashMap<>(request.getParameterMap()); // 克隆参数数组避免后续修改影响 paramsCopy.replaceAll((k, v) -> v.clone()); session.setAttribute(PARAMS_SESSION_KEY, paramsCopy); // 重定向到无查询字符串的当前Servlet String redirectUrl = request.getContextPath() + "/service-trampoline"; response.sendRedirect(redirectUrl); } } private void sendPostRequest(Map<String, String[]> params, HttpServletResponse response) throws IOException { // 构造表单数据和发起POST请求的逻辑同方案1 StringBuilder formData = new StringBuilder(); for (Map.Entry<String, String[]> entry : params.entrySet()) { String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8"); for (String value : entry.getValue()) { if (formData.length() > 0) { formData.append("&"); } formData.append(encodedKey) .append("=") .append(URLEncoder.encode(value, "UTF-8")); } } URL targetUrl = new URL("http://www.your.domain.com/service-get-or-post"); HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(formData.toString().getBytes("UTF-8")); os.flush(); } // 透传响应的逻辑同方案1 response.setStatus(conn.getResponseCode()); for (Map.Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) { if (header.getKey() != null) { header.getValue().forEach(value -> response.addHeader(header.getKey(), value)); } } try (InputStream is = conn.getInputStream(); OutputStream os = response.getOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } catch (IOException e) { try (InputStream es = conn.getErrorStream(); OutputStream os = response.getOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = es.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } } } }
注意事项
- Session过期处理:如果用户重定向后停留过久,Session可能过期导致参数丢失,可适当调整Session超时时间,或用短时效的Token替代Session存储参数。
- 编码一致性:确保全程使用UTF-8编码,避免参数乱码。
- 异常容错:添加目标服务超时、不可用等场景的异常处理,返回友好的错误响应。
内容的提问来源于stack exchange,提问作者Scott R. Young
相关产品推荐
相关产品推荐

