All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.spudsoft.mgmt.AccessLogOutputRoute Maven / Gradle / Ivy

Go to download

A few Vert.x HTTP Server routes for providing functionality similar to Springs actuators.

There is a newer version: 0.0.20
Show newest version
/*
 * Copyright (C) 2023 jtalbut
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package uk.co.spudsoft.mgmt;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import static io.vertx.core.http.HttpVersion.HTTP_1_0;
import static io.vertx.core.http.HttpVersion.HTTP_1_1;
import static io.vertx.core.http.HttpVersion.HTTP_2;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.impl.Utils;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * A Vertx HTTP Server route for outputting HTTP requests captured by AccessLogCaptureRoute.
 *
 * @author jtalbut
 */
public class AccessLogOutputRoute implements Handler {

  /**
   * The path at which the standardDeploy method will put the router.
   */
  public static final String PATH = "accesslog";
    
  private final RingBuffer buffer;

  /**
   * Constructor.
   * @param buffer The buffer from the AccessLogCaptureRoute.
   */
  public AccessLogOutputRoute(RingBuffer buffer) {
    this.buffer = buffer;
  }  
  
  /**
   * Deploy the route to the router passed in at the normal endpoint.
   * 
   * The router passed in should be a sub router that is inaccessible to normal users.
   * 
   * @param router The router that this handler will be attached to.
   */
  public void standardDeploy(Router router) {
    router.route(HttpMethod.GET, "/" + PATH)
            .handler(this::handle)
            .setName("Access Log")
            .produces(ContentTypes.TYPE_JSON)
            .produces(ContentTypes.TYPE_HTML)
            .produces(ContentTypes.TYPE_PLAIN)
            ;
  }
  
  /**
   * Factory method to do standard deployment on newly constructed route.
   * 
   * The router passed in should be a sub router that is inaccessible to normal users.
   * 
   * @param router The router that this handler will be attached to.
   * @param buffer The buffer from the AccessLogCaptureRoute.
   */
  public static void createAndDeploy(Router router, RingBuffer buffer) {
    AccessLogOutputRoute route = new AccessLogOutputRoute(buffer);
    route.standardDeploy(router);
  }
  
  private static JsonObject toJson(AccessLogCaptureRoute.AccessLogData record) {
    JsonObject jo = new JsonObject();
    
    jo.put("timestamp", ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.getTimestamp()), ZoneOffset.UTC).toString());
    if (record.getTimestamp() > 0) {
      jo.put("endTimestamp", ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.getEndTimestamp()), ZoneOffset.UTC).toString());
    }
    HttpServerRequest request = record.getRequest();    
    jo.put("headers", request.headers());
    jo.put("url", request.absoluteURI());    
    jo.put("bytesRead", request.bytesRead());
    HttpServerResponse response = record.getResponse();
    if (response != null) {
      jo.put("responseHeaders", response.headers());
      jo.put("statusCode", response.getStatusCode());
      jo.put("bytesWritten", response.bytesWritten());
    }
    return jo;
  }
  
  @Override
  public void handle(RoutingContext rc) {
    
    HttpServerRequest request = rc.request();
    
    if (request.method() == HttpMethod.GET) {
      
      AccessLogCaptureRoute.AccessLogData data[] = buffer.toArray(i -> new AccessLogCaptureRoute.AccessLogData[i]);
      
      ContentTypes.adjustFromParams(rc);
      
      if (ContentTypes.TYPE_JSON.equals(rc.getAcceptableContentType())) {
        
        JsonArray ja = new JsonArray();
        for (AccessLogCaptureRoute.AccessLogData record : data) {
          ja.add(toJson(record));
        }
        
        HttpServerResponse response = rc.response();
        response.setStatusCode(200);
        response.putHeader(HttpHeaderNames.CONTENT_TYPE, ContentTypes.TYPE_JSON);
        response.end(Json.encode(ja));
      } else if (ContentTypes.TYPE_HTML.equals(rc.getAcceptableContentType())) {
        HttpServerResponse response = rc.response();
        response.setStatusCode(200);
        response.putHeader(HttpHeaderNames.CONTENT_TYPE, ContentTypes.TYPE_HTML);
        response.setChunked(true);
        
        response.write("");
        response.write("");
        response.write("");
        response.write("");
        response.write("");
        response.write("");
        
        response.write("");
        response.write("\n");
        
        response.write("\n");
        
        int id = 0;
        for (AccessLogCaptureRoute.AccessLogData record : data) {
          ++id;
          response.write("");
          
          response.write("\n");
        }
        
        response.write("");
        
        response.write("
TimeMethodURLStatusDurationBytes Written
"); response.write(ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.getTimestamp()), ZoneOffset.UTC).toString()); response.write(""); response.write(record.getRequest().method().toString()); response.write(""); response.write(record.getRequest().absoluteURI()); response.write(""); if (record.getResponse() != null) { response.write(Integer.toString(record.getResponse().getStatusCode())); response.write(""); response.write(Long.toString(record.getEndTimestamp() - record.getTimestamp())); response.write(" ms"); response.write(""); response.write(Long.toString(record.getResponse().bytesWritten())); response.write(" B"); } else { response.write(""); response.write(""); } response.write("
"); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write(""); response.write("
Request HeadersResponse Headers
"); response.write(""); List keys = new ArrayList<>(request.headers().names()); keys.sort(String.CASE_INSENSITIVE_ORDER); for (String key : keys) { response.write(""); } response.write("
");
            response.write(key);
            response.write("
");
            List values = request.headers().getAll(key);
            boolean first = true;
            for (String value : values) {
              if (!first) {
                response.write("\n");
              }
              first = false;
              response.write(value);
            }
            response.write("
"); response.write(""); if (record.getResponse() != null) { keys = new ArrayList<>(record.getResponse().headers().names()); keys.sort(String.CASE_INSENSITIVE_ORDER); for (String key : keys) { response.write(""); } } response.write("
");
              response.write(key);
              response.write("
");
              List values = record.getResponse().headers().getAll(key);
              boolean first = true;
              for (String value : values) {
                if (!first) {
                  response.write("\n");
                }
                first = false;
                response.write(value);
              }
              response.write("
"); response.write(""); response.write(""); response.end(); } else { HttpServerResponse response = rc.response(); response.setStatusCode(200); response.putHeader(HttpHeaderNames.CONTENT_TYPE, ContentTypes.TYPE_PLAIN); response.setChunked(true); for (AccessLogCaptureRoute.AccessLogData record : data) { response.write(buildStringLog(record.getRequest(), record.getResponse(), record.getTimestamp())); response.write("\n"); } response.end(); } } else { rc.next(); } } private String getClientAddress(SocketAddress inetSocketAddress) { if (inetSocketAddress == null) { return null; } return inetSocketAddress.host(); } private String buildStringLog(HttpServerRequest request, HttpServerResponse response, long timestamp) { String versionFormatted = getVersionFormatted(request); Integer status = null; Long contentLength = null; if (response != null) { status = response.getStatusCode(); contentLength = response.bytesWritten(); } MultiMap headers = request.headers(); // as per RFC1945 the header is referer but it is not mandatory some implementations use referrer String referrer = headers.contains("referrer") ? headers.get("referrer") : headers.get("referer"); String userAgent = request.headers().get("user-agent"); referrer = referrer == null ? "-" : referrer; userAgent = userAgent == null ? "-" : userAgent; return String.format("%s - - [%s] \"%s %s %s\" %d %d \"%s\" \"%s\"", getClientAddress(request.remoteAddress()), Utils.formatRFC1123DateTime(timestamp), request.method(), request.absoluteURI(), versionFormatted, status, contentLength, referrer, userAgent); } String getVersionFormatted(HttpServerRequest request) { String versionFormatted; switch (request.version()){ case HTTP_1_0: versionFormatted = "HTTP/1.0"; break; case HTTP_1_1: versionFormatted = "HTTP/1.1"; break; case HTTP_2: versionFormatted = "HTTP/2.0"; break; default: versionFormatted = "-"; } return versionFormatted; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy