|
@@ -0,0 +1,241 @@
|
|
|
+package com.xxl.job.core.server;
|
|
|
+
|
|
|
+import com.xxl.job.core.biz.ExecutorBiz;
|
|
|
+import com.xxl.job.core.biz.impl.ExecutorBizImpl;
|
|
|
+import com.xxl.job.core.biz.model.LogParam;
|
|
|
+import com.xxl.job.core.biz.model.ReturnT;
|
|
|
+import com.xxl.job.core.biz.model.TriggerParam;
|
|
|
+import com.xxl.job.core.thread.ExecutorRegistryThread;
|
|
|
+import com.xxl.job.core.util.GsonTool;
|
|
|
+import com.xxl.job.core.util.XxlJobRemotingUtil;
|
|
|
+import io.netty.bootstrap.ServerBootstrap;
|
|
|
+import io.netty.buffer.Unpooled;
|
|
|
+import io.netty.channel.*;
|
|
|
+import io.netty.channel.nio.NioEventLoopGroup;
|
|
|
+import io.netty.channel.socket.SocketChannel;
|
|
|
+import io.netty.channel.socket.nio.NioServerSocketChannel;
|
|
|
+import io.netty.handler.codec.http.*;
|
|
|
+import io.netty.handler.timeout.IdleStateEvent;
|
|
|
+import io.netty.handler.timeout.IdleStateHandler;
|
|
|
+import io.netty.util.CharsetUtil;
|
|
|
+import io.netty.util.internal.ThrowableUtil;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Copy from : https://github.com/xuxueli/xxl-rpc
|
|
|
+ *
|
|
|
+ * @author xuxueli 2020-04-11 21:25
|
|
|
+ */
|
|
|
+public class EmbedServer {
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);
|
|
|
+
|
|
|
+ private ExecutorBiz executorBiz;
|
|
|
+ private Thread thread;
|
|
|
+
|
|
|
+ public void start(final String address, final int port, final String appName, final String accessToken) {
|
|
|
+ executorBiz = new ExecutorBizImpl();
|
|
|
+ thread = new Thread(new Runnable() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+
|
|
|
+ // param
|
|
|
+ EventLoopGroup bossGroup = new NioEventLoopGroup();
|
|
|
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // start server
|
|
|
+ ServerBootstrap bootstrap = new ServerBootstrap();
|
|
|
+ bootstrap.group(bossGroup, workerGroup)
|
|
|
+ .channel(NioServerSocketChannel.class)
|
|
|
+ .childHandler(new ChannelInitializer<SocketChannel>() {
|
|
|
+ @Override
|
|
|
+ public void initChannel(SocketChannel channel) throws Exception {
|
|
|
+ channel.pipeline()
|
|
|
+ .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
|
|
|
+ .addLast(new HttpServerCodec())
|
|
|
+ .addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
|
|
|
+ .addLast(new EmbedHttpServerHandler(executorBiz, accessToken));
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .childOption(ChannelOption.SO_KEEPALIVE, true);
|
|
|
+
|
|
|
+ // bind
|
|
|
+ ChannelFuture future = bootstrap.bind(port).sync();
|
|
|
+
|
|
|
+ logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
|
|
|
+
|
|
|
+ // start registry
|
|
|
+ startRegistry(appName, address);
|
|
|
+
|
|
|
+ // wait util stop
|
|
|
+ future.channel().closeFuture().sync();
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ if (e instanceof InterruptedException) {
|
|
|
+ logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
|
|
|
+ } else {
|
|
|
+ logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ // stop
|
|
|
+ try {
|
|
|
+ workerGroup.shutdownGracefully();
|
|
|
+ bossGroup.shutdownGracefully();
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error(e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+ thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
|
|
|
+ thread.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void stop() throws Exception {
|
|
|
+ // destroy server thread
|
|
|
+ if (thread!=null && thread.isAlive()) {
|
|
|
+ thread.interrupt();
|
|
|
+ }
|
|
|
+
|
|
|
+ // stop registry
|
|
|
+ stopRegistry();
|
|
|
+ logger.info(">>>>>>>>>>> xxl-job remoting server destroy success.");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // ---------------------- registry ----------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * netty_http
|
|
|
+ *
|
|
|
+ * Copy from : https://github.com/xuxueli/xxl-rpc
|
|
|
+ *
|
|
|
+ * @author xuxueli 2015-11-24 22:25:15
|
|
|
+ */
|
|
|
+ public static class EmbedHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(EmbedHttpServerHandler.class);
|
|
|
+
|
|
|
+ private ExecutorBiz executorBiz;
|
|
|
+ private String accessToken;
|
|
|
+ public EmbedHttpServerHandler(ExecutorBiz executorBiz, String accessToken) {
|
|
|
+ this.executorBiz = executorBiz;
|
|
|
+ this.accessToken = accessToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
|
|
|
+
|
|
|
+ // request parse
|
|
|
+ //final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
|
|
|
+ String requestData = msg.content().toString(CharsetUtil.UTF_8);
|
|
|
+ String uri = msg.uri();
|
|
|
+ HttpMethod httpMethod = msg.method();
|
|
|
+ boolean keepAlive = HttpUtil.isKeepAlive(msg);
|
|
|
+ String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
|
|
|
+
|
|
|
+ // do invoke
|
|
|
+ Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
|
|
|
+
|
|
|
+ // to json
|
|
|
+ String responseJson = GsonTool.toJson(responseObj);
|
|
|
+
|
|
|
+ // write response
|
|
|
+ writeResponse(ctx, keepAlive, responseJson);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
|
|
|
+
|
|
|
+ // valid
|
|
|
+ if (HttpMethod.POST != httpMethod) {
|
|
|
+ return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
|
|
|
+ }
|
|
|
+ if (uri==null || uri.trim().length()==0) {
|
|
|
+ return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
|
|
|
+ }
|
|
|
+ if (accessToken!=null
|
|
|
+ && accessToken.trim().length()>0
|
|
|
+ && !accessToken.equals(accessTokenReq)) {
|
|
|
+ return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
|
|
|
+ }
|
|
|
+
|
|
|
+ // services mapping
|
|
|
+ try {
|
|
|
+ if ("/beat".equals(uri)) {
|
|
|
+ return executorBiz.beat();
|
|
|
+ } else if ("/idleBeat".equals(uri)) {
|
|
|
+ int jobId = GsonTool.fromJson(requestData, Integer.class);
|
|
|
+ return executorBiz.idleBeat(jobId);
|
|
|
+ } else if ("/kill".equals(uri)) {
|
|
|
+ int jobId = GsonTool.fromJson(requestData, Integer.class);
|
|
|
+ return executorBiz.kill(jobId);
|
|
|
+ } else if ("/log".equals(uri)) {
|
|
|
+ LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
|
|
|
+ return executorBiz.log(logParam);
|
|
|
+ } else if ("/run".equals(uri)) {
|
|
|
+ TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
|
|
|
+ return executorBiz.run(triggerParam);
|
|
|
+ } else {
|
|
|
+ return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error(e.getMessage(), e);
|
|
|
+ return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.stackTraceToString(e));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * write response
|
|
|
+ */
|
|
|
+ private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
|
|
|
+ // write response
|
|
|
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8)); // Unpooled.wrappedBuffer(responseJson)
|
|
|
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8"); // HttpHeaderValues.TEXT_PLAIN.toString()
|
|
|
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
|
|
|
+ if (keepAlive) {
|
|
|
+ response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
|
|
+ }
|
|
|
+ ctx.writeAndFlush(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
|
+ ctx.flush();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
+ logger.error(">>>>>>>>>>> xxl-job provider netty_http server caught exception", cause);
|
|
|
+ ctx.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
|
+ if (evt instanceof IdleStateEvent) {
|
|
|
+ ctx.channel().close(); // beat 3N, close if idle
|
|
|
+ logger.debug(">>>>>>>>>>> xxl-job provider netty_http server close an idle channel.");
|
|
|
+ } else {
|
|
|
+ super.userEventTriggered(ctx, evt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---------------------- registry ----------------------
|
|
|
+
|
|
|
+ public void startRegistry(final String appName, final String address) {
|
|
|
+ // start registry
|
|
|
+ ExecutorRegistryThread.getInstance().start(appName, address);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void stopRegistry() {
|
|
|
+ // stop registry
|
|
|
+ ExecutorRegistryThread.getInstance().toStop();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|