diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2017-05-23 15:08:17 +0200 |
---|---|---|
committer | dbmeneses <duarte.meneses@sonarsource.com> | 2017-05-24 11:17:50 +0200 |
commit | 5bc0329e376728811d669c06c6631a2fb37845f9 (patch) | |
tree | efdcb7ba9e91e2b79ca15b9d2a8e16fbdaef925f | |
parent | ecd2b90b42b8f70d18f8ab6a6fb4353267893c29 (diff) | |
download | sonarqube-5bc0329e376728811d669c06c6631a2fb37845f9.tar.gz sonarqube-5bc0329e376728811d669c06c6631a2fb37845f9.zip |
SONAR-9301 Support HTTP redirect on scanner side
3 files changed, 179 insertions, 8 deletions
diff --git a/it/it-tests/src/test/java/it/analysis/RedirectTest.java b/it/it-tests/src/test/java/it/analysis/RedirectTest.java new file mode 100644 index 00000000000..6c4653625a3 --- /dev/null +++ b/it/it-tests/src/test/java/it/analysis/RedirectTest.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package it.analysis; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.InetAddress; + +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.MovedContextHandler; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.util.NetworkUtils; + +import it.Category3Suite; +import util.ItUtils; + +public class RedirectTest { + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + private static Server server; + private static int redirectPort; + + @Before + public static void beforeClass() throws Exception { + orchestrator.resetData(); + redirectPort = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress()); + + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setMaxThreads(500); + + server = new Server(threadPool); + // HTTP Configuration + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSendServerVersion(true); + httpConfig.setSendDateHeader(false); + + // Moved handler + MovedContextHandler movedContextHandler = new MovedContextHandler(); + movedContextHandler.setPermanent(true); + movedContextHandler.setNewContextURL(orchestrator.getServer().getUrl()); + server.setHandler(movedContextHandler); + + // http connector + ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + http.setPort(redirectPort); + server.addConnector(http); + server.start(); + } + + @After + public static void after() throws Exception { + server.stop(); + } + + @Test + public void testRedirect() { + SonarScanner sonarScanner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setScannerVersion("2.7") + .setProperty("sonar.host.url", "http://localhost:" + redirectPort); + BuildResult buildResult = orchestrator.executeBuild(sonarScanner); + + // logs show original URL + assertThat(buildResult.getLogs()).contains("ANALYSIS SUCCESSFUL, you can browse " + "http://localhost:" + redirectPort); + + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java index e652a21e28f..46aa3b8a3dc 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java @@ -41,6 +41,11 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.nullToEmpty; import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_MOVED_PERM; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; +import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; + /** * Connect to any SonarQube server available through HTTP or HTTPS. * <p>TLS 1.0, 1.1 and 1.2 are supported on both Java 7 and 8. SSLv3 is not supported.</p> @@ -58,6 +63,7 @@ public class HttpConnector implements WsConnector { private final HttpUrl baseUrl; private final String credentials; private final OkHttpClient okHttpClient; + private final OkHttpClient noRedirectOkHttpClient; private HttpConnector(Builder builder) { this.baseUrl = HttpUrl.parse(builder.url.endsWith("/") ? builder.url : format("%s/", builder.url)); @@ -82,6 +88,14 @@ public class HttpConnector implements WsConnector { okHttpClientBuilder.setSSLSocketFactory(builder.sslSocketFactory); okHttpClientBuilder.setTrustManager(builder.sslTrustManager); this.okHttpClient = okHttpClientBuilder.build(); + this.noRedirectOkHttpClient = newClientWithoutRedirect(this.okHttpClient); + } + + private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) { + return client.newBuilder() + .followRedirects(false) + .followSslRedirects(false) + .build(); } @Override @@ -109,7 +123,7 @@ public class HttpConnector implements WsConnector { completeUrlQueryParameters(getRequest, urlBuilder); Request.Builder okRequestBuilder = prepareOkRequestBuilder(getRequest, urlBuilder).get(); - return doCall(okRequestBuilder.build()); + return new OkHttpResponse(doCall(okHttpClient, okRequestBuilder.build())); } private WsResponse post(PostRequest postRequest) { @@ -139,8 +153,10 @@ public class HttpConnector implements WsConnector { }); body = bodyBuilder.build(); } - Request.Builder reqBuilder = prepareOkRequestBuilder(postRequest, urlBuilder); - return doCall(reqBuilder.post(body).build()); + Request.Builder okRequestBuilder = prepareOkRequestBuilder(postRequest, urlBuilder).post(body); + Response response = doCall(noRedirectOkHttpClient, okRequestBuilder.build()); + response = checkRedirect(response); + return new OkHttpResponse(response); } private HttpUrl.Builder prepareUrlBuilder(WsRequest wsRequest) { @@ -150,7 +166,7 @@ public class HttpConnector implements WsConnector { .newBuilder(); } - private static void completeUrlQueryParameters(BaseRequest request, HttpUrl.Builder urlBuilder) { + private static void completeUrlQueryParameters(BaseRequest<?> request, HttpUrl.Builder urlBuilder) { request.getParameters().getKeys() .forEach(key -> request.getParameters().getValues(key) .forEach(value -> urlBuilder.addQueryParameter(key, value))); @@ -167,16 +183,50 @@ public class HttpConnector implements WsConnector { return okHttpRequestBuilder; } - private OkHttpResponse doCall(Request okRequest) { - Call call = okHttpClient.newCall(okRequest); + private static Response doCall(OkHttpClient client, Request okRequest) { + Call call = client.newCall(okRequest); try { - Response okResponse = call.execute(); - return new OkHttpResponse(okResponse); + return call.execute(); } catch (IOException e) { throw new IllegalStateException("Fail to request " + okRequest.url(), e); } } + private Response checkRedirect(Response response) { + switch (response.code()) { + case HTTP_MOVED_PERM: + case HTTP_MOVED_TEMP: + case HTTP_TEMP_REDIRECT: + case HTTP_PERM_REDIRECT: + // OkHttpClient does not follow the redirect with the same HTTP method. A POST is + // redirected to a GET. Because of that the redirect must be manually implemented. + // See: + // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316 + // https://github.com/square/okhttp/issues/936#issuecomment-266430151 + return followPostRedirect(response); + default: + return response; + } + } + + private Response followPostRedirect(Response response) { + String location = response.header("Location"); + if (location == null) { + throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url())); + } + HttpUrl url = response.request().url().resolve(location); + + // Don't follow redirects to unsupported protocols. + if (url == null) { + throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location)); + } + + Request.Builder redirectRequest = response.request().newBuilder(); + redirectRequest.post(response.request().body()); + response.body().close(); + return doCall(noRedirectOkHttpClient, redirectRequest.url(url).build()); + } + /** * @since 5.5 */ diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java index ba6e9d4dc43..3cad8d93ff8 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java @@ -20,6 +20,7 @@ package org.sonarqube.ws.client; import java.io.File; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.List; @@ -63,6 +64,31 @@ public class HttpConnectorTest { } @Test + public void follow_redirects_post() throws IOException, InterruptedException { + MockWebServer server2 = new MockWebServer(); + server2.start(); + server2.url("").url().toString(); + + server.enqueue(new MockResponse() + .setResponseCode(302) + .setHeader("Location", server2.url("").url().toString())); + + server2.enqueue(new MockResponse() + .setResponseCode(200)); + + underTest = HttpConnector.newBuilder().url(serverUrl).build(); + PostRequest request = new PostRequest("api/ce/submit").setParam("projectKey", "project"); + WsResponse response = underTest.call(request); + + RecordedRequest recordedRequest = server2.takeRequest(); + + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getBody().readUtf8()).isEqualTo("projectKey=project"); + assertThat(response.requestUrl()).isEqualTo(server2.url("").url().toString()); + assertThat(response.code()).isEqualTo(200); + } + + @Test public void test_default_settings() throws Exception { answerHelloWorld(); underTest = HttpConnector.newBuilder().url(serverUrl).build(); |