/**
* Tencent is pleased to support the open source community by making MSEC available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the GNU General Public 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
*
* https://opensource.org/licenses/GPL-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 org.msec.net;
import api.monitor.msec.org.AccessMonitor;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import com.googlecode.protobuf.format.JsonFormat;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferIndexFinder;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.buffer.DynamicChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.msec.rpc.HttpFormatException;
import org.msec.rpc.HttpRequestParser;
import org.msec.rpc.RpcRequest;
import org.msec.rpc.ServiceFactory;
import srpc.Head;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class RequestDecoder extends OneToOneDecoder {
private static Logger log = Logger.getLogger(RequestDecoder.class.getName());
@Override
protected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, Object msg) throws Exception {
long receiveTime = System.currentTimeMillis();
if (!(msg instanceof ChannelBuffer)) {
return msg;
}
ChannelBuffer cb = (ChannelBuffer) NettyCodecUtils.getAttachment(channelHandlerContext, Constants.ATTACHMENT_BYTEBUFFER);
ChannelBuffer cb_ = (ChannelBuffer) msg;
if (cb == null) {
cb = cb_;
} else {
cb.writeBytes(cb_);
}
List<Object> messages = null;
int lastReadIndex = cb.readerIndex();
int deserializeMode = -1;
while (cb.readable()) {
byte stx = cb.readByte();
RpcRequest rpcRequest = null;
if (stx == (byte) '(') {
if (cb.readableBytes() < Constants.PKG_LEAST_LENGTH - 1) {
setAttachment(channelHandlerContext, channel, cb, lastReadIndex);
break;
}
//Test whether protocol is protobuf
//Format: ( + dwHeadLength + dwBodyLength + strHead + strBody + )
int headLength = cb.readInt();
int bodyLength = cb.readInt();
if (cb.readableBytes() < headLength + bodyLength + 1) {
setAttachment(channelHandlerContext, channel, cb, lastReadIndex);
break;
}
byte[] headBytes = new byte[headLength];
byte[] bodyBytes = new byte[bodyLength];
cb.readBytes(headBytes);
cb.readBytes(bodyBytes);
byte etx = cb.readByte();
if (etx != ')') {
log.error("Invalid package etx.");
throw new IllegalArgumentException("Request decode error: invalid package etx " + etx);
}
//parse protobuf package
rpcRequest = deserializeProtobufPackage(headBytes, bodyBytes);
rpcRequest.setSerializeMode(RpcRequest.SerializeMode.SERIALIZE_MODE_PROTOBUF);
} else {
//Test whether protocol is HTTP
cb.readerIndex(lastReadIndex);
int totalLength = cb.readableBytes();
byte[] totalBytes = new byte[totalLength];
cb.readBytes(totalBytes);
String total = new String(totalBytes);
int pos = total.indexOf("\r\n\r\n");
if (pos < 0) {
setAttachment(channelHandlerContext, channel, cb, lastReadIndex);
break;
}
int contentLength = getHTTPContentLength(total.substring(0, pos + 4));
if (totalLength < pos + 4 + contentLength) {
setAttachment(channelHandlerContext, channel, cb, lastReadIndex);
break;
}
cb.readerIndex(pos + 4 + contentLength);
//parse HTTP package
rpcRequest = deserializeHTTPPackage(total.substring(0, pos + 4 + contentLength));
rpcRequest.setSerializeMode(RpcRequest.SerializeMode.SERIALIZE_MODE_HTTP);
}
if (rpcRequest != null) {
if (messages == null) {
messages = new ArrayList<Object>();
}
messages.add(rpcRequest);
lastReadIndex = cb.readerIndex();
} else {
setAttachment(channelHandlerContext, channel, cb, lastReadIndex);
break;
}
}
return messages;
}
private RpcRequest deserializeHTTPPackage(String request) {
RpcRequest rpcRequest = new RpcRequest();
HttpRequestParser httpParser = new HttpRequestParser();
try {
httpParser.parseRequest(request);
} catch (IOException ex) {
log.error("HTTP Request parse failed." + ex.getMessage());
rpcRequest.setException(new IllegalArgumentException("Request decode error: HTTP Request parse failed." + ex.getMessage()));
return rpcRequest;
} catch (HttpFormatException ex) {
log.error("HTTP Request parse failed." + ex.getMessage());
rpcRequest.setException(new IllegalArgumentException("Request decode error: HTTP Request parse failed." + ex.getMessage()));
return rpcRequest;
}
rpcRequest.setHttpCgiName(httpParser.getCgiPath());
if (httpParser.getCgiPath().compareToIgnoreCase("/list") == 0) {
return rpcRequest;
}
if (httpParser.getCgiPath().compareToIgnoreCase("/invoke") != 0) {
log.error("HTTP cgi not found: " + httpParser.getCgiPath());
rpcRequest.setException(new IllegalArgumentException("HTTP cgi not found: " + httpParser.getURI()));
return rpcRequest;
}
String serviceMethodName = httpParser.getQueryString("methodName");
int pos = serviceMethodName.lastIndexOf('.');
if (pos == -1 || pos == 0 || pos == serviceMethodName.length() - 1) {
log.error("Invalid serviceMethodName (" + serviceMethodName + "). Must be in format like *.*");
rpcRequest.setException(new IllegalArgumentException("Invalid serviceMethodName (" + serviceMethodName + "). Must be in format like *.*"));
return rpcRequest;
}
String serviceName = serviceMethodName.substring(0, pos);
String methodName = serviceMethodName.substring(pos + 1);
String seq = httpParser.getQueryString("seq");
String param = httpParser.getQueryString("param");
if (param == null || param.isEmpty()) {
param = httpParser.getMessageBody();
}
try {
param = java.net.URLDecoder.decode(param, "UTF-8");
} catch (UnsupportedEncodingException e) {
param = "";
}
rpcRequest.setServiceName(serviceName);
rpcRequest.setMethodName(methodName);
if (seq == null || seq.isEmpty()) {
rpcRequest.setSeq(ServiceFactory.generateSequence());
} else {
rpcRequest.setSeq(Long.valueOf(seq).longValue());
}
log.info("In HTTP package service: " + serviceName + " method: " + methodName + " param: " + param);
rpcRequest.setFromModule("UnknownModule");
rpcRequest.setFlowid(rpcRequest.getSeq() ^ NettyCodecUtils.generateColorId(serviceName + methodName));
AccessMonitor.add("frm.rpc request incoming: " + methodName);
ServiceFactory.ServiceMethodEntry serviceMethodEntry = ServiceFactory.getServiceMethodEntry(serviceName, methodName);
if (serviceMethodEntry == null) {
log.error("No service method registered: " + rpcRequest.getServiceName() + "/" + rpcRequest.getMethodName());
rpcRequest.setException(new IllegalArgumentException("No service method registered: " + rpcRequest.getServiceName()
+ "/" + rpcRequest.getMethodName()));
return rpcRequest;
}
Message.Builder builder = serviceMethodEntry.getParamTypeBuilder();
try {
builder.clear();
JsonFormat.merge(param, builder);
} catch (JsonFormat.ParseException ex) {
log.error("Json parse failed." + ex.getMessage());
rpcRequest.setException(new IllegalArgumentException("Request decode error: Json parse failed." + ex.getMessage()));
return rpcRequest;
}
if ( !builder.isInitialized() ) {
log.error("Json to Protobuf failed: missing required fields");
rpcRequest.setException(new IllegalArgumentException("Json to Protobuf failed: missing required fields: " +
builder.getInitializationErrorString()));
return rpcRequest;
}
rpcRequest.setParameter(builder.build());
log.info("RPC Request received. ServiceMethodName: " + rpcRequest.getServiceName() + "/" + rpcRequest.getMethodName() +
"\tSeq: " + rpcRequest.getSeq() + "\tParameter: " + rpcRequest.getParameter());
return rpcRequest;
}
private RpcRequest deserializeProtobufPackage(byte[] headBytes, byte[] bodyBytes)
{
Head.CRpcHead pbHead = null;
RpcRequest rpcRequest = new RpcRequest();
try {
pbHead = Head.CRpcHead.parseFrom(headBytes);
rpcRequest.setSeq(pbHead.getSequence());
} catch (InvalidProtocolBufferException e) {
log.error("Parse protobuf head failed.");
rpcRequest.setException(new IllegalArgumentException("Parse protobuf head failed."));
return rpcRequest;
}
String serviceMethodName = pbHead.getMethodName().toStringUtf8();
int pos = serviceMethodName.lastIndexOf('.');
if (pos == -1 || pos == 0 || pos == serviceMethodName.length() - 1) {
log.error("Invalid serviceMethodName (" + serviceMethodName + "). Must be in format like *.*");
rpcRequest.setException(new IllegalArgumentException("Invalid serviceMethodName (" + serviceMethodName + "). Must be in format like *.*"));
return rpcRequest;
}
String serviceName = serviceMethodName.substring(0, pos);
String methodName = serviceMethodName.substring(pos + 1);
rpcRequest.setServiceName(serviceName);
rpcRequest.setMethodName(methodName);
rpcRequest.setFlowid(pbHead.getFlowId());
if (pbHead.getCaller() != null && !pbHead.getCaller().isEmpty()) {
rpcRequest.setFromModule(pbHead.getCaller().toStringUtf8());
} else {
rpcRequest.setFromModule("UnknownModule");
}
//If flowid == 0, we must generate one
if (rpcRequest.getFlowid() == 0) {
rpcRequest.setFlowid(rpcRequest.getSeq() ^ NettyCodecUtils.generateColorId(serviceMethodName));
}
AccessMonitor.add("frm.rpc request incoming: " + methodName);
ServiceFactory.ServiceMethodEntry serviceMethodEntry = ServiceFactory.getServiceMethodEntry(serviceName, methodName);
if (serviceMethodEntry == null) {
log.error("No service method registered: " + rpcRequest.getServiceName() + "/" + rpcRequest.getMethodName());
rpcRequest.setException(new IllegalArgumentException("No service method registered: " + rpcRequest.getServiceName()
+ "/" + rpcRequest.getMethodName()));
return rpcRequest;
}
MessageLite param = null;
try {
param = (MessageLite) serviceMethodEntry.getParamTypeParser().parseFrom(bodyBytes);
} catch (InvalidProtocolBufferException ex) {
log.error("Parse protobuf body failed.");
rpcRequest.setException(new IllegalArgumentException("RParse protobuf body failed." + ex.getMessage()));
return rpcRequest;
}
rpcRequest.setParameter(param);
log.info("RPC Request received. ServiceMethodName: " + rpcRequest.getServiceName() + "/" + rpcRequest.getMethodName() +
"\tSeq: " + rpcRequest.getSeq() + "\tParameter: " + rpcRequest.getParameter());
return rpcRequest;
}
private int getHTTPContentLength(String req) {
int ret = 0;
if ( !req.startsWith("POST") )
return ret;
String keyStr = "Content-Length:";
int spos = req.indexOf(keyStr);
if (spos < 0)
return ret;
spos += keyStr.length();
while (req.charAt(spos) == ' ') spos++;
if ( !Character.isDigit(req.charAt(spos)) )
return ret;
int epos = spos;
while (Character.isDigit(req.charAt(epos))) epos++;
ret = Integer.parseInt(req.substring(spos, epos));
return ret;
}
private void setAttachment(ChannelHandlerContext ctx, Channel channel, ChannelBuffer cb, int lastReadIndex) {
cb.readerIndex(lastReadIndex);
if (!(cb instanceof DynamicChannelBuffer) || cb.writerIndex() > 102400) {
ChannelBuffer db = ChannelBuffers.dynamicBuffer(cb.readableBytes() * 2, channel.getConfig().getBufferFactory());
db.writeBytes(cb);
cb = db;
}
NettyCodecUtils.setAttachment(ctx, Constants.ATTACHMENT_BYTEBUFFER, cb);
}
}