PageRenderTime 68ms CodeModel.GetById 32ms app.highlight 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/linbox/im/server/connector/tcp/handler/AuthHandler.java

https://gitlab.com/Mr.Tomato/linbox_server
Java | 264 lines | 203 code | 56 blank | 5 comment | 21 complexity | b0a1feb74021439305cc871e7ac30868 MD5 | raw file
  1package com.linbox.im.server.connector.tcp.handler;
  2
  3import com.alibaba.fastjson.JSON;
  4import com.linbox.im.message.*;
  5import com.linbox.im.server.connector.tcp.constant.HandlerName;
  6import com.linbox.im.server.service.IOutboxService;
  7import com.linbox.im.server.storage.dao.IServerDAO;
  8import com.linbox.im.server.storage.dao.IUserDAO;
  9import io.netty.channel.Channel;
 10import io.netty.channel.ChannelFuture;
 11import io.netty.channel.ChannelHandlerContext;
 12import io.netty.channel.ChannelInboundHandlerAdapter;
 13import io.netty.util.concurrent.Future;
 14import io.netty.util.concurrent.GenericFutureListener;
 15import org.apache.commons.lang.StringUtils;
 16import org.slf4j.Logger;
 17import org.slf4j.LoggerFactory;
 18import org.springframework.context.support.ClassPathXmlApplicationContext;
 19
 20import java.net.InetSocketAddress;
 21import java.util.Base64;
 22import java.util.concurrent.ScheduledExecutorService;
 23import java.util.concurrent.ScheduledFuture;
 24import java.util.concurrent.TimeUnit;
 25
 26/**
 27 * Created by lrsec on 6/28/15.
 28 */
 29public class AuthHandler extends ChannelInboundHandlerAdapter {
 30
 31    private static Logger logger = LoggerFactory.getLogger(AuthHandler.class);
 32
 33    private ScheduledExecutorService executor;
 34    private int loopRatio = 500;
 35    private int maxHandleTimeInMills = 5 * 1000;
 36    private ScheduledFuture task;
 37
 38    private long rId = -1;
 39    private String userId = null;
 40    private AES aes = null;
 41    private Base64.Encoder base64Encoder = null;
 42
 43    private IOutboxService outboxService;
 44    private IUserDAO userDAO;
 45    private IServerDAO serverDAO;
 46
 47    public AuthHandler(ClassPathXmlApplicationContext applicationContext, ScheduledExecutorService executor, int loopRatio, int maxHandleTimeInMills, AES aes) {
 48        this.executor = executor;
 49        this.loopRatio = loopRatio;
 50        this.maxHandleTimeInMills = maxHandleTimeInMills;
 51        this.aes = aes;
 52        this.outboxService = (IOutboxService)applicationContext.getBean("outboxService");
 53        this.userDAO = (IUserDAO) applicationContext.getBean("userDAO");
 54        this.serverDAO = (IServerDAO) applicationContext.getBean("serverDAO");
 55        this.base64Encoder = Base64.getEncoder();
 56    }
 57
 58    @Override
 59    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
 60        logger.error("Exception in AuthHandler. Close the connection for user: " + StringUtils.trimToEmpty(userId), cause);
 61        if (rId >= 0) {
 62            sendFailResponse(ctx, 500, cause.getMessage());
 63        }
 64
 65        ctx.close();
 66    }
 67
 68    @Override
 69    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
 70        logger.debug("Received AuthRequest. Message: {}", JSON.toJSONString(msg));
 71
 72        MessageWrapper wrapper = (MessageWrapper) msg;
 73
 74        if (wrapper == null) {
 75            logger.error("Get an null message wrapper in message handler");
 76            ctx.close();
 77            return;
 78        }
 79
 80        RequestResponseType type = wrapper.type;
 81
 82        if (type == null || type != RequestResponseType.AuthRequestMsg) {
 83            logger.error("The first message from client: {} is not AuthRequest. Close the connection. Message type is {}", ctx.channel().remoteAddress(), msg.getClass().getName());
 84            ctx.close();
 85            return;
 86        }
 87
 88        AuthRequest authRequest = (AuthRequest) wrapper.content;
 89        rId = authRequest.rId;
 90        boolean authenticated = isCertified(authRequest);
 91
 92        if (authenticated) {
 93            logger.debug("User {} authenticated.", userId);
 94
 95            InetSocketAddress address = (InetSocketAddress)ctx.channel().localAddress();
 96            String addressRecord = address.toString() + Long.toString(System.currentTimeMillis());
 97            serverDAO.registerConnection(userId, addressRecord);
 98            logger.debug("Create connection for user: {}. Remote address: {}.", userId, ctx.channel().remoteAddress());
 99
100            //TODO 动态密码的生成策略,在测试时关闭
101//            resetPassword(ctx);
102
103            task = executor.scheduleAtFixedRate(new SendMessageChecker(ctx.channel(), addressRecord), 0, loopRatio, TimeUnit.MILLISECONDS);
104
105            IMMessageHandler imMsgHandler = (IMMessageHandler) ctx.pipeline().get(HandlerName.HANDLER_MSG);
106            imMsgHandler.setUserId(userId);
107            IMIdleStateHandler imIdleStateHandler = (IMIdleStateHandler) ctx.pipeline().get(HandlerName.HANDLER_IDLE);
108            imIdleStateHandler.setUserId(userId);
109
110            ctx.pipeline().remove(this);
111
112            sendSuccessResponse(ctx);
113
114            return;
115        } else {
116            logger.error("User {} is not certified. Close the connection.", userId);
117            sendFailResponse(ctx, 401, "User is not certified");
118
119            ctx.close();
120            return;
121        }
122    }
123
124    private boolean isCertified(AuthRequest request) {
125        userId = request.userId;
126        return userDAO.isUserValid(request.userId, request.token);
127    }
128
129    private void resetPassword(final ChannelHandlerContext ctx) {
130        String password = serverDAO.getPassword(Long.parseLong(userId));
131
132        if (StringUtils.isBlank(password)) {
133            logger.error("Can not get im password for user: {}", userId);
134            sendFailResponse(ctx, 400, "Can not find user im password");
135            return;
136        } else {
137            aes.resetPassword(password);
138        }
139    }
140
141    private class SendMessageChecker implements Runnable {
142        private Logger logger = LoggerFactory.getLogger(SendMessageChecker.class);
143
144        private volatile boolean isConnectionClosed = false;
145        private Channel ch;
146        private String linkRecord;
147
148        public SendMessageChecker(Channel ch, String linkRecord) {
149            this.ch = ch;
150            this.linkRecord = linkRecord;
151        }
152
153        public void run() {
154            long start = System.currentTimeMillis();
155            while ((System.currentTimeMillis() - start <= maxHandleTimeInMills)) {
156                try {
157                    if(shouldClose()) {
158                        terminate();
159                        return;
160                    }
161
162                    final String msg = outboxService.get(userId);
163
164                    if (StringUtils.isBlank(msg)) {
165                        return;
166                    }
167
168                    final MessageWrapper wrapper = JSON.parseObject(msg, MessageWrapper.class);
169
170                    ChannelFuture future = ch.writeAndFlush(wrapper);
171
172                    logger.debug("Tcp sender send message for {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
173
174                    future.addListener(new GenericFutureListener() {
175                        public void operationComplete(Future future) throws Exception {
176                            if (!future.isSuccess()) {
177                                logger.info("Network I/O write fail. Should close connection for user {}.", userId);
178                                isConnectionClosed = true;
179
180                                Throwable t = future.cause();
181                                if (t != null) {
182                                    logger.info("Send message fail", t);
183                                }
184                            } else {
185                                logger.debug("Tcp sender send message success. user: {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
186                            }
187                        }
188                    });
189
190                } catch (Exception e) {
191                    logger.error("Exception in sending task for user: " + userId + ". But the sending loop will continue to work", e);
192                    return;
193                }
194            }
195        }
196
197        private boolean shouldClose() {
198            if (!ch.isActive()) {
199                logger.warn("Connection for user {} is inactive, should terminate the sending task.", userId);
200                return true;
201            }
202
203            if(isConnectionClosed) {
204                logger.warn("Connection for user {} is closed, should terminate the sending task.", userId);
205                return true;
206            }
207
208            String record = serverDAO.getConnection(userId);
209
210            if(!StringUtils.equals(record, linkRecord)) {
211                logger.warn("Connection is updated, should terminate the sending task for user {}. Local address {}. New connection address: {}", userId, StringUtils.trimToEmpty(linkRecord), StringUtils.trimToEmpty(record));
212
213                sendOfflineInfo();
214                return true;
215            }
216
217            return false;
218        }
219
220        private void terminate() {
221            logger.info("Terminate sending task for user {}", userId);
222
223            task.cancel(false);
224
225            try {
226                ch.close();
227            } catch (Exception e) {
228                logger.error("Exception when terminate sending task", e);
229            }
230        }
231
232        private void sendOfflineInfo() {
233            OfflineInfo offlineInfo = new OfflineInfo();
234            offlineInfo.userId = userId;
235
236            MessageWrapper wrapper = offlineInfo.toWrapper();
237            ch.writeAndFlush(wrapper);
238        }
239    }
240
241    private void sendSuccessResponse(ChannelHandlerContext ctx) {
242        AuthResponse response = new AuthResponse();
243        response.rId = rId;
244        response.userId = userId;
245        response.status = 200;
246        response.sendTime = System.currentTimeMillis();
247
248        MessageWrapper responseWrapper = response.toWrapper();
249        ctx.channel().writeAndFlush(responseWrapper);
250    }
251
252    private void sendFailResponse(ChannelHandlerContext ctx, int status, String errCode) {
253
254        AuthResponse response = new AuthResponse();
255        response.rId = rId;
256        response.userId = userId;
257        response.status = status;
258        response.errMsg = errCode;
259        response.sendTime = System.currentTimeMillis();
260
261        MessageWrapper responseWrapper = response.toWrapper();
262        ctx.channel().writeAndFlush(responseWrapper);
263    }
264}