aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2017-05-23 15:08:17 +0200
committerdbmeneses <duarte.meneses@sonarsource.com>2017-05-24 11:17:50 +0200
commit5bc0329e376728811d669c06c6631a2fb37845f9 (patch)
treeefdcb7ba9e91e2b79ca15b9d2a8e16fbdaef925f
parentecd2b90b42b8f70d18f8ab6a6fb4353267893c29 (diff)
downloadsonarqube-5bc0329e376728811d669c06c6631a2fb37845f9.tar.gz
sonarqube-5bc0329e376728811d669c06c6631a2fb37845f9.zip
SONAR-9301 Support HTTP redirect on scanner side
-rw-r--r--it/it-tests/src/test/java/it/analysis/RedirectTest.java95
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java66
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java26
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();