Request by restTemplate to Netty Server hangs the thread - java

RestTemplate example is below.
public class SimpleClient {
private final String URL;
private AsyncRestTemplate rest = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory());
private RestTemplate restTemplate = new RestTemplate(new Netty4ClientHttpRequestFactory());
public SimpleClient(String url) {
this.URL = url;
Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory();
try {
nettyFactory.setSslContext(SslContextBuilder.forClient().build());
} catch (SSLException e) {
e.printStackTrace();
}
rest = new AsyncRestTemplate(nettyFactory);
}
#Override
public ResponseEntity<ResponseData> doSendByPOST(RequestData data,Class<ResponseData> clazz) {
List<HttpMessageConverter<?>> messageConvertors = new ArrayList<>();
messageConvertors.add(new MappingJackson2HttpMessageConverter());
rest.setMessageConverters(messageConvertors);
restTemplate.setMessageConverters(messageConvertors);
HttpHeaders headers = new HttpHeaders();
ObjectMapper objectMapper = new ObjectMapper();
StringWriter writer = new StringWriter();
try {
objectMapper.writeValue(writer, data);
} catch (IOException e) {
e.printStackTrace();
}
headers.set(HttpHeaders.CONTENT_LENGTH,String.valueOf(writer.toString().getBytes(Charset.forName("UTF-8")).length));
headers.set(HttpHeaders.CONTENT_TYPE,"application/json");
HttpEntity<ResponseData> request = new HttpEntity<ResponseData>(headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
try {
parts.add("requestData", objectMapper.writeValueAsString(data));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// return restTemplate.exchange(this.URL,HttpMethod.POST ,request, clazz, parts);
ListenableFuture<ResponseEntity<ResponseData>> entity = rest.exchange(this.URL,HttpMethod.POST ,request, clazz, parts);
return extractResponseEntity(entity);
}
// ...
}
Netty read data from request channelRead method
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
DefaultHttpRequest defaultHttpRequest = (DefaultHttpRequest) msg;
if (EmptyHttpHeaders.is100ContinueExpected(defaultHttpRequest)) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.CONTINUE));
}
boolean keepAlive = EmptyHttpHeaders.isKeepAlive(defaultHttpRequest);
handle = frontController.dispatchRequest(defaultHttpRequest);
}
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
ByteArrayOutputStream body = new ByteArrayOutputStream(64);
ByteBuf content = httpContent.content();
if (content.isReadable()) {
//body.write(content.array());
content.readBytes(body,content.readableBytes());
//body.append(content.toString(CharsetUtil.UTF_8));
FullHttpResponse response = handle.handle(body);
if(response == null){
response = prepareDefaultResponse();
}
response.headers().set("content-type", "application/json");
response.headers().set("content-length", response.content().readableBytes());
response.headers().set("connection", HttpHeaderValues.KEEP_ALIVE);
}
if (msg instanceof LastHttpContent) {
//process request
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
The code below is working fine but I guess there is a problem with blocking io and nonblocking io. When the request is dispatched, I can not reach the HttpContent I only get HttpRequest as a msg parameter. Spring resttemplate waits for a response but Netty does not care :)
if (msg instanceof HttpRequest) {
DefaultHttpRequest defaultHttpRequest = (DefaultHttpRequest) msg;
if (EmptyHttpHeaders.is100ContinueExpected(defaultHttpRequest)) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.CONTINUE));
}
boolean keepAlive = EmptyHttpHeaders.isKeepAlive(defaultHttpRequest);
handle = frontController.dispatchRequest(defaultHttpRequest);
}
My problem is how to get response from netty server by rest template.
I have tried many ways to accomplish full req/resp.
When restTemplate request to Netty server it hangs the thread so I can not move on the distributed in memory cache implementation.
Hanging in RestTemplate.java Line : 681
Method waits forever when using Netty4ClientHttpRequestFactory.
response = request.execute();

From my understanding, you read HTTP post request that from Rest Client as HttpRequest Object lets call it first case so that means you don't even branch on the if (msg instanceof HttpContent) {} case (second one) your HTTP server just writes the default response without any content or header that you're setting in the second case. If this is the cause for the blocking on the client side you have to fill that default response just like on the second case an see what client do.
I think netty API provides this
https://netty.io/4.1/api/io/netty/handler/codec/http/DefaultFullHttpResponse.html
Also this example could give you an idea of what could be wrong server side.
http://www.seepingmatter.com/2016/03/30/a-simple-standalone-http-server-with-netty.html

Related

How do I transfer data from an asynchronous request to a class variable? OkHttp3, Java

I'm using an asynchronous request (because synchronous doesn't work correctly with this API), the OkHttp3 library. Inside the request, I get a response in the form of JSON. I need to somehow pass values from Json to a class variable. I decided to try it this way, but it doesn't work.
public String sessionId = null;
...
public QRcodeReader() throws Exception {
this.sessionId = null;
}
... // between this code I have 1 function, which reg my number.
// in this func I need to confirm my phone by code in SMS.
public void SmsCode(String Code) // String get from another class
{
SmsJson smsJson = new SmsJson("*phoneNumber*", "*secret_key*", "*os*", Code);
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
RequestBody body = RequestBody.create(
MediaType.parse("application/json"), gson.toJson(smsJson));
Request request = new Request.Builder()
.url("https://irkkt-mobile.nalog.ru:8888/v2/auth/phone/verify")
.addHeader("Host", HOST)
.addHeader("Accept", ACCEPT)
.addHeader("Device-OS", DEVICE_OS)
.addHeader("Device-ID", DEVICE_ID)
.addHeader("clientVersion", CLIENT_VERSION)
.addHeader("Accept-Language", ACCEPT_LANGUAGE)
.addHeader("User-Agent", USER_AGENT)
.post(body)
.build();
httpClient.newCall(request).enqueue(new Callback() {
#Override
public void onResponse(#NonNull Call call, #NonNull Response response) throws IOException {
try (Response responseBody = httpClient.newCall(request).execute()) {
if (!responseBody.isSuccessful())
throw new IOException("Unexpected code " + responseBody);
// Here I try to transfer data from request to class variable. Not Work.
SetSessionId(Objects.requireNonNull(responseBody.body()).string());
System.out.println(Objects.requireNonNull(responseBody.body()).string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
void SetSessionId(String sessionId){
this.sessionId = sessionId;
}
I need to use SessionID further along here:
public String GetTicketID(String QR){
TicketID ticketID = new TicketID(QR);
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
RequestBody body = RequestBody.create(
MediaType.parse("application/json"), gson.toJson(ticketID));
Request request = new Request.Builder()
.url("https://irkkt-mobile.nalog.ru:8888/v2/ticket")
.addHeader("Host", HOST)
.addHeader("Accept", ACCEPT)
.addHeader("Device-OS", DEVICE_OS)
.addHeader("Device-ID", DEVICE_ID)
.addHeader("clientVersion", CLIENT_VERSION)
.addHeader("Accept-Language", ACCEPT_LANGUAGE)
.addHeader("User-Agent", USER_AGENT)
.addHeader("sessionId", sessionId) // Here I get NULL and exception!
.post(body)
.build();
httpClient.newCall(request).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) throws IOException {
try (Response responseBody = httpClient.newCall(request).execute()) {
if (!responseBody.isSuccessful())
throw new IOException("Unexpected code " + responseBody);
System.out.println(Objects.requireNonNull(responseBody.body()).string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
return ""; // Here I need to transfer id to another variable, not did it yet.
}
The problem is that SessionID = Null and the request does not work.
I think I'm doing something wrong, but I can't figure out what. May be it is because async request..

Why ObjectMapperr does not write values to file?

I wrote a method that gets JSON, converts an object to java and writes JSON to a file.But writing to the file just does not work. Tell me what could be the reason?
public class ApiUtils {
public static HttpClient client = HttpClient.newHttpClient();
public static void getRequest(String url, String path) {
PostDTO postDTO = new PostDTO();
String pathJSONFile = "src/main/resources/Post.json";
List<PostPojo> postPojos = null;
HttpRequest request = HttpRequest.newBuilder()
.GET()
.header("accept", "application/json")
.uri(URI.create(url + path))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper objectMapper = new ObjectMapper();
postPojos = objectMapper.readValue(response.body(), new TypeReference<List<PostPojo>>() {
});
objectMapper.writeValue(Paths.get("allPost.json").toFile(), response.body());
postDTO.setStatus(response.statusCode());
postDTO.setPosts(postPojos);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(postDTO);
}
}
File allPost.json located in the folder resources.
You're writing the wrong object I think - your code sample says
objectMapper.writeValue(Paths.get("allPost.json").toFile(), response.body());
which is trying to write the InputStream of response.body(). I presume you meant to try and write out postPojos via
objectMapper.writeValue(Paths.get("allPost.json").toFile(), postPojos);
instead.

i write like this, but still got the error "A connection to XXX was leaked. Did you forget to close a response body? "

I don't know how to avoid it, i have closed the response body already!
Can someone help me solve this problem?
my code:
public String getPageFromServer(String activityKey) throws Exception {
String address = pageServerHolder.getServerAddressRandom();
String url = MessageFormat.format(URL, address, activityKey);
log.debug("=============== url [{}] ================", url);
OkHttpClient client = new OkHttpClient().newBuilder().connectTimeout(60, TimeUnit.SECONDS).callTimeout(1, TimeUnit.SECONDS).build();
final Request request = new Request.Builder().url(url).get().build();
Response response = client.newCall(request).execute();
try (ResponseBody body = response.body()) {
if (response.isSuccessful() && null != body) {
String bodyString = body.string();
if (StringUtils.isNotBlank(bodyString)) {
body.close();
return bodyString;
}
}
}
throw new RuntimeException(MessageFormat.format("获取活动页信息异常,url [{0}], response.code [{1}],response.message [{2}] ", url, response.code(), response.message()));
}
Your problem is that the Response is not closed. Your code should be something like this:
try (Response response = client.newCall(request).execute();
ResponseBody body = response.body()) {
if (response.isSuccessful() && null != body) {
String bodyString = body.string();
if (StringUtils.isNotBlank(bodyString)) {
return bodyString;
}
}
}
I'm not sure if you need to close the ResponseBody. It is possible that closing response will deal with it. However, there is little harm in a redundant close.
Check out the example here: https://square.github.io/okhttp/
ResponseBody implements the Closeable interface. You are creating ResponseBody in the resource section of the try block, it will be closed for you as you leave the try block.
Your "body.close()" should not be there.
try (Response response = httpClient.newCall(request).execute();
ResponseBody responseBody = response.body()) {
if (response.isSuccessful()) {
log.info("access tree from {} success", url);
return objectMapper.readValue(responseBody.string(), StorageTreeResponse.class);
} else {
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
I am wondering this is just a bug from okhttp client. Even the try resource syntax didn't solve the warnings.

How can we make asynchronous REST api call in Java?

I am using Spring RestTemplate and want to make a call to another service that doesn't return any response body. So, I don't want to wait for the response. So, it's just fire and forget, and continue with the remaining code. I am thinking of creating a new Thread to do this but really not sure what's the correct approach.
If you use Java 11, java support asynchronous HTTP Client. Asynchronous client using CompletableFuture in the back. You can see javadoc.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(response -> { System.out.println(response.statusCode());
return response; } )
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
The correct approach is to execute the async with a callback (using DeferredResult, like this (assuming we have a class someClass that we want to retrieve from the API:
#GetMapping(path = "/testingAsync")
public DeferredResult<String> value() throws ExecutionException, InterruptedException, TimeoutException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
String baseUrl = "http://someUrl/blabla";
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String value = "";
HttpEntity entity = new HttpEntity("parameters", requestHeaders);
final DeferredResult<String> result = new DeferredResult<>();
ListenableFuture<ResponseEntity<someClass>> futureEntity = restTemplate.getForEntity(baseUrl, someClass.class);
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<someClass>>() {
#Override
public void onSuccess(ResponseEntity<someClass> result) {
System.out.println(result.getBody().getName());
result.setResult(result.getBody().getName());
}
#Override
public void onFailure(Throwable ex) {
result.setErrorResult(ex.getMessage());
}
});
return result;
}
There are many ways you can use to fire the request using the AsyncRestTemplate
The simplest way is just like restTemplate and call exchange method:
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
JSONObject json = new JSONObject();
json.put("firstName","testUser");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<String>(json.toString(), headers);
Class<String> responseType = String.class;
ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.exchange("https://xxxxx.com/", HttpMethod.POST, requestEntity,responseType );
// If you want for the result then you can use
try {
//waits for the result
ResponseEntity<String> entity = future.get();
//prints body source code for the given URL
log.info(entity.getBody());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
If we want to play with the failure (fallback scenario) or success in that case we can use the below code :
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
JSONObject json = new JSONObject();
json.put("firstName","testUser");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<String>(json.toString(), headers);
//final DeferredResult<String> result = new DeferredResult<>();
ListenableFuture<ResponseEntity<String>> future =
asyncRestTemplate.postForEntity("https://xxxx.com", requestEntity, String.class);
future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onFailure(Throwable ex) {
// insert into the table or log or some other decision
log.info(ex.getMessage());
}
#Override
public void onSuccess(ResponseEntity<String> result) {
log.info(result.getBody());
log.info("Sucess");
}
});

Apache HttpClient - Out Of Memory Issue

I have a processor that reads messages from Apache Kafka and sends the data to a REST Endpoint.
The server only has 4 cores and 4 GB ram, out of which a max of 2GB is allocated to the java process
Messages are produced and consumed at the rate of 4k/second.
After running couple of minutes, the program goes out of memory.
What is the best way to call http rest end-points asynchronously and not wait for response
How to manage the httpClient connection? I was under the impression that I need to start the client an never close it so I can reuse the connection
Do you see any issues with the below code
public class SomeProcesor implements BProcessor {
private ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
private CompletionService<Boolean> pool = new ExecutorCompletionService<Boolean>(exec);
CloseableHttpAsyncClient httpclient = null ;
#Override
public void begin() {
httpclient = HttpAsyncClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(5000).setConnectTimeout(5000).setSocketTimeout(5000).build();
HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig).build();
// Start the client
httpclient.start();
}
#Override
public void process(MessageAndMetadata<?, ?> mMData, List<Map> events) {
List<Map<String, Object>> listMap = new ArrayList<>();
// loop and extract the data from events into the above List
//..
//..
// submit to seperate thread to post to HTTP
pool.submit(new HttpThread(listMap);
}
private class HttpThread implements Callable<Boolean> {
List<Map<String, Object>> listMap = null;
public HttpThread(List<Map<String, Object>> listMap) {
this.listMap = listMap;
}
#Override
public Boolean call() throws Exception {
return postToHttp(listMap);
}
}
private Boolean postToHttp(List<Map<String, Object>> listMap) throws UnsupportedEncodingException {
for (Map<String, Object> map : listMap) {
try {
HttpPost postRequest = new HttpPost("https://myserver:8080/services/collector");
postRequest.addHeader(HttpHeaders.ACCEPT, "application/json");
postRequest.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
postRequest.addHeader(HttpHeaders.CONNECTION, "keep-alive");
StringEntity input = new StringEntity(methodToConvertMapToJSON(map));
input.setContentType("application/json");
postRequest.setEntity(input);
httpclient.execute(postRequest, null);
} catch (Exception e) {
return false;
} catch (Throwable th) {
return false;
}
}
return true;
}
}
need consume the http response or release connection, otherwise the connection will consume resources.
change
httpclient.execute(postRequest, null);
to
HttpResponse response = httpclient.execute(postRequest, null).get();
if(response.getStatusLine().getStatusCode() != 200) {
// do something
}
// release the connection, better add to a finally clause
postRequest.releaseConnection();

Categories

Resources