I have a server that streams various things such as log output over long-lived HTTP responses. However, when using Jetty's proxy servlets, I haven't been able to get it to stream the response (it buffers the whole response before sending).
Using overriding a plain ProxyServlet class, the following appears to work:
#Override
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) {
super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
try {
response.getOutputStream().flush();
} catch (IOException e) {
log.warn("Error flushing", e);
}
}
However, doing that when overriding an AsyncProxyServlet doesn't work. (Full source code here.)
So, two questions:
When using ProxyServlet, is flushing after each bit of content received the way to go?
Is there a way to make it work with AsyncProxyServlet?
Got it working. The proper approach works whether async is used or not, which is to set the output buffer size when creating the Jetty server connectors.
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setOutputBufferSize(1024);
ServerConnector httpConnector = new ServerConnector(jettyServer,
new HttpConnectionFactory(httpConfig));
The default is 32768.
(Note: no need to override the onResponseContent method)
Related
I'm trying to download a file from Angular UI, even after I got exception in backed code still I'm getting 200 ok as the response.
Here is the code I have :
public ResponseEntity<Object> downloadDocument(#PathVariable("docId") Long docId,
HttpServletResponse response) {
OutPutStream outputStream = null;
try {
outputStream = response.getOutputStream();
docService.downloadDocument(docId,outputStream);
return ResponseEntity.ok().contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
.body("Success");
} catch(Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
} finally {
if (Objects.nonNull(outputStream)) {
IOUtils.closeQuietly(outputStream);
}
}
Can you please help me out what's wring here.
When you open the outputstream, the headers are sent. period.
There's no backtracking from that point on. You can't first open the outputstream and then later go: Oh, wait! No! nevermind! bad request!
Here's how it works - you pick a side and stick to it. You can either:
Handle it yourself; use response and the methods available there to set up your response; you can set headers, the return code and message, and you can obtain an outputstream for the response body, and send data that way. If you do this, you can't ALSO return a ResponseEntity!
Do NOT even add an HttpServletResponse parameter, and instead return a ResponseEntity object.
You're doing both, which is not allowed.
I'm frankly surprised; spring is a bit broken and ought to be throwing exceptions here, as it cannot possibly serve up what you're asking it to do here.
NB: Note that the type of an exception is usually more informative than the message (many exceptions don't even have a message).
Putting it all together:
public ResponseEntity<?> downloadDocument(#PathVariable("docId") Long docId) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
docService.downloadDocument(docId, baos);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
.body(baos.toByteArray());
} catch(Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
'toString' defaults to printing its own type if there is no message, and its own type plus the message if there is, so you get e.g.:
NullPointerException
or
NullPointerException: parameter foo
which is what you want (versus a literal blank string in the former case, and just 'parameter foo' in the latter, which isn't particularly insightful either).
messages are generally intended not to necessarily make sense without the context of the type of the exception.
NB: This will cache the entire content of the downloaded document into the memory of the server before sending it onwards. If the document is large, this is a bad idea, but, if you want to 'stream' it, you have a pretty serious problem inherent in the HTTP protocol: Once you start sending, you've already sent the 'error status' (i.e. you already sent 200 OK), so if the document download process throws an exception halfway through, you cannot go back and send an error message. You need some sort of wire protocol where you send the body in chunks and have a 'type' code you send, so that the recipient can scan for these and knows 'this type means there's more data flowing', and 'this type means an error occured and now I can read the error description'. That all gets quite complicated. By caching it, you avoid this mess. But if that document can be very large you're going to have to deal with the above, there are no quick fixes.
I tried to find any mention of handling of compression in new Java HTTP Client but failed. Is there a built-in configuration to handle for e.g. gzip or deflate compression?
I would expect to have a BodyHandler for e.g. something like this:
HttpResponse.BodyHandlers.ofGzipped(HttpResponse.BodyHandlers.ofString())
but I don't see any. I don't see any configuration in HttpClient either. Am I looking in the wrong place or was this intentionally not implemented and deferred to support libraries?
I was also surprised that the new java.net.http framework doesn't handle this automatically, but the following works for me to handle HTTP responses which are received as an InputStream and are either uncompressed or compressed with gzip:
public static InputStream getDecodedInputStream(
HttpResponse<InputStream> httpResponse) {
String encoding = determineContentEncoding(httpResponse);
try {
switch (encoding) {
case "":
return httpResponse.body();
case "gzip":
return new GZIPInputStream(httpResponse.body());
default:
throw new UnsupportedOperationException(
"Unexpected Content-Encoding: " + encoding);
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static String determineContentEncoding(
HttpResponse<?> httpResponse) {
return httpResponse.headers().firstValue("Content-Encoding").orElse("");
}
Note that I've not added support for the "deflate" type (because I don't currently need it, and the more I read about "deflate" the more of a mess it sounded). But I believe you can easily support "deflate" by adding a check to the above switch block and wrapping the httpResponse.body() in an InflaterInputStream.
You can use Methanol. It has decompressing BodyHandler implementations, with out-of-the-box support for gzip & deflate. There's also a module for brotli.
var response = client.send(request, MoreBodyHandlers.decoding(BodyHandlers.ofString()));
Note that you can use any BodyHandler you want. MoreBodyHandlers::decoding makes it seem to your handler like the response was never compressed! It takes care of the Content-Encoding header and all.
Better yet, you can use Methanol's own HttpClient, which does transparent decompression after adding the appropriate Accept-Encoding to your requests.
var client = Methanol.create();
var request = MutableRequest.GET("https://example.com");
var response = client.send(request, BodyHandlers.ofString()); // The response is transparently decompressed
No, gzip/deflate compression are not handled by default. You would have to implement that in your application code if you need it - e.g. by providing a customized BodySubscriber to handle it. Alternatively - you may want to have a look at whether some of the reactive stream libraries out there offer such a feature, in which case you might be able to pipe that in by using one of the BodyHandlers.fromSubscriber​(Flow.Subscriber<? super List<ByteBuffer>> subscriber) or BodyHandlers.ofPublisher() methods.
I have a requirement to read and write compressed (GZIP) streams without intermediate storage. Currently, I'm using Spring RestTemplate to do the writing, and Apache HTTP client to do the reading (see my answer here for an explanation of why RestTemplate can't be used for reading large streams). The implementation is fairly straightforward, where I slap a GZIPInputStream on the response InputStream and move on.
Now, I'd like to switch to using Spring 5 WebClient (just because I'm not a fan of status quo). However, WebClient is reactive in nature and deals with Flux<Stuff>; I believe it's possible to get a Flux<DataBuffer>, where DataBuffer is an abstraction over ByteBuffer. Question is, how do I decompress it on the fly without having to store the full stream in memory (OutOfMemoryError, I'm looking at you), or writing to local disk? It's worth mentioning that WebClient uses Netty under the hood.
Also see Reactor Netty issue-251.
Also related to Spring integration issue-2300.
I'll admit to not knowing much about (de)compression, however, I did my research, but none of the material available online seemed particularly helpful.
compression on java nio direct buffers
Writing GZIP file with nio
Reading a GZIP file from a FileChannel (Java NIO)
(de)compressing files using NIO
Iterable gzip deflate/inflate in Java
public class HttpResponseHeadersHandler extends ChannelInboundHandlerAdapter {
private final HttpHeaders httpHeaders;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpResponse &&
!HttpStatus.resolve(((HttpResponse) msg).status().code()).is1xxInformational()) {
HttpHeaders headers = ((HttpResponse) msg).headers();
httpHeaders.forEach(e -> {
log.warn("Modifying {} from: {} to: {}.", e.getKey(), headers.get(e.getKey()), e.getValue());
headers.set(e.getKey(), e.getValue());
});
}
ctx.fireChannelRead(msg);
}
}
Then I create a ClientHttpConnector to use with WebClient and in afterNettyContextInit add the handler:
ctx.addHandlerLast(new ReadTimeoutHandler(readTimeoutMillis, TimeUnit.MILLISECONDS));
ctx.addHandlerLast(new Slf4JLoggingHandler());
if (forceDecompression) {
io.netty.handler.codec.http.HttpHeaders httpHeaders = new ReadOnlyHttpHeaders(
true,
CONTENT_ENCODING, GZIP,
CONTENT_TYPE, APPLICATION_JSON
);
HttpResponseHeadersHandler headersModifier = new HttpResponseHeadersHandler(httpHeaders);
ctx.addHandlerFirst(headersModifier);
}
ctx.addHandlerLast(new HttpContentDecompressor());
This, of course, would fail for responses that are not GZIP compressed, so I use this instance of WebClient for a particular use case only, where I know for sure that the response is compressed.
Writing is easy: Spring has a ResourceEncoder, so InputStream can simply be converted to InputStreamResource, and voila!
Noting this here as it confused me a bit - the API has changed a bit as of 5.1.
I have a similar setup to the accepted answer for the ChannelInboundHandler:
public class GzipJsonHeadersHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpResponse
&& !HttpStatus.resolve(((HttpResponse) msg).status().code()).is1xxInformational()) {
HttpHeaders headers = ((HttpResponse) msg).headers();
headers.clear();
headers.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
headers.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
}
ctx.fireChannelRead(msg);
}
}
(The header values I needed are just hard-coded there for simplicity, otherwise it's identical.)
To register it however is different:
WebClient.builder()
.clientConnector(
new ReactorClientHttpConnector(
HttpClient.from(
TcpClient.create()
.doOnConnected(c -> {
c.addHandlerFirst(new HttpContentDecompressor());
c.addHandlerFirst(new HttpResponseHeadersHandler());
})
).compress(true)
)
)
.build();
It seems Netty now maintains a user list of handlers separate from (and after) the system list, and addHandlerFirst() only puts your handler at the front of the user list. It therefore requires an explicit call to HttpContentDecompressor to ensure it's definitely executed after your handler that inserts the correct headers.
I want to have two interceptors for every request I do on a webservice. One for the outgoing communication, and one for the response.
I am using ClientHttpRequestInterceptor which is working for the outgoing. I am setting it as follows:
//Rest template
RestTemplate tpl = api.getRestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add( new OutgoingRequestInterceptor() );
tpl.setInterceptors( interceptors );
However, I want something like this interceptor for the incoming (response). I checked Spring Framework sourcecode and I couldn't find anything for this.
Any tips?
Edit:
Maybe I am confused or something is wrong in my head. Im a bit ill today.
I've the following code in my interceptor class:
#Override
public ClientHttpResponse intercept( HttpRequest request, byte[] bytes, ClientHttpRequestExecution requestExecution ) throws IOException
{
SLog.d( "intercepted!!"+request.getURI()+". Bytes: "+bytes );
try
{
Thread.sleep( 5000 );
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
ClientHttpResponse response = requestExecution.execute( request, bytes );
SLog.d( "Response Headers: " + response.getHeaders());
return response;
}
Question: Is this code working for outgoing, incoming, or both?
Because the log:request.getUri() is returning the destination URL.
Then, on the Response object, I get the headers sent by WebService.
So what I am sure of, is that response is actually the server response. But... How about getUri() thingy? Is it triggered before actually sending the request, or after?
Okay. After some tricky debugging, I got it.
Even though the interceptor class is called ClientHttpRequestInterceptor, it's intercepting both. Request from client, and respose from Server.
This interceptor class is something like a wrapper.
So...
This method is the wrapper of the whole request. From BEFORE request and after the request is done.
This part of the code is triggered BEFORE request is sent to webservice
This part of the code ACTUALLY CONTACTS WEBSERVICE, so it "pauses" there until it gets the response from the web service.
We return the response generated. Notice that if you use method response.getBody() which is an InputStream, you will consume it, so it will be null afterwards. I say that because you CAN'T directly log it. You've to mirror it first.
I am writing a web service using Java JDK 1.7 and Jersey Web service Framework. One of the things I need to provide is a way to allow authenticated clients to download certain large data files ( 1-3 GB). Ideally I would like this to be a pause and resume type downloadable option. I tried the jersey multi-part API and was able to get it to work on my client machine upto 400 MB but beyond that it ran into out-of memory issues. I am also worried that the server might fail when faced with simultaneous download requests. Any thoughts on how this can be done? Is Netty an option? Any pointers on how Netty can be integrated into a existing Jersey based web service? Are there other frame works available to help accomplish this? I do have to use java for the web service. Any pointers will be helpful.
If you are getting stuck on out-of-memory issues, you should check how you are handling the data you are downloading. If you are using Jersey's ClientResponse, make sure you are using getEntityInputStream() and not getEntity(). This way, you can stream the data, write it to file, and toss it aside, rather than letting it build up in the Java heap space.
I can't really speak about your simultaneous download concerns, but if you are using the web services framework, then it should be handled properly.
For both issues, more info on your specific implementation, especially code, will help you get a better response.
The server and the client must both support HTTP chunked encoding which allows one to stream data using HTTP. The code below should work with Jersey 2.11.
For downloading large files, try this on the server:
#GET
#Path("/files/{fileName}")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public StreamingOutput getFile(#PathParam("fileName") final String fileName) throws Exception {
//create instance of StreamingOutput here
return streamingOutput;
}
Try this for a client GET request using steams to download a file.
public String getFileReq(File outFile) throws IOException {
client = ClientBuilder.newClient(new ClientConfig());
client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED");
WebTarget target = client.target(URI)
OutputStream fileOutputStream = new FileOutputStream(outFile);
InputStream fileInputStream = target.request().get(InputStream.class);
writeFile(fileInputStream, fileOutputStream);
}
public static void writeFile(InputStream fileInputStream, OutputStream outputStream) throws IOException {
try {
byte[] buffer = new byte[1024];
int bytesRead;
while((bytesRead = fileInputStream.read(buffer)) !=-1) {
outputStream.write(buffer, 0, bytesRead);
}
fileInputStream.close();
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.close();
}