Streaming bytes via HTTP PUT with JAX-RS - java

I have a workflow that involves doing a HTTP POST from my Java client to my web server. The body of the post has a specification object. I then pass that on from my webserver to Apache ZooKeeper (which runs in its own process on the server) that runs a big hairy calculation. I am struggling with figuring out how to send back the bytes to my webserver in streaming fashion. I need it to stream back because I have a HTTP GET request on my webserver from my Java client that is waiting to stream back the bytes. I cannot wait for the whole calculation to finish, I want bytes sent as soon as possible back to the client.
Most the examples online for JAX-RS that do a HTTP PUT from the client side and on the webserver side don't have examples for streaming code. I'll post what I have so far, but it doesn't work.
Here is my ZooKeeper Java code, which calls a JAX-RS client-side PUT. I am really unsure of how to do this, I have never tried streaming data with JAX-RS.
final Client client = ClientBuilder.newClient();
final WebTarget createImageTarget = client.target("groups/{imageGroupUuid:" + Regex.UUID + "}");
StreamingOutput imageResponse = createImageTarget.request(MediaType.APPLICATION_OCTET_STREAM).put(Entity.entity(createRandomImageDataBytes(imageConfigurationObject), MediaType.APPLICATION_OCTET_STREAM), StreamingOutput.class);
Here is my webserver code which handles the HTTP PUT. It is just a stub because I have no confidence in my client side HTTP PUT.
#PUT
#PATH("groups/{uuid:" + Regex.UUID + "}")
#Consumes(MediaType.APPLICATION_OCTET_STREAM)
public void updateData(StreamingOutput streamingOutput)
{
}

Try something like this:
#GET
#Produces(MediaType.TEXT_PLAIN)
#Path("/{arg}")
public Response get(#PathParam("arg") {
//get your data based on "arg"
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream os) throws IOException, WebApplicationException {
Writer writer = new BufferedWriter(new OutputStreamWriter(os));
for (org.neo4j.graphdb.Path path : paths) {
writer.write(path.toString() + "\n");
}
writer.flush();
}
};
return Response.ok(stream).build();
}
#PUT
#Consumes("application/octet-stream")
public Response putFile(#Context HttpServletRequest request,
#PathParam("fileId") long fileId,
InputStream fileInputStream) throws Throwable {
// Do something with the fileInputStream
// etc
}

Related

Okhttp convert Response to Spring ResponseEntity

I have a Springboot 2.2 webservice with a POST REST api.
I am using Okhttp client to make the request to a third party service.
I want to return the exact response from the third party service to the caller of my webservice.
All the okhttp recipes refer to:
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
I have tried returning the okhttp Response in my api but I only return to the caller example:
{
"redirect": false,
"successful": true
}
So it does not insert the message returned from the third party service.
Does anyone know how to convert Okhttp Response object to a Spring ResponseEntity object so that I may return the http response code and message
In this case, you can use the HttpServletResponse in the Spring-Webservice directly and pipe the InputStream given from okhttp ResponseBody to the OutputStream of the HttpServletResponse.
#PostMapping(path="/my/webservice")
public void postSomething(HttpServletResponse response)throws IOException{
okhttp3.Response redirectedResponse = redirectCall();
if(redirectedResponse.isSuccessful())
pipe(response.getOutputStream(), redirectedResponse.body.byteStream());
}
private void pipe(OutputStream output, InputStream input)throws IOException{
byte chunk[] = new byte[1024];
while(input.read(chunk, 0, 1024) != -1){
output.write(chunk);
}
}

How to return 206 Response with Streamingout

I have a jax-rs service implemented in jersey to fetch some millions of record from db
#Produces(MediaType.TEXT_PLAIN)
public Response streamExample() {
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream os) throws IOException,
WebApplicationException {
Writer writer = new BufferedWriter(new OutputStreamWriter(os));
// fetch from resultset and write// writer.write("test");
writer.flush(); // <-- This is very important. Do not forget.
}
};
return Response.ok(stream).build();
}
I have a test case where i need to respond with HttpStatus :206(partial content), which means there has to be a body with all the results processed and a http status code 206.
All i can implement either client gets a 200(OK), 500(server error), but i have no solution to send the body alongwith a different status code . The reason is first it sets the response status and then calls streaming function(where i call db to fetch all results) lazily. so unless i process the function , i cant say if there will be a 206 error.
How do i implement this?

Send Spring response stream immediately rather than wait until method return

I have a request mapping that looks like this:
private final static byte[] byteArray = ...;
#RequestMapping(value=Array("/foobar"))
void sendByteArray(#RequestBody Request request, OutputStream os) {
os.write(byteArray);
os.flush();
doLengthyCleanup();
}
I'm finding that the request client does not actually receive the response body until after the service has completed doLengthyCleanup().
Since the cleanup doesn't affect the response itself, I'd like to improve my response time by performing the cleanup after sending the response. How can I do this?
#RequestMapping(value=Array("/foobar"))
void sendByteArray(#RequestBody Request request, OutputStream os) {
os.write(byteArray);
os.flush(); // not sure
doLengthyCleanup();
}
#Async
void doLengthyCleanup() {
// this will be executed asynchronously
}
Update: taken from this question
If you are calling the #Async method from another method in the same class, unless you enable AspectJ proxy mode for the #EnableAsync (and provide a weaver of course) that won't work (google "proxy self-invocation"). The easiest fix is to put the #Async method in another #Bean.
As shown in this answer, you need to indicate to Spring that you are handling the response yourself by accepting the response directly and setting the status code yourself:
void sendByteArray(#RequestBody Request request, HttpServletResponse response) {
response.setStatus(HttpStatus.SC_OK);
OutputStream os = response.getOutputStream();
os.write(byteArray);
os.flush();
os.close();
doLengthyCleanup();
}

Very peculiar :HTTP Status 405 - Method Not Allowed

[using Apache Tomcat/7.0.27]
It seems I only get this error
(HTTP Status 405 - Method Not Allowed)
when I try to make a REST request directly from the browser.
E.g by pasting this in the address bar :
http://localhost:8080/restExample/rest/catalog/video/14951/hello
When I run my test client Main.java everything works fine.
Any ideas as to why it wont let me execute a REST through the browser?
Client Side:
public class Main{
public static void main(String [] args){
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(getBaseURI(_package));
runPutRequest(service,"video/128/This is the content with the new description");
}
}
...
private static void runPutRequest(WebResource service,String path){
String response = service.path("rest/catalog/"+path).accept(MediaType.APPLICATION_XML).put(String.class);
System.out.println("Post Response :"+response);
}
Server side:
#PUT
#Path("/video/{video-id}/{short-descr}")
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_XML)
public Video updateVideo(#PathParam("video-id") int contentid,
#PathParam("short-descr") String descr)
{
//Video video = searchByContentId(contentid);
Video video = videoMap.get(contentid);
video.setDescription(descr);
videoMap.put(contentid,video);
if( videoMap.get(contentid) != null){
return videoMap.get(contentid);
}else{
throw new UnsupportedOperationException("NO object found");
}
}
The browser will issue a GET request for your resource - which you have declared as a #PUT on the server-side and are PUT-ing to it from your client-side code. The browser is trying to 'fetch' (or GET) the resource and nothing exists for #GET
Generally, the Browser uses GET HTTP method to make requests. Your server side component is only capable to response to PUT requests, and that’s why you get that error code.
There exist REST clients for browsers that are capable of doing PUT, POST, and DELETE requests. I prefer Simple REST Client for Chrome.

Use GZIP, JSON responses and JQuery

However, I want to compress my responses with GZIP wheren possible. I tried using the Compression filter code available for free download in the headfirst site. It works great for html, images, css and javascript.
I post the filter next. It checks if GZIP is an accepted encoding and it adds gzip as Content-Encoding. See: wrappedResp.setHeader("Content-Encoding", "gzip");
public class CompressionFilter implements Filter {
private ServletContext ctx;
private FilterConfig cfg;
/**
* The init method saves the config object and a quick reference to the
* servlet context object (for logging purposes).
*/
public void init(FilterConfig cfg)
throws ServletException {
this.cfg = cfg;
ctx = cfg.getServletContext();
//ctx.log(cfg.getFilterName() + " initialized.");
}
/**
* The heart of this filter wraps the response object with a Decorator
* that wraps the output stream with a compression I/O stream.
* Compression of the output stream is only performed if and only if
* the client includes an Accept-Encoding header (specifically, for gzip).
*/
public void doFilter(ServletRequest req,
ServletResponse resp,
FilterChain fc)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// Dose the client accept GZIP compression?
String valid_encodings = request.getHeader("Accept-Encoding");
if ( (valid_encodings != null) && (valid_encodings.indexOf("gzip") > -1) ) {
// Then wrap the response object with a compression wrapper
// We'll look at this class in a minute.
CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response);
// Declare that the response content is being GZIP encoded.
wrappedResp.setHeader("Content-Encoding", "gzip");
// Chain to the next component (thus processing the request)
fc.doFilter(request, wrappedResp);
// A GZIP compression stream must be "finished" which also
// flushes the GZIP stream buffer which sends all of its
// data to the original response stream.
GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
gzos.finish();
// The container handles the rest of the work.
//ctx.log(cfg.getFilterName() + ": finished the request.");
} else {
fc.doFilter(request, response);
//ctx.log(cfg.getFilterName() + ": no encoding performed.");
}
}
public void destroy() {
// nulling out my instance variables
cfg = null;
ctx = null;
}
}
I was using the next code to send JSON responses in Struts web application.
public ActionForward get(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
JSONObject json = // Do some logic here
RequestUtils.populateWithJSON(response, json);
return null;
}
public static void populateWithJSON(HttpServletResponse response,JSONObject json) {
if(json!=null) {
response.setContentType("text/x-json;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
try {
response.getWriter().write(json.toString());
} catch (IOException e) {
throw new ApplicationException("IOException in populateWithJSON", e);
}
}
}
It works fine without compression but if I compress JSON responses, I can not see my JSON objects anymore. I handle JSON Ajax calls with JQuery with code snippets as follows:
$.post(url,parameters, function(json) {
// Do some DOM manipulation with the data contained in the JSON Object
}, "json");
If I see the response with Firebug it is empty.
Should I refractor my compression filter to skip compression in JSON responses? or there is a workaround to this?
For me, it looks like JQuery does not recognize the response as JSON because I am adding the Gzip compression.
If I see the response with Firebug it
is empty.
There's your clue - it's not a JQuery problem, it's server-side. (I'm afraid I can't help you with that, other than to suggest you stop looking at the client-side)
There's no problem gzipping ajax responses - if you can't see the response in Firebug, then JQuery can't see it either.
you have to add one more header "content-encoding: gzip" if you are compressing it.
Have you tried with an explicit java-based client to ensure it's a problem with jQuery or browser? If java client fails, something is wrong with server response.
But I am guessing that whereas browser can deal with uncompression with direct requests, this is perhaps not applied to Ajax calls.
It's an interesting question, I hope we'll get a more definitive answer. :)

Categories

Resources