Java tutorial
/* * Copyright 2014 WANdisco * * WANdisco licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package c5db.control; import c5db.C5ServerConstants; import c5db.interfaces.C5Module; import c5db.interfaces.C5Server; import c5db.interfaces.ControlModule; import c5db.interfaces.DiscoveryModule; import c5db.interfaces.discovery.NodeInfoReply; import c5db.interfaces.server.CommandRpcRequest; import c5db.messages.generated.CommandReply; import c5db.messages.generated.ModuleType; import c5db.util.C5Futures; import com.google.common.util.concurrent.AbstractService; import com.google.common.util.concurrent.ListenableFuture; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.protostuff.Message; import org.jetlang.channels.AsyncRequest; import org.jetlang.channels.Request; import org.jetlang.fibers.Fiber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * Starts a HTTP service to listen for and respond to control messages. */ public class ControlService extends AbstractService implements ControlModule { private static final Logger LOG = LoggerFactory.getLogger(ControlService.class); private final C5Server server; private final Fiber serviceFiber; private final EventLoopGroup acceptConnectionGroup; private final EventLoopGroup ioWorkerGroup; private final int modulePort; private DiscoveryModule discoveryModule; private Channel listenChannel; public ControlService(C5Server server, Fiber serviceFiber, EventLoopGroup acceptConnectionGroup, EventLoopGroup ioWorkerGroup, int modulePort) { this.server = server; this.serviceFiber = serviceFiber; this.acceptConnectionGroup = acceptConnectionGroup; this.ioWorkerGroup = ioWorkerGroup; this.modulePort = modulePort; controlClient = new SimpleControlClient(ioWorkerGroup); } private final SimpleControlClient controlClient; @Override public void doMessage(Request<CommandRpcRequest<?>, CommandReply> request) { ListenableFuture<NodeInfoReply> nodeInfoFuture = discoveryModule .getNodeInfo(request.getRequest().receipientNodeId, ModuleType.ControlRpc); C5Futures.addCallback(nodeInfoFuture, nodeInfo -> { if (nodeInfo.found) { String firstAddress = nodeInfo.addresses.get(0); int port = nodeInfo.port; InetSocketAddress socketAddress = null; try { socketAddress = new InetSocketAddress(InetAddress.getByName(firstAddress), port); C5Futures.addCallback(controlClient.sendRequest(request.getRequest(), socketAddress), request::reply, exception -> { CommandReply reply = new CommandReply(false, "", "Transport error: " + exception); request.reply(reply); }, serviceFiber); } catch (UnknownHostException e) { LOG.error("Bad address", e); CommandReply reply = new CommandReply(false, "", "Bad remote address:" + e); request.reply(reply); } } else { CommandReply reply = new CommandReply(false, "", "Bad node id " + request.getRequest().receipientNodeId); request.reply(reply); } }, exception -> { LOG.error("Unable to find nodeId! {}", request.getRequest().receipientNodeId); CommandReply reply = new CommandReply(false, "", "Unable to find NodeId!"); request.reply(reply); }, serviceFiber); } @Override public ModuleType getModuleType() { return ModuleType.ControlRpc; } @Override public boolean hasPort() { return true; } @Override public int port() { return modulePort; } @Override public String acceptCommand(String commandString) throws InterruptedException { return null; } private class MessageHandler extends SimpleChannelInboundHandler<CommandRpcRequest<? extends Message>> { @Override protected void channelRead0(ChannelHandlerContext ctx, CommandRpcRequest<? extends Message> msg) throws Exception { System.out.println("Server read off: " + msg); // this should match our local node id! if (msg.receipientNodeId != server.getNodeId()) { sendErrorReply(ctx.channel(), new Exception("Bad nodeId!")); return; } AsyncRequest.withOneReply(serviceFiber, server.getCommandRequests(), msg, reply -> { // got reply! System.out.println("Reply to client: " + reply); ctx.channel().writeAndFlush(reply); ctx.channel().close(); }, 1000, TimeUnit.MILLISECONDS, () -> sendErrorReply(ctx.channel(), new Exception("Timed out request"))); } } private void sendErrorReply(Channel channel, Exception ex) { CommandReply reply = new CommandReply(false, "", ex.toString()); channel.writeAndFlush(reply); channel.close(); } @Override protected void doStart() { serviceFiber.start(); serviceFiber .execute(() -> C5Futures.addCallback(server.getModule(ModuleType.Discovery), (C5Module module) -> { discoveryModule = (DiscoveryModule) module; startHttpRpc(); }, this::notifyFailed, serviceFiber)); } private void startHttpRpc() { try { ServerBootstrap serverBootstrap = new ServerBootstrap(); ServerBootstrap serverBootstrap1 = serverBootstrap.group(acceptConnectionGroup, ioWorkerGroup) .channel(NioServerSocketChannel.class).option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.SO_BACKLOG, 100).childOption(ChannelOption.TCP_NODELAY, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // pipeline.addLast("logger", new LoggingHandler(LogLevel.DEBUG)); pipeline.addLast("http-server", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(C5ServerConstants.MAX_CALL_SIZE)); pipeline.addLast("encode", new ServerHttpProtostuffEncoder()); pipeline.addLast("decode", new ServerHttpProtostuffDecoder()); pipeline.addLast("translate", new ServerDecodeCommandRequest()); pipeline.addLast("inc-messages", new MessageHandler()); } }); serverBootstrap.bind(modulePort).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { // yay listenChannel = future.channel(); notifyStarted(); } else { LOG.error("Unable to bind to port {}", modulePort); notifyFailed(future.cause()); } } }); } catch (Exception e) { notifyFailed(e); } } @Override protected void doStop() { try { listenChannel.close().get(); } catch (InterruptedException | ExecutionException e) { notifyFailed(e); } notifyStopped(); } }