package cc.blynk.server.api.http.handlers;
import cc.blynk.server.acme.ContentHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.StandardCharsets;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
/**
* The Blynk Project.
* Created by Dmitriy Dumanskiy.
* Created on 10.12.15.
*/
@ChannelHandler.Sharable
public class LetsEncryptHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = LogManager.getLogger(LetsEncryptHandler.class);
private static final String LETS_ENCRYPT_PATH = "/.well-known/acme-challenge/";
private final ContentHolder contentHolder;
public LetsEncryptHandler(ContentHolder contentHolder) {
this.contentHolder = contentHolder;
}
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", StandardCharsets.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
// Close the connection as soon as the error message is sent.
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof FullHttpRequest)) {
return;
}
FullHttpRequest req = (FullHttpRequest) msg;
if (req.uri().startsWith(LETS_ENCRYPT_PATH)) {
try {
serveContent(ctx, req);
} finally {
ReferenceCountUtil.release(req);
}
return;
}
ctx.fireChannelRead(req);
}
private void serveContent(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
if (!request.decoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
if (request.method() != HttpMethod.GET) {
return;
}
final String content = contentHolder.content;
if (content == null) {
log.error("No content for certificate.");
return;
}
log.info("Delivering content {}", content);
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
HttpUtil.setContentLength(response, content.length());
response.headers().set(CONTENT_TYPE, "text/html");
if (HttpUtil.isKeepAlive(request)) {
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.write(response);
// Write the content.
ChannelFuture lastContentFuture;
ByteBuf buf = ctx.alloc().buffer(content.length());
buf.writeBytes(content.getBytes());
ctx.write(buf);
lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
// Decide whether to close the connection or not.
if (!HttpUtil.isKeepAlive(request)) {
// Close the connection when the whole content is written out.
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
}
}