uk.co.spudsoft.mgmt.AccessLogOutputRoute Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vertx-management-endpoints Show documentation
Show all versions of vertx-management-endpoints Show documentation
A few Vert.x HTTP Server routes for providing functionality similar to Springs actuators.
/*
* 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("Time Method URL Status Duration Bytes Written \n");
response.write("\n");
int id = 0;
for (AccessLogCaptureRoute.AccessLogData record : data) {
++id;
response.write("");
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("Request Headers ");
response.write("Response Headers ");
response.write(" ");
response.write("");
response.write("");
response.write("");
response.write("");
List keys = new ArrayList<>(request.headers().names());
keys.sort(String.CASE_INSENSITIVE_ORDER);
for (String key : keys) {
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("
");
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(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("
\n");
}
response.write("");
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