aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-ce
diff options
context:
space:
mode:
authorSteve Marion <steve.marion@sonarsource.com>2022-08-22 11:20:33 +0200
committersonartech <sonartech@sonarsource.com>2022-08-26 20:03:24 +0000
commit56c0b6970046c4e6ff6242000bac220dcc9ac273 (patch)
tree6df7fe88c76b2fd9648da7a5973a4e6ed50811cd /server/sonar-ce
parentc93c3f278b303acb5be01c3e493d64280a3edd63 (diff)
downloadsonarqube-56c0b6970046c4e6ff6242000bac220dcc9ac273.tar.gz
sonarqube-56c0b6970046c4e6ff6242000bac220dcc9ac273.zip
SONAR-15399 Replace nanohttpd used by CE for monitoring and log control.
Remove httpd dependency, replace it by apache http client imported by ES-client lib. Remove httpAction registering and dispatching mechanism.
Diffstat (limited to 'server/sonar-ce')
-rw-r--r--server/sonar-ce/build.gradle1
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java90
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java45
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java63
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java42
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java26
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java97
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java75
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java35
9 files changed, 253 insertions, 221 deletions
diff --git a/server/sonar-ce/build.gradle b/server/sonar-ce/build.gradle
index 8225b29b676..054ac8d7c51 100644
--- a/server/sonar-ce/build.gradle
+++ b/server/sonar-ce/build.gradle
@@ -14,7 +14,6 @@ dependencies {
compile 'com.hazelcast:hazelcast-kubernetes'
compile 'commons-io:commons-io'
compile 'org.apache.commons:commons-dbcp2'
- compile 'org.nanohttpd:nanohttpd'
compile 'org.sonarsource.api.plugin:sonar-plugin-api'
compile project(':server:sonar-ce-common')
compile project(':server:sonar-ce-task')
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
index 53289686c41..2a844475ec0 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
@@ -19,27 +19,24 @@
*/
package org.sonar.ce.httpd;
-import fi.iki.elonen.NanoHTTPD;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
-import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
import java.util.Properties;
-import org.sonar.api.Startable;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.impl.bootstrap.HttpServer;
+import org.apache.http.impl.bootstrap.ServerBootstrap;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
import org.slf4j.LoggerFactory;
+import org.sonar.api.Startable;
import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND;
import static java.lang.Integer.parseInt;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
@@ -48,35 +45,35 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
* It listens on loopback address only, so it does not need to be secure (no HTTPS, no authentication).
*/
public class CeHttpServer implements Startable {
-
private final Properties processProps;
private final List<HttpAction> actions;
- private final ActionRegistryImpl actionRegistry;
- private final CeNanoHttpd nanoHttpd;
+ private HttpServer httpServer;
public CeHttpServer(Properties processProps, List<HttpAction> actions) {
this.processProps = processProps;
this.actions = actions;
- this.actionRegistry = new ActionRegistryImpl();
- this.nanoHttpd = new CeNanoHttpd(InetAddress.getLoopbackAddress().getHostAddress(), 0, actionRegistry);
}
@Override
public void start() {
try {
- registerActions();
- nanoHttpd.start();
- registerHttpUrl();
+ this.httpServer = buildHttpServer();
+ httpServer.start();
+ registerServerUrl();
} catch (IOException e) {
throw new IllegalStateException("Can not start local HTTP server for System Info monitoring", e);
}
}
- private void registerActions() {
- actions.forEach(action -> action.register(this.actionRegistry));
+ private HttpServer buildHttpServer() {
+ ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap();
+ serverBootstrap.setLocalAddress(InetAddress.getLoopbackAddress());
+ actions.forEach(httpAction -> serverBootstrap.registerHandler(httpAction.getContextPath(), httpAction));
+ serverBootstrap.registerHandler("/*", new NotFoundHttpRequestHandler());
+ return serverBootstrap.create();
}
- private void registerHttpUrl() {
+ private void registerServerUrl() {
int processNumber = parseInt(processProps.getProperty(PROPERTY_PROCESS_INDEX));
File shareDir = new File(processProps.getProperty(PROPERTY_SHARED_PATH));
try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(shareDir, processNumber)) {
@@ -88,55 +85,18 @@ public class CeHttpServer implements Startable {
@Override
public void stop() {
- nanoHttpd.stop();
+ this.httpServer.stop();
}
// visible for testing
String getUrl() {
- return "http://" + nanoHttpd.getHostname() + ":" + nanoHttpd.getListeningPort();
+ return "http://" + this.httpServer.getInetAddress().getHostAddress() + ":" + this.httpServer.getLocalPort();
}
- private static class CeNanoHttpd extends NanoHTTPD {
- private final ActionRegistryImpl actionRegistry;
-
- CeNanoHttpd(String hostname, int port, ActionRegistryImpl actionRegistry) {
- super(hostname, port);
- this.actionRegistry = actionRegistry;
- }
-
+ private static class NotFoundHttpRequestHandler implements HttpRequestHandler {
@Override
- public Response serve(IHTTPSession session) {
- return actionRegistry.getAction(session)
- .map(action -> serveFromAction(session, action))
- .orElseGet(() -> newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT, format("Error 404, '%s' not found.", session.getUri())));
- }
-
- private static Response serveFromAction(IHTTPSession session, HttpAction action) {
- try {
- return action.serve(session);
- } catch (Exception e) {
- return newFixedLengthResponse(INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage());
- }
- }
- }
-
- private static final class ActionRegistryImpl implements HttpAction.ActionRegistry {
- private final Map<String, HttpAction> actionsByPath = new HashMap<>();
-
- @Override
- public void register(String path, HttpAction action) {
- requireNonNull(path, "path can't be null");
- requireNonNull(action, "action can't be null");
- checkArgument(!path.isEmpty(), "path can't be empty");
- checkArgument(!path.startsWith("/"), "path must not start with '/'");
- String fixedPath = path.toLowerCase(Locale.ENGLISH);
- HttpAction existingAction = actionsByPath.put(fixedPath, action);
- checkState(existingAction == null, "Action '%s' already registered for path '%s'", existingAction, fixedPath);
- }
-
- Optional<HttpAction> getAction(NanoHTTPD.IHTTPSession session) {
- String path = session.getUri().substring(1).toLowerCase(Locale.ENGLISH);
- return Optional.ofNullable(actionsByPath.get(path));
+ public void handle(HttpRequest request, HttpResponse response, HttpContext context) {
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_FOUND);
}
}
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java
index 06b26bd035c..0fc97547887 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java
@@ -19,33 +19,32 @@
*/
package org.sonar.ce.httpd;
-import fi.iki.elonen.NanoHTTPD.IHTTPSession;
-import fi.iki.elonen.NanoHTTPD.Response;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
/**
- * a Http action of the CE's HTTP server handles a request for a specified path.
- *
- * <p>
- * Method {@link #register(ActionRegistry)} of the action will be called right before the HTTP server is started (server
- * is started by the ioc container). It's the action's responsibility to call the method
- * {@link ActionRegistry#register(ActionRegistry)} to register itself for a given path.
- * </p>
- * <p>
- * Method {@link #serve(IHTTPSession)} will be called each time a request matching the path the action registered itself
- * for.
- * </p>
+ * A Http action of the CE's HTTP server handles a request for a specified path.
*/
-public interface HttpAction {
- void register(ActionRegistry registry);
+public interface HttpAction extends HttpRequestHandler {
+ /**
+ * Provides a context path to be registered on.
+ * It must not be empty and start with a '/'.
+ * @return the context path as a String
+ */
+ String getContextPath();
- Response serve(IHTTPSession session);
+ void handle(HttpRequest request, HttpResponse response);
- interface ActionRegistry {
- /**
- * @throws NullPointerException if {@code path} of {@code action} is {@code null}
- * @throws IllegalArgumentException if {@code path} is empty or starts with {@code /}
- * @throws IllegalStateException if an action is already registered for the specified path (case is ignored)
- */
- void register(String path, HttpAction action);
+ default void handle(HttpRequest request, HttpResponse response, HttpContext context)
+ throws HttpException {
+ try {
+ this.handle(request, response);
+ // catch Throwable because we want to respond a clean 500 to client even on Error
+ } catch (Throwable t) {
+ throw new HttpException(t.getMessage(), t);
+ }
}
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
index 41fab8920d3..7515afe02e3 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
@@ -19,22 +19,29 @@
*/
package org.sonar.ce.logging;
-import fi.iki.elonen.NanoHTTPD;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Optional;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.entity.StringEntity;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.api.utils.log.Loggers;
import org.sonar.ce.httpd.HttpAction;
import org.sonar.server.log.ServerLogging;
-import static fi.iki.elonen.NanoHTTPD.MIME_PLAINTEXT;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.BAD_REQUEST;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
-import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;
import static java.lang.String.format;
public class ChangeLogLevelHttpAction implements HttpAction {
- private static final String PATH = "changeLogLevel";
+ private static final String PATH = "/changeLogLevel";
private static final String PARAM_LEVEL = "level";
private final ServerLogging logging;
@@ -44,27 +51,49 @@ public class ChangeLogLevelHttpAction implements HttpAction {
}
@Override
- public void register(ActionRegistry registry) {
- registry.register(PATH, this);
+ public String getContextPath() {
+ return PATH;
}
@Override
- public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) {
- if (session.getMethod() != NanoHTTPD.Method.POST) {
- return newFixedLengthResponse(METHOD_NOT_ALLOWED, MIME_PLAINTEXT, null);
+ public void handle(HttpRequest request, HttpResponse response) {
+ if (!"POST".equals(request.getRequestLine().getMethod())) {
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_METHOD_NOT_ALLOWED);
+ return;
}
- String levelStr = session.getParms().get(PARAM_LEVEL);
- if (levelStr == null || levelStr.isEmpty()) {
- return newFixedLengthResponse(BAD_REQUEST, MIME_PLAINTEXT, format("Parameter '%s' is missing", PARAM_LEVEL));
+ HttpEntityEnclosingRequest postRequest = (HttpEntityEnclosingRequest) request;
+ final URI requestUri;
+ try {
+ requestUri = new URI(postRequest.getRequestLine().getUri());
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("the request URI can't be syntactically invalid", e);
}
+
+ List<NameValuePair> requestParams = URLEncodedUtils.parse(requestUri, StandardCharsets.UTF_8);
+ Optional<String> levelRequested = requestParams.stream()
+ .filter(nvp -> PARAM_LEVEL.equals(nvp.getName()))
+ .map(NameValuePair::getValue)
+ .findFirst();
+
+ final String levelStr;
+ if (levelRequested.isEmpty()) {
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_REQUEST);
+ response.setEntity(new StringEntity(format("Parameter '%s' is missing", PARAM_LEVEL), StandardCharsets.UTF_8));
+ return;
+ } else {
+ levelStr = levelRequested.get();
+ }
+
try {
LoggerLevel level = LoggerLevel.valueOf(levelStr);
logging.changeLevel(level);
- return newFixedLengthResponse(OK, MIME_PLAINTEXT, null);
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK);
} catch (IllegalArgumentException e) {
Loggers.get(ChangeLogLevelHttpAction.class).debug("Value '{}' for parameter '" + PARAM_LEVEL + "' is invalid: {}", levelStr, e);
- return newFixedLengthResponse(BAD_REQUEST, MIME_PLAINTEXT, format("Value '%s' for parameter '%s' is invalid", levelStr, PARAM_LEVEL));
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_REQUEST);
+ response.setEntity(
+ new StringEntity(format("Value '%s' for parameter '%s' is invalid", levelStr, PARAM_LEVEL), StandardCharsets.UTF_8));
}
}
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java
index c5f5b69d67d..f1260fa1b60 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java
@@ -19,20 +19,22 @@
*/
package org.sonar.ce.systeminfo;
-import fi.iki.elonen.NanoHTTPD;
-import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
import org.sonar.ce.httpd.HttpAction;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import static fi.iki.elonen.NanoHTTPD.MIME_PLAINTEXT;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.*;
-import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;
-
public class SystemInfoHttpAction implements HttpAction {
- private static final String PATH = "systemInfo";
+ private static final String PATH = "/systemInfo";
private static final String PROTOBUF_MIME_TYPE = "application/x-protobuf";
private final List<SystemInfoSection> sectionProviders;
@@ -42,22 +44,24 @@ public class SystemInfoHttpAction implements HttpAction {
}
@Override
- public void register(ActionRegistry registry) {
- registry.register(PATH, this);
+ public String getContextPath() {
+ return PATH;
}
-
@Override
- public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) {
- if (session.getMethod() != NanoHTTPD.Method.GET) {
- return newFixedLengthResponse(METHOD_NOT_ALLOWED, MIME_PLAINTEXT, null);
+ public void handle(HttpRequest request, HttpResponse response) {
+ if (!"GET".equals(request.getRequestLine().getMethod())) {
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_METHOD_NOT_ALLOWED);
+ response.setEntity(new StringEntity("Only GET is allowed", StandardCharsets.UTF_8));
+ return;
}
-
ProtobufSystemInfo.SystemInfo.Builder infoBuilder = ProtobufSystemInfo.SystemInfo.newBuilder();
- for (SystemInfoSection sectionProvider : sectionProviders) {
- ProtobufSystemInfo.Section section = sectionProvider.toProtobuf();
- infoBuilder.addSections(section);
- }
+
+ sectionProviders.stream()
+ .map(SystemInfoSection::toProtobuf)
+ .forEach(infoBuilder::addSections);
+
byte[] bytes = infoBuilder.build().toByteArray();
- return newFixedLengthResponse(OK, PROTOBUF_MIME_TYPE, new ByteArrayInputStream(bytes), bytes.length);
+ response.setEntity(new ByteArrayEntity(bytes, ContentType.create(PROTOBUF_MIME_TYPE)));
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK);
}
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
index 35bc0ea878c..8dfef1a5c5f 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
@@ -19,16 +19,21 @@
*/
package org.sonar.ce.httpd;
-import fi.iki.elonen.NanoHTTPD;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Properties;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.StringEntity;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -78,7 +83,6 @@ public class CeHttpServerTest {
Response response = call(underTest.getUrl() + action);
assertThat(response.code()).isEqualTo(404);
- assertThat(response.body().string()).isEqualTo("Error 404, '" + action + "' not found.");
}
@Test
@@ -90,7 +94,7 @@ public class CeHttpServerTest {
@Test
public void action_is_matched_on_URL_ignoring_case() throws IOException {
Response response = call(underTest.getUrl() + "/pOMpoM");
- assertIsPomPomResponse(response);
+ assertThat(response.code()).isEqualTo(404);
}
@Test
@@ -132,26 +136,28 @@ public class CeHttpServerTest {
}
private static class PomPomAction implements HttpAction {
+
@Override
- public void register(ActionRegistry registry) {
- registry.register("pompom", this);
+ public String getContextPath() {
+ return "/pompom";
}
@Override
- public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) {
- return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, "ok");
+ public void handle(HttpRequest request, HttpResponse response) {
+ response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK);
+ response.setEntity(new StringEntity("ok", StandardCharsets.UTF_8));
}
}
private static class FailingAction implements HttpAction {
@Override
- public void register(ActionRegistry registry) {
- registry.register("failing", this);
+ public String getContextPath() {
+ return "/failing";
}
@Override
- public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) {
+ public void handle(HttpRequest request, HttpResponse response) {
throw FAILING_ACTION;
}
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java
index 5b8dbaeb143..bba695aabe3 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java
@@ -19,28 +19,99 @@
*/
package org.sonar.ce.httpd;
-import fi.iki.elonen.NanoHTTPD;
-import java.util.Map;
-import javax.annotation.Nullable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.HttpContext;
+import org.jetbrains.annotations.NotNull;
+import org.mockito.ArgumentCaptor;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
-public abstract class CeHttpUtils {
+public final class CeHttpUtils {
private CeHttpUtils() {
// prevents instantiation
}
- public static NanoHTTPD.IHTTPSession createHttpSession(NanoHTTPD.Method method) {
- return createHttpSession(method, null);
+ private static HttpResponse performRequestOnHandler(HttpAction handler, HttpRequest request) throws HttpException {
+ HttpResponse mockResponse = mock(HttpResponse.class);
+ HttpContext mockContext = mock(HttpContext.class);
+ handler.handle(request, mockResponse, mockContext);
+ return mockResponse;
}
- public static NanoHTTPD.IHTTPSession createHttpSession(NanoHTTPD.Method method, @Nullable Map<String, String> params) {
- NanoHTTPD.IHTTPSession res = mock(NanoHTTPD.IHTTPSession.class);
- when(res.getMethod()).thenReturn(method);
- if (params != null) {
- when(res.getParms()).thenReturn(params);
+ @NotNull
+ private static HttpRequest buildGetRequest() {
+ return new BasicHttpRequest("GET", "");
+ }
+
+ @NotNull
+ private static HttpPost buildPostRequest(List<NameValuePair> urlArgs, List<NameValuePair> bodyArgs) {
+ final URI requestUri;
+ try {
+ requestUri = new URIBuilder()
+ .setScheme("http")
+ .setHost("localhost")
+ .addParameters(urlArgs)
+ .build();
+ } catch (URISyntaxException urise) {
+ throw new IllegalStateException("built URI is not valid, this should not be possible", urise);
}
- return res;
+ HttpPost request = new HttpPost(requestUri);
+ request.setEntity(new UrlEncodedFormEntity(bodyArgs, StandardCharsets.UTF_8));
+ return request;
+ }
+
+ private static byte[] extractResponseBody(HttpResponse mockResponse) throws IOException {
+ final ArgumentCaptor<HttpEntity> httpEntityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
+ verify(mockResponse).setEntity(httpEntityCaptor.capture());
+ return IOUtils.buffer(httpEntityCaptor.getValue().getContent()).readAllBytes();
+ }
+
+ private static void verifyStatusCode(int expectedCode, HttpResponse mockResponse) {
+ verify(mockResponse).setStatusLine(any(), eq(expectedCode));
+ }
+
+ public static void testHandlerForGetWithoutResponseBody(HttpAction handler, int expectedCode) throws HttpException {
+ HttpRequest request = buildGetRequest();
+ HttpResponse mockResponse = performRequestOnHandler(handler, request);
+ verifyStatusCode(expectedCode, mockResponse);
+ }
+
+ public static byte[] testHandlerForGetWithResponseBody(HttpAction handler, int expectedCode) throws HttpException, IOException {
+ HttpRequest request = buildGetRequest();
+ HttpResponse mockResponse = performRequestOnHandler(handler, request);
+ verifyStatusCode(expectedCode, mockResponse);
+ return extractResponseBody(mockResponse);
+ }
+
+ public static void testHandlerForPostWithoutResponseBody(
+ HttpAction httpAction, List<NameValuePair> urlArgs, List<NameValuePair> bodyArgs, int expectedCode) throws HttpException {
+ HttpPost request = buildPostRequest(urlArgs, bodyArgs);
+ HttpResponse mockResponse = performRequestOnHandler(httpAction, request);
+ verifyStatusCode(expectedCode, mockResponse);
+ }
+
+ public static byte[] testHandlerForPostWithResponseBody(
+ HttpAction httpAction, List<NameValuePair> urlArgs, List<NameValuePair> bodyArgs, int expectedCode) throws HttpException, IOException {
+ HttpPost request = buildPostRequest(urlArgs, bodyArgs);
+ HttpResponse mockResponse = performRequestOnHandler(httpAction, request);
+ verifyStatusCode(expectedCode, mockResponse);
+ return extractResponseBody(mockResponse);
}
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
index 970b298209c..b17dbe67f60 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
@@ -19,92 +19,71 @@
*/
package org.sonar.ce.logging;
-import com.google.common.collect.ImmutableMap;
-import fi.iki.elonen.NanoHTTPD;
import java.io.IOException;
-import org.apache.commons.io.IOUtils;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.apache.http.HttpException;
+import org.apache.http.HttpStatus;
+import org.apache.http.message.BasicNameValuePair;
import org.junit.Test;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.httpd.HttpAction;
+import org.sonar.ce.httpd.CeHttpUtils;
import org.sonar.server.log.ServerLogging;
-import static fi.iki.elonen.NanoHTTPD.Method.GET;
-import static fi.iki.elonen.NanoHTTPD.Method.POST;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.BAD_REQUEST;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.sonar.ce.httpd.CeHttpUtils.createHttpSession;
public class ChangeLogLevelHttpActionTest {
private ServerLogging serverLogging = mock(ServerLogging.class);
private ChangeLogLevelHttpAction underTest = new ChangeLogLevelHttpAction(serverLogging);
@Test
- public void register_to_path_changeLogLevel() {
- HttpAction.ActionRegistry actionRegistry = mock(HttpAction.ActionRegistry.class);
-
- underTest.register(actionRegistry);
-
- verify(actionRegistry).register("changeLogLevel", underTest);
+ public void serves_METHOD_NOT_ALLOWED_error_when_method_is_not_POST() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForGetWithoutResponseBody(underTest, HttpStatus.SC_METHOD_NOT_ALLOWED);
}
@Test
- public void serves_METHOD_NOT_ALLOWED_error_when_method_is_not_POST() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(GET));
- assertThat(response.getStatus()).isEqualTo(METHOD_NOT_ALLOWED);
+ public void serves_BAD_REQUEST_error_when_parameter_level_is_missing() throws IOException, HttpException {
+ byte[] responseBody = CeHttpUtils.testHandlerForPostWithResponseBody(underTest, List.of(), List.of(), HttpStatus.SC_BAD_REQUEST);
+ assertThat(new String(responseBody, StandardCharsets.UTF_8)).isEqualTo("Parameter 'level' is missing");
}
@Test
- public void serves_BAD_REQUEST_error_when_parameter_level_is_missing() throws IOException {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST));
-
- assertThat(response.getStatus()).isEqualTo(BAD_REQUEST);
- assertThat(IOUtils.toString(response.getData())).isEqualTo("Parameter 'level' is missing");
+ public void serves_BAD_REQUEST_error_when_value_of_parameter_level_is_not_LEVEL_in_uppercase() throws IOException, HttpException {
+ byte[] responseBody = CeHttpUtils.testHandlerForPostWithResponseBody(
+ underTest, List.of(new BasicNameValuePair("level", "info")), List.of(), HttpStatus.SC_BAD_REQUEST);
+ assertThat(new String(responseBody, StandardCharsets.UTF_8)).isEqualTo("Value 'info' for parameter 'level' is invalid");
}
@Test
- public void serves_BAD_REQUEST_error_when_value_of_parameter_level_is_not_LEVEL_in_uppercase() throws IOException {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST, ImmutableMap.of("level", "info")));
-
- assertThat(response.getStatus()).isEqualTo(BAD_REQUEST);
- assertThat(IOUtils.toString(response.getData())).isEqualTo("Value 'info' for parameter 'level' is invalid");
- }
-
- @Test
- public void changes_server_logging_if_level_is_ERROR() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST, ImmutableMap.of("level", "ERROR")));
-
- assertThat(response.getStatus()).isEqualTo(OK);
+ public void changes_server_logging_if_level_is_ERROR() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForPostWithoutResponseBody(
+ underTest, List.of(new BasicNameValuePair("level", "ERROR")), List.of(), HttpStatus.SC_OK);
verify(serverLogging).changeLevel(LoggerLevel.ERROR);
}
@Test
- public void changes_server_logging_if_level_is_INFO() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST, ImmutableMap.of("level", "INFO")));
-
- assertThat(response.getStatus()).isEqualTo(OK);
+ public void changes_server_logging_if_level_is_INFO() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForPostWithoutResponseBody(
+ underTest, List.of(new BasicNameValuePair("level", "INFO")), List.of(), HttpStatus.SC_OK);
verify(serverLogging).changeLevel(LoggerLevel.INFO);
}
@Test
- public void changes_server_logging_if_level_is_DEBUG() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST, ImmutableMap.of("level", "DEBUG")));
-
- assertThat(response.getStatus()).isEqualTo(OK);
+ public void changes_server_logging_if_level_is_DEBUG() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForPostWithoutResponseBody(
+ underTest, List.of(new BasicNameValuePair("level", "DEBUG")), List.of(), HttpStatus.SC_OK);
verify(serverLogging).changeLevel(LoggerLevel.DEBUG);
}
@Test
- public void changes_server_logging_if_level_is_TRACE() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST, ImmutableMap.of("level", "TRACE")));
-
- assertThat(response.getStatus()).isEqualTo(OK);
+ public void changes_server_logging_if_level_is_TRACE() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForPostWithoutResponseBody(
+ underTest, List.of(new BasicNameValuePair("level", "TRACE")), List.of(), HttpStatus.SC_OK);
verify(serverLogging).changeLevel(LoggerLevel.TRACE);
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
index 2b661af1bdc..ca8814c5932 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
@@ -19,23 +19,19 @@
*/
package org.sonar.ce.systeminfo;
-import fi.iki.elonen.NanoHTTPD;
+import java.io.IOException;
import java.util.Arrays;
+import java.util.List;
+import org.apache.http.HttpException;
+import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;
-import org.sonar.ce.httpd.HttpAction;
+import org.sonar.ce.httpd.CeHttpUtils;
import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import static fi.iki.elonen.NanoHTTPD.Method.GET;
-import static fi.iki.elonen.NanoHTTPD.Method.POST;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.sonar.ce.httpd.CeHttpUtils.createHttpSession;
public class SystemInfoHttpActionTest {
private SystemInfoSection stateProvider1 = new JvmStateSection("state1");
@@ -48,28 +44,17 @@ public class SystemInfoHttpActionTest {
}
@Test
- public void register_to_path_systemInfo() {
- HttpAction.ActionRegistry actionRegistry = mock(HttpAction.ActionRegistry.class);
-
- underTest.register(actionRegistry);
-
- verify(actionRegistry).register("systemInfo", underTest);
- }
-
- @Test
- public void serves_METHOD_NOT_ALLOWED_error_when_method_is_not_GET() {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(POST));
- assertThat(response.getStatus()).isEqualTo(METHOD_NOT_ALLOWED);
+ public void serves_METHOD_NOT_ALLOWED_error_when_method_is_not_GET() throws HttpException, IOException {
+ CeHttpUtils.testHandlerForPostWithoutResponseBody(underTest, List.of(), List.of(), HttpStatus.SC_METHOD_NOT_ALLOWED);
}
@Test
public void serves_data_from_SystemInfoSections() throws Exception {
- NanoHTTPD.Response response = underTest.serve(createHttpSession(GET));
- assertThat(response.getStatus()).isEqualTo(OK);
- ProtobufSystemInfo.SystemInfo systemInfo = ProtobufSystemInfo.SystemInfo.parseFrom(response.getData());
+ byte[] responsePayload = CeHttpUtils.testHandlerForGetWithResponseBody(underTest, HttpStatus.SC_OK);
+
+ ProtobufSystemInfo.SystemInfo systemInfo = ProtobufSystemInfo.SystemInfo.parseFrom(responsePayload);
assertThat(systemInfo.getSectionsCount()).isEqualTo(2);
assertThat(systemInfo.getSections(0).getName()).isEqualTo("state1");
assertThat(systemInfo.getSections(1).getName()).isEqualTo("state2");
}
-
}