public void handle()

in languagetool-server/src/main/java/org/languagetool/server/LanguageToolHttpHandler.java [93:284]


  public void handle(HttpExchange httpExchange) throws IOException {
    long startTime = System.currentTimeMillis();
    String remoteAddress = null;
    Map<String, String> parameters = new HashMap<>();
    int reqId = reqCounter.incrementRequestCount();
    ServerMetricsCollector.getInstance().logRequest();
    boolean incrementHandleCount = false;
    String requestId = getRequestId(httpExchange);
    MDC.MDCCloseable mdcRequestID = MDC.putCloseable("rID", requestId);
    Attributes attributes = Attributes.builder()
                  .put(SemanticAttributes.HTTP_METHOD, httpExchange.getRequestMethod())
                  .put(SemanticAttributes.HTTP_ROUTE, httpExchange.getRequestURI().getRawPath())
                  .put("http.path_group", httpExchange.getRequestURI().getRawPath())
                  .put("request.id", requestId)
                  .build();
    Span globalSpan = TelemetryProvider.INSTANCE.createSpan("handle-http-request", attributes);
    try (Scope scope = globalSpan.makeCurrent()) {
      URI requestedUri = httpExchange.getRequestURI();
      String path = requestedUri.getRawPath();
      logger.info("Handling {} {}", httpExchange.getRequestMethod(), path);
      if (config.getServerURL() != null) {
        path = config.getServerURL().relativize(new URI(requestedUri.getPath())).getRawPath();
        if (!path.startsWith("/")) {
          path = "/" + path;
        }
      }
      if (path.startsWith("/v2/stop") && config.isStoppable()) {
        logger.warn("Stopping server by external command");
        httpServer.stop();
        return;
      }
      if (path.startsWith("/v2/")) {
        // healthcheck should come before other limit checks (requests per time etc.), to be sure it works: 
        String pathWithoutVersion = path.substring("/v2/".length());
        if (pathWithoutVersion.equals("healthcheck")) {
          String message = "Healthcheck failed: There are currently too many parallel requests.";
          if (workQueueFull(httpExchange, parameters, message) || textCheckerQueueFull(httpExchange, message)) {
            ServerMetricsCollector.getInstance().logFailedHealthcheck();
            return;
          } else {
            httpExchange.getResponseHeaders().set("Content-Type", "text/plain");

            String requestMethod = httpExchange.getRequestMethod();
            if ("HEAD".equalsIgnoreCase(requestMethod)) {
              // Send HTTP 200 without content
              httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, -1);
            } else {
              String ok = "OK";
              httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, ok.getBytes(ENCODING).length);
              httpExchange.getResponseBody().write(ok.getBytes(ENCODING));
            }

            ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_OK);
            return;
          }
        }
      }
      String referrer = httpExchange.getRequestHeaders().getFirst("Referer");
      String origin = httpExchange.getRequestHeaders().getFirst("Origin");   // Referer can be turned off with meta tags, so also check this
      for (String ref : config.getBlockedReferrers()) {
        String errorMessage = null;
        if (ref != null && !ref.isEmpty()) {
          if (referrer != null && ServerTools.siteMatches(referrer, ref)) {
            errorMessage = "Error: Access with referrer " + referrer + " denied.";
          } else if (origin != null && ServerTools.siteMatches(origin, ref)) {
            errorMessage = "Error: Access with origin " + origin + " denied.";
          }
        }
        if (errorMessage != null) {
          sendError(httpExchange, HttpURLConnection.HTTP_FORBIDDEN, errorMessage);
          logError(errorMessage, HttpURLConnection.HTTP_FORBIDDEN, parameters, httpExchange);
          ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_FORBIDDEN);
          return;
        }
      }
      String origAddress = httpExchange.getRemoteAddress().getAddress().getHostAddress();
      String realAddressOrNull = getRealRemoteAddressOrNull(httpExchange, config);
      remoteAddress = realAddressOrNull != null ? realAddressOrNull : origAddress;
      reqCounter.incrementHandleCount(remoteAddress, reqId);
      incrementHandleCount = true;
      // According to the Javadoc, "Closing an exchange without consuming all of the request body is
      // not an error but may make the underlying TCP connection unusable for following exchanges.",
      // so we consume the request now, even before checking for request limits:
      parameters = getRequestQuery(httpExchange, requestedUri);
      if (requestLimiter != null && limitPath(path) && !allowSkipRequestLimit(httpExchange.getRequestHeaders())) {
        try {
          UserLimits userLimits = ServerTools.getUserLimits(parameters, config);
          requestLimiter.checkAccess(remoteAddress, parameters, httpExchange.getRequestHeaders(), userLimits);
        } catch (TooManyRequestsException e) {
          String errorMessage = "Error: Access from " + remoteAddress + " denied: " + e.getMessage();
          int code = 429; // too many requests
          sendError(httpExchange, code, errorMessage);
          // already logged via DatabaseAccessLimitLogEntry
          logError(errorMessage, code, parameters, httpExchange, false);
          return;
        }
      }
      if (errorRequestLimiter != null &&
          !allowSkipRequestLimit(httpExchange.getRequestHeaders()) &&
          !errorRequestLimiter.wouldAccessBeOkay(remoteAddress, parameters, httpExchange.getRequestHeaders())) {
        String textSizeMessage = getTextOrDataSizeMessage(parameters);
        String errorMessage = "Error: Access from " + remoteAddress + " denied - too many recent timeouts. " +
                textSizeMessage +
                " Allowed maximum timeouts: " + errorRequestLimiter.getRequestLimit() +
                " per " + errorRequestLimiter.getRequestLimitPeriodInSeconds() + " seconds";
        int code = 429; // too many requests
        sendError(httpExchange, code, errorMessage);
        logError(errorMessage, code, parameters, httpExchange);
        return;
      }
      if (workQueueFull(httpExchange, parameters, "Error: There are currently too many parallel requests. Please try again later.")) {
        ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.QUEUE_FULL);
        return;
      }
      if (allowedIps == null || allowedIps.contains(origAddress)) {
        if (path.startsWith("/v2/")) {
          ApiV2 apiV2 = new ApiV2(textCheckerV2, config.getAllowOriginUrl());
          String pathWithoutVersion = path.substring("/v2/".length());
          final Map<String, String> finalParameters = parameters;
          final String finalRemoteAddress = remoteAddress;
          TelemetryProvider.INSTANCE.createSpan("/v2", Attributes.empty(), () -> apiV2.handleRequest(pathWithoutVersion, httpExchange, finalParameters, errorRequestLimiter, finalRemoteAddress, config));
        } else if (path.endsWith("/Languages")) {
          throw new BadRequestException("You're using an old version of our API that's not supported anymore. Please see " + API_DOC_URL);
        } else if (path.equals("/")) {
          throw new BadRequestException("Missing arguments for LanguageTool API. Please see " + API_DOC_URL);
        } else if (path.contains("/v2/")) {
          throw new BadRequestException("You have '/v2/' in your path, but not at the root. Try an URL like 'http://server/v2/...' ");
        } else if (path.equals("/favicon.ico")) {
          sendError(httpExchange, HttpURLConnection.HTTP_NOT_FOUND, "Not found");
        } else {
          throw new BadRequestException("This is the LanguageTool API. You have not specified any parameters. Please see " + API_DOC_URL);
        }
      } else {
        String errorMessage = "Error: Access from " + StringTools.escapeXML(origAddress) + " denied";
        sendError(httpExchange, HttpURLConnection.HTTP_FORBIDDEN, errorMessage);
        throw new RuntimeException(errorMessage);
      }
    } catch (Exception e) {
      String response;
      int errorCode;
      boolean textLoggingAllowed = false;
      boolean logStacktrace = true;
      Throwable rootCause = ExceptionUtils.getRootCause(e);
      if (e instanceof TextTooLongException || rootCause instanceof TextTooLongException) {
        errorCode = HttpURLConnection.HTTP_ENTITY_TOO_LARGE;
        response = e.getMessage();
        logStacktrace = false;
      } else if (e instanceof ErrorRateTooHighException || rootCause instanceof ErrorRateTooHighException) {
        errorCode = HttpURLConnection.HTTP_BAD_REQUEST;
        response = ExceptionUtils.getRootCause(e).getMessage();
        logStacktrace = false;
      } else if (hasCause(e, AuthException.class)) {
        errorCode = HttpURLConnection.HTTP_FORBIDDEN;
        response = AuthException.class.getName() + ": " + e.getMessage();
        logStacktrace = false;
      } else if (e instanceof BadRequestException || rootCause instanceof BadRequestException) {
        errorCode = HttpURLConnection.HTTP_BAD_REQUEST;
        response = e.getMessage();
      } else if (e instanceof PathNotFoundException || rootCause instanceof PathNotFoundException) {
        errorCode = HttpURLConnection.HTTP_NOT_FOUND;
        response = e.getMessage();
      } else if (e instanceof TimeoutException || rootCause instanceof TimeoutException) {
        errorCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
        if (e.getMessage().contains("Checking took longer than")) {
          response = e.getMessage(); // more specific information already provided
        } else {
          response = "Checking took longer than " + config.getMaxCheckTimeMillisAnonymous() / 1000.0f + " seconds, which is this server's limit. Please make sure you have selected the proper language or consider submitting a shorter text.";
        }
      } else if (e instanceof UnavailableException) {
        errorCode = HTTP_UNAVAILABLE;
        response = e.getMessage();
      } else {
        response = "Internal Error: " + e.getMessage();
        errorCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
        textLoggingAllowed = true;
      }
      long endTime = System.currentTimeMillis();
      logError(remoteAddress, e, errorCode, httpExchange, parameters, textLoggingAllowed, logStacktrace, endTime-startTime);
      sendError(httpExchange, errorCode, "Error: " + response);
      globalSpan.recordException(e);
      globalSpan.setStatus(StatusCode.ERROR);
    } finally {
      logger.info("Handled request in {}ms; sending code {}", System.currentTimeMillis() - startTime, httpExchange.getResponseCode());
      httpExchange.close();
      mdcRequestID.close();
      globalSpan.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, httpExchange.getResponseCode());
      globalSpan.end();
      if (incrementHandleCount) {
        reqCounter.decrementHandleCount(reqId);
      }
    }
  }