This is AS-IS code.
public void send() throws IOException {
CloseableHttpResponse httpResponse = null;
CloseableHttpClient httpClient = null;
try {
HttpPost post = new HttpPost(url);
httpResponse = httpClient.execute(post);
} finally
{
closeapi(httpClient);
closeapi(httpResponse);
}
}
public static void closeapi(Closeable obj)
{
if (obj != null)
{
try
{
obj.close();
} catch (IOException e)
{
logger.error(LP.EXCEPTION, e);
}
}
}
And I changed that code using feign client. like this.
**[MessageFeignClient.class]**
#FeignClient(
contextId = "messageClient",
url = "${url}",
name = "messageclient",
configuration = {FeignConfiguration.class, FeignRetryConfiguration.class},
primary = false
)
public interface MessageClient {
#PostMapping("write")
PushMessageResultRdo write(
#RequestHeader("Key1") String key1,
#RequestHeader("Key2") String key2,
#RequestBody PushMessage pushMessage);
#PostMapping("end")
PushMessageResultRdo end(
#RequestHeader("Key1") String key1,
#RequestHeader("Key2") String key2,
#RequestBody PushMessage pushMessage);
}
**[MessageService.java]**
#Slf4j
#Component
#RequiredArgsConstructor
public class MessageService {
private final MessageClient messageClient;
#Override
public PushResultRdo apply(PushMessage pushMessage) {
try {
return messageClient.write(pushMessage.getKey1(), pushMessage.getKey2(), pushMessage);
} catch (HystrixRuntimeException e) {
log.error(e);
}
return PushResultRdo.defaultError();
}
}
Functionally, there is no problem.
But using feign call is not closed httpclient.
Response status is 200. But request is alived.
I checked tcp stream through wireshark program.
When messageClient.write called, after then I expect TCP [FIN, ACK] sequence.
But If using feign client, there is no connection close.
I want to close request connection.
Anyone help, please.
Thank you!
This is a useful way, but I think it's not a good solution.
I figure out feign options.
[HttpClientFeignConfiguration.class]
this.connectionManagerTimer.schedule(new TimerTask() {
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000L, (long)httpClientProperties.getConnectionTimerRepeat());
Feign client has a schedule task about closing connections.
This task's period is 3000 seconds(this is default value. you can change period. ex. feign.httpclient.connectionTimerRepeat = 30)
And connectionManager call PoolingHttpClientConnectionManager.class, it checks pool expired.
[PoolingHttpClientConnectionManager.class]
if (timeToLive > 0L) {
long deadline = this.created + timeUnit.toMillis(timeToLive);
this.validityDeadline = deadline > 0L ? deadline : 9223372036854775807L;
} else {
this.validityDeadline = 9223372036854775807L;
}
Feign default timetoLive is 900 Seconds. So If you want to close immediately after feign call, then change the timetolive value and timeToLiveUnit.
feign:
httpclient:
timeToLive: 30
# timeToLiveUnit : MILLISECONDS
Related
I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}
I have a situation where I need to return an "accepted" response for every request received and publish the actual response later to a separate endpoint outside the service.
To implement the 'accepted' Response I implemented a filter.
public class AcknowledgementFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.abortWith(Response.accepted().build());
// call Resource method in new Thread() . <------ ?
}
}
Implementation of service endpoints:
#Path("/vendor")
public class VendorHandler {
#POST
public void addVendor(VendorRequest addVendorRequest)){
vendor = new Vendor();
Client.publish(vendor); // publish request to an endpoint
return null;
}
How do I call the addVendor of VendorHandler(or any method depends on request) from the acknowledgement filter?
Is there any other way to implement an accepted response for every request then process the request separately?
You can use AsyncResponse,
#GET
#ManagedAsync
#Produces(MediaType.APPLICATION_JSON)
public void getLives(#Suspended final AsyncResponse asyncResponse,
#DefaultValue("0") #QueryParam("newestid") final int newestId,
#QueryParam("oldestid") final Integer oldestId) {
asyncResponse.setTimeoutHandler(asyncResponse1 -> {
logger.info("reached timeout");
asyncResponse1.resume(Response.ok().build());
});
asyncResponse.setTimeout(5, TimeUnit.MINUTES);
try {
List<Life> lives = oldestId == null ?
Lifes.getLastLives(newestId) : Lifes.getOlderLives(oldestId);
if (lives.size() > 0) {
final GenericEntity<List<Life>> entity = new GenericEntity<List<Life>>(lives) {
};
asyncResponse.resume(entity);
} else LifeProvider.suspend(asyncResponse);
} catch (SQLException e) {
logger.error(e, e);
asyncResponse.resume(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
}
}
Check this Link for more details.
Sometimes requests fail, and in case it's not due to client error (i.e.: not 4xx) then I'd like to retry to send the same request.
All my requests have a request id header, and when I send a certain request again (retry) I need to send the same id as the one used in the first place.
This sounds like a simple thing, but it turns out to be quite hard to accomplish with Retrofit 2, unless of course I'm missing something.
All of my requests are async, and so in the callback, in case I need to retry I'm doing this:
public void onResponse(final Call<T> call, final Response<T> response) {
if (response.isSuccessful()) {
handleResponse(response);
} else if (response.code() >= 400 && response.code() < 500) {
handleClientError(response);
} else {
call.clone().enqueue(this);
}
}
I also have an interceptor for adding headers to all requests:
new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
final Request request = chain.request();
final Request.Builder newRequestBuilder = request.newBuilder()
.addHeader("Header1-name", "Header1-value")
.addHeader("Header2-name", "Header2-value")
...
.addHeader("HeaderN-name", "HeaderN-value");
if (request.header("REQUEST-ID") == null) {
newRequestBuilder.addHeader("REQUEST-ID", UUID.randomUUID().toString());
}
return chain.proceed(newRequestBuilder.build());
}
};
I thought that since I'm cloning the Call then the (retry) request will have the headers of the previous one but that's not the case (probably because the Call is cloned and not the Request).
My problem is that I have no way of identifying the request or call in my Interceptor.intercept so I can't maintain a map of request/calls to id.
There's also no way of adding info to the calls/requests (as they are not generated by me and lack and setters for such a case).
I thought that maybe I can use the Request.tag method but again, I have no control of the object instance that is assigned there and the objects are different between requests.
And if we're already on this subject, what is this tag anyway? I can't find documentation about it.
Any idea how I can somehow pull this off?
Thanks
I took another approach to this, instead of using network interceptors and callbacks, I wrote a solution which uses a network interceptor:
class BackoffRetryInteceptor implements Interceptor {
private static final long RETRY_INITIAL_DELAY = 500;
private static final long RETRY_MAX_DELAY = 35000;
#Override
public Response intercept(final Chain chain) throws IOException {
Request request = chain.request();
final Headers.Builder headersBuilder = new Headers.Builder();
addHeaders(headersBuilder);
final Headers headers = headersBuilder.build();
long delay = RETRY_INITIAL_DELAY;
Response response = null;
IOException exception = null;
while (delay < RETRY_MAX_DELAY) {
exception = null;
request = request.newBuilder().headers(headers).build();
try {
response = chain.proceed(request);
if (response.isSuccessful() || response.code() != 500) {
return response;
}
} catch (IOException e) {
exception = e;
}
try {
Thread.sleep(delay);
delay *= 2;
} catch (InterruptedException e) {
delay = RETRY_MAX_DELAY;
}
}
if (exception != null) {
throw exception;
}
return response;
}
private static void addHeaders(final Headers.Builder headers) {
headers.add("Header1-name", "Header1-value")
.add("Header2-name", "Header2-value")
...
.add("HeaderN-name", "HeaderN-value")
.add("Request-Id", UUID.randomUUID().toString());
}
}
This seems to work well in my tests.
The main problem though is the blocking of the network threads.
If anyone can think up a better solution I'd love to hear it.
I've a class that call a Rest web service to receive a file from server. While bytes are transferred, I've created an Async task, it checks if connection with server is fine to allow the stop connection if an error appears.
This async task has a loop that I have to stop:
#Component
public class ConnectionTest {
#Async
//Check connection with the server, if for three attemp it failes, throw exception
public void checkServerConnection(String serverIp) throws Exception{
int count=0;
for(;;Thread.sleep(7000)){
try{
System.out.println("TEST");
URL url = new URL(serverIp);
HttpURLConnection con = (HttpURLConnection) url
.openConnection();
con.connect();
if (con.getResponseCode() == 200){
System.out.println("Connection established!!");
}
if (count>0) count=0;
}catch(Exception e){
count++;
if (count==3)
throw new Exception("Connection error");
}
}
}
}
but how can I stop this method from the caller?
#Autowired
private ConnectionTest connectionTest;
#Override
public Response getFile(String username, String password, String serverIp, String toStorePath, String filePath){
ResponseEntity<byte[]> responseEntity = null;
try{
//it is used to check if connection of the client with the server goes down
connectionTest.checkServerConnection();
RestClient restClient = new RestClient(username, password);
// SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// requestFactory.setBufferRequestBody(false);
// restClient.setRequestFactory(requestFactory);
// RestTemplate restClient = new RestTemplate();
responseEntity = restClient.getForEntity(serverIp + "client/file/?filePath={filePath}", byte[].class, filePath);
//TODO kill async task and return false
UPDATE: as #Thomas has suggested I've used a boolean variable in ConnectionTest, I changed for cycle with while (!stop) and after the web service call I set ConnectionTest.setStop(true).
Pay attention to set stop=false before loop (and not as instance field) otherwise only the first request has this value and goes inside the while.
UPDATE 2
This is the my last code, it seems to work, maybe I should change while loop with wait-notify:
public Response getFile(String username, String password, String serverIp, String toStorePath, String filePath){
try{
//it is used to check if connection of the client with the server goes down
Future<Boolean> isConnect = connectionTest.checkServerConnection(serverIp);
Future<ResponseEntity<byte[]>> downloadResult = downloadAsync.makeRequest(username, password, serverIp, filePath);
while(!isConnect.isDone() && !downloadResult.isDone()){
}
if (isConnect.isDone()){
downloadResult.cancel(true);
return new Response(false, false, "Error with server connection!", null);
}else{
connectionTest.setStop(true);
ResponseEntity<byte[]> responseEntity = downloadResult.get();
if (MediaType.TEXT_PLAIN.toString().equals(responseEntity.getHeaders().getContentType().toString())){
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(new FileException("Error with file transfert!"));
return new Response(false, false, new String(Base64.decodeBase64(responseEntity.getBody()),Charset.forName("UTF-8")), errorResponse);
}else{
Path p = Paths.get(filePath);
String fileName = p.getFileName().toString();
FileOutputStream fos = new FileOutputStream(toStorePath+"\\"+ fileName);
fos.write(responseEntity.getBody());
fos.close();
return new Response(true, true, "Your file has been downloaded!", null);
}
}
}catch(Exception e){
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
return new Response(false, false, "Error on the client side!" , errorResponse);
}
}
connection check async:
#Component
public class ConnectionTest {
private boolean stop;
#Async
//Check connection with the server, if for three attemp it failes, throw exception
/**
*
* #param serverIp
* #throws IOException
*/
public Future<Boolean> checkServerConnection(String serverIp) throws IOException {
int count=0;
stop = false;
while (!stop){
try{
Thread.sleep(7000);
System.out.println("TEST");
//java.net.InetAddress.getByName(SERVER_ADDRESSS);
URL url = new URL(serverIp);
HttpURLConnection con = (HttpURLConnection) url
.openConnection();
con.connect();
if (count>0) count=0;
}catch(Exception e){
count++;
System.out.println(count);
if (count==3)
return new AsyncResult<Boolean>(stop);
}
}
return new AsyncResult<Boolean>(stop);
}
/**
* #return the stop
*/
public boolean isStop() {
return stop;
}
/**
* #param stop the stop to set
*/
public void setStop(boolean stop) {
this.stop = stop;
}
}
download async:
#Component
public class DownloadAsync {
#Async
public Future<ResponseEntity<byte[]>> makeRequest(String username, String password, String serverIp, String filePath){
RestClient restClient = new RestClient(username, password);
ResponseEntity<byte[]> response= restClient.getForEntity(serverIp + "client/file/?filePath={filePath}", byte[].class, filePath);
return new AsyncResult<ResponseEntity<byte[]>>(response);
}
}
When you deal with an #Async method, a good practice is to return a Future object from it because you need a connection point between the client and task code.
Let's make your task method return a Future:
public Future<Integer> checkServerConnection(String serverIp) {
// other code here
return new AsyncResult<>(count);
}
You'll need to add a couple of imports:
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.AsyncResult;
Finally, in the client code let's get the Future:
Future<Integer> checkTask = connectionTest.checkServerConnection();
Now, you can do some useful things with the checkTask. For example:
// Check if the task was completed including by an exception being thrown.
checkTask.isDone();
// Get the task result.
Integer count = checkTask.get(); // Note: this is a blocking method.
// If the task was finished by throwing an exception,
// get() method will also throw an exception.
// You can get the cause exception like this:
if (checkTask.isDone()) {
try {
checkTask.get();
} catch(Exception e) {
Exception cause = e.getCause(); // this will be your new Exception("Connection error")
}
}
// Not recommended, but you can also cancel the task:
checkTask.cancel(mayInterruptIfRunning);
first off I don't want to perplex the issue any further so I am going to give you a high level description for doing this. Particularly, look how this is done very elegantly in android, using publish delegates.
Basically, a publish delegate consists of 2 portions. First, an overridden method to publish changes, and another method to receive changes. The time interval in which changes are received, depend on the "CHUNK" size currently in the queue and the data size, but generally, you can think of this as a best effort attempt to receive publish events.
So a big high level picture is this.
ASYNCTASK
IN BACKGROUND (DOWNLOAD OVER TIME)
IN BACKGROUND (PUBLISH DOWNLOAD PROGRESS)
PUBLISH RECEIVER ( RECEIVE UPDATE OF THE DOWNLOAD [perhaps in percent]
MAKE A DECISION FROM HERE.
I am not neglecting the importance of the Spring context here, but I think once you receive this post, you will accept it's applicability, regardless of framework.
Best,
Mobile Dev
AT
I'm trying to write a HTTP client that uses HTTP keep-alive connections. When I connection from the ClientBoostrap I get the channel. Can I reuse this for sending multiple HTTP requests? Is there any examples demonstrating the HTTP Keep Alive functionality?
Also I have another question. Now my client works without keep-alive connections. I'm calling the channel.close in the messageReceived method of the ClientHandler. But it seems the connections are not getting closed and after some time the sockets run out and I get a BindException. Any pointers will be really appreciated.
Thanks
As long as the Connection header is not set to CLOSE (and possible the HttpVersion is 1.1, though uncertain) by a line of code similar to this...
request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
...your channel should remain open for multiple request/response pairs.
Here is some example code that I whipped up today to test it. You can bounce any number of requests off of Google prior to the channel closing:
public class TestHttpClient {
static class HttpResponseReader extends SimpleChannelUpstreamHandler {
int remainingRequests = 2;
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
HttpResponse response = (HttpResponse) e.getMessage();
System.out.println("Beginning -------------------");
System.out.println(new String(response.getContent().slice(0, 50).array()));
System.out.println("End -------------------\n");
if(remainingRequests-- > 0)
sendRequest(ctx.getChannel());
else
ctx.getChannel().close();
}
}
public static void main(String[] args) {
ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory());
bootstrap.setPipeline(Channels.pipeline(
new HttpClientCodec(),
new HttpResponseReader()));
// bootstrap.setOption("child.keepAlive", true); // no apparent effect
ChannelFuture future = bootstrap.connect(new InetSocketAddress("google.com", 80));
Channel channel = future.awaitUninterruptibly().getChannel();
channel.getCloseFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
// this winds up getting called immediately after the receipt of the first message by HttpResponseReader!
System.out.println("Channel closed");
}
});
sendRequest(channel);
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void sendRequest(Channel channel) {
// Prepare the HTTP request.
HttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "http://www.google.com");
request.setHeader(HttpHeaders.Names.HOST, "google.com");
request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
channel.write(request);
}
}