【问题标题】:REST Streaming JSON OutputREST 流式 JSON 输出
【发布时间】:2018-01-20 00:04:37
【问题描述】:

我们有需要发回 JSON 输出的 JAX RS 实现。但是响应的大小是巨大的。客户端也期望同样的同步。 因此我尝试使用 StreamingOutput... 但客户端并没有真正以块的形式获取数据。 下面是示例 sn-p:

服务器端

streamingOutput = new StreamingOutput() {

    @Override
    public void write(OutputStream out) throws IOException, WebApplicationException {
        JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(out);
        jsonGenerator.writeStartArray();
        for(int i=0; i < 10; i++) {
            jsonGenerator.writeStartObject();

            jsonGenerator.writeStringField("Response_State", "Response State - " + i);
            jsonGenerator.writeStringField("Response_Report", "Response Report - " + i);
            jsonGenerator.writeStringField("Error_details", "Error Details - " + i);

            jsonGenerator.writeEndObject();;
            jsonGenerator.flush();

            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        jsonGenerator.writeEndArray();

        jsonGenerator.close();

    }
};

return Response.status(200).entity(streamingOutput).build();

客户

HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost("http://localhost:8080/AccessData/FetchReport");
post.setHeader("Content-type", "application/json");
ResponseHandler<HttpResponse> responseHandler = new BasicResponseHandler();
StringEntity entity = new StringEntity(jsonRequest); //jsonRequest is       request string
post.setEntity(entity);
HttpResponse response = client.execute(post);
BufferedReader buffReader = new BufferedReader(new      InputStreamReader(response.getEntity().getContent()));
JsonParser jsonParser = new JsonFactory().createParser(buffReader);
while(jsonParser.nextToken() != JsonToken.END_OBJECT) {
    System.out.println(jsonParser.getCurrentName() + ":" +      jsonParser.getCurrentValue());
}
String output;
while((output = buffReader.readLine()) != null) {
    System.out.println(output);
}

在服务器端代码中,我放置睡眠调用只是为了模拟数据块之间的间隙。我需要的是客户端应该在服务器抛出数据时接收数据块。 但是在这里,客户总是得到完整的响应。 任何可能的解决方案?

提前致谢。

【问题讨论】:

    标签: java json output streaming jax-rs


    【解决方案1】:

    客户端似乎没有正确实现:使用解析器读取对象数组。

    另外,我想推荐一个数据传输对象的读写,而不是低级逐个字段的读写。

    为了完整起见,这里有一个完整的草稿示例,使用:Jersey 2.25.1, Jetty 9.2.14.v20151106。

    常见

    ResponseData

    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    public class ResponseData {
        private final String responseState;
        private final String responseReport;
        private final String errorDetails;
    
        @JsonCreator
        public ResponseData(
            @JsonProperty("Response_State") final String responseState,
            @JsonProperty("Response_Report") final String responseReport,
            @JsonProperty("Error_details") final String errorDetails) {
            this.responseState = responseState;
            this.responseReport = responseReport;
            this.errorDetails = errorDetails;
        }
    
        public String getResponseState() {
            return this.responseState;
        }
    
        public String getResponseReport() {
            return this.responseReport;
        }
    
        public String getErrorDetails() {
            return this.errorDetails;
        }
    
        @Override
        public String toString() {
            return String.format(
                "ResponseData: responseState: %s; responseReport: %s; errorDetails: %s",
                this.responseState,
                this.responseReport,
                this.errorDetails
            );
        }
    }
    

    服务

    ServerProgram

    import java.net.URI;
    import org.glassfish.jersey.jackson.JacksonFeature;
    import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
    import org.glassfish.jersey.server.ResourceConfig;
    
    public class ServerProgram {
        public static void main(final String[] args) {
            final URI uri = URI.create("http://localhost:8080/");
            final ResourceConfig resourceConfig = new ResourceConfig(TestResource.class);
            resourceConfig.register(JacksonFeature.class);
            JettyHttpContainerFactory.createServer(uri, resourceConfig);
        }
    }
    

    TestResource
    import com.fasterxml.jackson.core.JsonFactory;
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import java.io.IOException;
    import java.io.OutputStream;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.StreamingOutput;
    
    @Path("/")
    public class TestResource {
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Response getData() {
            final StreamingOutput streamingOutput = new JsonStreamingOutput();
            return Response.status(200).entity(streamingOutput).build();
        }
    
        private static class JsonStreamingOutput implements StreamingOutput {
            @Override
            public void write(final OutputStream outputStream) throws IOException, WebApplicationException {
                final ObjectMapper objectMapper = new ObjectMapper();
                final JsonFactory jsonFactory = objectMapper.getFactory();
                try (final JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream)) {
                    jsonGenerator.writeStartArray();
    
                    for (int i = 0; i < 10; i++) {
                        final ResponseData responseData = new ResponseData(
                            "Response State - " + i,
                            "Response Report - " + i,
                            "Error Details - " + i
                        );
                        jsonGenerator.writeObject(responseData);
                        jsonGenerator.flush();
    
                        try {
                            Thread.currentThread().sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    jsonGenerator.writeEndArray();
                }
            }
        }
    }
    

    客户

    ClientProgram

    import com.fasterxml.jackson.core.JsonFactory;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonToken;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import javax.ws.rs.client.Client;
    import javax.ws.rs.client.ClientBuilder;
    import javax.ws.rs.core.MediaType;
    import org.glassfish.jersey.client.ClientProperties;
    
    public class ClientProgram {
        public static void main(final String[] args) throws IOException {
            Client client = null;
            try {
                client = ClientBuilder.newClient();
                client.property(ClientProperties.READ_TIMEOUT, 10000);
    
                try (final InputStream inputStream = client
                    .target("http://localhost:8080/")
                    .request(MediaType.APPLICATION_JSON)
                    .get(InputStream.class);
                    final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
                    processStream(bufferedInputStream);
                }
            } finally {
                if (client != null) {
                    client.close();
                }
            }
        }
    
        private static void processStream(final InputStream inputStream) throws IOException {
            final ObjectMapper objectMapper = new ObjectMapper();
            final JsonFactory jsonFactory = objectMapper.getFactory();
            try (final JsonParser jsonParser = jsonFactory.createParser(inputStream)) {
                final JsonToken arrayToken = jsonParser.nextToken();
                if (arrayToken == null) {
                    // TODO: Return or throw exception.
                    return;
                }
    
                if (!JsonToken.START_ARRAY.equals(arrayToken)) {
                    // TODO: Return or throw exception.
                    return;
                }
    
                // Iterate through the objects of the array.
                while (JsonToken.START_OBJECT.equals(jsonParser.nextToken())) {
                    final ResponseData responseData = jsonParser.readValueAs(ResponseData.class);
                    System.out.println(responseData);
                }
            }
        }
    }
    

    希望这会有所帮助。

    【讨论】:

    • 确实感谢您提供详细的示例。但是,我注意到客户端程序不会立即从服务器端获得第一次提交。只有在服务器端大约 30 次提交之后,客户端才会收到消息。如果我增加消息大小,那么客户端不需要等待那么多,但它仍然需要等待。对此有任何想法吗?似乎是某种缓冲问题,尽管这不应该发生,因为代码在写入后刷新......谢谢。
    • @SoumenGhosh,这很有趣。也许将其作为一个单独的问题提出会更好?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 2017-07-29
    • 2016-09-06
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多