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.

DefaultHttpDownloaderIT.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT 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 License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.core.util;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.PrintStream;
  25. import java.net.InetSocketAddress;
  26. import java.net.SocketAddress;
  27. import java.net.SocketTimeoutException;
  28. import java.net.URI;
  29. import java.net.URISyntaxException;
  30. import java.nio.charset.StandardCharsets;
  31. import java.util.Properties;
  32. import java.util.zip.GZIPOutputStream;
  33. import org.hamcrest.BaseMatcher;
  34. import org.hamcrest.Description;
  35. import org.junit.jupiter.api.AfterAll;
  36. import org.junit.jupiter.api.BeforeAll;
  37. import org.junit.jupiter.api.Test;
  38. import org.junit.jupiter.api.Timeout;
  39. import org.junit.jupiter.api.io.TempDir;
  40. import org.simpleframework.http.Request;
  41. import org.simpleframework.http.Response;
  42. import org.simpleframework.http.core.Container;
  43. import org.simpleframework.http.core.ContainerServer;
  44. import org.simpleframework.transport.connect.SocketConnection;
  45. import org.sonar.api.CoreProperties;
  46. import org.sonar.api.config.internal.MapSettings;
  47. import org.sonar.api.platform.Server;
  48. import org.sonar.api.utils.SonarException;
  49. import static org.assertj.core.api.Assertions.assertThat;
  50. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  51. import static org.mockito.Mockito.mock;
  52. import static org.mockito.Mockito.when;
  53. import static org.sonar.api.utils.HttpDownloader.HttpException;
  54. class DefaultHttpDownloaderIT {
  55. @TempDir
  56. private File temporaryFolder;
  57. private static SocketConnection socketConnection;
  58. private static String baseUrl;
  59. @BeforeAll
  60. static void startServer() throws IOException {
  61. socketConnection = new SocketConnection(new ContainerServer(new Container() {
  62. public void handle(Request req, Response resp) {
  63. try {
  64. if (req.getPath().getPath().contains("/redirect/")) {
  65. resp.setCode(303);
  66. resp.setValue("Location", "/redirected");
  67. } else if (req.getPath().getPath().contains("/gzip/")) {
  68. if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
  69. throw new IllegalStateException("Should accept gzip");
  70. }
  71. resp.setValue("Content-Encoding", "gzip");
  72. GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
  73. gzipOutputStream.write("GZIP response".getBytes());
  74. gzipOutputStream.close();
  75. } else if (req.getPath().getPath().contains("/redirected")) {
  76. resp.getPrintStream().append("redirected");
  77. } else if (req.getPath().getPath().contains("/error")) {
  78. writeErrorResponse(req, resp);
  79. } else {
  80. writeDefaultResponse(req, resp);
  81. }
  82. } catch (IOException e) {
  83. throw new IllegalStateException(e);
  84. } finally {
  85. try {
  86. resp.close();
  87. } catch (IOException ignored) {
  88. }
  89. }
  90. }
  91. }));
  92. SocketAddress address = socketConnection.connect(new InetSocketAddress("localhost", 0));
  93. baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
  94. }
  95. private static PrintStream writeDefaultResponse(Request req, Response resp) throws IOException {
  96. return resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
  97. }
  98. private static void writeErrorResponse(Request req, Response resp) throws IOException {
  99. resp.setCode(500);
  100. resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
  101. }
  102. @AfterAll
  103. static void stopServer() throws IOException {
  104. if (null != socketConnection) {
  105. socketConnection.close();
  106. }
  107. }
  108. @Test
  109. // To disable the timeout in debug mode, run the test with -Djunit.jupiter.execution.timeout.mode=disabled_on_debug
  110. @Timeout(10)
  111. void openStream_network_errors() throws IOException, URISyntaxException {
  112. // host not accepting connections
  113. String url = "http://127.0.0.1:1";
  114. assertThatThrownBy(() -> {
  115. DefaultHttpDownloader downloader = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig());
  116. downloader.openStream(new URI(url));
  117. })
  118. .isInstanceOf(SonarException.class)
  119. .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
  120. @Override
  121. public boolean matches(Object ex) {
  122. return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
  123. }
  124. @Override
  125. public void describeTo(Description arg0) {
  126. }
  127. });
  128. }
  129. @Test
  130. void downloadBytes() throws URISyntaxException {
  131. byte[] bytes = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readBytes(new URI(baseUrl));
  132. assertThat(bytes).hasSizeGreaterThan(10);
  133. }
  134. @Test
  135. void readString() throws URISyntaxException {
  136. String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
  137. assertThat(text.length()).isGreaterThan(10);
  138. }
  139. @Test
  140. void readGzipString() throws URISyntaxException {
  141. String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
  142. assertThat(text).isEqualTo("GZIP response");
  143. }
  144. @Test
  145. void readStringWithDefaultTimeout() throws URISyntaxException {
  146. String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
  147. assertThat(text.length()).isGreaterThan(10);
  148. }
  149. @Test
  150. void downloadToFile() throws URISyntaxException, IOException {
  151. File toFile = new File(temporaryFolder, "downloadToFile.txt");
  152. new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
  153. assertThat(toFile).exists();
  154. assertThat(toFile.length()).isGreaterThan(10L);
  155. }
  156. @Test
  157. void shouldNotCreateFileIfFailToDownload() throws Exception {
  158. File toFile = new File(temporaryFolder, "downloadToFile.txt");
  159. try {
  160. new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI("http://localhost:1"), toFile);
  161. } catch (SonarException e) {
  162. assertThat(toFile).doesNotExist();
  163. }
  164. }
  165. @Test
  166. void userAgent_includes_version_and_SERVER_ID_when_server_is_provided() throws URISyntaxException, IOException {
  167. Server server = mock(Server.class);
  168. when(server.getVersion()).thenReturn("2.2");
  169. MapSettings settings = new MapSettings();
  170. settings.setProperty(CoreProperties.SERVER_ID, "blablabla");
  171. InputStream stream = new DefaultHttpDownloader(server, settings.asConfig()).openStream(new URI(baseUrl));
  172. Properties props = new Properties();
  173. props.load(stream);
  174. stream.close();
  175. assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 # blablabla");
  176. }
  177. @Test
  178. void userAgent_includes_only_version_when_there_is_no_SERVER_ID_and_server_is_provided() throws URISyntaxException, IOException {
  179. Server server = mock(Server.class);
  180. when(server.getVersion()).thenReturn("2.2");
  181. InputStream stream = new DefaultHttpDownloader(server, new MapSettings().asConfig()).openStream(new URI(baseUrl));
  182. Properties props = new Properties();
  183. props.load(stream);
  184. stream.close();
  185. assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
  186. }
  187. @Test
  188. void followRedirect() throws URISyntaxException {
  189. String content = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
  190. assertThat(content).isEqualTo("redirected");
  191. }
  192. @Test
  193. void supported_schemes() {
  194. assertThat(new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
  195. }
  196. @Test
  197. void uri_description() throws URISyntaxException {
  198. String description = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
  199. assertThat(description).isEqualTo("http://sonarsource.org");
  200. }
  201. @Test
  202. void readBytes_whenServerReturnsError_shouldThrow() throws URISyntaxException {
  203. DefaultHttpDownloader downloader = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig());
  204. URI errorUri = new URI(baseUrl + "/error");
  205. assertThatThrownBy(() -> downloader.readBytes(errorUri)).isInstanceOf(HttpException.class).hasMessage("Fail to download [" + errorUri + "]. Response code: 500");
  206. }
  207. }