如何构建可代理请求与响应的JAX RS Resource?
构建JAX RS代理Resource实现请求转发与响应处理
核心解决思路
直接在Resource内用客户端转发会遇到SSE与常规响应兼容、编码失控等问题,推荐结合JAX RS客户端API转发请求,搭配容器响应拦截器统一处理头部与编码,单独适配SSE流式场景。
一、通用代理Resource实现
实现一个接收全请求方法的代理Resource,预留请求头部修改逻辑,同时单独处理SSE响应:
import javax.ws.rs.*; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.*; import java.io.InputStream; @Path("/proxy") public class ProxyResource { private final Client client = ClientBuilder.newClient(); private final String targetBaseUrl = "http://your-target-service.com"; // 处理OPTIONS预检请求 @OPTIONS @Path("{path:.*}") public Response handleOptions(@PathParam("path") String path, @Context HttpHeaders headers) { return Response.ok() .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") .header("Access-Control-Allow-Headers", headers.getRequestHeader("Access-Control-Request-Headers").get(0)) .build(); } @GET @Path("{path:.*}") public Response proxyGet(@PathParam("path") String path, @Context UriInfo uriInfo, @Context HttpHeaders headers) { return proxyRequest("GET", path, uriInfo, headers, null); } @POST @Path("{path:.*}") @Consumes(MediaType.WILDCARD) public Response proxyPost(@PathParam("path") String path, @Context UriInfo uriInfo, @Context HttpHeaders headers, InputStream requestBody) { return proxyRequest("POST", path, uriInfo, headers, requestBody); } // 同理可实现PUT、DELETE等请求方法 private Response proxyRequest(String method, String path, UriInfo uriInfo, HttpHeaders headers, InputStream requestBody) { // 拼接目标服务URI,包含原始查询参数 String targetUri = targetBaseUrl + "/" + path; if (!uriInfo.getQueryParameters().isEmpty()) { targetUri += "?" + uriInfo.getQueryParameters().toString().substring(1, uriInfo.getQueryParameters().toString().length()-1); } // 复制原始请求头部,可按需修改 Invocation.Builder requestBuilder = client.target(targetUri) .request() .headers(headers.getRequestHeaders()); // 示例:添加代理标识头部 requestBuilder.header("X-Proxy-Source", "jaxrs-proxy"); // 发送请求并获取目标响应 Response targetResponse; if (requestBody != null) { targetResponse = requestBuilder.method(method, Entity.entity(requestBody, headers.getMediaType())); } else { targetResponse = requestBuilder.method(method); } // 特殊处理SSE响应:直接复用原始响应,避免JAX RS重新编码破坏流式传输 if (MediaType.SERVER_SENT_EVENTS_TYPE.equals(targetResponse.getMediaType())) { return Response.fromResponse(targetResponse) .header("X-SSE-Proxy", "processed") .build(); } // 常规响应:直接传递,后续交给拦截器处理编码与头部 return Response.fromResponse(targetResponse).build(); } }
二、统一控制响应编码与头部修改
使用WriterInterceptor强制响应编码与目标服务一致,批量修改响应头部:
import javax.ws.rs.WebApplicationException; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; public class ProxyResponseInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { // 复用目标服务的原始编码,防止JAX RS自动重新编码 String originalCharset = context.getMediaType().getParameters().get("charset"); if (originalCharset != null) { context.setMediaType(context.getMediaType().withCharset(originalCharset)); } // 批量修改响应头部示例 context.getHeaders().add("X-Proxy-Modified", "true"); context.getHeaders().remove("Server"); // 移除目标服务的Server标识 context.proceed(); } }
在Application类中注册拦截器:
import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class ProxyApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); classes.add(ProxyResource.class); classes.add(ProxyResponseInterceptor.class); return classes; } }
三、验证响应编码是否变化
- 在拦截器中添加日志记录编码信息:
// 在ProxyResponseInterceptor的aroundWriteTo方法中添加 System.out.println("Original charset: " + originalCharset); System.out.println("Final response charset: " + context.getMediaType().getParameters().get("charset"));
- 用curl命令测试查看响应头:
curl -v http://your-proxy-domain.com/proxy/your-target-path
检查Content-Type字段的charset参数是否与目标服务一致。
内容的提问来源于stack exchange,提问作者LooneyTuunz
相关产品推荐
相关产品推荐

