any frameworks to handle REST Client server list? [closed]
Asked Answered
G

2

6

The idea is that REST Client can be configured with list of REST Servers. So the servers list will rotate on the REST Client in a round robin fashion.

e.g. REST Client Application. I'll be configuring server list (REST_SERVER1,REST_SERVER2, REST_SERVER3)

1 request -> REST_SERVER1
2 request -> REST_SERVER2
3 request  -> REST_SERVER3
4 request -> REST_SERVER1

I searched so much I couldn't found a proper framework that supports this functionality.

Gavra answered 17/7, 2012 at 10:4 Comment(12)
This is something which can be easily handled at physical server level by using a load balancer. Isn't it?Florid
What do you consider being a failure in your case?Mortensen
@JeromeLouvel it could be mostly network downtime and server maintenance or with regard to server upgrades. is there any REST Client framework that can handle this neatly?Gavra
@Florid well it's more adhoc. we time to time tend to change the servers based on seasons and our disaster recovery servers. So ideally I was hoping that the REST Client which is the Application Server that remains unchanged. would be able to work with active set off server list from property file may be.Gavra
Nobody in their right mind would ever do this. You just have redundant application servers and the web browser connect via a reverse proxy and a load balancer. If any of your application servers are down, the application keeps chugging along, because the other servers take the load.Musk
@Musk true.unfortunately we don't have the control to these servers anymore. It's out of our hands, and they don't want to do that as well. So our Rest Client should be configured to use the given server list.we have no choice but to handle it our self.Gavra
Well as an idea then, you can proxy the requests to their servers via your server (in a round robin fashion if you like). So basically your send a request to your server always, but your server just queries their servers from your configurable list.Musk
@Musk thanks alot.Thought this was something someone else may have faced too.so I was more looking if there's any decent java framework library that can muster these. for I could reduce many issues that may arise and not having to build it from the scratch.But so far it appears I have do it, you have any code pointers perhaps?Gavra
I wonder am I the only one to ever have this problem?Gavra
I doubt this problem is common enough or problematic enough to warrant a framework. Since the solution is so easy (as I've shown!!) I doubt anyone would build a framework for this and personally I wouldn't want to be constricted by a framework for something like this.Delanadelancey
@Delanadelancey probably u have not thought about scale-ability of your application.. the idea of proper management. if you're still not convinced i suggest you look architecture here hector-client.github.com/hector/build/html/content/…Gavra
@Macon, isnt cassandra for the server side? There any many frameworks/projects for dealing with large numbers of requests. Your question was about a client.Delanadelancey
D
6

I'd just wrap your client then repeat the request until it succeeds. That way you can neatly keep track of which servers are working. Here's some code I adapted from our system...

import java.io.IOException;
import java.net.URL;
import java.util.Calendar;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

public class RestClient {

    private final String mediaType = "application/json";
    private RestServer[] servers = new RestServer[] {new RestServer("server1", 8080), new RestServer("server2", 8080)};

    protected RestClient() {
    }

    protected ClientResponse post(String methodUrl, Object postData) throws IOException {
        return doRequest(methodUrl, postData, true);
    }

    protected ClientResponse get(String methodUrl) throws IOException {
        return doRequest(methodUrl, null, false);
    }

    private ClientResponse doRequest(String methodUrl, Object postData, boolean isPost) throws IOException {

        Client client = Client.create();

        for (RestServer restServer : servers) {

            if (!restServer.shouldTry()) {
                System.out.println(restServer + " not ready");
                continue;
            }

            System.out.println("Trying with " + restServer);

            try {
                URL url = new URL("http", restServer.getHost(), restServer.getPort(), '/' + methodUrl);
                WebResource webResource = client.resource(url.toString());

                System.out.println("Calling " + url);
                ClientResponse response = isPost
                    ? webResource.type(mediaType).post(ClientResponse.class, postData)
                    : webResource.type(mediaType).get(ClientResponse.class);

                if (response.getStatus() < 300) {
                    restServer.succeeded();
                    return response;
                }

                restServer.failed();

            } catch (Exception ex) {
                System.out.println(restServer + " failed with exception " + ex.getMessage());
                restServer.failed();
            }
        }

        // No servers worked
        return null;
    }
}

class RestServer {

    private final int TIME_TO_WAIT_BEFORE_TRYING_AGAIN = 1000 * 30; // 30 seconds
    private String host;
    private int port;
    private Calendar lastAttempted;
    private boolean lastCallFailed;

    public RestServer(String host, int port) {
        this.host = host;
        this.port = port;
        lastAttempted = Calendar.getInstance();
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public void failed() {
        lastCallFailed = true;
        lastAttempted = Calendar.getInstance();
    }

    public void succeeded() {
        lastCallFailed = false;
        lastAttempted = Calendar.getInstance();
    }

    public boolean shouldTry() {
        if (!lastCallFailed)
            return true;

        return Calendar.getInstance().compareTo(lastAttempted) > TIME_TO_WAIT_BEFORE_TRYING_AGAIN;
    }

    @Override
    public String toString() {
        return new StringBuilder(host).append(':').append(port).toString();
    }
}
Delanadelancey answered 26/7, 2012 at 17:0 Comment(0)
S
1

On the fear adding a bunch of components to complicate your application setup, I might consider a quick-and-dirty, pure Java solution.

Here's something fun I came up with using Spring's RestTemplate. If you are comfortable with interceptors, aspects, other things that can wrap up a method call, you can apply these principles to wrap up all of the various RestTemplate REST calls. See the RestTemplate javadoc

import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.*;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

public class Stuff {

    // Make this configurable.
    Collection<String> serverList;
    // or do something a little smarter, like this interface. I'll use this in this example.
    ServerLookup serverLookup;

    interface ServerLookup {
        Iterator<String> getValidServerListIterator();
        void markUnreachableServer(String url);
    }

    // Do it externally around RestTemplate...
    @Test
    public void testNormalRestTemplate() throws Exception {
        RestTemplate restTemplate = new RestTemplate();
        Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
        while (serverIterator.hasNext()) {
            String server = serverIterator.next();
            try {
                Object obj = restTemplate.getForObject(server + "/objectIdentifier/511", Object.class);
                break;
            } catch (ResourceAccessException e) {
                serverLookup.markUnreachableServer(server);
            }
        }
    }

    // or you can try to 'enhance' RestTemplate to contain the retry logic within. It's a bit hacky, but more fun.
    @Test
    public void testMyRestTemplate() {
        RestTemplate rt = new MyRestTemplate();
        Object obj = rt.getForObject("/objectIdentifier/511", Object.class);
        rt.delete("/objectIdentifier/511");
    }

    // Here's a way to (hackily) augment RestTemplate with retry functionality
    class MyRestTemplate extends RestTemplate {

        // Unfortunately RestTemplate probably wasn't designed for much extensibility. URI objects can't be made from
        // URL fragments, so these two methods are the 'furthest in' that we can override and cover all RestTemplate
        // REST methods.
        @Override
        public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
                             ResponseExtractor<T> responseExtractor, Object... urlVariables) throws RestClientException {

            Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
            while (serverIterator.hasNext()) {
                String server = serverIterator.next();
                // prefix the URL fragment passed in with a server
                String fullUrl = server + url;
                UriTemplate uriTemplate = new HttpUrlTemplate(fullUrl);
                URI expanded = uriTemplate.expand(urlVariables);
                try {
                    return doExecute(expanded, method, requestCallback, responseExtractor);
                } catch (ResourceAccessException e) {
                    serverLookup.markUnreachableServer(server);
                }
            }
            throw new RuntimeException("Unable to reach any servers in the server list for " + url);
        }

        @Override
        public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
                             ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables) throws RestClientException {

            Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
            while (serverIterator.hasNext()) {
                String server = serverIterator.next();
                // prefix the URL fragment passed in with a server
                String fullUrl = server + url;
                UriTemplate uriTemplate = new HttpUrlTemplate(fullUrl);
                URI expanded = uriTemplate.expand(urlVariables);
                try {
                    return doExecute(expanded, method, requestCallback, responseExtractor);
                } catch (ResourceAccessException e) {
                    serverLookup.markUnreachableServer(server);
                }
            }
            throw new RuntimeException("Unable to reach any servers in the server list for " + url);
        }

        /** Exact duplicate of the inner class of RestTemplate. Can not touch privates. */
        class HttpUrlTemplate extends UriTemplate {
            public HttpUrlTemplate(String uriTemplate) {
                super(uriTemplate);
            }

            @Override
            protected URI encodeUri(String uri) {
                try {
                    String encoded = UriUtils.encodeHttpUrl(uri, "UTF-8");
                    return new URI(encoded);
                }
                catch (UnsupportedEncodingException ex) {
                    // should not happen, UTF-8 is always supported
                    throw new IllegalStateException(ex);
                }
                catch (URISyntaxException ex) {
                    throw new IllegalArgumentException("Could not create HTTP URL from [" + uri + "]: " + ex, ex);
                }
            }
        }
    }
}
Serval answered 26/7, 2012 at 2:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.