You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构建可代理请求与响应的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;
    }
}

三、验证响应编码是否变化

  1. 在拦截器中添加日志记录编码信息:
// 在ProxyResponseInterceptor的aroundWriteTo方法中添加
System.out.println("Original charset: " + originalCharset);
System.out.println("Final response charset: " + context.getMediaType().getParameters().get("charset"));
  1. 用curl命令测试查看响应头:
curl -v http://your-proxy-domain.com/proxy/your-target-path

检查Content-Type字段的charset参数是否与目标服务一致。


内容的提问来源于stack exchange,提问作者LooneyTuunz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:25:15