/** * This method should be used when the caller wants to dispatch the request to a server chosen by * the load balancer, instead of specifying the server in the request's URI. It calculates the * final URI by calling {@link #computeFinalUriWithLoadBalancer(ClientRequest)} and then calls * {@link #execute(ClientRequest)}. * * @param request request to be dispatched to a server chosen by the load balancer. The URI can be * a partial URI which does not contain the host name or the protocol. */ public T executeWithLoadBalancer(S request) throws ClientException { int retries = 0; boolean done = false; boolean retryOkayOnOperation = okToRetryOnAllOperations; retryOkayOnOperation = request.isRetriable(); // Is it okay to retry for this particular operation? // see if maxRetries has been overriden int numRetries = maxAutoRetriesNextServer; IClientConfig overriddenClientConfig = request.getOverrideConfig(); if (overriddenClientConfig != null) { try { numRetries = Integer.parseInt( "" + overriddenClientConfig.getProperty( CommonClientConfigKey.MaxAutoRetriesNextServer, maxAutoRetriesNextServer)); } catch (Exception e) { logger.warn( "Invalid maxAutoRetriesNextServer requested for RestClient:" + this.getClientName()); } try { // Retry operation can be forcefully turned on or off for this particular request Boolean requestSpecificRetryOn = Boolean.valueOf( "" + overriddenClientConfig.getProperty( CommonClientConfigKey.RequestSpecificRetryOn, "false")); retryOkayOnOperation = requestSpecificRetryOn.booleanValue(); } catch (Exception e) { logger.warn("Invalid RequestSpecificRetryOn set for RestClient:" + this.getClientName()); } } T response = null; do { try { S resolved = computeFinalUriWithLoadBalancer(request); response = executeOnSingleServer(resolved); done = true; } catch (Exception e) { boolean shouldRetry = false; if (e instanceof ClientException) { // we dont want to retry for PUT/POST and DELETE, we can for GET shouldRetry = retryOkayOnOperation && numRetries > 0; } if (shouldRetry) { retries++; if (retries > numRetries) { throw new ClientException( ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED, "NUMBER_OF_RETRIES_NEXTSERVER_EXCEEDED :" + numRetries + " retries, while making a RestClient call for:" + request.getUri() + ":" + getDeepestCause(e).getMessage(), e); } logger.error( "Exception while executing request which is deemed retry-able, retrying ..., Next Server Retry Attempt#:" + retries + ", URI tried:" + request.getUri()); } else { if (e instanceof ClientException) { throw (ClientException) e; } else { throw new ClientException( ClientException.ErrorType.GENERAL, "Unable to execute request for URI:" + request.getUri(), e); } } } } while (!done); return response; }
/** * Compute the final URI from a partial URI in the request. The following steps are performed: * <li>if host is missing and there is a load balancer, get the host/port from server chosen from * load balancer * <li>if host is missing and there is no load balancer, try to derive host/port from virtual * address set with the client * <li>if host is present and the authority part of the URI is a virtual address set for the * client, and there is a load balancer, get the host/port from server chosen from load * balancer * <li>if host is present but none of the above applies, interpret the host as the actual physical * address * <li>if host is missing but none of the above applies, throws ClientException * * @param original Original URI passed from caller * @return new request with the final URI */ protected S computeFinalUriWithLoadBalancer(S original) throws ClientException { URI newURI; URI theUrl = original.getUri(); if (theUrl == null) { throw new ClientException(ClientException.ErrorType.GENERAL, "NULL URL passed in"); } String host = theUrl.getHost(); Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original); String scheme = schemeAndPort.first(); int port = schemeAndPort.second(); // Various Supported Cases // The loadbalancer to use and the instances it has is based on how it was registered // In each of these cases, the client might come in using Full Url or Partial URL ILoadBalancer lb = getLoadBalancer(); Object loadBalancerKey = original.getLoadBalancerKey(); if (host == null) { // Partial URL Case // well we have to just get the right instances from lb - or we fall back if (lb != null) { Server svc = lb.chooseServer(loadBalancerKey); if (svc == null) { throw new ClientException( ClientException.ErrorType.GENERAL, "LoadBalancer returned null Server for :" + clientName); } host = svc.getHost(); port = svc.getPort(); if (host == null) { throw new ClientException( ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } if (logger.isDebugEnabled()) { logger.debug(clientName + " using LB returned Server:" + svc + "for request:" + theUrl); } } else { // No Full URL - and we dont have a LoadBalancer registered to // obtain a server // if we have a vipAddress that came with the registration, we // can use that else we // bail out if (vipAddresses != null && vipAddresses.contains(",")) { throw new ClientException( ClientException.ErrorType.GENERAL, this.clientName + "Partial URI of (" + theUrl + ") has been sent in to RestClient (with no LB) to be executed." + " Also, there are multiple vipAddresses and hence RestClient cant pick" + "one vipAddress to complete this partial uri"); } else if (vipAddresses != null) { try { Pair<String, Integer> hostAndPort = deriveHostAndPortFromVipAddress(vipAddresses); host = hostAndPort.first(); port = hostAndPort.second(); } catch (URISyntaxException e) { throw new ClientException( ClientException.ErrorType.GENERAL, this.clientName + "Partial URI of (" + theUrl + ") has been sent in to RestClient (with no LB) to be executed." + " Also, the configured/registered vipAddress is unparseable (to determine host and port)"); } } else { throw new ClientException( ClientException.ErrorType.GENERAL, this.clientName + " has no LoadBalancer registered and passed in a partial URL request (with no host:port)." + " Also has no vipAddress registered"); } } } else { // Full URL Case // This could either be a vipAddress or a hostAndPort or a real DNS // if vipAddress or hostAndPort, we just have to consult the loadbalancer // but if it does not return a server, we should just proceed anyways // and assume its a DNS // For restClients registered using a vipAddress AND executing a request // by passing in the full URL (including host and port), we should only // consult lb IFF the URL passed is registered as vipAddress in Discovery boolean shouldInterpretAsVip = false; if (lb != null) { shouldInterpretAsVip = isVipRecognized(original.getUri().getAuthority()); } if (shouldInterpretAsVip) { Server svc = lb.chooseServer(loadBalancerKey); if (svc != null) { host = svc.getHost(); port = svc.getPort(); if (host == null) { throw new ClientException( ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } if (logger.isDebugEnabled()) { logger.debug("using LB returned Server:" + svc + "for request:" + theUrl); } } else { // just fall back as real DNS if (logger.isDebugEnabled()) { logger.debug( host + ":" + port + " assumed to be a valid VIP address or exists in the DNS"); } } } else { // consult LB to obtain vipAddress backed instance given full URL // Full URL execute request - where url!=vipAddress if (logger.isDebugEnabled()) { logger.debug("Using full URL passed in by caller (not using LB/Discovery):" + theUrl); } } } // end of creating final URL if (host == null) { throw new ClientException( ClientException.ErrorType.GENERAL, "Request contains no HOST to talk to"); } // just verify that at this point we have a full URL try { String urlPath = ""; if (theUrl.getRawPath() != null && theUrl.getRawPath().startsWith("/")) { urlPath = theUrl.getRawPath(); } else { urlPath = "/" + theUrl.getRawPath(); } newURI = new URI( scheme, theUrl.getUserInfo(), host, port, urlPath, theUrl.getQuery(), theUrl.getFragment()); return (S) original.replaceUri(newURI); } catch (URISyntaxException e) { throw new ClientException(ClientException.ErrorType.GENERAL, e.getMessage()); } }
/** * Execute the request on single server after the final URI is calculated. This method takes care * of retries and update server stats. */ protected T executeOnSingleServer(S request) throws ClientException { boolean done = false; int retries = 0; boolean retryOkayOnOperation = okToRetryOnAllOperations; if (request.isRetriable()) { retryOkayOnOperation = true; } int numRetries = maxAutoRetries; URI uri = request.getUri(); Server server = new Server(uri.getHost(), uri.getPort()); ServerStats serverStats = null; ILoadBalancer lb = this.getLoadBalancer(); if (lb instanceof AbstractLoadBalancer) { LoadBalancerStats lbStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats(); serverStats = lbStats.getSingleServerStat(server); } IClientConfig overriddenClientConfig = request.getOverrideConfig(); if (overriddenClientConfig != null) { try { numRetries = Integer.parseInt( "" + overriddenClientConfig.getProperty( CommonClientConfigKey.MaxAutoRetries, maxAutoRetries)); } catch (Exception e) { logger.warn("Invalid maxRetries requested for RestClient:" + this.clientName); } } T response = null; Exception lastException = null; if (tracer == null) { tracer = Monitors.newTimer(this.getClass().getName() + "_ExecutionTimer", TimeUnit.MILLISECONDS); } do { noteOpenConnection(serverStats, request); Stopwatch w = tracer.start(); try { response = execute(request); done = true; } catch (Exception e) { if (serverStats != null) { serverStats.addToFailureCount(); } lastException = e; if (isCircuitBreakerException(e) && serverStats != null) { serverStats.incrementSuccessiveConnectionFailureCount(); } boolean shouldRetry = retryOkayOnOperation && numRetries >= 0 && isRetriableException(e); if (shouldRetry) { retries = handleRetry(uri.toString(), retries, numRetries, e); } else { ClientException niwsClientException = generateNIWSException(uri.toString(), e); throw niwsClientException; } } finally { w.stop(); noteRequestCompletion( serverStats, request, response, lastException, w.getDuration(TimeUnit.MILLISECONDS)); } } while (!done); return response; }