package cc.blynk.server.api.http.handlers; import cc.blynk.core.http.handlers.*; import cc.blynk.server.Holder; import cc.blynk.server.admin.http.handlers.IpFilterHandler; import cc.blynk.server.admin.http.logic.ConfigsLogic; import cc.blynk.server.admin.http.logic.HardwareStatsLogic; import cc.blynk.server.admin.http.logic.StatsLogic; import cc.blynk.server.admin.http.logic.UsersLogic; import cc.blynk.server.api.http.HttpAPIServer; import cc.blynk.server.api.http.logic.HttpAPILogic; import cc.blynk.server.api.http.logic.ResetPasswordLogic; import cc.blynk.server.api.http.logic.business.AdminAuthHandler; import cc.blynk.server.api.http.logic.business.AuthCookieHandler; import cc.blynk.server.api.http.logic.ide.IDEAuthLogic; import cc.blynk.server.api.websockets.handlers.WebSocketHandler; import cc.blynk.server.api.websockets.handlers.WebSocketWrapperEncoder; import cc.blynk.server.api.websockets.handlers.WebSocketsGenericLoginHandler; import cc.blynk.server.core.dao.CSVGenerator; import cc.blynk.server.core.protocol.handlers.DefaultExceptionHandler; import cc.blynk.server.core.protocol.handlers.decoders.MessageDecoder; import cc.blynk.server.core.protocol.handlers.encoders.MessageEncoder; import cc.blynk.server.core.stats.GlobalStats; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; import java.net.InetSocketAddress; import static cc.blynk.core.http.Response.redirect; /** * Utility handler used to define what protocol should be handled * on same port : http or websockets. * * The Blynk Project. * Created by Dmitriy Dumanskiy. * Created on 27.02.17. */ @ChannelHandler.Sharable public class HttpAndWebSocketUnificatorHandler extends ChannelInboundHandlerAdapter implements DefaultExceptionHandler { private final static String BLYNK_LANDING = "http://www.blynk.cc"; private final String region; private final GlobalStats stats; private final WebSocketsGenericLoginHandler genericLoginHandler; private final String rootPath; private final boolean isUnpacked; private final IpFilterHandler ipFilterHandler; private final AuthCookieHandler authCookieHandler; private final ResetPasswordLogic resetPasswordLogic; private final HttpAPILogic httpAPILogic; private final IDEAuthLogic ideAuthLogic; private final NoMatchHandler noMatchHandler; private final UsersLogic usersLogic; private final StatsLogic statsLogic; private final ConfigsLogic configsLogic; private final HardwareStatsLogic hardwareStatsLogic; private final AdminAuthHandler adminAuthHandler; private final CookieBasedUrlReWriterHandler cookieBasedUrlReWriterHandler; public HttpAndWebSocketUnificatorHandler(Holder holder, int port, String rootPath, boolean isUnpacked) { this.region = holder.region; this.stats = holder.stats; this.genericLoginHandler = new WebSocketsGenericLoginHandler(holder, port); this.rootPath = rootPath; this.isUnpacked = isUnpacked; this.ipFilterHandler = new IpFilterHandler(holder.props.getCommaSeparatedValueAsArray("allowed.administrator.ips")); //http API handlers this.resetPasswordLogic = new ResetPasswordLogic(holder); this.httpAPILogic = new HttpAPILogic(holder); this.ideAuthLogic = new IDEAuthLogic(holder); this.noMatchHandler = new NoMatchHandler(); //admin API handlers this.usersLogic = new UsersLogic(holder, rootPath); this.statsLogic = new StatsLogic(holder, rootPath); this.configsLogic = new ConfigsLogic(holder, rootPath); this.hardwareStatsLogic = new HardwareStatsLogic(holder, rootPath); this.adminAuthHandler = new AdminAuthHandler(holder, rootPath); this.authCookieHandler = new AuthCookieHandler(holder.sessionDao); this.cookieBasedUrlReWriterHandler = new CookieBasedUrlReWriterHandler(rootPath, "/static/admin.html", "/static/login.html"); } public HttpAndWebSocketUnificatorHandler(Holder holder, int port, String rootPath) { this(holder, port, rootPath, false); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { final FullHttpRequest req = (FullHttpRequest) msg; String uri = req.uri(); if (uri.equals("/")) { //for local server do redirect to admin page try { if (region.equals("local")) { ctx.writeAndFlush(redirect(rootPath)); } else { ctx.writeAndFlush(redirect(BLYNK_LANDING)); } } finally { req.release(); } return; } else if (uri.startsWith(rootPath) || uri.startsWith("/static")) { initAdminPipeline(ctx, msg); } else if (req.uri().startsWith(HttpAPIServer.WEBSOCKET_PATH)) { initWebSocketPipeline(ctx, HttpAPIServer.WEBSOCKET_PATH); } else { initHttpPipeline(ctx); } ctx.fireChannelRead(msg); } private boolean isIpNotAllowed(ChannelHandlerContext ctx) { InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); return !ipFilterHandler.accept(ctx, remoteAddress); } private void initAdminPipeline(ChannelHandlerContext ctx, Object msg) { if (isIpNotAllowed(ctx)) { ctx.close(); return; } ChannelPipeline pipeline = ctx.pipeline(); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(adminAuthHandler); pipeline.addLast(authCookieHandler); pipeline.addLast(cookieBasedUrlReWriterHandler); pipeline.addLast(new UrlReWriterHandler("/favicon.ico", "/static/favicon.ico")); pipeline.addLast(new StaticFileHandler(isUnpacked, new StaticFile("/static", false))); pipeline.addLast(usersLogic); pipeline.addLast(statsLogic); pipeline.addLast(configsLogic); pipeline.addLast(hardwareStatsLogic); pipeline.addLast(resetPasswordLogic); pipeline.addLast(httpAPILogic); pipeline.addLast(noMatchHandler); pipeline.remove(this); pipeline.remove(LetsEncryptHandler.class); } private void initHttpPipeline(ChannelHandlerContext ctx) { ChannelPipeline pipeline = ctx.pipeline(); pipeline.addLast("HttpChunkedWrite", new ChunkedWriteHandler()); pipeline.addLast("HttpUrlMapper", new UrlReWriterHandler("/favicon.ico", "/static/favicon.ico")); pipeline.addLast("HttpStaticFile", new StaticFileHandler(isUnpacked, new StaticFile("/static"), new StaticFileEdsWith(CSVGenerator.CSV_DIR, ".csv.gz"))); pipeline.addLast(resetPasswordLogic); pipeline.addLast(httpAPILogic); pipeline.addLast(ideAuthLogic); pipeline.addLast(noMatchHandler); pipeline.remove(this); } private void initWebSocketPipeline(ChannelHandlerContext ctx, String websocketPath) { ChannelPipeline pipeline = ctx.pipeline(); //websockets specific handlers pipeline.addLast("WSWebSocketServerProtocolHandler", new WebSocketServerProtocolHandler(websocketPath, true)); pipeline.addLast("WSWebSocket", new WebSocketHandler(stats)); pipeline.addLast("WSMessageDecoder", new MessageDecoder(stats)); pipeline.addLast("WSSocketWrapper", new WebSocketWrapperEncoder()); pipeline.addLast("WSMessageEncoder", new MessageEncoder(stats)); pipeline.addLast("WSWebSocketGenericLoginHandler", genericLoginHandler); pipeline.remove(this); pipeline.remove(LetsEncryptHandler.class); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { handleGeneralException(ctx, cause); } }