aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-ws/src
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2017-05-23 15:08:17 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2017-09-15 09:57:00 +0200
commit4f3018eb093f9c2d5406df7674eb0e9a7b50e44c (patch)
tree4187b0d2e72d98c8c1b3a26dd33da1615bdd0150 /sonar-ws/src
parent1de512a131f84fb1b6656150347d2b075b478780 (diff)
downloadsonarqube-4f3018eb093f9c2d5406df7674eb0e9a7b50e44c.tar.gz
sonarqube-4f3018eb093f9c2d5406df7674eb0e9a7b50e44c.zip
SONAR-9301 Support HTTP redirect on scanner side
Diffstat (limited to 'sonar-ws/src')
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java79
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java16
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java26
3 files changed, 100 insertions, 21 deletions
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 6569eb3535b..9d4c7fdc1ea 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
@@ -40,6 +40,10 @@ import static com.google.common.base.Preconditions.checkArgument;
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.
@@ -56,9 +60,9 @@ public class HttpConnector implements WsConnector {
* It is required for further usage of {@link HttpUrl#resolve(String)}.
*/
private final HttpUrl baseUrl;
- private final String credentials;
private final String systemPassCode;
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));
@@ -67,13 +71,10 @@ public class HttpConnector implements WsConnector {
OkHttpClientBuilder okHttpClientBuilder = new OkHttpClientBuilder();
okHttpClientBuilder.setUserAgent(builder.userAgent);
- if (isNullOrEmpty(builder.login)) {
- // no login nor access token
- this.credentials = null;
- } else {
+ if (!isNullOrEmpty(builder.login)) {
// password is null when login represents an access token. In this case
// the Basic credentials consider an empty password.
- this.credentials = Credentials.basic(builder.login, nullToEmpty(builder.password));
+ okHttpClientBuilder.setCredentials(Credentials.basic(builder.login, nullToEmpty(builder.password)));
}
this.systemPassCode = builder.systemPassCode;
okHttpClientBuilder.setProxy(builder.proxy);
@@ -84,6 +85,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
@@ -111,7 +120,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) {
@@ -141,8 +150,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) {
@@ -152,7 +163,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)));
@@ -163,27 +174,57 @@ public class HttpConnector implements WsConnector {
.url(urlBuilder.build())
.header("Accept", getRequest.getMediaType())
.header("Accept-Charset", "UTF-8");
- if (credentials != null) {
- okHttpRequestBuilder.header("Authorization", credentials);
- }
if (systemPassCode != null) {
okHttpRequestBuilder.header("X-Sonar-Passcode", systemPassCode);
}
- getRequest.getHeaders().getNames().forEach(name ->
- okHttpRequestBuilder.header(name, getRequest.getHeaders().getValue(name).get()));
+ getRequest.getHeaders().getNames().forEach(name -> okHttpRequestBuilder.header(name, getRequest.getHeaders().getValue(name).get()));
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/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java
index a9d4985fff5..8737a68ea0c 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java
@@ -63,6 +63,7 @@ public class OkHttpClientBuilder {
private String userAgent;
private Proxy proxy;
+ private String credentials;
private String proxyLogin;
private String proxyPassword;
private long connectTimeoutMs = -1;
@@ -138,6 +139,13 @@ public class OkHttpClientBuilder {
}
/**
+ * Set credentials that will be passed on every request
+ */
+ public void setCredentials(String credentials) {
+ this.credentials = credentials;
+ }
+
+ /**
* Sets the default read timeout for new connections. A value of 0 means no timeout.
* Default is defined by OkHttp (10 seconds in OkHttp 3.3).
*/
@@ -158,7 +166,7 @@ public class OkHttpClientBuilder {
if (readTimeoutMs >= 0) {
builder.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS);
}
- builder.addNetworkInterceptor(this::addUserAgent);
+ builder.addNetworkInterceptor(this::addHeaders);
if (proxyLogin != null) {
builder.proxyAuthenticator((route, response) -> {
if (response.request().header(PROXY_AUTHORIZATION) != null) {
@@ -187,11 +195,14 @@ public class OkHttpClientBuilder {
return builder.build();
}
- private Response addUserAgent(Interceptor.Chain chain) throws IOException {
+ private Response addHeaders(Interceptor.Chain chain) throws IOException {
Request.Builder newRequest = chain.request().newBuilder();
if (userAgent != null) {
newRequest.header("User-Agent", userAgent);
}
+ if (credentials != null) {
+ newRequest.header("Authorization", credentials);
+ }
return chain.proceed(newRequest.build());
}
@@ -285,4 +296,5 @@ public class OkHttpClientBuilder {
return kmf.getKeyManagers();
}
+
}
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 5c07cd7021b..4e1ca719ac4 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;
@@ -71,6 +72,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();