diff options
author | Steve Marion <steve.marion@sonarsource.com> | 2022-08-22 11:20:33 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-08-26 20:03:24 +0000 |
commit | 56c0b6970046c4e6ff6242000bac220dcc9ac273 (patch) | |
tree | 6df7fe88c76b2fd9648da7a5973a4e6ed50811cd /server/sonar-ce | |
parent | c93c3f278b303acb5be01c3e493d64280a3edd63 (diff) | |
download | sonarqube-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')
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"); } - } |