]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15399 Replace nanohttpd used by CE for monitoring and log control.
authorSteve Marion <steve.marion@sonarsource.com>
Mon, 22 Aug 2022 09:20:33 +0000 (11:20 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 26 Aug 2022 20:03:24 +0000 (20:03 +0000)
Remove httpd dependency, replace it by apache http client imported by ES-client lib. Remove httpAction registering and dispatching mechanism.

server/sonar-ce/build.gradle
server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java
server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java
server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpUtils.java
server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java

index 8225b29b67630ca44a66fd2a1b5978f4fc7af932..054ac8d7c5141e814d21e8a15f82612a0d47526c 100644 (file)
@@ -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')
index 53289686c414be2aca8c23501661cc249c9f014a..2a844475ec053e55e5d79699389ea843b4bc4599 100644 (file)
  */
 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);
     }
   }
 }
index 06b26bd035cb9c5e12bae7f883df54d435c0394a..0fc97547887a72bc2972124731b375627adc07f5 100644 (file)
  */
 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);
+    }
   }
 }
index 41fab8920d3bdfbe544bd0ad29445eae92cdff9e..7515afe02e375e18be22006a19c51a071c14874b 100644 (file)
  */
 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));
     }
   }
 }
index c5f5b69d67d3007979edb8094b5bf955b4c4de53..f1260fa1b608e7801860cc4deba9cbf3090d4ac0 100644 (file)
  */
 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);
   }
 }
index 35bc0ea878cb64ce0c0fbf6c6d64ba0eeb80e1dc..8dfef1a5c5f16c1a24ccb4dfc3ec892ffe8f2b3c 100644 (file)
  */
 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;
     }
   }
index 5b8dbaeb1432fdc07b7d46262ffaa3ceb139b843..bba695aabe30119e6e3b5d3d9e70c9196fe5fd96 100644 (file)
  */
 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);
   }
 }
index 970b298209c1cb1a64289d40dee38eed41b7bea9..b17dbe67f606a923426015426af529f3c11aaa2f 100644 (file)
  */
 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);
   }
index 2b661af1bdc0b8f7fb9bb6fbf4fd2b42a3d95a03..ca8814c5932e66744356a13749daa8f879e3fe42 100644 (file)
  */
 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");
   }
-
 }