dealing with Streams and ContentProducer - Java - java

I'm pretty new to coding with streams but now I have to do it for more efficient Http coding.
Here is code that I wrote(not working) to get ContentProducer for HttpClient:
public static ContentProducer getContentProducer(final Context context, final UUID gId)
{
return new ContentProducer()
{
public void writeTo(OutputStream outputStream) throws IOException
{
outputStream = new Base64.OutputStream(new FileOutputStream(StorageManager.getFileFromName(context, gId.toString())));
outputStream.flush();
}
};
}
I'm using Base64 streaming encoder from here: http://iharder.sourceforge.net/current/java/base64/
My goal is to use this function to provide data that I read from binary file to HttpClient as base64 encoded stream.
This is how I consume content producers:
private MyHttpResponse processPOST(String url, ContentProducer requestData)
{
MyHttpResponse response = new MyHttpResponse();
try
{
HttpPost request = new HttpPost(serviceURL + url);
HttpEntity entity = new EntityTemplate(requestData);
request.setEntity(entity);
ResponseHandler<String> handler = new BasicResponseHandler();
response.Body = mHttpClient.execute(request, handler);
}
catch (HttpResponseException e)
{
}
catch (Throwable e)
{
}
return response;
}
I have another ContentProducer which works with GSON streamer(and it's working):
public ContentProducer getContentProducer(final Context context)
{
return new ContentProducer()
{
public void writeTo(OutputStream outputStream) throws IOException
{
Gson myGson = MyGsonWrapper.getMyGson();
JsonWriter writer = new JsonWriter(new OutputStreamWriter(outputStream, "UTF-8"));
writer.beginObject();
// stuff
writer.endObject();
writer.flush();
}
};
}
My question is: How to make my first example work. Am I doing it correctly? Right now I get empty post on server side, so it seems like no data coming through.

EDIT:
I believe that the issue is that you are being passed an OutputStream in your ContentProviders writeTo() method, and you are overwriting it with your own OutputStream. The contract of that class/method probably requires you to write your data to the OutputStream passed to you.
Based on looking at the Android Documentation, I do not see a way for you to specify the OutputStream to use, so you will probably need to just write out the data of the file to the OutputStream that is passed in.
Instead, you should do something like this:
public void writeTo(OutputStream outputStream) throws IOException
{
byte[] buf = createByteBufferFromFile(StorageManager.getFileFromName(context, gId.toString()));
outputStream.write(buf);
outputStream.flush();
}
Of course you will need to provide an implementation to the createByteBufferFromFile(...) method that I mention. Again, you should note that it is not likely that you will be using the Base64 OutputStream, so if that is a necessity, then you may have to find a different approach.

Related

Retrieving String from JAX-RS Response

In short terms, I simplified the problem a lot. I am calling this code, and the response is received with status 200 (OK):
Receiver.java:
Response response = componentInstanceService.getResource(componentResourceType);
However, I don't know how can I retrieve the String contained in the body from this method:
Sender.java:
#Override
public Response getResource(ComponentResourceType resourceType) {
String path = getPath();
return Response.ok(this.getClass().getResourceAsStream(path)).build();
}
Please note that the communication between classes is working fine, as long as the Response is OK, however, how can I retrieve the String that Response contains?
This is what I would like to do roughly:
Receiver:
String result = componentInstanceService.getResource(componentResourceType);
The documentation for Response makes this pretty clear:
static Response.ResponseBuilder ok(java.lang.Object entity)
Create a new ResponseBuilder that contains a representation.
And:
abstract java.lang.Object getEntity()
Return the response entity.
In other words, the object you passed to Response.ok is the entity. You can retrieve it with the Response’s getEntity() method.
Obviously, you will need to cast it:
Response response = componentInstanceService.getResource(componentResourceType);
InputStream dataSource = (InputStream) response.getEntity();
Then you can read the stream as text. You haven’t mentioned the charset of your text files, so I’ll assume it’s UTF-8:
String result;
try (Scanner scanner = new Scanner(dataSource, StandardCharsets.UTF_8)) {
result = scanner.useDelimiter("\\z").next();
}
Update:
I suspected this might happen. You are returning a raw InputStream, which has no information about what type of data it is.
Change Sender.java to return a DataSource:
#Override
public DataSource getResource(ComponentResourceType resourceType) {
String path = getPath();
return new URLDataSource(this.getClass().getResource(path));
}
This way, the JAX-RS service will not only return HTTP 200 OK, but will also return a Content-Type header corresponding to the intuited type of your file.
You should then be able to invoke the method with:
DataSource dataSource = componentInstanceService.getResource(componentResourceType);
String result;
try (Scanner scanner = new Scanner(dataSource.getInputStream(), StandardCharsets.UTF_8)) {
result = scanner.useDelimiter("\\z").next();
}
There actually is a more robust way to read a DataSource. You can wrap it in a DataHandler:
DataSource dataSource = componentInstanceService.getResource(componentResourceType);
DataHandler handler = new DataHandler(dataSource);
DataFlavor flavor = DataFlavor.selectBestTextFlavor(
handler.getTransferDataFlavors());
if (flavor == null) {
// This should never happen with text files.
throw new IllegalArgumentException(
"Data has no flavors capable of supplying text.");
}
String result;
try (Reader reader = flavor.getReaderForText(handler)) {
StringBuilder s = new StringBuilder();
int c;
while ((c = reader.read()) >= 0) {
s.append((char) c);
}
result = s.toString();
} catch (UnsupportedFlavorException e) {
// Since we started with a flavor provided by the DataHandler,
// we should never get here.
throw new RuntimeException(e);
}
If you want to read the string from the body simply use
String result = componentInstanceService.getResource(componentResourceType).readEntity(String.class);

Getting InputStream with RestTemplate

I am using URL class to read an InputStream from it. Is there any way I can use RestTemplate for this?
InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName()));
How can I get InputStream with RestTemplate instead of using URL?
The previous answers are not wrong, but they don't go into the depth that I like to see. There are cases when dealing with low level InputStream is not only desirable, but necessary, the most common example being streaming a large file from source (some web server) to destination (a database). If you try to use a ByteArrayInputStream, you will be, not so surprisingly, greeted with OutOfMemoryError. Yes, you can roll your own HTTP client code, but you'll have to deal with erroneous response codes, response converters etc. If you are already using Spring, looking to RestTemplate is a natural choice.
As of this writing, spring-web:5.0.2.RELEASE has a ResourceHttpMessageConverter that has a boolean supportsReadStreaming, which if set, and the response type is InputStreamResource, returns InputStreamResource; otherwise it returns a ByteArrayResource. So clearly, you're not the only one that asked for streaming support.
However, there is a problem: RestTemplate closes the response soon after the HttpMessageConverter runs. Thus, even if you asked for InputStreamResource, and got it, it's no good, because the response stream has been closed. I think this is a design flaw that they overlooked; it should've been dependent on the response type. So unfortunately, for reading, you must consume the response fully; you can't pass it around if using RestTemplate.
Writing is no problem though. If you want to stream an InputStream, ResourceHttpMessageConverter will do it for you. Under the hood, it uses org.springframework.util.StreamUtils to write 4096 bytes at a time from the InputStream to the OutputStream.
Some of the HttpMessageConverter support all media types, so depending on your requirement, you may have to remove the default ones from RestTemplate, and set the ones you need, being mindful of their relative ordering.
Last but not the least, implementations of ClientHttpRequestFactory has a boolean bufferRequestBody that you can, and should, set to false if you are uploading a large stream. Otherwise, you know, OutOfMemoryError. As of this writing, SimpleClientHttpRequestFactory (JDK client) and HttpComponentsClientHttpRequestFactory (Apache HTTP client) support this feature, but not OkHttp3ClientHttpRequestFactory. Again, design oversight.
Edit:
Filed ticket SPR-16885.
Spring has a org.springframework.http.converter.ResourceHttpMessageConverter. It converts Spring's org.springframework.core.io.Resource class.
That Resource class encapsulates a InputStream, which you can obtain via someResource.getInputStream().
Putting this all together, you can actually get an InputStream via RestTemplate out-of-the-box by specifying Resource.class as your RestTemplate invocation's response type.
Here is an example using one of RestTemplate's exchange(..) methods:
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.core.io.Resource;
ResponseEntity<Resource> responseEntity = restTemplate.exchange( someUrlString, HttpMethod.GET, someHttpEntity, Resource.class );
InputStream responseInputStream;
try {
responseInputStream = responseEntity.getBody().getInputStream();
}
catch (IOException e) {
throw new RuntimeException(e);
}
// use responseInputStream
You should not get the InputStream directly. RestTemplate is meant to encapsulate processing the response (and request) content. Its strength is handling all the IO and handing you a ready-to-go Java object.
One of RestTemplate's original authors, Brian Clozel, has stated:
RestTemplate is not meant to stream the response body; its contract
doesn't allow it, and it's been around for so long that changing such
a basic part of its behavior cannot be done without disrupting many
applications.
You'll need to register appropriate HttpMessageConverter objects. Those will have access to the response's InputStream, through an HttpInputMessage object.
As Abdull suggests, Spring does come with an HttpMessageConverter implementation for Resource which itself wraps an InputStream, ResourceHttpMessageConverter. It doesn't support all Resource types, but since you should be programming to interfaces anyway, you should just use the superinterface Resource.
The current implementation (4.3.5), will return a ByteArrayResource with the content of the response stream copied to a new ByteArrayInputStream which you can access.
You don't have to close the stream. The RestTemplate takes care of that for you. (This is unfortunate if you try to use a InputStreamResource, another type supported by the ResourceHttpMessageConverter, because it wraps the underlying response's InputStream but is closed before it can be exposed to your client code.)
I encountered the same issue and solved it by extending RestTemplate and closing the connection only after the stream is read.
you can see the code here: https://github.com/ItamarBenjamin/stream-rest-template
Thanks to Abhijit Sarkar's answer for leading the way.
I needed to download a heavy JSON stream and break it into small streamable manageable pieces of data.
The JSON is composed of objects that have big properties: such big properties can be serialized to a file, and thus removed from the unmarshalled JSON object.
Another use case is to download a JSON stream object by object, process it like a map/reduce algorythm and produce a single output without having to load the whole stream in memory.
Yet another use case is to read a big JSON file and only pick a few objects based on a condition, while unmarshalling to Plain Old Java Objects.
Here is an example: we'd like to stream a very huge JSON file that is an array, and we'd like to retrieve only the first object in the array.
Given this big file on a server, available at http://example.org/testings.json :
[
{ "property1": "value1", "property2": "value2", "property3": "value3" },
{ "property1": "value1", "property2": "value2", "property3": "value3" },
... 1446481 objects => a file of 104 MB => take quite long to download...
]
Each row of this JSON array can be parsed as this object:
#lombok.Data
public class Testing {
String property1;
String property2;
String property3;
}
You need this class make the parsing code reusable:
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
#FunctionalInterface
public interface JsonStreamer<R> {
/**
* Parse the given JSON stream, process it, and optionally return an object.<br>
* The returned object can represent a downsized parsed version of the stream, or the result of a map/reduce processing, or null...
*
* #param jsonParser the parser to use while streaming JSON for processing
* #return the optional result of the process (can be {#link Void} if processing returns nothing)
* #throws IOException on streaming problem (you are also strongly encouraged to throw HttpMessageNotReadableException on parsing error)
*/
R stream(JsonParser jsonParser) throws IOException;
}
And this class to parse:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
#AllArgsConstructor
public class StreamingHttpMessageConverter<R> implements HttpMessageConverter<R> {
private final JsonFactory factory;
private final JsonStreamer<R> jsonStreamer;
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false; // We only support reading from an InputStream
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(MediaType.APPLICATION_JSON);
}
#Override
public R read(Class<? extends R> clazz, HttpInputMessage inputMessage) throws IOException {
try (InputStream inputStream = inputMessage.getBody();
JsonParser parser = factory.createParser(inputStream)) {
return jsonStreamer.stream(parser);
}
}
#Override
public void write(R result, MediaType contentType, HttpOutputMessage outputMessage) {
throw new UnsupportedOperationException();
}
}
Then, here is the code to use to stream the HTTP response, parse the JSON array and return only the first unmarshalled object:
// You should #Autowire these:
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper();
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
// If detectRequestFactory true (default): HttpComponentsClientHttpRequestFactory will be used and it will consume the entire HTTP response, even if we close the stream early
// If detectRequestFactory false: SimpleClientHttpRequestFactory will be used and it will close the connection as soon as we ask it to
RestTemplate restTemplate = restTemplateBuilder.detectRequestFactory(false).messageConverters(
new StreamingHttpMessageConverter<>(jsonFactory, jsonParser -> {
// While you use a low-level JsonParser to not load everything in memory at once,
// you can still profit from smaller object mapping with the ObjectMapper
if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_ARRAY) {
if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_OBJECT) {
return objectMapper.readValue(jsonParser, Testing.class);
}
}
return null;
})
).build();
final Testing firstTesting = restTemplate.getForObject("http://example.org/testings.json", Testing.class);
log.debug("First testing object: {}", firstTesting);
You can pass in your own response extractor. Here is an example where I write out the json to disk in a streaming fashion -
RestTemplate restTemplate = new RestTemplateBuilder().basicAuthentication("user", "their_password" ).build();
int responseSize = restTemplate.execute(uri,
HttpMethod.POST,
(ClientHttpRequest requestCallback) -> {
requestCallback.getHeaders().setContentType(MediaType.APPLICATION_JSON);
requestCallback.getBody().write(body.getBytes());
},
responseExtractor -> {
FileOutputStream fos = new FileOutputStream(new File("out.json"));
return StreamUtils.copy(responseExtractor.getBody(), fos);
}
)
Very simple, yet efficient solution would be using ResponseExtractor. It's especially useful when you want to operate on very large InputStream and your RAM is limited.
Here is how you should be implementing it:
public void consumerInputStreamWithoutBuffering(String url, Consumer<InputStream> streamConsumer) throws IOException {
final ResponseExtractor responseExtractor =
(ClientHttpResponse clientHttpResponse) -> {
streamConsumer.accept(clientHttpResponse.getBody());
return null;
};
restTemplate.execute(url, HttpMethod.GET, null, responseExtractor);
}
And then, invoke the method anywhere you need:
Consumer<InputStream> doWhileDownloading = inputStream -> {
//Use inputStream for your business logic...
};
consumerInputStreamWithoutBuffering("https://localhost.com/download", doWhileDownloading);
Please, be aware of the following common pitfall:
public InputStream getInputStreamFromResponse(String url) throws IOException {
final ResponseExtractor<InputStream> responseExtractor =
clientHttpResponse -> clientHttpResponse.getBody();
return restTemplate.execute(url, HttpMethod.GET, null, responseExtractor);
}
Here InputStream will be closed before you can access it
I solve it by doing that.
I hope it will help you all.
#GetMapping("largeFile")
public ResponseEntity<InputStreamResource> downloadLargeFile(
#RequestParam("fileName") String fileName
) throws IOException {
RestTemplate restTemplate = new RestTemplate();
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<InputStreamResource> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("tmp/" + fileName);
Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING);
return new InputStreamResource(new FileInputStream(String.format("tmp/%s", fileName)));
};
InputStreamResource response = restTemplate.execute(
String.format("http://%s:%s/file/largeFileRestTemplate?fileName=%s", host, "9091", fileName),
HttpMethod.GET,
requestCallback,
responseExtractor
);
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", fileName))
.body(response);
}
As a variant you can consume response as bytes and than convert to stream
byte data[] = restTemplate.execute(link, HttpMethod.GET, null, new BinaryFileExtractor());
return new ByteArrayInputStream(data);
Extractor is
public class BinaryFileExtractor implements ResponseExtractor<byte[]> {
#Override
public byte[] extractData(ClientHttpResponse response) throws IOException {
return ByteStreams.toByteArray(response.getBody());
}
}

Parsing a XML not keeping duplicated namespaces in the parent node and child node

Before I start: I know that the child node inherits the namespace from the parent node and that's why my problem occurs. Unfortunately, the webservice I am sending my XML doesn't accept the child node without the namespace and, as it is a government entity, a change in their part is rather unlikely.
That being said, I am using Spring-WS to make the communication between my application and the webservice, so in one way or the other the framework uses a transformer to parse my payload Source to the framework's payload Result:
transformer.transform(Source, Result);
Before that transformation take place, my XML has these two nodes like it follows here:
<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="3.10">
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
After the transformation, the second namespace is removed(as I said before, I know the reason):
<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="3.10">
<NFe>
I am also aware that I can use marshallers to achieve the same result and writing the parse code myself. Using that approach is also ok and would be acceptable, but I don't know any other way to achieve the same thing (transforming the javax.xml.transform.Source into javax.xml.transform.Result) using other approach besides the one listed above.
I have two questions then:
1 - Can I avoid the behaviour I am having with the default approach(without using marshallers)?
2 - Is there any other tool that would make the same transformation?
I've been through the same trouble. Unfortunately (or not) WebServiceTemplate with implementation of SOAPMessageFactory (such as SaajSoapMessageFactory) will do everything possible to assure you are sending a well-formed XML as a request by tying you to the Transformers from Source to Result, including never let you repeat 'xmlns' in children when you already did in parent. You have a couple of elegant options to try - what doesn't mean they're the simplest ones. You can work at XML level by using javax.xml.ws.Service and Dispatch interface, which is quite easy if you don't need SSL authentication. Check these links out (first one is written in Pt-BR):
http://www.guj.com.br/t/nfe-v2-00-veja-como-consumir-o-ws/297304
https://alesaudate.wordpress.com/2010/08/09/how-to-dynamically-select-a-certificate-alias-when-invoking-web-services/
Also you can try another message factory, such as DomPoxMessageFactory. This link might be useful:
http://forum.spring.io/forum/spring-projects/web-services/128221-webservicetemplate-get-it-to-stop-adding-soap-envelope
However, if changing the structure of your project isn't an option (which was my case), I have a workaround for you. Yes, a workaround, but once the target webservice IS EXPECTING a malformed XML, I absolve myself :D
I just created abstractions of HttpComponentsMessageSender and HttpComponentsConnection classes, the second one is instantiated through the first one's method createConnection(URI uri). So I can create my WebServiceTemplate like this:
WebServiceTemplate wst = new WebServiceTemplate(new SaajSoapMessageFactory());
wst.setMessageSender(new CustomHttpComponentsMessageSender());
Sadly you'll need to reply the createConnecion method to the new abstraction just to instantiate the custom connection. As I said, it's a workaround!
#Override
public WebServiceConnection createConnection(URI uri) throws IOException {
HttpPost httpPost = new HttpPost(uri);
if (isAcceptGzipEncoding()) {
httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
HttpTransportConstants.CONTENT_ENCODING_GZIP);
}
HttpContext httpContext = createContext(uri);
return new CustomHttpComponentsConnection(getHttpClient(), httpPost, httpContext);
}
The message is effectively sent inside the method onSendAfterWrite(WebServiceMessage message) of the HttpComponentsConnection class I'm abstracting from. Surprisingly, the 'message' parameter isn't used inside the method. It's there only for inheritance rules. And the good news: It's a protected method. The downside, again, is that I need to copy almost the entire class in order to change only this method, once the fields has no public visibility, and framework will need them in response handling. So, I'll post my entire class down:
public class CustomHttpComponentsConnection extends HttpComponentsConnection {
private final HttpClient httpClient;
private final HttpPost httpPost;
private final HttpContext httpContext;
private HttpResponse httpResponse;
private ByteArrayOutputStream requestBuffer;
protected CustomHttpComponentsConnection(HttpClient httpClient, HttpPost httpPost, HttpContext httpContext) {
super(httpClient, httpPost, httpContext);
Assert.notNull(httpClient, "httpClient must not be null");
Assert.notNull(httpPost, "httpPost must not be null");
this.httpClient = httpClient;
this.httpPost = httpPost;
this.httpContext = httpContext;
}
public HttpResponse getHttpResponse() {
return httpResponse;
}
public HttpPost getHttpPost() {
return httpPost;
}
#Override
protected OutputStream getRequestOutputStream() throws IOException {
return requestBuffer;
}
#Override
protected void onSendBeforeWrite(WebServiceMessage message) throws IOException {
requestBuffer = new ByteArrayOutputStream();
}
#Override
protected void onSendAfterWrite(WebServiceMessage message) throws IOException {
OutputStream out = getRequestOutputStream();
String str = out.toString();
str = str.replaceAll("<NFe>", "<NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\">");
ByteArrayOutputStream bs = new ByteArrayOutputStream();
bs.write(str.getBytes());
getHttpPost().setEntity(new ByteArrayEntity(bs.toByteArray()));
requestBuffer = null;
if (httpContext != null) {
httpResponse = httpClient.execute(httpPost, httpContext);
}
else {
httpResponse = httpClient.execute(httpPost);
}
}
#Override
protected int getResponseCode() throws IOException {
return httpResponse.getStatusLine().getStatusCode();
}
#Override
protected String getResponseMessage() throws IOException {
return httpResponse.getStatusLine().getReasonPhrase();
}
#Override
protected long getResponseContentLength() throws IOException {
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
return entity.getContentLength();
}
return 0;
}
#Override
protected InputStream getRawResponseInputStream() throws IOException {
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
return entity.getContent();
}
throw new IllegalStateException("Response has no enclosing response entity, cannot create input stream");
}
#Override
public Iterator<String> getResponseHeaderNames() throws IOException {
Header[] headers = httpResponse.getAllHeaders();
String[] names = new String[headers.length];
for (int i = 0; i < headers.length; i++) {
names[i] = headers[i].getName();
}
return Arrays.asList(names).iterator();
}
#Override
public Iterator<String> getResponseHeaders(String name) throws IOException {
Header[] headers = httpResponse.getHeaders(name);
String[] values = new String[headers.length];
for (int i = 0; i < headers.length; i++) {
values[i] = headers[i].getValue();
}
return Arrays.asList(values).iterator();
}
Again, this is the easiest way I found when changing project structure is not an option. Hope this helps.
I don't think then there is any other approach for your transformation. As you know marshallers are the best practice to use in these scenarios. Better to use JAXB.

OutputStream.flush() not committing response data to HttpServletResponse

I am using the jetty HTTPClient and ContentExchange to implement a proxy. ContentExchange has several hook methods that can be overridden to execute code when certain pieces of the response are loaded. My problem is with loading the response body from a jetty Buffer to the HttpServletResponse object returned to the client. The response content is JSON, and shorter JSON is correctly exported to the client, but longer JSON does not show up in the response, and results in the following error: SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data. The documentation does not show any maximum length to the outputStream object, so I am unsure why the content would be cut off/not loading at all.
ContentExchange contentExchange = new ContentExchange() {
ServletOutputStream outputStream;
String contentString = "";
#Override
protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException {
super.onResponseStatus(version, status, reason);
httpResponse.setStatus(status);
remoteLRSRequest.setStatus(status);
remoteLRSRequest.setResponse(reason.toString());
}
#Override
protected void onResponseHeader(Buffer name, Buffer value) throws IOException {
super.onResponseHeader(name, value);
httpResponse.setHeader(name.toString(), value.toString());
}
#Override
protected void onResponseContent(Buffer content) throws IOException {
if (outputStream == null) {
outputStream = httpResponse.getOutputStream();
}
content.writeTo(outputStream);
outputStream.flush();
}
#Override
protected void onResponseComplete() throws IOException {
outputStream.close();
super.onResponseComplete();
}
}
Note: When I step through this code in my debugger, I can see the entirety of the response content loaded into the outputStream. However, it seems that flush() is not correctly committing the response data.
This was a misdiagnosis of the actual problem. The response I was relaying contained a "Transfer-Endcoding: chunked" header that was causing problems.

Limit output payload response in CXF JAX-RS based service

I have multiple jax-rs services built using cxf/spring. I want to control the output payload response size of all services. For simplicity sake, let's say none of api's in any of the services should ever return a JSON response payload more than 500 characters and I want to control this in one place instead of relying on individual services to adhere to this requirement. (We already have other features built into the custom framework/base component that all services depend on).
I have tried implementing this using JAX-RS's WriterInterceptor, ContainerResponseFilter and CXF's Phase Interceptor, but none of the approaches seem to be completely satisfy my requirement. More details on what I've done so far:
Option 1: (WriterInteceptor) In the overridden method, I get the ouputstream and set the max size of the cache to 500. When I invoke an api that returns more than 500 characters in the response payload, I get an HTTP 400 Bad Request status, but the response body contains the entire JSON payload.
#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();
}
}
Option 2a: (CXF Phase Inteceptor) In the overridden method, I get the response as String from the ouputstream and check it's size. If it's greater than 500, I create a new Response object with only the data Too much data and set it in the message. Even if the response is > 500 characters, I get an HTTP 200 OK status with the entire JSON. Only when I use the phase as POST_MARSHAL or a later phase, I'm able to get hold of the JSON response and check it's length, but by that time the response has already been streamed to the client.
#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();
}
}
}
Option 2b: (CXF Phase Inteceptor) Same as above, but only the contents of if block is changed. If response length is greater than 500, I create a new output stream with the string Too much data and set it in message. But if the response payload is > 500 characters, I still get an HTTP 200 OK status with an invalid JSON response (entire JSON + additional text) i.e., the response looks like this: [{"data":"", ...}, {...}]Too much data (the text 'Too much data' is appended to the 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);
}
Option 3: (ContainerResponseFilter) Using the ContainerResponseFilter, I added a Content-Length response header with value as 500. If response length is > 500, I get an HTTP 200 OK status with an invalid JSON response (truncated to 500 characters). If the response length is < 500, still get an HTTP 200 OK status, but the client waits for more data to be returned by the server (as expected) and times out, which isn't a desirable solution.
#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");
}
}
Any suggestions on how I can tweak the above approaches to get what I want or any other different pointers?
I resolved this partially using help from this answer. I say partially because I'm successfully able to control the payload, but the not the response status code. Ideally, if the response length is greater than 500 and I modify the message content, I would like to send a different response status code (other than 200 OK). But this is a good enough solution for me to proceed at this point. If I figure out how to update the status code as well, I'll come back and update this answer.
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);
}
}

Categories