在基于CXF JAX-RS的服务中限制输出有效负载响应

时间:2015-04-28 02:49:56

标签: java spring rest cxf jax-rs

我有多个使用cxf / spring构建的jax-rs服务。我想控制所有服务的输出有效负载响应大小。为简单起见,让我们说任何服务中的api都不应该返回超过 500 字符的JSON响应有效负载,我想在一个地方控制它依靠个人服务来遵守这一要求。 (我们已经在自定义框架/基础组件中内置了所有服务所依赖的其他功能。)

我尝试使用JAX-RS WriterInterceptorContainerResponseFilter和CXF' Phase Interceptor来实现这一点,但这些方法似乎都不能完全满足我的要求需求。关于我到目前为止所做的更多细节:

选项1:(WriterInteceptor)在重写方法中,我得到ouputstream并将缓存的最大大小设置为500.当我调用在响应中返回超过500个字符的api时有效负载,我得到HTTP 400错误请求状态,但响应正文包含整个JSON有效负载。

@Provider
public class ResponsePayloadInterceptor implements WriterInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();

        CacheAndWriteOutputStream cacheAndWriteOutputStream = new CacheAndWriteOutputStream(outputStream);
        cacheAndWriteOutputStream.setMaxSize(500);
        context.setOutputStream(cacheAndWriteOutputStream);

        context.proceed();
    }
}

选项2a:(CXF Phase Inteceptor)在重写方法中,我从ouputstream获取响应为String并检查它的大小。如果它大于500,我创建一个只有数据太多数据的新Response对象,并在消息中设置它。即使响应是> 500个字符,我获得了整个JSON的HTTP 200 OK状态。只有当我将阶段用作POST_MARSHAL或稍后阶段时,我才能获得JSON响应并检查其长度,但到那时响应已经流式传输到客户。

@Provider
public class ResponsePayloadInterceptor extends AbstractPhaseInterceptor<Message> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);

    public ResponsePayloadInterceptor() {
        super(Phase.POST_MARSHAL);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        LOGGER.info("handleMessage() - Response intercepted");
        try {
            OutputStream outputStream = message.getContent(OutputStream.class);
...
            CachedOutputStream cachedOutputStream = (CachedOutputStream) outputStream;
            String responseBody = IOUtils.toString(cachedOutputStream.getInputStream(), "UTF-8");
...
            LOGGER.info("handleMessage() - Response: {}", responseBody);
            LOGGER.info("handleMessage() - Response Length: {}", responseBody.length());
            if (responseBody.length() > 500) {
                Response response = Response.status(Response.Status.BAD_REQUEST)
                                            .entity("Too much data").build();
                message.getExchange().put(Response.class, response);
            }
        } catch (IOException e) {
            LOGGER.error("handleMessage() - Error");
            e.printStackTrace();
        }
    }
}

选项2b:(CXF Phase Inteceptor)与上述相同,但只更改了if块的内容。如果响应长度大于500,我使用字符串太多数据创建一个新的输出流,并将其设置在消息中。但是,如果响应有效载荷是> 500个字符,我仍然得到HTTP 200 OK状态,其中包含无效的JSON响应(整个JSON +附加文本),即响应如下:[{"data":"", ...}, {...}]Too much data(文本&#39;数据太多&# 39;附加到JSON)

        if (responseBody.length() > 500) {
            InputStream inputStream = new ByteArrayInputStream("Too much data".getBytes("UTF-8"));
            outputStream.flush();
            IOUtils.copy(inputStream, outputStream);

            OutputStream out = new CachedOutputStream();
            out.write("Too much data".getBytes("UTF-8"));
            message.setContent(OutputStream.class, out);
        }

选项3:(ContainerResponseFilter)使用ContainerResponseFilter,我添加了Content-Length响应标头,其值为500.如果响应长度为> 500,我得到一个HTTP 200 OK状态,其中包含无效的JSON响应(截断为500个字符)。如果响应长度<1。 500,仍然获得HTTP 200 OK状态,但客户端等待服务器返回更多数据(如预期的那样)并超时,这不是一个理想的解决方案。

@Provider
public class ResponsePayloadFilter implements ContainerResponseFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadFilter.class);

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        LOGGER.info("filter() - Response intercepted");
        CachedOutputStream cos = (CachedOutputStream) responseContext.getEntityStream();
        StringBuilder responsePayload = new StringBuilder();
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        if (cos.getInputStream().available() > 0) {
            IOUtils.copy(cos.getInputStream(), out);
            byte[] responseEntity = out.toByteArray();
            responsePayload.append(new String(responseEntity));
        }

        LOGGER.info("filter() - Content: {}", responsePayload.toString());
        responseContext.getHeaders().add("Content-Length", "500");
    }
}

有关如何调整上述方法以获取我想要的内容或任何其他不同指针的任何建议?

1 个答案:

答案 0 :(得分:0)

我使用此answer的帮助部分解决了这个问题。我说部分是因为我能够成功控制有效载荷,但不是响应状态码。理想情况下,如果响应长度大于500并且我修改了消息内容,我想发送一个不同的响应状态代码(200 OK除外)。但这对我来说是一个很好的解决方案。如果我也想知道如何更新状态代码,我会回来更新这个答案。

import org.apache.commons.io.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ResponsePayloadInterceptor extends AbstractPhaseInterceptor<Message> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);

    public ResponsePayloadInterceptor() {
        super(Phase.PRE_STREAM);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        LOGGER.info("handleMessage() - Response intercepted");
        try {
            OutputStream outputStream = message.getContent(OutputStream.class);
            CachedOutputStream cachedOutputStream = new CachedOutputStream();
            message.setContent(OutputStream.class, cachedOutputStream);

            message.getInterceptorChain().doIntercept(message);

            cachedOutputStream.flush();
            cachedOutputStream.close();

            CachedOutputStream newCachedOutputStream = (CachedOutputStream) message.getContent(OutputStream.class);
            String currentResponse = IOUtils.toString(newCachedOutputStream.getInputStream(), "UTF-8");
            newCachedOutputStream.flush();
            newCachedOutputStream.close();

            if (currentResponse != null) {
                LOGGER.info("handleMessage() - Response: {}", currentResponse);
                LOGGER.info("handleMessage() - Response Length: {}", currentResponse.length());

                if (currentResponse.length() > 500) {
                    InputStream replaceInputStream = IOUtils.toInputStream("{\"message\":\"Too much data\"}", "UTF-8");

                    IOUtils.copy(replaceInputStream, outputStream);
                    replaceInputStream.close();

                    message.setContent(OutputStream.class, outputStream);
                    outputStream.flush();
                    outputStream.close();
                } else {
                    InputStream replaceInputStream = IOUtils.toInputStream(currentResponse, "UTF-8");

                    IOUtils.copy(replaceInputStream, outputStream);
                    replaceInputStream.close();

                    message.setContent(OutputStream.class, outputStream);
                    outputStream.flush();
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            LOGGER.error("handleMessage() - Error", e);
            throw new RuntimeException(e);
        }
    }