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.

HttpConnectorTest.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 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.sonarqube.ws.client;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.net.InetSocketAddress;
  24. import java.net.Proxy;
  25. import java.net.SocketTimeoutException;
  26. import java.util.Base64;
  27. import java.util.List;
  28. import java.util.Random;
  29. import java.util.concurrent.TimeUnit;
  30. import javax.net.ssl.SSLSocketFactory;
  31. import okhttp3.ConnectionSpec;
  32. import okhttp3.mockwebserver.MockResponse;
  33. import okhttp3.mockwebserver.MockWebServer;
  34. import okhttp3.mockwebserver.RecordedRequest;
  35. import org.apache.commons.io.FileUtils;
  36. import org.apache.commons.io.IOUtils;
  37. import org.apache.commons.lang.RandomStringUtils;
  38. import org.apache.commons.lang.StringUtils;
  39. import org.hamcrest.core.IsInstanceOf;
  40. import org.junit.After;
  41. import org.junit.Before;
  42. import org.junit.Rule;
  43. import org.junit.Test;
  44. import org.junit.rules.ExpectedException;
  45. import org.junit.rules.TemporaryFolder;
  46. import org.sonarqube.ws.MediaTypes;
  47. import static java.nio.charset.StandardCharsets.UTF_8;
  48. import static okhttp3.Credentials.basic;
  49. import static org.assertj.core.api.Assertions.assertThat;
  50. import static org.junit.Assert.fail;
  51. import static org.mockito.Mockito.mock;
  52. import static org.sonarqube.ws.client.HttpConnector.newBuilder;
  53. public class HttpConnectorTest {
  54. @Rule
  55. public TemporaryFolder temp = new TemporaryFolder();
  56. @Rule
  57. public ExpectedException expectedException = ExpectedException.none();
  58. private MockWebServer server;
  59. private String serverUrl;
  60. private HttpConnector underTest;
  61. @Before
  62. public void setUp() throws Exception {
  63. server = new MockWebServer();
  64. server.start();
  65. serverUrl = server.url("").url().toString();
  66. }
  67. @After
  68. public void stop() throws Exception {
  69. server.close();
  70. }
  71. @Test
  72. public void follow_redirects_post() throws IOException, InterruptedException {
  73. MockWebServer server2 = new MockWebServer();
  74. server2.start();
  75. server2.url("").url().toString();
  76. server.enqueue(new MockResponse()
  77. .setResponseCode(302)
  78. .setHeader("Location", server2.url("").url().toString()));
  79. server2.enqueue(new MockResponse()
  80. .setResponseCode(200));
  81. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  82. PostRequest request = new PostRequest("api/ce/submit").setParam("projectKey", "project");
  83. WsResponse response = underTest.call(request);
  84. RecordedRequest recordedRequest = server2.takeRequest();
  85. assertThat(recordedRequest.getMethod()).isEqualTo("POST");
  86. assertThat(recordedRequest.getBody().readUtf8()).isEqualTo("projectKey=project");
  87. assertThat(response.requestUrl()).isEqualTo(server2.url("").url().toString());
  88. assertThat(response.code()).isEqualTo(200);
  89. }
  90. @Test
  91. public void test_default_settings() throws Exception {
  92. answerHelloWorld();
  93. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  94. assertThat(underTest.baseUrl()).isEqualTo(serverUrl);
  95. GetRequest request = new GetRequest("api/issues/search").setMediaType(MediaTypes.PROTOBUF);
  96. WsResponse response = underTest.call(request);
  97. // verify default timeouts on client
  98. assertThat(underTest.okHttpClient().connectTimeoutMillis()).isEqualTo(HttpConnector.DEFAULT_CONNECT_TIMEOUT_MILLISECONDS);
  99. assertThat(underTest.okHttpClient().readTimeoutMillis()).isEqualTo(HttpConnector.DEFAULT_READ_TIMEOUT_MILLISECONDS);
  100. // verify response
  101. assertThat(response.hasContent()).isTrue();
  102. assertThat(response.content()).isEqualTo("hello, world!");
  103. // verify the request received by server
  104. RecordedRequest recordedRequest = server.takeRequest();
  105. assertThat(recordedRequest.getMethod()).isEqualTo("GET");
  106. assertThat(recordedRequest.getPath()).isEqualTo("/api/issues/search");
  107. assertThat(recordedRequest.getHeader("Accept")).isEqualTo(MediaTypes.PROTOBUF);
  108. assertThat(recordedRequest.getHeader("Accept-Charset")).isEqualTo("UTF-8");
  109. assertThat(recordedRequest.getHeader("User-Agent")).startsWith("okhttp/");
  110. // compression is handled by OkHttp
  111. assertThat(recordedRequest.getHeader("Accept-Encoding")).isEqualTo("gzip");
  112. }
  113. @Test
  114. public void add_headers_to_GET_request() throws Exception {
  115. answerHelloWorld();
  116. GetRequest request = new GetRequest("api/issues/search")
  117. .setHeader("X-Foo", "fooz")
  118. .setHeader("X-Bar", "barz");
  119. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  120. underTest.call(request);
  121. RecordedRequest recordedRequest = server.takeRequest();
  122. assertThat(recordedRequest.getHeader("X-Foo")).isEqualTo("fooz");
  123. assertThat(recordedRequest.getHeader("X-Bar")).isEqualTo("barz");
  124. }
  125. @Test
  126. public void use_basic_authentication() throws Exception {
  127. answerHelloWorld();
  128. underTest = HttpConnector.newBuilder()
  129. .url(serverUrl)
  130. .credentials("theLogin", "thePassword")
  131. .build();
  132. GetRequest request = new GetRequest("api/issues/search");
  133. underTest.call(request);
  134. RecordedRequest recordedRequest = server.takeRequest();
  135. assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theLogin", "thePassword"));
  136. }
  137. @Test
  138. public void use_basic_authentication_with_null_password() throws Exception {
  139. answerHelloWorld();
  140. underTest = HttpConnector.newBuilder()
  141. .url(serverUrl)
  142. .credentials("theLogin", null)
  143. .build();
  144. GetRequest request = new GetRequest("api/issues/search");
  145. underTest.call(request);
  146. RecordedRequest recordedRequest = server.takeRequest();
  147. assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theLogin", ""));
  148. }
  149. @Test
  150. public void use_basic_authentication_with_utf8_login_and_password() throws Exception {
  151. answerHelloWorld();
  152. String login = "我能";
  153. String password = "吞下";
  154. underTest = HttpConnector.newBuilder()
  155. .url(serverUrl)
  156. .credentials(login, password)
  157. .build();
  158. GetRequest request = new GetRequest("api/issues/search");
  159. underTest.call(request);
  160. RecordedRequest recordedRequest = server.takeRequest();
  161. // do not use OkHttp Credentials.basic() in order to not use the same code as the code under test
  162. String expectedHeader = "Basic " + Base64.getEncoder().encodeToString((login + ":" + password).getBytes(UTF_8));
  163. assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(expectedHeader);
  164. }
  165. /**
  166. * Access token replaces the couple {login,password} and is sent through
  167. * the login field
  168. */
  169. @Test
  170. public void use_access_token() throws Exception {
  171. answerHelloWorld();
  172. underTest = HttpConnector.newBuilder()
  173. .url(serverUrl)
  174. .token("theToken")
  175. .build();
  176. GetRequest request = new GetRequest("api/issues/search");
  177. underTest.call(request);
  178. RecordedRequest recordedRequest = server.takeRequest();
  179. assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theToken", ""));
  180. }
  181. @Test
  182. public void systemPassCode_sets_header_when_value_is_not_null() throws InterruptedException {
  183. answerHelloWorld();
  184. String systemPassCode = new Random().nextBoolean() ? "" : RandomStringUtils.randomAlphanumeric(21);
  185. underTest = HttpConnector.newBuilder()
  186. .url(serverUrl)
  187. .systemPassCode(systemPassCode)
  188. .build();
  189. GetRequest request = new GetRequest("api/issues/search");
  190. underTest.call(request);
  191. RecordedRequest recordedRequest = server.takeRequest();
  192. assertThat(recordedRequest.getHeader("X-sonar-passcode"))
  193. .isEqualTo(systemPassCode);
  194. }
  195. @Test
  196. public void use_proxy_authentication() throws Exception {
  197. try (MockWebServer proxy = new MockWebServer()) {
  198. proxy.start();
  199. underTest = HttpConnector.newBuilder()
  200. .url(serverUrl)
  201. .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy.getHostName(), proxy.getPort())))
  202. .proxyCredentials("theProxyLogin", "theProxyPassword")
  203. .build();
  204. GetRequest request = new GetRequest("api/issues/search");
  205. proxy.enqueue(new MockResponse().setResponseCode(407));
  206. proxy.enqueue(new MockResponse().setBody("OK!"));
  207. underTest.call(request);
  208. RecordedRequest recordedRequest = proxy.takeRequest();
  209. assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull();
  210. recordedRequest = proxy.takeRequest();
  211. assertThat(recordedRequest.getHeader("Proxy-Authorization")).isEqualTo(basic("theProxyLogin", "theProxyPassword"));
  212. }
  213. }
  214. @Test
  215. public void use_proxy_authentication_wrong_crendentials() throws Exception {
  216. try (MockWebServer proxy = new MockWebServer()) {
  217. proxy.start();
  218. underTest = HttpConnector.newBuilder()
  219. .url(serverUrl)
  220. .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy.getHostName(), proxy.getPort())))
  221. .proxyCredentials("theProxyLogin", "wrongPassword")
  222. .build();
  223. GetRequest request = new GetRequest("api/issues/search");
  224. proxy.enqueue(new MockResponse().setResponseCode(407));
  225. proxy.enqueue(new MockResponse().setResponseCode(407));
  226. proxy.enqueue(new MockResponse().setResponseCode(407));
  227. underTest.call(request);
  228. RecordedRequest recordedRequest = proxy.takeRequest();
  229. assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull();
  230. recordedRequest = proxy.takeRequest();
  231. assertThat(recordedRequest.getHeader("Proxy-Authorization")).isEqualTo(basic("theProxyLogin", "wrongPassword"));
  232. assertThat(proxy.getRequestCount()).isEqualTo(2);
  233. }
  234. }
  235. @Test
  236. public void override_timeouts() {
  237. underTest = HttpConnector.newBuilder()
  238. .url(serverUrl)
  239. .readTimeoutMilliseconds(42)
  240. .connectTimeoutMilliseconds(74)
  241. .build();
  242. assertThat(underTest.okHttpClient().readTimeoutMillis()).isEqualTo(42);
  243. assertThat(underTest.okHttpClient().connectTimeoutMillis()).isEqualTo(74);
  244. }
  245. @Test
  246. public void send_user_agent() throws Exception {
  247. answerHelloWorld();
  248. underTest = HttpConnector.newBuilder()
  249. .url(serverUrl)
  250. .userAgent("Maven Plugin/2.3")
  251. .build();
  252. underTest.call(new GetRequest("api/issues/search"));
  253. RecordedRequest recordedRequest = server.takeRequest();
  254. assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("Maven Plugin/2.3");
  255. }
  256. @Test
  257. public void fail_if_unknown_implementation_of_request() {
  258. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  259. try {
  260. underTest.call(mock(WsRequest.class));
  261. fail();
  262. } catch (IllegalArgumentException e) {
  263. assertThat(e).hasMessageContaining("Unsupported implementation: ");
  264. }
  265. }
  266. @Test
  267. public void fail_if_malformed_URL() {
  268. expectedException.expect(IllegalArgumentException.class);
  269. expectedException.expectMessage("Malformed URL: 'wrong URL'");
  270. underTest = newBuilder().url("wrong URL").build();
  271. }
  272. @Test
  273. public void send_post_request() throws Exception {
  274. answerHelloWorld();
  275. PostRequest request = new PostRequest("api/issues/search")
  276. .setParam("severity", "MAJOR")
  277. .setMediaType(MediaTypes.PROTOBUF);
  278. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  279. WsResponse response = underTest.call(request);
  280. // verify response
  281. assertThat(response.hasContent()).isTrue();
  282. assertThat(response.content()).isEqualTo("hello, world!");
  283. // verify the request received by server
  284. RecordedRequest recordedRequest = server.takeRequest();
  285. assertThat(recordedRequest.getMethod()).isEqualTo("POST");
  286. assertThat(recordedRequest.getPath()).isEqualTo("/api/issues/search");
  287. assertThat(recordedRequest.getBody().readUtf8()).isEqualTo("severity=MAJOR");
  288. assertThat(recordedRequest.getHeader("Accept")).isEqualTo("application/x-protobuf");
  289. }
  290. @Test
  291. public void add_header_to_POST_request() throws Exception {
  292. answerHelloWorld();
  293. PostRequest request = new PostRequest("api/issues/search")
  294. .setHeader("X-Foo", "fooz")
  295. .setHeader("X-Bar", "barz");
  296. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  297. underTest.call(request);
  298. RecordedRequest recordedRequest = server.takeRequest();
  299. assertThat(recordedRequest.getHeader("X-Foo")).isEqualTo("fooz");
  300. assertThat(recordedRequest.getHeader("X-Bar")).isEqualTo("barz");
  301. }
  302. @Test
  303. public void upload_file() throws Exception {
  304. answerHelloWorld();
  305. File reportFile = temp.newFile();
  306. FileUtils.write(reportFile, "the report content");
  307. PostRequest request = new PostRequest("api/report/upload")
  308. .setParam("project", "theKey")
  309. .setPart("report", new PostRequest.Part(MediaTypes.TXT, reportFile))
  310. .setMediaType(MediaTypes.PROTOBUF);
  311. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  312. WsResponse response = underTest.call(request);
  313. assertThat(response.hasContent()).isTrue();
  314. RecordedRequest recordedRequest = server.takeRequest();
  315. assertThat(recordedRequest.getMethod()).isEqualTo("POST");
  316. assertThat(recordedRequest.getPath()).isEqualTo("/api/report/upload?project=theKey");
  317. String body = IOUtils.toString(recordedRequest.getBody().inputStream());
  318. assertThat(body)
  319. .contains("Content-Disposition: form-data; name=\"report\"")
  320. .contains("Content-Type: text/plain")
  321. .contains("the report content");
  322. }
  323. @Test
  324. public void http_error() throws Exception {
  325. server.enqueue(new MockResponse().setResponseCode(404));
  326. PostRequest request = new PostRequest("api/issues/search");
  327. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  328. WsResponse wsResponse = underTest.call(request);
  329. assertThat(wsResponse.code()).isEqualTo(404);
  330. }
  331. @Test
  332. public void support_base_url_ending_with_slash() throws Exception {
  333. assertThat(serverUrl).endsWith("/");
  334. underTest = HttpConnector.newBuilder().url(StringUtils.removeEnd(serverUrl, "/")).build();
  335. GetRequest request = new GetRequest("api/issues/search");
  336. answerHelloWorld();
  337. WsResponse response = underTest.call(request);
  338. assertThat(response.hasContent()).isTrue();
  339. }
  340. @Test
  341. public void support_base_url_with_context() {
  342. // just to be sure
  343. assertThat(serverUrl).endsWith("/");
  344. underTest = HttpConnector.newBuilder().url(serverUrl + "sonar").build();
  345. GetRequest request = new GetRequest("api/issues/search");
  346. answerHelloWorld();
  347. assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
  348. request = new GetRequest("/api/issues/search");
  349. answerHelloWorld();
  350. assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
  351. }
  352. @Test
  353. public void support_tls_versions_of_java8() {
  354. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  355. assertTlsAndClearTextSpecifications(underTest);
  356. assertThat(underTest.okHttpClient().sslSocketFactory()).isInstanceOf(SSLSocketFactory.getDefault().getClass());
  357. }
  358. @Test
  359. public void override_timeout_on_get() {
  360. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  361. server.enqueue(new MockResponse().setBodyDelay(100, TimeUnit.MILLISECONDS).setBody("Hello delayed"));
  362. expectedException.expect(IllegalStateException.class);
  363. expectedException.expectCause(IsInstanceOf.instanceOf(SocketTimeoutException.class));
  364. WsResponse call = underTest.call(new GetRequest("/").setTimeOutInMs(5));
  365. assertThat(call.content()).equals("Hello delayed");
  366. }
  367. @Test
  368. public void override_timeout_on_post() {
  369. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  370. // Headers are not affected by setBodyDelay, let's throttle the answer
  371. server.enqueue(new MockResponse().throttleBody(1,100, TimeUnit.MILLISECONDS).setBody("Hello delayed"));
  372. expectedException.expect(IllegalStateException.class);
  373. expectedException.expectCause(IsInstanceOf.instanceOf(SocketTimeoutException.class));
  374. WsResponse call = underTest.call(new PostRequest("/").setTimeOutInMs(5));
  375. assertThat(call.content()).equals("Hello delayed");
  376. }
  377. @Test
  378. public void override_timeout_on_post_with_redirect() {
  379. underTest = HttpConnector.newBuilder().url(serverUrl).build();
  380. server.enqueue(new MockResponse().setResponseCode(301).setHeader("Location:", "/redirect"));
  381. // Headers are not affected by setBodyDelay, let's throttle the answer
  382. server.enqueue(new MockResponse().throttleBody(1,100, TimeUnit.MILLISECONDS).setBody("Hello delayed"));
  383. expectedException.expect(IllegalStateException.class);
  384. expectedException.expectCause(IsInstanceOf.instanceOf(SocketTimeoutException.class));
  385. WsResponse call = underTest.call(new PostRequest("/").setTimeOutInMs(5));
  386. assertThat(call.content()).equals("Hello delayed");
  387. }
  388. private void assertTlsAndClearTextSpecifications(HttpConnector underTest) {
  389. List<ConnectionSpec> connectionSpecs = underTest.okHttpClient().connectionSpecs();
  390. assertThat(connectionSpecs).hasSize(2);
  391. // TLS. tlsVersions()==null means all TLS versions
  392. assertThat(connectionSpecs.get(0).tlsVersions()).isNull();
  393. assertThat(connectionSpecs.get(0).isTls()).isTrue();
  394. // HTTP
  395. assertThat(connectionSpecs.get(1).tlsVersions()).isNull();
  396. assertThat(connectionSpecs.get(1).isTls()).isFalse();
  397. }
  398. private void answerHelloWorld() {
  399. server.enqueue(new MockResponse().setBody("hello, world!"));
  400. }
  401. }