Skip to content

Instantly share code, notes, and snippets.

@tokuhirom
Last active November 13, 2016 03:39
Show Gist options
  • Save tokuhirom/3c53b782d92cc01607a761cf9aac92ab to your computer and use it in GitHub Desktop.
Save tokuhirom/3c53b782d92cc01607a761cf9aac92ab to your computer and use it in GitHub Desktop.
Recording access logs with RxNetty
package com.example.server;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
import java.util.function.Consumer;
import java.util.function.Function;
import static java.time.temporal.ChronoField.*;
public class RxNettyAccessLogFilter implements RequestHandler<ByteBuf, ByteBuf> {
private final RequestHandler<ByteBuf, ByteBuf> next;
private final Function<HttpServerRequest<ByteBuf>, String> userIdGenerator;
private final Consumer<String> writer;
private volatile CacheEntry cacheEntry;
// 10/Oct/2000:13:55:36 -0700
private static final DateTimeFormatter COMBINED_LOG_DATE_TIME_FORMATTER =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue(DAY_OF_MONTH, 2)
.appendLiteral("/")
.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
.appendLiteral("/")
.appendValue(YEAR, 4)
.appendLiteral(":")
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(":")
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.appendLiteral(" ")
.appendOffset("+HHMMss", "Z")
.toFormatter();
public RxNettyAccessLogFilter(RequestHandler<ByteBuf, ByteBuf> next, Function<HttpServerRequest<ByteBuf>, String> userIdGenerator, Consumer<String> writer) {
this.next = next;
this.userIdGenerator = userIdGenerator;
this.writer = writer;
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
Observable<Void> observable = next.handle(request, response);
writer.accept(renderLog(request, response, userIdGenerator));
return observable;
}
public String renderLog(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response, Function<HttpServerRequest<ByteBuf>, String> userIdGenerator) {
return getRemoteAddress(request) + " - - " + userIdGenerator.apply(request)
+ " [" + renderTimestamp() + "] \""
+ request.getHttpMethod()
+ " " + request.getUri()
+ " " + request.getHttpVersion()
+ "\" " + response.getStatus().code()
+ " " + response.getHeaders().getContentLength(0)
+ " \"" + headerOrDefault(request, "Referer", "-") + "\" \""
+ headerOrDefault(request, "User-Agent", "-") + "\"";
}
private String renderTimestamp() {
long now = System.currentTimeMillis() / 1000;
CacheEntry cache = cacheEntry;
if (cache != null && cache.timestamp == now) {
return cache.msg;
} else {
String msg = ZonedDateTime.now().format(COMBINED_LOG_DATE_TIME_FORMATTER);
this.cacheEntry = new CacheEntry(now, msg);
return msg;
}
}
private static String getRemoteAddress(HttpServerRequest<ByteBuf> request) {
SocketAddress socketAddress = request.getNettyChannel().remoteAddress();
if (socketAddress instanceof InetSocketAddress) {
return ((InetSocketAddress) socketAddress).getAddress()
.getHostAddress();
} else {
return socketAddress.toString();
}
}
private static String headerOrDefault(HttpServerRequest<ByteBuf> request, String key, String defaultValue) {
String s = request.getHeaders().get(key);
return s != null ? s : defaultValue;
}
private static class CacheEntry {
private final long timestamp;
private final String msg;
public CacheEntry(long timestamp, String msg) {
this.timestamp = timestamp;
this.msg = msg;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment