/* * Copyright 2016 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.go.agent.launcher; import com.thoughtworks.go.agent.ServerUrlGenerator; import com.thoughtworks.go.agent.common.ssl.GoAgentServerHttpClientBuilder; import com.thoughtworks.go.agent.common.util.Downloader; import com.thoughtworks.go.util.PerfTimer; import com.thoughtworks.go.util.SslVerificationMode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import java.io.*; public class ServerBinaryDownloader implements Downloader { private static final Log LOG = LogFactory.getLog(ServerBinaryDownloader.class); private final ServerUrlGenerator urlGenerator; private String md5 = null; private String sslPort; private static final String MD5_HEADER = "Content-MD5"; @Deprecated // for backward compatibility private static final String SSL_PORT_HEADER = "Cruise-Server-Ssl-Port"; private static final int HTTP_TIMEOUT_IN_MILLISECONDS = 5000; private GoAgentServerHttpClientBuilder httpClientBuilder; public ServerBinaryDownloader(ServerUrlGenerator urlGenerator, File rootCertFile, SslVerificationMode sslVerificationMode) throws Exception { this(new GoAgentServerHttpClientBuilder(rootCertFile, sslVerificationMode), urlGenerator); } protected ServerBinaryDownloader(GoAgentServerHttpClientBuilder httpClientBuilder, ServerUrlGenerator urlGenerator) { this.httpClientBuilder = httpClientBuilder; this.urlGenerator = urlGenerator; } public String getMd5() { return md5; } public String getSslPort() { return sslPort; } public boolean downloadIfNecessary(final DownloadableFile downloadableFile) { boolean updated = false; boolean downloaded = false; while (!updated) { try { fetchUpdateCheckHeaders(downloadableFile); if (downloadableFile.doesNotExist() || !downloadableFile.isChecksumEquals(getMd5())) { PerfTimer timer = PerfTimer.start("Downloading new " + downloadableFile + " with md5 signature: " + md5); downloaded = download(downloadableFile); timer.stop(); } updated = true; } catch (Exception e) { LOG.error("Couldn't update " + downloadableFile + ". Sleeping for 1m. Error: ", e); try { int period = Integer.parseInt(System.getProperty("sleep.for.download", "60000")); Thread.sleep(period); } catch (InterruptedException ie) { /* we don't care. Stupid checked exception.*/ } } } return downloaded; } void fetchUpdateCheckHeaders(DownloadableFile downloadableFile) throws Exception { String url = downloadableFile.validatedUrl(urlGenerator); final HttpRequestBase request = new HttpHead(url); request.setConfig(RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT_IN_MILLISECONDS).build()); try ( CloseableHttpClient httpClient = httpClientBuilder.build(); CloseableHttpResponse response = httpClient.execute(request) ) { handleInvalidResponse(response, url); this.md5 = response.getFirstHeader(MD5_HEADER).getValue(); this.sslPort = response.getFirstHeader(SSL_PORT_HEADER).getValue(); } } protected synchronized boolean download(final DownloadableFile downloadableFile) throws Exception { File toDownload = downloadableFile.getLocalFile(); LOG.info("Downloading " + toDownload); String url = downloadableFile.url(urlGenerator); final HttpRequestBase request = new HttpGet(url); request.setConfig(RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT_IN_MILLISECONDS).build()); try (CloseableHttpClient httpClient = httpClientBuilder.build(); CloseableHttpResponse response = httpClient.execute(request)) { LOG.info("Got server response"); if (response.getEntity() == null) { LOG.error("Unable to read file from the server response"); return false; } handleInvalidResponse(response, url); try (BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(downloadableFile.getLocalFile()))) { response.getEntity().writeTo(outStream); LOG.info("Piped the stream to " + downloadableFile); } } return true; } private void handleInvalidResponse(HttpResponse response, String url) throws IOException { StringWriter sw = new StringWriter(); try (PrintWriter out = new PrintWriter(sw)) { out.print("Problem accessing server at "); out.println(url); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { LOG.info("Response code: " + response.getStatusLine().getStatusCode()); out.println("Few Possible Causes: "); out.println("1. Your Go Server is down or not accessible."); out.println("2. This agent might be incompatible with your Go Server. Please fix the version mismatch between Go Server and Go Agent."); throw new ClientProtocolException(sw.toString()); } else if (response.getFirstHeader(MD5_HEADER) == null || response.getFirstHeader(SSL_PORT_HEADER) == null) { out.print("Missing required headers '"); out.print(MD5_HEADER); out.print("' and '"); out.print(SSL_PORT_HEADER); out.println("' in response."); throw new ClientProtocolException(sw.toString()); } } } }