You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ServerConnection.java 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. * SonarQube Runner - API
  3. * Copyright (C) 2011 SonarSource
  4. * sonarqube@googlegroups.com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
  19. */
  20. package org.sonar.runner.impl;
  21. import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
  22. import com.github.kevinsawicki.http.HttpRequest;
  23. import java.io.File;
  24. import java.io.IOException;
  25. import java.net.ConnectException;
  26. import java.net.URL;
  27. import java.net.UnknownHostException;
  28. import java.text.MessageFormat;
  29. import java.util.Properties;
  30. import java.util.regex.Matcher;
  31. import java.util.regex.Pattern;
  32. import org.apache.commons.io.FileUtils;
  33. import org.sonar.home.cache.Logger;
  34. import org.sonar.home.cache.PersistentCache;
  35. class ServerConnection {
  36. private static final String SONAR_SERVER_CAN_NOT_BE_REACHED = "Sonar server ''{0}'' can not be reached";
  37. private static final String STATUS_RETURNED_BY_URL_IS_INVALID = "Status returned by url : ''{0}'' is invalid : {1}";
  38. static final int CONNECT_TIMEOUT_MILLISECONDS = 5000;
  39. static final int READ_TIMEOUT_MILLISECONDS = 60000;
  40. private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
  41. private final String serverUrl;
  42. private final String userAgent;
  43. private final PersistentCache wsCache;
  44. private final boolean isCacheEnable;
  45. private final Logger logger;
  46. private ServerConnection(String serverUrl, String app, String appVersion, boolean isCacheEnable, PersistentCache cache, Logger logger) {
  47. this.logger = logger;
  48. this.serverUrl = removeEndSlash(serverUrl);
  49. this.userAgent = app + "/" + appVersion;
  50. this.wsCache = cache;
  51. this.isCacheEnable = isCacheEnable;
  52. }
  53. private static String removeEndSlash(String url) {
  54. return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
  55. }
  56. static ServerConnection create(Properties properties, PersistentCache cache, Logger logger) {
  57. String serverUrl = properties.getProperty("sonar.host.url");
  58. String app = properties.getProperty(InternalProperties.RUNNER_APP);
  59. String appVersion = properties.getProperty(InternalProperties.RUNNER_APP_VERSION);
  60. boolean enableCache = isCacheEnabled(properties);
  61. return new ServerConnection(serverUrl, app, appVersion, enableCache, cache, logger);
  62. }
  63. private static boolean isCacheEnabled(Properties properties) {
  64. String analysisMode = properties.getProperty("sonar.analysis.mode");
  65. return "issues".equalsIgnoreCase(analysisMode);
  66. }
  67. /**
  68. *
  69. * @throws HttpRequestException If there is an underlying IOException related to the connection
  70. * @throws IOException If the HTTP response code is != 200
  71. */
  72. private String downloadString(String url, boolean saveCache) throws HttpRequestException, IOException {
  73. HttpRequest httpRequest = null;
  74. try {
  75. httpRequest = newHttpRequest(new URL(url));
  76. String charset = getCharsetFromContentType(httpRequest.contentType());
  77. if (charset == null || "".equals(charset)) {
  78. charset = "UTF-8";
  79. }
  80. if (!httpRequest.ok()) {
  81. throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, url, httpRequest.code()));
  82. }
  83. byte[] body = httpRequest.bytes();
  84. if (saveCache) {
  85. try {
  86. wsCache.put(url, body);
  87. } catch (IOException e) {
  88. logger.warn("Failed to cache WS call: " + e.getMessage());
  89. }
  90. }
  91. return new String(body, charset);
  92. } finally {
  93. if (httpRequest != null) {
  94. httpRequest.disconnect();
  95. }
  96. }
  97. }
  98. void download(String path, File toFile) {
  99. String fullUrl = serverUrl + path;
  100. try {
  101. logger.debug("Download " + fullUrl + " to " + toFile.getAbsolutePath());
  102. HttpRequest httpRequest = newHttpRequest(new URL(fullUrl));
  103. if (!httpRequest.ok()) {
  104. throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, fullUrl, httpRequest.code()));
  105. }
  106. httpRequest.receive(toFile);
  107. } catch (Exception e) {
  108. if (e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException) {
  109. logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
  110. }
  111. FileUtils.deleteQuietly(toFile);
  112. throw new IllegalStateException("Fail to download: " + fullUrl, e);
  113. }
  114. }
  115. /**
  116. * Tries to fetch from server and falls back to cache. If both attempts fail, it throws the exception
  117. * linked to the server connection failure.
  118. */
  119. String downloadStringCache(String path) throws IOException {
  120. String fullUrl = serverUrl + path;
  121. try {
  122. return downloadString(fullUrl, isCacheEnable);
  123. } catch (HttpRequest.HttpRequestException e) {
  124. if (isCausedByConnection(e) && isCacheEnable) {
  125. return fallbackToCache(fullUrl, e);
  126. }
  127. logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
  128. throw e;
  129. }
  130. }
  131. private static boolean isCausedByConnection(Exception e) {
  132. return e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException ||
  133. e.getCause() instanceof java.net.SocketTimeoutException;
  134. }
  135. private String fallbackToCache(String fullUrl, HttpRequest.HttpRequestException originalException) {
  136. logger.info(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + ", trying cache", serverUrl));
  137. try {
  138. String cached = wsCache.getString(fullUrl);
  139. if (cached != null) {
  140. return cached;
  141. }
  142. logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + " and data is not cached", serverUrl));
  143. throw originalException;
  144. } catch (IOException e) {
  145. throw new IllegalStateException("Failed to access cache", e);
  146. }
  147. }
  148. private HttpRequest newHttpRequest(URL url) {
  149. HttpRequest request = HttpRequest.get(url);
  150. request.trustAllCerts().trustAllHosts();
  151. request.acceptGzipEncoding().uncompress(true);
  152. request.connectTimeout(CONNECT_TIMEOUT_MILLISECONDS).readTimeout(READ_TIMEOUT_MILLISECONDS);
  153. request.userAgent(userAgent);
  154. return request;
  155. }
  156. /**
  157. * Parse out a charset from a content type header.
  158. *
  159. * @param contentType e.g. "text/html; charset=EUC-JP"
  160. * @return "EUC-JP", or null if not found. Charset is trimmed and upper-cased.
  161. */
  162. static String getCharsetFromContentType(String contentType) {
  163. if (contentType == null) {
  164. return null;
  165. }
  166. Matcher m = CHARSET_PATTERN.matcher(contentType);
  167. if (m.find()) {
  168. return m.group(1).trim().toUpperCase();
  169. }
  170. return null;
  171. }
  172. }