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); } } }