251 lines
8.4 KiB
Java
Executable File
251 lines
8.4 KiB
Java
Executable File
/**
|
||
* 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<Command, CommandResponse> executor;
|
||
|
||
public HttpServerHandler(AbstracteExecutor<Command, CommandResponse> 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包含: <br>
|
||
* 1.请求行 ,Method Request-URI Http-Version CRLF<br>
|
||
* 2.消息头 <br>
|
||
* 3.请求正文 <br>
|
||
*/
|
||
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实体正文和内容标头的基类 <br>
|
||
* 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<Integer, String> 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;
|
||
}
|
||
|
||
}
|