jenkins插件更新页面的一个报错

doCheckUpdatesServer()

1
2
3
4
5
6
7
8
9
10
11
12

首先从
doCheckUpdatesServer()

这里 开始 执行 start(),在执行 start 方法之前需要初始化 updateServerRetrier 变量,

FormValidation result = updateServerRetrier.start();

在 Retrier.java 类中的 有个 start() 方法,中会执行 result = callable.call(); 这个。

这个callable 是什么呢? 来自 java.util.concurrent.Callable;

updateServerRetrier初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

看 doCheckUpdatesServer() 中怎么初始化 updateServerRetrier的。updateServerRetrier 是 Retrier<FormValidation> 类型的。

// We'll check the update servers with a try-retry mechanism. The retrier is built with a builder
Retrier<FormValidation> updateServerRetrier = new Retrier.Builder<>(
// the action to perform
this::checkUpdatesServer,

// the way we know whether this attempt was right or wrong
(currentAttempt, result) -> result.kind == FormValidation.Kind.OK,

// the action name we are trying to perform
"check updates server")

// the number of attempts to try
.withAttempts(CHECK_UPDATE_ATTEMPTS)

// the delay between attempts
.withDelay(CHECK_UPDATE_SLEEP_TIME_MILLIS)

// whatever exception raised is considered as a fail attempt (all exceptions), not a failure
.withDuringActionExceptions(new Class[] {Exception.class})

// what we do with a failed attempt due to an allowed exception, return an FormValidation.error with the message
.withDuringActionExceptionListener((attempt, e) -> FormValidation.errorWithMarkup(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage()))

// lets get our retrier object
.build();

Retrier 类的构造方法是 私有的,不能外部使用。所以这里调用了 内部类 Builder

初始化是 调用的 new Retrier.Builder<>(), 这个方法需要几个参数:
public Builder(@NonNull Callable<V> callable, @NonNull BiPredicate<Integer, V> checkResult, @NonNull String action) {
this.callable = callable;
this.action = action;
this.checkResult = checkResult;
}
可以发现第一个 参数就是 callable。 继续看 doCheckUpdatesServer() 中,把 这个 this::checkUpdatesServer 作为第一个参数传入 callable了。

初始化 Retrier.Builder 后又调用了 withAttempts , withDelay, withDuringActionExceptions, withDuringActionExceptionListener 这么几个方法,

这几个方法是为了设置几个成员变量的,最后在调用 build() 完成 updateServerRetrier 的初始化。
build() 方法里面 会 new Retrier<>(this) 这个的。这个this是 内部类 Builder 。然后通过 内部类 Builder 上的 成员变量
赋值给 Retrier 上。


Retrier start 方法

在 Retrier.java  类中的 有个 start() 方法 中会执行 result = callable.call(); 这个,
然后这个方法抛出了异常,被捕获到了,然后会初始化 result,
这个就是 最开始 doCheckUpdatesServer 里面这句
FormValidation result = updateServerRetrier.start(); 这里得到的result。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    public @CheckForNull V start() throws Exception {
V result = null;
int currentAttempt = 0;
boolean success = false;

while (currentAttempt < attempts && !success) {
currentAttempt++;
try {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, Messages.Retrier_Attempt(currentAttempt, action));
}
result = callable.call();

} catch (Exception e) {
if (duringActionExceptions == null || Stream.of(duringActionExceptions).noneMatch(exception -> exception.isAssignableFrom(e.getClass()))) {
// if the raised exception is not considered as a controlled exception doing the action, rethrow it
LOGGER.log(Level.WARNING, Messages.Retrier_ExceptionThrown(currentAttempt, action), e);
throw e;
} else {
// if the exception is considered as a failed action, notify it to the listener
LOGGER.log(Level.INFO, Messages.Retrier_ExceptionFailed(currentAttempt, action), e);
if (duringActionExceptionListener != null) {
LOGGER.log(Level.INFO, Messages.Retrier_CallingListener(e.getLocalizedMessage(), currentAttempt, action));
result = duringActionExceptionListener.apply(currentAttempt, e);
}
}
}

// After the call and the call to the listener, which can change the result, test the result
success = checkResult.test(currentAttempt, result);
if (!success) {
if (currentAttempt < attempts) {
LOGGER.log(Level.WARNING, Messages.Retrier_AttemptFailed(currentAttempt, action));
LOGGER.log(Level.FINE, Messages.Retrier_Sleeping(delay, action));
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
LOGGER.log(Level.FINE, Messages.Retrier_Interruption(action));
Thread.currentThread().interrupt(); // flag this thread as interrupted
currentAttempt = attempts; // finish
}
} else {
// Failed to perform the action
LOGGER.log(Level.INFO, Messages.Retrier_NoSuccess(action, attempts));
}
} else {
LOGGER.log(Level.INFO, Messages.Retrier_Success(action, currentAttempt));
}
}

return result;
}


result = duringActionExceptionListener.apply(currentAttempt, e); 我们看这个 result怎么生成的,
是调用了duringActionExceptionListener变量的apply() 方法,

这个 duringActionExceptionListener 是什么玩意呢? 是个 private BiFunction<Integer, Exception, V> duringActionExceptionListener;
这个变量是 在 这里
.withDuringActionExceptionListener((attempt, e) -> FormValidation.errorWithMarkup(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage())) 给传入进去的。

我们发现这个 玩意是一个 java lambda 表达式,其实也就是一个匿名方法,具体就是 类似下面这样,方法接收2个参数。在上面 调用 apply 时候传入这2个参数。
f(attempt, e) {
return FormValidation.errorWithMarkup( e.getClass().getSimpleName() + ": " + e.getLocalizedMessage() )
}


其实 调用 apply(currentAttempt, e) 就是调用这个 lambda表达式,
也就是调用 FormValidation.errorWithMarkup() 这个方法了,可以看到 e 就是 先前抛出的 异常, 好像 currentAttempt 这个参数没有用到。
errorWithMarkup() 方法会 返回个 FormValidation ,有字符串错误 生成个 FormValidation 对象。
FormValidation 继承了 IOException 实现了 HttpResponse 接口的。这个里面有个 public abstract String renderHtml(); 需要重新实现的。
这个就是 拼接出来html展示的字符串了。里面是用 div 来拼接出来的。
"<div class=" + kind.name().toLowerCase(Locale.ENGLISH) + ">
<img src='" + req.getContextPath() + Jenkins.RESOURCE_PATH + "/images/none.gif' height=16 width=1>"
+ message +
"</div>";
到这里 我最后返回的 result 值已经生成初始化好了。result 是 FormValidation 对象。

接着 Retrier.java 类中的 start() 方法中 又做了一 个 判断

做的检查判断 是 success = checkResult.test(currentAttempt, result); 这个 checkResult 来自 初始化 Retrier.Builder<> 的第三个参数,
也是一个 lambda 表达式 (currentAttempt, result) -> result.kind == FormValidation.Kind.OK, 类似就是下面这个,方法接收2个参数。在上面 调用 test 时候传入这2个参数。
f(currentAttempt, result) {
return result.kind == FormValidation.Kind.OK
}

最后 就 返回 这个 result 给开头的那个 doCheckUpdatesServer() 方法了。



最后 通过 lastErrorCheckUpdateCenters = Messages.PluginManager_CheckUpdateServerError(result.getMessage());
吧这个result 格式化配上本地化字符串赋值给lastErrorCheckUpdateCenters变量了。

在 src/main/resources/hudson/PluginManager/advanced.jelly 中的 check.jelly 文件中会 显示 这个 lastErrorCheckUpdateCenters 变量的值到 web 页面上。

第一个参数 checkUpdatesServer 又是什么呢?

是 PluginManager.java 中的一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

其实执行顺序 就是 doCheckUpdatesServer() ---> checkUpdatesServer()

checkUpdatesServer()中 for 循环所有site,其中只有一个site,URL是:https://updates.jenkins-zh.cn/update-center.json

FormValidation v = site.updateDirectlyNow();
调用 下面的
updateDirectlyNow(DownloadService.signatureCheck);
调用下面的这个方法
updateData(DownloadService.loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), signatureCheck);


public @NonNull FormValidation updateDirectlyNow() throws IOException {
return updateDirectlyNow(DownloadService.signatureCheck);
}

@Restricted(NoExternalUse.class)
public @NonNull FormValidation updateDirectlyNow(boolean signatureCheck) throws IOException {
return updateData(DownloadService.loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), signatureCheck);
}

里面调用了 DownloadService.loadJSON 方法
public static String loadJSON(URL src) throws IOException {
URLConnection con = ProxyConfiguration.open(src);
if (con instanceof HttpURLConnection) {
// prevent problems from misbehaving plugins disabling redirects by default
((HttpURLConnection) con).setInstanceFollowRedirects(true);
}
try (InputStream is = con.getInputStream()) {
String jsonp = IOUtils.toString(is, StandardCharsets.UTF_8);
int start = jsonp.indexOf('{');
int end = jsonp.lastIndexOf('}');
if (start >= 0 && end > start) {
return jsonp.substring(start, end + 1);
} else {
throw new IOException("Could not find JSON in " + src);
}
}
}
到这里 try (InputStream is = con.getInputStream()) { 这一行 抛出异常了。

到这里 updateData 抛出了异常:java.io.IOException: Server returned HTTP response code: 403 for URL: https://cdn.jsdelivr.net/gh/jenkins-zh/update-center-mirror/tsinghua/dynamic-2.277/update-center.json


1
2
3
4
5
6
7


FormValidation.error(e, "Signature verification failed in "+name);

Signature verification failed in update site &#039;default&#039; <a href='#' class='showDetails'>(显示详情)</a><pre style='display:none'>java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors<br>
at java.lang.Thread.run(Thread.java:748)<br>
</pre>
1
2
3
4
5

<div class="error"><img src="/jenkins/images/none.gif" width="1" height="16">连接http://10.0.12.116:8080/jenkins/pluginManager/advanced失败。 <a href="#" class="showDetails">(显示详情)</a><pre style="display:none">java.net.UnknownHostException: s<br>
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)<br>
at java.lang.Thread.run(Thread.java:748)<br>
</pre></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

FormValidation.error(e, Messages.ProxyConfiguration_FailedToConnectViaProxy(testUrl));

private static FormValidation _error(Kind kind, Throwable e, String message) {
if (e==null) return _errorWithMarkup(Util.escape(message),kind);

return _errorWithMarkup(
Util.escape(message)+
" <a href='#' class='showDetails'>"
+ Messages.FormValidation_Error_Details()
+ "</a><pre style='display:none'>"
+ Util.escape(Functions.printThrowable(e)) +
"</pre>", kind
);
}
1
2
3
4
5


连接http://localhost:8080/jenkins/pluginManager/advanced失败。 <a href='#' class='showDetails'>(显示详情)</a><pre style='display:none'>java.net.UnknownHostException: sdfs<br> at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)<br> at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)<br> at java.net.Socket.connect(Socket.java:607)<br> at java.net.Socket.connect(Socket.java:556)<br> at java.net.Socket.&lt;init&gt;(Socket.java:452)<br> at java.net.Socket.&lt;init&gt;(Socket.java:304)<br> at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:87)<br> at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:129)<br> at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:714)<br> at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:394)<br> at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:178)<br> at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:404)<br> at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:330)<br> at hudson.ProxyConfiguration$DescriptorImpl.doValidateProxy(ProxyConfiguration.java:458)<br> at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)<br> at org.kohsuke.stapler.Function$MethodFunction.invoke(Function.java:396)<br> at org.kohsuke.stapler.Function$InstanceFunction.invoke(Function.java:408)<br> at org.kohsuke.stapler.interceptor.RequirePOST$Processor.invoke(RequirePOST.java:77)<br> at org.kohsuke.stapler.PreInvokeInterceptedFunction.invoke(PreInvokeInterceptedFunction.java:26)<br> at org.kohsuke.stapler.Function.bindAndInvoke(Function.java:212)<br> at org.kohsuke.stapler.Function.bindAndInvokeAndServeResponse(Function.java:145)<br> at org.kohsuke.stapler.MetaClass$11.doDispatch(MetaClass.java:536)<br> at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)<br> at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:766)<br> at org.kohsuke.stapler.Stapler.invoke(Stapler.java:898)<br> at org.kohsuke.stapler.MetaClass$4.doDispatch(MetaClass.java:281)<br> at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)<br> at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:766)<br> at org.kohsuke.stapler.Stapler.invoke(Stapler.java:898)<br> at org.kohsuke.stapler.Stapler.invoke(Stapler.java:694)<br> at org.kohsuke.stapler.Stapler.service(Stapler.java:240)<br> at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)<br> at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:760)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1617)<br> at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:226)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:154)<br> at org.jenkinsci.plugins.ssegateway.Endpoint$SSEListenChannelFilter.doFilter(Endpoint.java:248)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at jenkins.telemetry.impl.UserLanguages$AcceptLanguageFilter.doFilter(UserLanguages.java:129)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at jenkins.security.ResourceDomainFilter.doFilter(ResourceDomainFilter.java:76)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at io.jenkins.blueocean.auth.jwt.impl.JwtAuthenticationFilter.doFilter(JwtAuthenticationFilter.java:60)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at io.jenkins.blueocean.ResourceCacheControl.doFilter(ResourceCacheControl.java:134)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at hudson.plugins.locale.LocaleFilter.doFilter(LocaleFilter.java:42)<br> at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)<br> at hudson.util.PluginServletFilter.doFilter(PluginServletFilter.java:157)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at hudson.security.csrf.CrumbFilter.doFilter(CrumbFilter.java:153)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:84)<br> at hudson.security.ChainedServletFilter.doFilter(ChainedServletFilter.java:90)<br> at hudson.security.HudsonFilter.doFilter(HudsonFilter.java:171)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:51)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:82)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at org.kohsuke.stapler.DiagnosticThreadNameFilter.doFilter(DiagnosticThreadNameFilter.java:30)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at jenkins.security.SuspiciousRequestFilter.doFilter(SuspiciousRequestFilter.java:36)<br> at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)<br> at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)<br> at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)<br> at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:512)<br> at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)<br> at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)<br> at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1592)<br> at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)<br> at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1296)<br> at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)<br> at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)<br> at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1562)<br> at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)<br> at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1211)<br> at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)<br> at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:221)<br> at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)<br> at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)<br> at org.eclipse.jetty.server.Server.handle(Server.java:500)<br> at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:386)<br> at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:562)<br> at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:378)<br> at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:270)<br> at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)<br> at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)<br> at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)<br> at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)<br> at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)<br> at java.lang.Thread.run(Thread.java:748)<br></pre>