简体   繁体   中英

How to send immediately asynchronous response for request

I'm trying to find a asynchronous way to return immediately response for client request.

What I need is only to log request data, call new thread to request expensive operations on other servers(some backend operations) and without waiting for response from them return immediately 200 status response to client.

At this moment I'm trying to do it with CompletableFuture but I'm missing something.

package com.example.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.concurrent.CompletableFuture;

@Path("/class")
public class AsynchronousResponse {

private static final Logger LOGGER = (Logger) LogManager.getLogger(AsynchronousResponse.class.getName());
private static final int HTTP_STATUS_OK = 200;
private static final String EMPTY_CONTENT = "";

@Context
private HttpServletRequest httpRequest;

@Path("/method")
@GET
@Consumes(MediaType.APPLICATION_JSON)
public Response asyncResponseSupply() {
    LOGGER.info(httpRequest.getSession().getId() + " : New session received");
    CompletableFuture.supplyAsync(this::veryExpensiveOperations);
    LOGGER.info(httpRequest.getSession().getId() + " : Returning empty response... ");
    return Response.status(HTTP_STATUS_OK).entity(EMPTY_CONTENT).build();
}

// need to do some operations on data from httpRequest
private String veryExpensiveOperations() {
    LOGGER.info(httpRequest.toString());
    LOGGER.info("Start very expensive operations");
    try {
        Thread.sleep(3000);
        LOGGER.info("Finished");
        return "DONE";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
        LOGGER.error("Error: " + e.getMessage());
        return "ERROR";
    }
}

}

All in all I get immediately response, but veryExpensiveOperations() method seems to lose httpRequest values and it's too bad for me becouse I need to call other webservices with values from client request. Thanks for help!

For my app I'm using Jetty ver. 9.2.18.v20160721

Once the dispatch to the servlet is complete, the response is committed and the request and response objects are recycled.

For processing on the servlet spec, you must use the AsyncContext (obtained via the HttpServletRequest.startAsync() call) to let the container know that the request / response is not yet complete, and processing for it is occurring in a non-dispatch thread (like your CompletableFuture.supplyAsync() call)

As for how to mix JAX-RS and AsyncContext , I have no idea. I don't even know if JAX-RS has been updated yet to support AsyncContext .

Thanks Joakim for your answer(I can't upvote it becouse I'm quite new here and I don't have enough reputation). Based on your sugestion about AsyncContext I found a way to send quick response. My code after changes(works fine for both runAsync and supplyAsync) :

package com.example.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;


@Path("/test")
public class AsynchronousResponse {

private static final Logger LOGGER = (Logger) LogManager.getLogger(AsynchronousResponse.class.getName());
private static final int HTTP_STATUS_OK = 200;
private static final String EMPTY_CONTENT = "";

@Context
private HttpServletRequest httpRequest;

private AsyncContext asyncContext;

@Path("/supply")
@GET
@Consumes(MediaType.APPLICATION_JSON)
public Response asyncResponseSupply() {
    String loggerPrefix = httpRequest.getSession().getId() + " :[SUPPLY] ";
    LOGGER.info(loggerPrefix + "New session received");
    LOGGER.info(loggerPrefix + "Active threads on controller init: " + Thread.activeCount());
    asyncContext = httpRequest.startAsync();
    ForkJoinPool pool = new ForkJoinPool(
            Runtime.getRuntime().availableProcessors(),
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
            null,
            true);
    CompletableFuture.supplyAsync(this::veryExpensiveOperations, pool);
    LOGGER.info(loggerPrefix + "Actual active Threads on controller return: " + Thread.activeCount());
    LOGGER.info(loggerPrefix + "Returning empty response... ");
    return Response.status(HTTP_STATUS_OK).entity(EMPTY_CONTENT).build();
}

@Path("/run")
@GET
@Consumes(MediaType.APPLICATION_JSON)
public Response asyncResponseSupplyRun() {
    String loggerPrefix = httpRequest.getSession().getId() + " :[RUN] ";
    LOGGER.info(loggerPrefix + "New session received");
    LOGGER.info(loggerPrefix + "Active threads on controller init: " + Thread.activeCount());
    asyncContext = httpRequest.startAsync();
    ForkJoinPool pool = new ForkJoinPool(
            Runtime.getRuntime().availableProcessors(),
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
            null,
            true);
    CompletableFuture.runAsync(this::veryExpensiveOperations, pool);
    LOGGER.info(loggerPrefix + "Actual active Threads on controller return: " + Thread.activeCount());
    LOGGER.info(loggerPrefix + "Returning empty response... ");
    return Response.status(HTTP_STATUS_OK).entity(EMPTY_CONTENT).build();
}

// need to do some operations on data from httpRequest
private String veryExpensiveOperations() {
    String loggerPrefix = httpRequest.getSession().getId() + " :[veryExpensiveOperations] ";
    LOGGER.info(loggerPrefix + "Request toString: " + httpRequest.toString());
    LOGGER.info(loggerPrefix + "Start very expensive operations");

    try {
        Thread.sleep(3000);
        LOGGER.info(loggerPrefix + "Thread sleep finished");
        LOGGER.info(loggerPrefix + "Actual active Threads: " + Thread.activeCount());
        Thread.currentThread().interrupt();
        asyncContext.complete();
        return "DONE";

    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
        LOGGER.error(loggerPrefix + "Error: " + e.getMessage());
        asyncContext.complete();
        return "ERROR";

    }
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM