/** * Copyright 2018-2028 WindChat Group * * Licensed 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 com.windchat.im.connector.http.handler; import java.net.InetSocketAddress; import java.util.Base64; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.windchat.common.command.Command; import com.windchat.common.command.CommandResponse; import com.windchat.common.constant.CharsetCoding; import com.windchat.common.constant.HttpUriAction; import com.windchat.common.crypto.AESCrypto; import com.windchat.common.executor.AbstracteExecutor; import com.windchat.common.logs.LogUtils; import com.windchat.common.utils.StringHelper; import com.akaxin.proto.core.PluginProto; import com.windchat.im.connector.constant.AkxProject; import com.windchat.im.connector.constant.HttpConst; import com.windchat.im.connector.constant.PluginConst; import com.windchat.im.connector.session.PluginSession; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; /** * 处理Http请求 * * @author Sam{@link an.guoyue254@gmail.com} * @since 2017-11-28 18:49:38 */ public class HttpServerHandler extends ChannelInboundHandlerAdapter { private static Logger logger = LoggerFactory.getLogger(HttpServerHandler.class); private HttpRequest request; private String httpClientIp; private AbstracteExecutor executor; public HttpServerHandler(AbstracteExecutor executor) { this.executor = executor; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { logger.debug("{} client connect to http server... client={}", AkxProject.PLN, ctx.channel().toString()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { /** * 一次Http请求,分两次处理,第一次处理Http消息头,第二次请求http消息体 */ /** * http-request包含:
* 1.请求行 ,Method Request-URI Http-Version CRLF
* 2.消息头
* 3.请求正文
*/ if (msg instanceof HttpRequest) { request = (HttpRequest) msg; if (!checkLegalRequest()) { logger.error("{} http request method error. please use post!", AkxProject.PLN); ctx.close(); return; } String sitePluginId = request.headers().get(PluginConst.SITE_PLUGIN_ID); if (StringUtils.isEmpty(sitePluginId)) { logger.error("{} http request illegal with error pluginId={}.", AkxProject.PLN, sitePluginId); ctx.close(); return; } // set socket ip InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress(); httpClientIp = address.getAddress().getHostAddress(); if (!checkLegalClientIp(sitePluginId, httpClientIp)) { logger.error("{} http request illegal IP={}.", AkxProject.PLN, httpClientIp); ctx.close(); return; } // 检测URI,http://127.0.0.1:8280/akaxin-plugin-api/hai/user/friends if (checkRequestUri()) { logger.error("akaxin plugin http request illegal uri={}.", AkxProject.PLN, request.uri()); ctx.close(); return; } logger.debug("{} request uri:{} clientIp={}", AkxProject.PLN, request.uri(), httpClientIp); } /** * HttpContent:表示HTTP实体正文和内容标头的基类
* method.name=POST 传输消息体存在内容 */ if (msg instanceof LastHttpContent) { HttpContent content = (HttpContent) msg; ByteBuf httpByteBuf = content.content(); if (httpByteBuf == null) { return; } if (!checkLegalRequest()) { ctx.close(); return; } // String clientIp = request.headers().get(HttpConst.HTTP_H_FORWARDED); String sitePluginId = request.headers().get(PluginConst.SITE_PLUGIN_ID); byte[] contentBytes = new byte[httpByteBuf.readableBytes()]; httpByteBuf.readBytes(contentBytes); httpByteBuf.release(); logger.debug("{} http request IP={} pluginId={}", AkxProject.PLN, httpClientIp, sitePluginId); if (StringUtils.isEmpty(sitePluginId)) { logger.error("{} http request body illegal pluginId={}.", AkxProject.PLN, sitePluginId); ctx.close(); return; } // 查询扩展的auth——key String authKey = PluginSession.getInstance().getPluginAuthKey(sitePluginId); logger.debug("http request ip={} pluginId={} authKey={}", httpClientIp, sitePluginId, authKey); if (StringUtils.isNotEmpty(authKey)) { // byte[] tsk = AESCrypto.generateTSKey(authKey); byte[] tsk = authKey.getBytes(CharsetCoding.ISO_8859_1); byte[] decContent = AESCrypto.decrypt(tsk, contentBytes); contentBytes = decContent; } PluginProto.ProxyPluginPackage pluginPackage = PluginProto.ProxyPluginPackage.parseFrom(contentBytes); Map proxyHeader = pluginPackage.getPluginHeaderMap(); String requestTime = proxyHeader.get(PluginProto.PluginHeaderKey.PLUGIN_TIMESTAMP_VALUE); long currentTime = System.currentTimeMillis(); boolean timeOut = true; if (StringUtils.isNotEmpty(requestTime)) { long timeMills = Long.valueOf(requestTime); if (currentTime - timeMills < 10 * 1000l) { timeOut = false; } } logger.debug("{} client={} http request timeOut={} currTime={} reqTime={}", AkxProject.PLN, httpClientIp, timeOut, currentTime, requestTime); if (!timeOut) { Command command = new Command(); command.setField(PluginConst.PLUGIN_AUTH_KEY, authKey); if (proxyHeader != null) { command.setSiteUserId(proxyHeader.get(PluginProto.PluginHeaderKey.CLIENT_SITE_USER_ID_VALUE)); } command.setChannelContext(ctx); command.setUri(request.uri()); command.setParams(Base64.getDecoder().decode(pluginPackage.getData())); command.setClientIp(httpClientIp); command.setStartTime(System.currentTimeMillis()); command.setPluginId(sitePluginId); command.setPluginAuthKey(authKey); logger.info("{} client={} uri={} http server handler command={}", AkxProject.PLN, httpClientIp, request.uri(), command.toString()); CommandResponse response = this.executor.execute(HttpUriAction.HTTP_ACTION.getRety(), command); LogUtils.requestResultLog(logger, command, response); } else { // 超时10s,认为此请求失效,直接断开连接 ctx.close(); logger.error("{} client={} http request error.timeOut={} currTime={} reqTime={}", AkxProject.PLN, httpClientIp, timeOut, currentTime, requestTime); } } } catch (Exception e) { ctx.close(); logger.error(StringHelper.format("{} http request error.", AkxProject.PLN), e); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); logger.error(StringHelper.format("{} channel exception caught", AkxProject.PLN), cause); } /** * 所有扩展请求站点的http服务请求,全部使用post请求 * * @return */ private boolean checkLegalRequest() { String methodName = request.method().name(); if (HttpConst.HTTP_M_POST.equals(methodName)) { return true; } return false; } // 预留处理请求ip过滤 private boolean checkLegalClientIp(String pluginId, String ip) { // #TODO 使用缓存 String allowIps = PluginSession.getInstance().getPluginAllowIp(pluginId); if (StringUtils.isNotEmpty(allowIps)) { if (!allowIps.contains(ip)) { return false; } } return true; } private boolean checkRequestUri() { String uri = request.uri(); if (StringUtils.isNotEmpty(uri)) { return uri.startsWith("/akaxin-plugin-api/hai/") || uri.startsWith("//akaxin-plugin-api/hai/") || uri.startsWith("akaxin-plugin-api/hai/"); } return false; } }