Browse Source

SONAR-9301 Support HTTP redirect on scanner side

tags/6.5-M1
Duarte Meneses 7 years ago
parent
commit
72da23ae2c

+ 95
- 0
it/it-tests/src/test/java/it/analysis/RedirectTest.java View File

@@ -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);
}
}

+ 58
- 8
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java View File

@@ -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
*/

+ 26
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java View File

@@ -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;
@@ -62,6 +63,31 @@ public class HttpConnectorTest {
serverUrl = server.url("").url().toString();
}

@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();

Loading…
Cancel
Save