I have created a WSClient according to the Play Documentation. And using that client object I use WSRequest to get my response. But all I'm getting is a null body and 0 as the server response code. And when I debug to where i request get() it says java.lang.illegalstateexception: closed.
Following is my code.
WS Client
private WSClient wsClient() throws IOException {
akka.stream.Materializer materializer = akka.stream.ActorMaterializer.create(akka.actor.ActorSystem.create());
// Set up the client config (you can also use a parser here):
scala.Option<String> noneString = scala.None$.empty();
WSClientConfig wsClientConfig = new WSClientConfig(
Duration.apply(120000, TimeUnit.SECONDS), // connectionTimeout
Duration.apply(120000, TimeUnit.SECONDS), // idleTimeout
Duration.apply(120000, TimeUnit.SECONDS), // requestTimeout
true, // followRedirects
true, // useProxyProperties
noneString, // userAgent
true, // compressionEnabled / enforced
SSLConfigFactory.defaultConfig());
AhcWSClientConfig clientConfig = AhcWSClientConfigFactory.forClientConfig(wsClientConfig);
// Add underlying asynchttpclient options to WSClient
AhcConfigBuilder builder = new AhcConfigBuilder(clientConfig);
DefaultAsyncHttpClientConfig.Builder ahcBuilder = builder.configure();
AsyncHttpClientConfig.AdditionalChannelInitializer logging = new AsyncHttpClientConfig.AdditionalChannelInitializer() {
#Override
public void initChannel(io.netty.channel.Channel channel) throws IOException {
channel.pipeline().addFirst("log", new io.netty.handler.logging.LoggingHandler("debug"));
}
};
ahcBuilder.setHttpAdditionalChannelInitializer(logging);
WSClient customWSClient = new play.libs.ws.ahc.AhcWSClient(ahcBuilder.build(), materializer);
customWSClient.close();
return customWSClient;
}
Request Handler
Future future = executorService.submit(new Runnable() {
#Override
public void run() {
WSRequest wsRequest = wsClient.url(url);
WSRequest complexRequest = wsRequest.setHeader(header.getKey(), header.getValue())
.setRequestTimeout(120000).setMethod(requestMethod);
CompletionStage<WSResponse> responsePromise = complexRequest.get();
CompletionStage<Result> promiseResult = responsePromise.thenApplyAsync(responseAfter -> {
int responseStatus = responseAfter.getStatus();
String body = responseAfter.getBody();
restResponse.setBody(body);
restResponse.setStatus(responseStatus);
return ok();
});
}
});
future.get();
executorService.shutdown();
I also use ExecutorService for asynchronous handling.
I searched everywhere for this problem and I still haven't found any solution for it.
Error in Debug
Debug Error
New Debug Error
Not Completed Error
The problem here is that the WSClient is being closed with customWSClient.close(). After this it is not possible to make requests.
Related
Hi when am loading the page the first subscription request for SSE is working fine.
but when SSE timeout and go for subscribe again am getting 503 error.
My backend code:
#RestController
#Slf4j
public class UpdateNotification {
#Autowired
SseService sseService;
#GetMapping(value = "/api/v1/item/subscription", consumes = MediaType.ALL_VALUE, produces = MediaType.ALL_VALUE)
public SseEmitter subscribe(#RequestParam("id") long id) throws InterruptedException, IOException {
SseEmitter emitter = new SseEmitter();
sseService.add(id, emitter);
emitter.onCompletion(() -> sseService.remove(id, emitter));
emitter.onError((ex)->log.info("Error>> "+ ex.getMessage()));
return emitter;
}
#Async
public void produce(#RequestBody final MessageDTO message) {
GetData(message);
}
public void GetData(final MessageDTO message) {
sseService.getSsEmitters(message.getItemDTO().getId()).forEach((SseEmitter emitter) -> {
try {
emitter.send(message.getPrice(), MediaType.APPLICATION_JSON);
} catch (IOException e) {
emitter.complete();
sseService.remove(message.getId(), emitter);
e.printStackTrace();
}
});
}
}
my frontend javascript code
function initialize() {
var itemId=$("#itemId").text();
const eventSource = new EventSource('/api/v1/item/subscription?id='+itemId);
eventSource.onmessage = e => {
const msg = e.data;
$("#price").text(msg);
Notification.requestPermission(function () {
if (Notification.permission === 'granted') {
// user approved.
var text = msg;
var notification = new Notification('Notification Alert!', {body: text});
setTimeout(notification.close(), 6 * 1000) // close in 5 sec
} else if (Notification.permission === 'denied') {
// user denied.
} else { // Notification.permission === 'default'
// user didn’t make a decision.
// You can’t send notifications until they grant permission.
}
});
};
eventSource.onopen = e => console.log('open');
eventSource.onerror = e => {
if (e.readyState == EventSource.CLOSED) {
console.log('close');
} else {
console.log(e);
}
};
eventSource.addEventListener('second', function (e) {
console.log('second', e.data);
}, false);
}
window.onload = initialize();
first request send request successfully
When timeout happen subscription request automatic generate which throwing 503 error
I have no idea why am getting 503 on subscribe again after timeout. Please help me to solve this problem.
I fixed that problem by moving GetData(message) method from controller class to service class with implementation now it working fine.
This is best way to stay with apache tomcat while making reactive programming in springboot. Or you can go with spring reactive(Webflux) which run at netty server.
I am using a Library called milo, it is programmed with the Java 8 attribute like the use of CompletableFuture.
And now I want to get data from REST using OkHttp.But I don't know how to implement this with CompletableFuture.
Below is my code
#Override
public void run(OpcUaClient client, CompletableFuture<OpcUaClient> future) throws Exception {
// synchronous connect
client.connect().get();
List<NodeId> nodeIds = ImmutableList.of(new NodeId(2, "HelloWorld/ScalarTypes/Int32"));
OkHttpClient okHttpClient = new OkHttpClient();
Request.Builder requestBuilder = new Request.Builder().url("http://localhost:8080/greeting");
for (int i = 0; i < 10; i++) {
Request request = requestBuilder.build();
Call call= okHttpClient.newCall(request);
final GreetingModel[] greetingModel = {new GreetingModel()};
call.enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
logger.error("Writing is wrong");
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
String json = response.body().string();
logger.info("Connecting is good");
greetingModel[0] = JSON.parseObject(json, GreetingModel.class);
}
});
Variant v = new Variant(greetingModel[0].getId());
// don't write status or timestamps
DataValue dv = new DataValue(v, null, null);
// write asynchronously....
CompletableFuture<List<StatusCode>> f =
client.writeValues(nodeIds, ImmutableList.of(dv));
// ...but block for the results so we write in order
List<StatusCode> statusCodes = f.get();
StatusCode status = statusCodes.get(0);
if (status.isGood()) {
logger.info("Wrote '{}' to nodeId={}", v, nodeIds.get(0));
}
}
future.complete(client);
}
}
And the figure shows the result.The writing operation is ahead the data acquisition from okhttp.
result
Js :
$('#loaderImage').show();
$http.get('/utilities/longProcess')
.success(function(data, status, headers, config) {
console.log('Completed');
$scope.sampleJSON = data.pmdStructureWrapper;
$scope.sampleJSONDuplicates = data.pmdDuplicates;
$scope.$watch('sampleJSON', setTimeout(function() {
$('.panel-body li').each(function() {
if ($.trim($(this).text()) === "") {
$(this).hide();
}
});
}, 1000));
$('#loaderImage').hide();
})
.error(function(data, status, header, config) {
});
Controller :
#RequestMapping("/utilities/longProcess")
public DeferredResult<String> async(HttpServletResponse response, HttpServletRequest request) {
DeferredResult<String> dr = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> {
return callURL(response, request);
}, ex).thenAccept((String message) -> {
dr.setResult(message);
});
return dr;
}
private String callURL(HttpServletResponse response, HttpServletRequest request){
PMDMainWrapper pmdMainWrapper = new PMDMainWrapper();
Map<String, PMDStructureWrapper> codeReviewByClass = new HashMap<>();
String partnerURL = this.partnerURL;
String toolingURL = this.toolingURL;
Cookie[] cookies = request.getCookies();
List<PMDStructure> violationStructure = null;
try {
violationStructure = metadataLoginUtil.startReviewer(partnerURL, toolingURL, cookies);
} catch (Exception e) {
e.printStackTrace();
}
PMDStructureWrapper pmdStructureWrapper = null;
List<PMDStructure> pmdStructureList = null;
List<PMDStructure> pmdDuplicatesList = new ArrayList<>();
int size = violationStructure.size();
long start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
if (codeReviewByClass.containsKey(violationStructure.get(i).getName())) {
PMDStructureWrapper pmdStructureWrapper1 = codeReviewByClass.get(violationStructure.get(i).getName());
List<PMDStructure> pmdStructures = pmdStructureWrapper1.getPmdStructures();
pmdStructures.add(violationStructure.get(i));
pmdStructureWrapper1.setPmdStructures(pmdStructures);
} else {
pmdStructureList = new ArrayList<>();
pmdStructureList.add(violationStructure.get(i));
pmdStructureWrapper = new PMDStructureWrapper();
pmdStructureWrapper.setPmdStructures(pmdStructureList);
codeReviewByClass.put(violationStructure.get(i).getName(), pmdStructureWrapper);
}
}
long stop = System.currentTimeMillis();
LOGGER.info("Total Time Taken from PMDController "+ String.valueOf(stop-start));
if (!codeReviewByClass.isEmpty()) {
pmdMainWrapper.setPmdStructureWrapper(codeReviewByClass);
pmdMainWrapper.setPmdDuplicates(pmdDuplicatesList);
Gson gson = new GsonBuilder().create();
return gson.toJson(pmdMainWrapper);
}
return "";
}
I am going with async process because when the app is hosted in heroku, it takes almost 120 seconds to return the result to the page, but as per heroku documentation the rest api should return within 30 seconds otherwise it terminates the process,
But still after implementing the above logic I am seeing the timeout error.
I have kept a console log in the javascript console.log('Completed'); but that gets printed only when it returns the result from callURL method which is taking more than 120 seconds to return.
What i wanted to implement is when the UI sends a request, it should keep receiving a message which says still loading, so that the request does not gets timeedout?
CompletableFuture.supplyAsync() runs the specified supplier in a different thread (one from the ForkJoinThreadPool for default). thenAccept() method only runs after the previous execution returns. So, it won't return fast in your case, you're just calling the long running call in a different thread.
Instead, define a common object, which acts as a cache (such as HttpSession), and make the CompletableFuture return the object stored there. And execute callURL()only when the cache is empty:
#RequestMapping("/utilities/longProcess")
public CompletableFuture<String> async(HttpServletResponse response, HttpServletRequest request) {
HttpSession session = request.getSession();
return CompletableFuture.supplyAsync(() -> session.getAttribute("CACHED_RESULT"))
.thenComposeAsync(obj -> {
if (obj == null) {
CompletableFuture.supplyAsync(() -> callUrl(request, response))
.thenAccept(result -> session.setAttribute("CACHED_RESULT", result));
return CompletableFuture.completedFuture("not ready yet");
}
return CompletableFuture.completedFuture(obj.toString());
});
You can also add a timestamp to see when you did the last call to callUrl() and don't call callUrl() again when you've made a call but not received the answer yet.
I'm trying to create a websocket and dynamically recalculate its header in every message sent. Is it possible?
I was trying to use an interceptor but is only called once.
public void run() {
// only open a websocket if there aren't websockets already open
if (this.webSocket == null || !this.openingWS) {
this.openingWS = true;
wsBuilder = new OkHttpClient.Builder();
OkHttpClient client = wsBuilder.addInterceptor(this)
.readTimeout(0, TimeUnit.MILLISECONDS)
.build();
Request request = new Request.Builder()
.url("wss://...")
.build();
client.newWebSocket(request, this);
// Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
client.dispatcher().executorService().shutdown();
}
}
#Override public void onOpen(WebSocket webSocket, Response response) {
this.openingWS = false; // already open
this.webSocket = webSocket; // storing websocket for future usages
if (listener != null) listener.onWSOpen();
}
public void sendCommand(String cmd) {
System.out.println("SEND " + cmd);
if (webSocket != null) webSocket.send(cmd);
}
This same class is implementing the interceptor
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (!isSpecial()) return chain.proceed(originalRequest);
okhttp3.Request.Builder builder = originalRequest.newBuilder()
.addHeader("text", "...")
.addHeader("dfds", "...");
Request compressedRequest = builder.build();
return chain.proceed(compressedRequest);
}
The authentication code sent in the header will change every X seconds/minutes.
If it's not possible to change dynamically the header, what is the best way to approach this kind of connection?
Thank you for your help.
I think the headers are send only first time when you request the connection, later is depends on frames between the client and the server.
So if you want to inform the server that you had changed the header then send message with your new header. Or you can close the connection and start a new one with the new header.
I have this code for server:
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(Handlers.path()
.addPrefixPath("/item", new ItemHandler())
)
.build();
server.start();
And handler:
private class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
exchange.getPathParameters(); // always null
//ItemModel item = new ItemModel(1);
//exchange.getResponseSender().send(mapper.writeValueAsString(item));
}
}
I want to send request /item/10 and get 10 in my parameter. How to specify path and get it?
You need a PathTemplateHandler and not a PathHandler, see:
Undertow server = Undertow.builder()
.addHttpListener(8080, "0.0.0.0")
.setHandler(Handlers.pathTemplate()
.add("/item/{itemId}", new ItemHandler())
)
.build();
server.start();
Then, in your ItemHandler:
class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
// Method 1
PathTemplateMatch pathMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY);
String itemId1 = pathMatch.getParameters().get("itemId");
// Method 2
String itemId2 = exchange.getQueryParameters().get("itemId").getFirst();
}
}
The method 2 works because Undertow merges parameters in the path with the query parameters by default.
If you do not want this behavior, you can use instead:
Handlers.pathTemplate(false)
The same applies to the RoutingHandler, this is probably what you want to use eventually to handle multiple paths.
Handlers.rounting() or Handlers.routing(false)