摘要:序本文主要研究一下的參數這里有一個類型的變量,用來記錄請求次數另外還有一個,讀取的是值,讀取不到默認取,為進入該方法的時候,調用,遞增請求次數,然后判斷有無超出限制,有則返回帶有異常的,即通過返回如果沒有超出限制,但是執行請求失敗,則
序
本文主要研究一下jdk httpclient的retry參數
DEFAULT_MAX_ATTEMPTSjava.net.http/jdk/internal/net/http/MultiExchange.java
class MultiExchange{ static final Logger debug = Utils.getDebugLogger("MultiExchange"::toString, Utils.DEBUG); private final HttpRequest userRequest; // the user request private final HttpRequestImpl request; // a copy of the user request final AccessControlContext acc; final HttpClientImpl client; final HttpResponse.BodyHandler responseHandler; final HttpClientImpl.DelegatingExecutor executor; final AtomicInteger attempts = new AtomicInteger(); HttpRequestImpl currentreq; // used for retries & redirect HttpRequestImpl previousreq; // used for retries & redirect Exchange exchange; // the current exchange Exchange previous; volatile Throwable retryCause; volatile boolean expiredOnce; volatile HttpResponse response = null; // Maximum number of times a request will be retried/redirected // for any reason static final int DEFAULT_MAX_ATTEMPTS = 5; static final int max_attempts = Utils.getIntegerNetProperty( "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS ); //...... }
這里有一個AtomicInteger類型的attempts變量,用來記錄請求次數
另外還有一個max_attempts,讀取的是jdk.httpclient.redirects.retrylimit值,讀取不到默認取DEFAULT_MAX_ATTEMPTS,為5
MultiExchange.responseAsyncImpljava.net.http/jdk/internal/net/http/MultiExchange.java
private CompletableFutureresponseAsyncImpl() { CompletableFuture cf; if (attempts.incrementAndGet() > max_attempts) { cf = failedFuture(new IOException("Too many retries", retryCause)); } else { if (currentreq.timeout().isPresent()) { responseTimerEvent = ResponseTimerEvent.of(this); client.registerTimer(responseTimerEvent); } try { // 1. apply request filters // if currentreq == previousreq the filters have already // been applied once. Applying them a second time might // cause some headers values to be added twice: for // instance, the same cookie might be added again. if (currentreq != previousreq) { requestFilters(currentreq); } } catch (IOException e) { return failedFuture(e); } Exchange exch = getExchange(); // 2. get response cf = exch.responseAsync() .thenCompose((Response response) -> { HttpRequestImpl newrequest; try { // 3. apply response filters newrequest = responseFilters(response); } catch (IOException e) { return failedFuture(e); } // 4. check filter result and repeat or continue if (newrequest == null) { if (attempts.get() > 1) { Log.logError("Succeeded on attempt: " + attempts); } return completedFuture(response); } else { this.response = new HttpResponseImpl<>(currentreq, response, this.response, null, exch); Exchange oldExch = exch; return exch.ignoreBody().handle((r,t) -> { previousreq = currentreq; currentreq = newrequest; expiredOnce = false; setExchange(new Exchange<>(currentreq, this, acc)); return responseAsyncImpl(); }).thenCompose(Function.identity()); } }) .handle((response, ex) -> { // 5. handle errors and cancel any timer set cancelTimer(); if (ex == null) { assert response != null; return completedFuture(response); } // all exceptions thrown are handled here CompletableFuture errorCF = getExceptionalCF(ex); if (errorCF == null) { return responseAsyncImpl(); } else { return errorCF; } }) .thenCompose(Function.identity()); } return cf; }
進入該方法的時候,調用attempts.incrementAndGet(),遞增請求次數,然后判斷有無超出限制,有則返回帶有new IOException("Too many retries", retryCause)異常的failedFuture,即通過CompletableFuture.completeExceptionally返回
如果沒有超出限制,但是執行請求失敗,則調用getExceptionalCF來判斷是否應該重試,如果返回null,則重試,通過再次調用responseAsyncImpl,通過這種遞歸調用完成重試邏輯
MultiExchange.getExceptionalCFjava.net.http/jdk/internal/net/http/MultiExchange.java
/** * Takes a Throwable and returns a suitable CompletableFuture that is * completed exceptionally, or null. */ private CompletableFuturegetExceptionalCF(Throwable t) { if ((t instanceof CompletionException) || (t instanceof ExecutionException)) { if (t.getCause() != null) { t = t.getCause(); } } if (cancelled && t instanceof IOException) { if (!(t instanceof HttpTimeoutException)) { t = toTimeoutException((IOException)t); } } else if (retryOnFailure(t)) { Throwable cause = retryCause(t); if (!(t instanceof ConnectException)) { if (!canRetryRequest(currentreq)) { return failedFuture(cause); // fails with original cause } } // allow the retry mechanism to do its work retryCause = cause; if (!expiredOnce) { if (debug.on()) debug.log(t.getClass().getSimpleName() + " (async): retrying...", t); expiredOnce = true; // The connection was abruptly closed. // We return null to retry the same request a second time. // The request filters have already been applied to the // currentreq, so we set previousreq = currentreq to // prevent them from being applied again. previousreq = currentreq; return null; } else { if (debug.on()) { debug.log(t.getClass().getSimpleName() + " (async): already retried once.", t); } t = cause; } } return failedFuture(t); } private boolean retryOnFailure(Throwable t) { return t instanceof ConnectionExpiredException || (RETRY_CONNECT && (t instanceof ConnectException)); } /** Returns true if the given request can be automatically retried. */ private static boolean canRetryRequest(HttpRequest request) { if (RETRY_ALWAYS) return true; if (isIdempotentRequest(request)) return true; return false; } /** Returns true is given request has an idempotent method. */ private static boolean isIdempotentRequest(HttpRequest request) { String method = request.method(); switch (method) { case "GET" : case "HEAD" : return true; default : return false; } } private Throwable retryCause(Throwable t) { Throwable cause = t instanceof ConnectionExpiredException ? t.getCause() : t; return cause == null ? t : cause; } /** True if ALL ( even non-idempotent ) requests can be automatic retried. */ private static final boolean RETRY_ALWAYS = retryPostValue(); /** True if ConnectException should cause a retry. Enabled by default */ private static final boolean RETRY_CONNECT = retryConnect(); private static boolean retryPostValue() { String s = Utils.getNetProperty("jdk.httpclient.enableAllMethodRetry"); if (s == null) return false; return s.isEmpty() ? true : Boolean.parseBoolean(s); } private static boolean retryConnect() { String s = Utils.getNetProperty("jdk.httpclient.disableRetryConnect"); if (s == null) return false; return s.isEmpty() ? true : Boolean.parseBoolean(s); }
如果cancelled為true且是IOException則直接返回,否則先判斷retryOnFailure再判斷canRetryRequest(如果不是ConnectException才走canRetryRequest這個判斷)
retryOnFailure方法判斷如果是ConnectionExpiredException或者是ConnectException且開啟retryConnect,則返回true
RETRY_CONNECT讀取的是jdk.httpclient.disableRetryConnect參數,如果值為null,則方法返回false,即不進行retryConnect
canRetryRequest首先判斷RETRY_ALWAYS,在判斷isIdempotentRequest(GET、HEAD方法才重試),都不是則返回false
RETRY_ALWAYS讀取的是jdk.httpclient.enableAllMethodRetry,如果值為null,則方法返回false,即不進行retryPostValue
如果該重試的話,則返回null,responseAsyncImpl里頭在getExceptionalCF返回null的時候,重新調用了一次responseAsyncImpl,通過遞歸調用來完成重試邏輯
NetPropertiesjava.base/sun/net/NetProperties.java
public class NetProperties { private static Properties props = new Properties(); static { AccessController.doPrivileged( new PrivilegedAction() { public Void run() { loadDefaultProperties(); return null; }}); } private NetProperties() { }; /* * Loads the default networking system properties * the file is in jre/lib/net.properties */ private static void loadDefaultProperties() { String fname = StaticProperty.javaHome(); if (fname == null) { throw new Error("Can"t find java.home ??"); } try { File f = new File(fname, "conf"); f = new File(f, "net.properties"); fname = f.getCanonicalPath(); InputStream in = new FileInputStream(fname); BufferedInputStream bin = new BufferedInputStream(in); props.load(bin); bin.close(); } catch (Exception e) { // Do nothing. We couldn"t find or access the file // so we won"t have default properties... } } /** * Get a networking system property. If no system property was defined * returns the default value, if it exists, otherwise returns * null
. * @param key the property name. * @throws SecurityException if a security manager exists and its *checkPropertiesAccess
method doesn"t allow access * to the system properties. * @return theString
value for the property, * ornull
*/ public static String get(String key) { String def = props.getProperty(key); try { return System.getProperty(key, def); } catch (IllegalArgumentException e) { } catch (NullPointerException e) { } return null; } /** * Get an Integer networking system property. If no system property was * defined returns the default value, if it exists, otherwise returns *null
. * @param key the property name. * @param defval the default value to use if the property is not found * @throws SecurityException if a security manager exists and its *checkPropertiesAccess
method doesn"t allow access * to the system properties. * @return theInteger
value for the property, * ornull
*/ public static Integer getInteger(String key, int defval) { String val = null; try { val = System.getProperty(key, props.getProperty(key)); } catch (IllegalArgumentException e) { } catch (NullPointerException e) { } if (val != null) { try { return Integer.decode(val); } catch (NumberFormatException ex) { } } return defval; } /** * Get a Boolean networking system property. If no system property was * defined returns the default value, if it exists, otherwise returns *null
. * @param key the property name. * @throws SecurityException if a security manager exists and its *checkPropertiesAccess
method doesn"t allow access * to the system properties. * @return theBoolean
value for the property, * ornull
*/ public static Boolean getBoolean(String key) { String val = null; try { val = System.getProperty(key, props.getProperty(key)); } catch (IllegalArgumentException e) { } catch (NullPointerException e) { } if (val != null) { try { return Boolean.valueOf(val); } catch (NumberFormatException ex) { } } return null; } }
這里通過loadDefaultProperties先加載默認配置,讀取的是JAVA_HOME/conf/net.properties文件
然后getString、getInteger、getBoolean方法采用的是System.getProperty來讀取,而net.properties值僅僅作為System.getProperty的defaultValue
因此要設置httpclient相關參數,只需要通過System.setProperty或者-D來設置即可
net.properties
/Library/java/JavaVirtualMachines/jdk-11.jdk/Contents/home/conf/net.properties
java.net.useSystemProxies=false http.nonProxyHosts=localhost|127.*|[::1] ftp.nonProxyHosts=localhost|127.*|[::1] jdk.http.auth.tunneling.disabledSchemes=Basic
net.properties文件默認設置了如上四個參數相關異常 HttpTimeoutException
java.net.http/java/net/http/HttpTimeoutException.java
/** * Thrown when a response is not received within a specified time period. * * @since 11 */ public class HttpTimeoutException extends IOException { private static final long serialVersionUID = 981344271622632951L; /** * Constructs an {@code HttpTimeoutException} with the given detail message. * * @param message * The detail message; can be {@code null} */ public HttpTimeoutException(String message) { super(message); } }
屬于java.net.http包,繼承至IOException
如果設置了request的timeout,則注冊ResponseTimerEvent,在超時時拋出HttpTimeoutException: request timed out,同時設置MultiExchange的cancelled為true
這類由于客戶端設置超時引起的HttpTimeoutException,不會進行重試,即使開啟相關重試參數
如果這個時間設置得太短,則在connect的時候就超時了,這個時候會拋出HttpConnectTimeoutException,而非HttpTimeoutException: request timed out
HttpConnectTimeoutExceptionjava.net.http/java/net/http/HttpConnectTimeoutException.java
/** * Thrown when a connection, over which an {@code HttpRequest} is intended to be * sent, is not successfully established within a specified time period. * * @since 11 */ public class HttpConnectTimeoutException extends HttpTimeoutException { private static final long serialVersionUID = 321L + 11L; /** * Constructs an {@code HttpConnectTimeoutException} with the given detail * message. * * @param message * The detail message; can be {@code null} */ public HttpConnectTimeoutException(String message) { super(message); } }
屬于java.net.http包,繼承至HttpTimeoutException
如果設置了client的connectTimeout,則會注冊ConnectTimerEvent,在超時時拋出ConnectException("HTTP connect timed out"),同時設置MultiExchange的cancelled為true,這個在MultiExchange.getExceptionalCF方法里頭會被包裝為HttpConnectTimeoutException
ConnectionExpiredExceptionjava.net.http/jdk/internal/net/http/common/ConnectionExpiredException.java
/** * Signals that an end of file or end of stream has been reached * unexpectedly before any protocol specific data has been received. */ public final class ConnectionExpiredException extends IOException { private static final long serialVersionUID = 0; /** * Constructs a {@code ConnectionExpiredException} with a detail message of * "subscription is finished" and the given cause. * * @param cause the throwable cause */ public ConnectionExpiredException(Throwable cause) { super("subscription is finished", cause); } }
一般是在read error的時候觸發,比如EOFException,IOException("connection reset by peer),或者SSLHandshakeException
小結jdk httpclient的retry參數涉及到的參數如下:
jdk.httpclient.redirects.retrylimit(默認為5,用來控制重試次數,不過實際上還有expiredOnce參數,看代碼貌似頂多重試一次)
jdk.httpclient.disableRetryConnect(默認為null,即RETRY_CONNECT為false,不在ConnectException的時候retry)
jdk.httpclient.enableAllMethodRetry(默認為null,即RETRY_ALWAYS為false,即需要判斷請求方法是否冪等來決定是否重試)
是否重試的判斷邏輯如下:
如果重試次數超過限制,則返回失敗,否則往下
如果cancelled為true(這里如果request設置了timeout,觸發時cancelled設置為true)且是IOException(例如設置了連接超時拋出的HttpConnectTimeoutException),則不走重試邏輯;否則往下
如果retryOnFailure(ConnectionExpiredException,或者ConnectException且開啟retryConnect),則往下
如果是異常不是ConnectException,則還額外判斷canRetryRequest(判斷該請求類型是否允許重試),滿足則往下
如果expiredOnce為false,則返回null,即滿足重試條件,走遞歸重試
docHttpClient javadoc
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77355.html
摘要:序本文主要研究一下的這里如果的為,則會創建這里如果是的話,參數傳遞的是如果是同步的方法,則傳的值是這里創建了一個,然后調用,這里使用了可以看到這里使用的是的方法注意這個方法是才有的,也是在這里使用的由于默認是使用創建的, 序 本文主要研究一下jdk httpclient的executor HttpClientImpl java.net.http/jdk/internal/net/htt...
摘要:序本文主要研究一下的異常實例代碼異常日志如下最后調用這里調用獲取連接如果沒有連接會新創建一個,走的是這里先是調用了獲取連接,然后調用進行連接這里委托給這里如果有設置的話,則會創建一個調用進行連接,如果連接未 序 本文主要研究一下httpclient的connect timeout異常 實例代碼 @Test public void testConnectTimeout()...
摘要:調用計算的時間,這個方法會清理移除并過期的連接除了清理過期的連接外,還通過間接觸發,去清理關閉或異常的連接 序 本文主要研究一下jdk httpclient的ConnectionPool HttpConnection HttpConnection.getConnection java.net.http/jdk/internal/net/http/HttpConnection.java ...
摘要:的類圖如下主要根據創建擴展了,創建攔截的,這里會設置攔截器,這是集成的核心,當發起請求調用的時候,會先經過攔截器,然后才真正發起請求。和是配合使用的,最大重試次數是針對每一個的,如果設置,這樣觸發最大重試次數就是次。 上一篇文章我們分析了ribbon的核心原理,接下來我們來看看springcloud是如何集成ribbon的,不同的springcloud的組件(feign,zuul,Re...
摘要:內部使用了的動態代理為目標接口生成了一個動態代理類,這里會生成一個動態代理原理統一的方法攔截器,同時為接口的每個方法生成一個攔截器,并解析方法上的元數據,生成一個請求模板。的核心源碼解析到此結束了,不知道是否對您有無幫助,可留言跟我交流。 Feign是一個聲明式的Web服務客戶端。這使得Web服務客戶端的寫入更加方便 要使用Feign創建一個界面并對其進行注釋。它具有可插拔注釋支持,包...
閱讀 1979·2019-08-30 15:54
閱讀 3602·2019-08-29 13:07
閱讀 3129·2019-08-29 12:39
閱讀 1793·2019-08-26 12:13
閱讀 1552·2019-08-23 18:31
閱讀 2164·2019-08-23 18:05
閱讀 1852·2019-08-23 18:00
閱讀 1049·2019-08-23 17:15