aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>2022-01-24 10:01:00 +0100
committersonartech <sonartech@sonarsource.com>2022-02-18 15:48:03 +0000
commita9e3735302b847752a6abd60397f91fb9b91e4b7 (patch)
tree4ce1976e6eec8d77f5da6f3a7a17a700ee125d96 /server
parentdd5c24bc96a10a3b81bd1c94d40b936ba451cd90 (diff)
downloadsonarqube-a9e3735302b847752a6abd60397f91fb9b91e4b7.tar.gz
sonarqube-a9e3735302b847752a6abd60397f91fb9b91e4b7.zip
SONAR-15918 making the new endpoint /api/push/sonarlint_events async
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/public/WEB-INF/web.xml31
-rw-r--r--server/sonar-webserver-pushapi/build.gradle5
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushAction.java30
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java29
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushWsTest.java2
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java27
-rw-r--r--server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/DumbPushResponse.java139
-rw-r--r--server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/TestPushRequest.java98
-rw-r--r--server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/WsPushActionTester.java34
-rw-r--r--server/sonar-webserver-ws/build.gradle2
-rw-r--r--server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java5
-rw-r--r--server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletResponse.java10
-rw-r--r--server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletRequestTest.java7
-rw-r--r--server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletResponseTest.java16
-rw-r--r--server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java115
-rw-r--r--server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WsUtilsTest.java4
-rw-r--r--server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/DumbResponse.java20
-rw-r--r--server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestRequest.java8
-rw-r--r--server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestResponse.java16
-rw-r--r--server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestableResponse.java35
-rw-r--r--server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/WsActionTester.java2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java1
22 files changed, 521 insertions, 115 deletions
diff --git a/server/sonar-web/public/WEB-INF/web.xml b/server/sonar-web/public/WEB-INF/web.xml
index d2ba0126c87..da4795bfbef 100644
--- a/server/sonar-web/public/WEB-INF/web.xml
+++ b/server/sonar-web/public/WEB-INF/web.xml
@@ -2,23 +2,26 @@
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- id="SonarQube"
- version="3.0"
- metadata-complete="true">
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ id="SonarQube"
+ version="3.0"
+ metadata-complete="true">
<display-name>SonarQube</display-name>
<filter>
<filter-name>ServletFilters</filter-name>
<filter-class>org.sonar.server.platform.web.MasterServletFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>UserSessionFilter</filter-name>
<filter-class>org.sonar.server.platform.web.UserSessionFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>SetCharacterEncodingFilter</filter-name>
<filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
+ <async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
@@ -27,26 +30,32 @@
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>org.sonar.server.platform.web.SecurityServletFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RootFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RootFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RedirectFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RedirectFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RequestUidFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RequestIdFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>WebPagesFilter</filter-name>
<filter-class>org.sonar.server.platform.web.WebPagesFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>CacheControlFilter</filter-name>
<filter-class>org.sonar.server.platform.web.CacheControlFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<!-- order of execution is important -->
@@ -88,6 +97,20 @@
</filter-mapping>
<servlet>
+ <servlet-name>default-servlet</servlet-name>
+ <servlet-class>
+ org.apache.catalina.servlets.DefaultServlet
+ </servlet-class>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>default</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+
+ <servlet>
<servlet-name>static</servlet-name>
<servlet-class>org.sonar.server.platform.web.StaticResourcesServlet</servlet-class>
</servlet>
diff --git a/server/sonar-webserver-pushapi/build.gradle b/server/sonar-webserver-pushapi/build.gradle
index 963886381b7..a2c94714bd0 100644
--- a/server/sonar-webserver-pushapi/build.gradle
+++ b/server/sonar-webserver-pushapi/build.gradle
@@ -5,12 +5,15 @@ sonarqube {
}
dependencies {
+ compile 'javax.servlet:javax.servlet-api'
compile project(':server:sonar-webserver-auth')
compile project(':server:sonar-webserver-ws')
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
-
testCompile testFixtures(project(':server:sonar-webserver-ws'))
+
+ testFixturesApi project(':sonar-testing-harness')
+ testFixturesCompileOnly testFixtures(project(':server:sonar-webserver-ws'))
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushAction.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushAction.java
index 405145b77ac..fb613de127a 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushAction.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushAction.java
@@ -19,9 +19,35 @@
*/
package org.sonar.server.pushapi;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.ServletResponse;
import org.sonar.server.ws.WsAction;
-public interface ServerPushAction extends WsAction {
- //marker interface
+public abstract class ServerPushAction implements WsAction {
+
+ protected boolean isServerSideEventsRequest(ServletRequest request) {
+ Map<String, String> headers = request.getHeaders();
+ String accept = headers.get("accept");
+ if (accept != null) {
+ return accept.contains("text/event-stream");
+ }
+ return false;
+ }
+
+ protected void setHeadersForResponse(ServletResponse response) throws IOException {
+ response.stream().setStatus(HttpServletResponse.SC_OK);
+ response.stream().setCharacterEncoding(StandardCharsets.UTF_8.name());
+ response.stream().setMediaType("text/event-stream");
+ // By adding this header, and not closing the connection,
+ // we disable HTTP chunking, and we can use write()+flush()
+ // to send data in the text/event-stream protocol
+ response.setHeader("Connection", "close");
+ response.stream().flushBuffer();
+ }
+
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
index 3c73eefd492..eb72c1c4e81 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
@@ -19,14 +19,20 @@
*/
package org.sonar.server.pushapi.sonarlint;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletResponse;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.pushapi.ServerPushAction;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.ServletResponse;
-public class SonarLintPushAction implements ServerPushAction {
+public class SonarLintPushAction extends ServerPushAction {
private static final Logger LOGGER = Loggers.get(SonarLintPushAction.class);
@@ -56,14 +62,29 @@ public class SonarLintPushAction implements ServerPushAction {
}
@Override
- public void handle(Request request, Response response) {
+ public void handle(Request request, Response response) throws IOException {
+ ServletRequest servletRequest = (ServletRequest) request;
+ ServletResponse servletResponse = (ServletResponse) response;
+
String projectKeys = request.getParam(PROJECT_PARAM_KEY).getValue();
String languages = request.getParam(LANGUAGE_PARAM_KEY).getValue();
- //to remove later
+ // to remove later
LOGGER.debug(projectKeys != null ? projectKeys : "");
LOGGER.debug(languages != null ? languages : "");
- response.noContent();
+ AsyncContext asyncContext = servletRequest.startAsync();
+ asyncContext.setTimeout(0);
+
+ if (!isServerSideEventsRequest(servletRequest)) {
+ servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ return;
+ }
+
+ setHeadersForResponse(servletResponse);
+
+ //test response to remove later
+ response.stream().output().write("Hello world".getBytes(StandardCharsets.UTF_8));
+ response.stream().output().flush();
}
}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushWsTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushWsTest.java
index 184044a2ea4..3b5d4667075 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushWsTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushWsTest.java
@@ -46,7 +46,7 @@ public class ServerPushWsTest {
assertThat(controller.actions()).isNotEmpty();
}
- private static class DummyServerPushAction implements ServerPushAction {
+ private static class DummyServerPushAction extends ServerPushAction {
@Override
public void define(WebService.NewController context) {
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
index 87a7203a40a..f6e9f5c7187 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
@@ -21,9 +21,9 @@ package org.sonar.server.pushapi.sonarlint;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
-import org.sonar.server.ws.TestRequest;
+import org.sonar.server.pushapi.TestPushRequest;
+import org.sonar.server.pushapi.WsPushActionTester;
import org.sonar.server.ws.TestResponse;
-import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.tuple;
public class SonarLintPushActionTest {
- private final WsActionTester ws = new WsActionTester(new SonarLintPushAction());
+ private final WsPushActionTester ws = new WsPushActionTester(new SonarLintPushAction());
@Test
public void defineTest() {
@@ -45,18 +45,31 @@ public class SonarLintPushActionTest {
}
@Test
- public void handle_returnsNoResponseWhenParamsProvided() {
- TestResponse response = ws.newRequest()
+ public void handle_returnsNoResponseWhenParamsAndHeadersProvided() {
+ TestResponse response = ws.newPushRequest()
.setParam("projectKeys", "project1,project2")
.setParam("languages", "java")
+ .setHeader("accept", "text/event-stream")
.execute();
- assertThat(response.getStatus()).isEqualTo(204);
+ assertThat(response.getInput()).isEqualTo("Hello world");
+ }
+
+ @Test
+ public void handle_whenAcceptHeaderNotProvided_statusCode406() {
+ TestResponse testResponse = ws.newPushRequest().
+ setParam("projectKeys", "project1,project2")
+ .setParam("languages", "java")
+ .execute();
+
+ assertThat(testResponse.getStatus()).isEqualTo(406);
}
@Test
public void handle_whenParamsNotProvided_throwException() {
- TestRequest testRequest = ws.newRequest();
+ TestPushRequest testRequest = ws.newPushRequest()
+ .setHeader("accept", "text/event-stream");
+
assertThatThrownBy(testRequest::execute)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("The 'projectKeys' parameter is missing");
diff --git a/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/DumbPushResponse.java b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/DumbPushResponse.java
new file mode 100644
index 00000000000..d8bd4641f0a
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/DumbPushResponse.java
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 org.sonar.server.pushapi;
+
+import com.google.common.base.Throwables;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonar.server.ws.ServletResponse;
+import org.sonar.server.ws.TestableResponse;
+
+import static org.mockito.Mockito.mock;
+
+public class DumbPushResponse extends ServletResponse implements TestableResponse {
+
+ public DumbPushResponse() {
+ super(mock(HttpServletResponse.class));
+ }
+
+ private DumbPushResponse.InMemoryStream stream;
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ private Map<String, String> headers = new HashMap<>();
+
+ public class InMemoryStream extends ServletStream {
+ private String mediaType;
+
+ private int status = 200;
+
+ public InMemoryStream() {
+ super(mock(HttpServletResponse.class));
+ }
+
+ @Override
+ public ServletStream setMediaType(String s) {
+ this.mediaType = s;
+ return this;
+ }
+
+ @Override
+ public ServletStream setStatus(int i) {
+ this.status = i;
+ return this;
+ }
+
+ @Override
+ public OutputStream output() {
+ return output;
+ }
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public ServletStream stream() {
+ if (stream == null) {
+ stream = new DumbPushResponse.InMemoryStream();
+ }
+ return stream;
+ }
+
+ @Override
+ public Response noContent() {
+ stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT);
+ IOUtils.closeQuietly(output);
+ return this;
+ }
+
+ @CheckForNull
+ public String mediaType() {
+ return ((DumbPushResponse.InMemoryStream) stream()).mediaType;
+ }
+
+ public int status() {
+ return ((InMemoryStream) stream()).status;
+ }
+
+ @Override
+ public Response setHeader(String name, String value) {
+ headers.put(name, value);
+ return this;
+ }
+
+ public Collection<String> getHeaderNames() {
+ return headers.keySet();
+ }
+
+ @CheckForNull
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ public byte[] getFlushedOutput() {
+ try {
+ output.flush();
+ return output.toByteArray();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/TestPushRequest.java b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/TestPushRequest.java
new file mode 100644
index 00000000000..ace34b5db09
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/TestPushRequest.java
@@ -0,0 +1,98 @@
+package org.sonar.server.pushapi;/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+import com.google.common.base.Throwables;
+import java.util.Map;
+import java.util.Optional;
+import javax.servlet.AsyncContext;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+
+import static org.mockito.Mockito.mock;
+
+public class TestPushRequest extends ServletRequest {
+
+ private TestRequest testRequest = new TestRequest();
+
+ public TestPushRequest() {
+ super(null);
+ }
+
+ @Override
+ public AsyncContext startAsync() {
+ return mock(AsyncContext.class);
+ }
+
+ @Override
+ public String method() {
+ return testRequest.method();
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return testRequest.hasParam(key);
+ }
+
+ @Override
+ public Map<String, String[]> getParams() {
+ return testRequest.getParams();
+ }
+
+ @Override
+ public String readParam(String key) {
+ return testRequest.readParam(key);
+ }
+
+ @Override
+ public String getMediaType() {
+ return testRequest.getMediaType();
+ }
+
+ public TestPushRequest setParam(String key, String value) {
+ testRequest.setParam(key, value);
+ return this;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ return testRequest.getHeaders();
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return testRequest.header(name);
+ }
+
+ public TestPushRequest setHeader(String name, String value) {
+ testRequest.setHeader(name, value);
+ return this;
+ }
+
+ public TestResponse execute() {
+ try {
+ DumbPushResponse response = new DumbPushResponse();
+ action().handler().handle(this, response);
+ return new TestResponse(response);
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/WsPushActionTester.java b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/WsPushActionTester.java
new file mode 100644
index 00000000000..cb52c1cff32
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/testFixtures/java/org/sonar/server/pushapi/WsPushActionTester.java
@@ -0,0 +1,34 @@
+package org.sonar.server.pushapi;/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+import org.sonar.server.ws.WsAction;
+import org.sonar.server.ws.WsActionTester;
+
+public class WsPushActionTester extends WsActionTester {
+ public WsPushActionTester(WsAction wsAction) {
+ super(wsAction);
+ }
+
+ public TestPushRequest newPushRequest() {
+ TestPushRequest request = new TestPushRequest();
+ request.setAction(action);
+ return request;
+ }
+}
diff --git a/server/sonar-webserver-ws/build.gradle b/server/sonar-webserver-ws/build.gradle
index 33d0249b7f1..3fa8fed9bb6 100644
--- a/server/sonar-webserver-ws/build.gradle
+++ b/server/sonar-webserver-ws/build.gradle
@@ -20,6 +20,8 @@ dependencies {
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly 'org.apache.tomcat.embed:tomcat-embed-core'
+ testCompile 'com.tngtech.java:junit-dataprovider'
+ testCompile 'junit:junit'
testCompile 'com.google.code.findbugs:jsr305'
testCompile 'javax.servlet:javax.servlet-api'
testCompile 'org.apache.tomcat.embed:tomcat-embed-core'
diff --git a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java
index 89226f6d9a9..cfc18bb2969 100644
--- a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java
+++ b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletRequest.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.CheckForNull;
+import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import org.sonar.api.impl.ws.PartImpl;
import org.sonar.api.impl.ws.ValidatingRequest;
@@ -122,6 +123,10 @@ public class ServletRequest extends ValidatingRequest {
}
}
+ public AsyncContext startAsync() {
+ return source.startAsync();
+ }
+
private boolean isMultipartContent() {
String contentType = source.getContentType();
return contentType != null && contentType.toLowerCase(ENGLISH).startsWith(MULTIPART);
diff --git a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletResponse.java b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletResponse.java
index f02e380142a..8d294b4552e 100644
--- a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletResponse.java
+++ b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/ServletResponse.java
@@ -80,6 +80,16 @@ public class ServletResponse implements Response {
response.reset();
return this;
}
+
+ public ServletStream flushBuffer() throws IOException {
+ response.flushBuffer();
+ return this;
+ }
+
+ public ServletStream setCharacterEncoding(String charset) {
+ response.setCharacterEncoding(charset);
+ return this;
+ }
}
@Override
diff --git a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletRequestTest.java b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletRequestTest.java
index 79f57aed7b8..159be0e20eb 100644
--- a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletRequestTest.java
+++ b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletRequestTest.java
@@ -210,4 +210,11 @@ public class ServletRequestTest {
assertThat(underTest.getReader()).isEqualTo(reader);
}
+ @Test
+ public void startAsync() {
+ underTest.startAsync();
+
+ verify(source).startAsync();
+ }
+
}
diff --git a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletResponseTest.java b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletResponseTest.java
index c5099cd677c..19cbc9f93f8 100644
--- a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletResponseTest.java
+++ b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/ServletResponseTest.java
@@ -19,6 +19,8 @@
*/
package org.sonar.server.ws;
+import java.io.IOException;
+import java.nio.charset.Charset;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
@@ -84,6 +86,20 @@ public class ServletResponseTest {
}
@Test
+ public void setCharacterEncoding_encodingIsSet() {
+ underTest.stream().setCharacterEncoding("UTF-8");
+
+ verify(response).setCharacterEncoding("UTF-8");
+ }
+
+ @Test
+ public void flushBuffer_bufferIsFlushed() throws IOException {
+ underTest.stream().flushBuffer();
+
+ verify(response).flushBuffer();
+ }
+
+ @Test
public void test_output() {
assertThat(underTest.stream().output()).isEqualTo(output);
}
diff --git a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java
index 6e4e694d67b..e138a6bc316 100644
--- a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java
+++ b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java
@@ -19,11 +19,15 @@
*/
package org.sonar.server.ws;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.ClientAbortException;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.RequestHandler;
@@ -45,6 +49,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+@RunWith(DataProviderRunner.class)
public class WebServiceEngineTest {
@Rule
@@ -68,37 +73,27 @@ public class WebServiceEngineTest {
}
}
- @Test
- public void ws_returns_successful_response() {
- Request request = new TestRequest().setPath("/api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ @DataProvider
+ public static Object[][] responseData() {
+ return new Object[][] {
+ {"/api/ping", "pong", 200},
+ {"api/ping", "pong", 200},
+ {"api/ping.json", "pong", 200},
+ {"xxx/ping", "{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}", 404},
+ {"api/xxx", "{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}", 404}
+ };
}
@Test
- public void accept_path_that_does_not_start_with_slash() {
- Request request = new TestRequest().setPath("api/ping");
+ @UseDataProvider("responseData")
+ public void ws_returns_successful_response(String path, String output, int statusCode) {
+ Request request = new TestRequest().setPath(path);
DumbResponse response = run(request, newPingWs(a -> {
}));
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void request_path_can_contain_valid_media_type() {
- Request request = new TestRequest().setPath("api/ping.json");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.stream().outputAsString()).isEqualTo(output);
+ assertThat(response.status()).isEqualTo(statusCode);
}
@Test
@@ -108,8 +103,8 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newPingWs(a -> {
}));
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown action extension: bat\"}]}");
}
@@ -121,29 +116,7 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler(handler)));
assertThat(response.stream().outputAsString()).isEmpty();
- assertThat(response.stream().status()).isEqualTo(204);
- }
-
- @Test
- public void return_404_if_controller_does_not_exist() {
- Request request = new TestRequest().setPath("xxx/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
- }
-
- @Test
- public void return_404_if_action_does_not_exist() {
- Request request = new TestRequest().setPath("api/xxx");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
+ assertThat(response.status()).isEqualTo(204);
}
@Test
@@ -153,7 +126,7 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newWs("api/foo", a -> a.setPost(true)));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method POST is required\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
@@ -164,7 +137,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
@@ -175,7 +148,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method PUT is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
@@ -186,7 +159,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method DELETE is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
@@ -196,7 +169,7 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newPingWs(a -> a.setPost(true)));
assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
@@ -206,7 +179,7 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> request.param("unknown"))));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"BUG - parameter \\u0027unknown\\u0027 is undefined for action \\u0027foo\\u0027\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
@@ -219,7 +192,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The \\u0027bar\\u0027 parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
@@ -233,7 +206,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The \\u0027bar\\u0027 parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
@@ -246,7 +219,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("hello");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
@@ -259,7 +232,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("bar");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
@@ -272,7 +245,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("json");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
@@ -285,7 +258,7 @@ public class WebServiceEngineTest {
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Value of parameter \\u0027format\\u0027 (yml) must be one of: [json, xml]\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
@@ -295,8 +268,8 @@ public class WebServiceEngineTest {
DumbResponse response = run(request, newFailWs());
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"An error has occurred. Please contact your administrator\"}]}");
- assertThat(response.stream().status()).isEqualTo(500);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(500);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).filteredOn(l -> l.contains("Fail to process request api/foo")).isNotEmpty();
}
@@ -310,8 +283,8 @@ public class WebServiceEngineTest {
assertThat(response.stream().outputAsString()).isEqualTo(
"{\"errors\":[{\"msg\":\"Bad request !\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
@@ -328,8 +301,8 @@ public class WebServiceEngineTest {
+ "{\"msg\":\"two\"},"
+ "{\"msg\":\"three\"}"
+ "]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
@@ -343,8 +316,8 @@ public class WebServiceEngineTest {
assertThat(response.stream().outputAsString()).isEqualTo(
"{\"scope\":\"PROJECT\",\"errors\":[{\"msg\":\"Bad request !\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
@@ -357,8 +330,8 @@ public class WebServiceEngineTest {
})));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"this should not fail %s\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
}
@Test
diff --git a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WsUtilsTest.java b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WsUtilsTest.java
index 71a4cbcc1c3..79bb015322c 100644
--- a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WsUtilsTest.java
+++ b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/WsUtilsTest.java
@@ -43,7 +43,7 @@ public class WsUtilsTest {
Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
WsUtils.writeProtobuf(msg, request, response);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(response.outputAsString())
.startsWith("{")
.contains("\"key\":\"I1\"")
@@ -59,7 +59,7 @@ public class WsUtilsTest {
Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
WsUtils.writeProtobuf(msg, request, response);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.PROTOBUF);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.PROTOBUF);
assertThat(Issues.Issue.parseFrom(response.getFlushedOutput()).getKey()).isEqualTo("I1");
}
diff --git a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/DumbResponse.java b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/DumbResponse.java
index 9b91d24949b..adebb870aba 100644
--- a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/DumbResponse.java
+++ b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/DumbResponse.java
@@ -35,7 +35,7 @@ import org.sonar.api.server.ws.Response;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.utils.text.XmlWriter;
-public class DumbResponse implements Response {
+public class DumbResponse implements Response, TestableResponse {
private InMemoryStream stream;
private final ByteArrayOutputStream output = new ByteArrayOutputStream();
@@ -47,15 +47,6 @@ public class DumbResponse implements Response {
private int status = 200;
- @CheckForNull
- public String mediaType() {
- return mediaType;
- }
-
- public int status() {
- return status;
- }
-
@Override
public Response.Stream setMediaType(String s) {
this.mediaType = s;
@@ -107,6 +98,15 @@ public class DumbResponse implements Response {
return new String(output.toByteArray(), StandardCharsets.UTF_8);
}
+ @CheckForNull
+ public String mediaType() {
+ return stream().mediaType;
+ }
+
+ public int status() {
+ return stream().status;
+ }
+
@Override
public Response setHeader(String name, String value) {
headers.put(name, value);
diff --git a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestRequest.java b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestRequest.java
index dc083b685ad..d37f7a1e00f 100644
--- a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestRequest.java
+++ b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestRequest.java
@@ -73,17 +73,17 @@ public class TestRequest extends ValidatingRequest {
}
@Override
- protected String readParam(String key) {
+ public String readParam(String key) {
return params.get(key);
}
@Override
- protected List<String> readMultiParam(String key) {
+ public List<String> readMultiParam(String key) {
return multiParams.get(key);
}
@Override
- protected InputStream readInputStreamParam(String key) {
+ public InputStream readInputStreamParam(String key) {
String value = readParam(key);
if (value == null) {
return null;
@@ -92,7 +92,7 @@ public class TestRequest extends ValidatingRequest {
}
@Override
- protected Part readPart(String key) {
+ public Part readPart(String key) {
return parts.get(key);
}
diff --git a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestResponse.java b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestResponse.java
index c2511c22310..6fa82315e90 100644
--- a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestResponse.java
+++ b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestResponse.java
@@ -33,14 +33,14 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestResponse {
- private final DumbResponse dumbResponse;
+ private final TestableResponse testableResponse;
- public TestResponse(DumbResponse dumbResponse) {
- this.dumbResponse = dumbResponse;
+ public TestResponse(TestableResponse dumbResponse) {
+ this.testableResponse = dumbResponse;
}
public InputStream getInputStream() {
- return new ByteArrayInputStream(dumbResponse.getFlushedOutput());
+ return new ByteArrayInputStream(testableResponse.getFlushedOutput());
}
public <T extends GeneratedMessageV3> T getInputObject(Class<T> protobufClass) {
@@ -55,20 +55,20 @@ public class TestResponse {
}
public String getInput() {
- return new String(dumbResponse.getFlushedOutput(), StandardCharsets.UTF_8);
+ return new String(testableResponse.getFlushedOutput(), StandardCharsets.UTF_8);
}
public String getMediaType() {
- return dumbResponse.stream().mediaType();
+ return testableResponse.mediaType();
}
public int getStatus() {
- return dumbResponse.stream().status();
+ return testableResponse.status();
}
@CheckForNull
public String getHeader(String headerKey) {
- return dumbResponse.getHeader(headerKey);
+ return testableResponse.getHeader(headerKey);
}
public void assertJson(String expectedJson) {
diff --git a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestableResponse.java b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestableResponse.java
new file mode 100644
index 00000000000..056c9e68019
--- /dev/null
+++ b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/TestableResponse.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 org.sonar.server.ws;
+
+import org.sonar.api.server.ws.Response;
+
+public interface TestableResponse {
+
+ byte[] getFlushedOutput();
+
+ Response.Stream stream();
+
+ String getHeader(String headerKey);
+
+ int status();
+
+ String mediaType();
+}
diff --git a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/WsActionTester.java b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/WsActionTester.java
index dbe31b268e4..863a5aaec1a 100644
--- a/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/WsActionTester.java
+++ b/server/sonar-webserver-ws/src/testFixtures/java/org/sonar/server/ws/WsActionTester.java
@@ -25,7 +25,7 @@ import org.sonar.api.server.ws.WebService;
public class WsActionTester {
public static final String CONTROLLER_KEY = "test";
- private final WebService.Action action;
+ protected final WebService.Action action;
public WsActionTester(WsAction wsAction) {
WebService.Context context = new WebService.Context();
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java b/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java
index bd39d2cde7b..81a6b0a1b8e 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java
@@ -54,6 +54,7 @@ class TomcatAccessLog {
appender.setEncoder(fileEncoder);
appender.start();
valve.addAppender(appender);
+ valve.setAsyncSupported(true);
tomcat.getHost().getPipeline().addValve(valve);
}
}