package io.mycat.backend.nio;
import io.mycat.MycatServer;
import io.mycat.net.Connection;
import io.mycat.net.ConnectionException;
import io.mycat.net.NIOHandler;
import io.mycat.server.Capabilities;
import io.mycat.server.executors.LoadDataResponseHandler;
import io.mycat.server.executors.ResponseHandler;
import io.mycat.server.packet.*;
import io.mycat.server.packet.util.ByteUtil;
import io.mycat.server.packet.util.CharsetUtil;
import io.mycat.server.packet.util.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class MySQLBackendConnectionHandler implements
NIOHandler<MySQLBackendConnection> {
private static final Logger LOGGER = LoggerFactory
.getLogger(MySQLBackendConnectionHandler.class);
private static final int RESULT_STATUS_INIT = 0;
private static final int RESULT_STATUS_HEADER = 1;
private static final int RESULT_STATUS_FIELD_EOF = 2;
@Override
public void onConnected(MySQLBackendConnection con) throws IOException {
//con.asynRead();
}
@Override
public void handle(MySQLBackendConnection con, ByteBuffer buf, int start,
int readedLength) {
switch (con.getState()) {
case connecting: {
doConnecting(con, buf, start, readedLength);
return;
}
case connected: {
try {
doHandleBusinessMsg(con, buf, start, readedLength);
} catch (Exception e) {
LOGGER.warn("caught err of con "+con, e);
}
return;
}
default:
LOGGER.warn("not handled connecton state err " + con.getState()
+ " for con " + con);
break;
}
}
@Override
public void onConnectFailed(MySQLBackendConnection source, Throwable e) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.connectionError(e, source);
}
}
private void handleLogin(MySQLBackendConnection source, byte[] data) {
try {
switch (data[4]) {
case OkPacket.FIELD_COUNT:
HandshakePacket packet = source.getHandshake();
if (packet == null) {
processHandShakePacket(source, data);
// 发送认证数据包
source.authenticate();
break;
}
// 处理认证结果
source.setAuthenticated(true);
source.setState(Connection.State.connected);
boolean clientCompress = Capabilities.CLIENT_COMPRESS == (Capabilities.CLIENT_COMPRESS & packet.serverCapabilities);
boolean usingCompress = MycatServer.getInstance().getConfig()
.getSystem().getUseCompression() == 1;
if (clientCompress && usingCompress) {
source.setSupportCompress(true);
}
if (source.getRespHandler() != null) {
source.getRespHandler().connectionAcquired(source);
}
break;
case ErrorPacket.FIELD_COUNT:
ErrorPacket err = new ErrorPacket();
err.read(data);
String errMsg = new String(err.message);
LOGGER.warn("can't connect to mysql server ,errmsg:" + errMsg
+ " " + source);
// source.close(errMsg);
throw new ConnectionException(err.errno, errMsg);
case EOFPacket.FIELD_COUNT:
auth323(source, data[3]);
break;
default:
packet = source.getHandshake();
if (packet == null) {
processHandShakePacket(source, data);
// 发送认证数据包
source.authenticate();
break;
} else {
throw new RuntimeException("Unknown Packet!");
}
}
} catch (RuntimeException e) {
if (source.getRespHandler() != null) {
source.getRespHandler().connectionError(e, source);
return;
}
throw e;
}
}
@Override
public void onClosed(MySQLBackendConnection source, String reason) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.connectionClose(source, reason);
}
}
private void doConnecting(MySQLBackendConnection con, ByteBuffer buf,
int start, int readedLength) {
byte[] data = new byte[readedLength];
buf.position(start);
buf.get(data, 0, readedLength);
handleLogin(con, data);
}
public void doHandleBusinessMsg(final MySQLBackendConnection source,
final ByteBuffer buf, final int start, final int readedLength) {
byte[] data = new byte[readedLength];
buf.position(start);
buf.get(data, 0, readedLength);
handleData(source, data);
}
public void connectionError(Throwable e) {
}
protected void handleData(final MySQLBackendConnection source, byte[] data) {
ResultStatus resultStatus = source.getSqlResultStatus();
switch (resultStatus.getResultStatus()) {
case RESULT_STATUS_INIT:
switch (data[4]) {
case OkPacket.FIELD_COUNT:
handleOkPacket(source, data);
break;
case ErrorPacket.FIELD_COUNT:
handleErrorPacket(source, data);
break;
case RequestFilePacket.FIELD_COUNT:
handleRequestPacket(source, data);
break;
default:
resultStatus.setResultStatus(RESULT_STATUS_HEADER);
resultStatus.setHeader(data);
resultStatus.setFields(new ArrayList<byte[]>((int) ByteUtil
.readLength(data, 4)));
}
break;
case RESULT_STATUS_HEADER:
switch (data[4]) {
case ErrorPacket.FIELD_COUNT:
resultStatus.setResultStatus(RESULT_STATUS_INIT);
handleErrorPacket(source, data);
break;
case EOFPacket.FIELD_COUNT:
resultStatus.setResultStatus(RESULT_STATUS_FIELD_EOF);
handleFieldEofPacket(source, data);
break;
default:
resultStatus.getFields().add(data);
}
break;
case RESULT_STATUS_FIELD_EOF:
switch (data[4]) {
case ErrorPacket.FIELD_COUNT:
resultStatus.setResultStatus(RESULT_STATUS_INIT);
handleErrorPacket(source, data);
break;
case EOFPacket.FIELD_COUNT:
resultStatus.setResultStatus(RESULT_STATUS_INIT);
handleRowEofPacket(source, data);
break;
default:
handleRowPacket(source, data);
}
break;
default:
throw new RuntimeException("unknown status!");
}
}
/**
* OK数据包处理
*/
private void handleOkPacket(MySQLBackendConnection source, byte[] data) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.okResponse(data, source);
}else {
closeNoHandler(source);
}
}
/**
* ERROR数据包处理
*/
private void handleErrorPacket(MySQLBackendConnection source, byte[] data) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.errorResponse(data, source);
} else {
closeNoHandler(source);
}
}
/**
* load data file 请求文件数据包处理
*/
private void handleRequestPacket(MySQLBackendConnection source, byte[] data) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null && respHand instanceof LoadDataResponseHandler) {
((LoadDataResponseHandler) respHand).requestDataResponse(data,
source);
} else {
closeNoHandler(source);
}
}
/**
* 字段数据包结束处理
*/
private void handleFieldEofPacket(final MySQLBackendConnection source,
byte[] data) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.fieldEofResponse(source.getSqlResultStatus().getHeader(),
source.getSqlResultStatus().getFields(), data, source);
} else {
closeNoHandler(source);
}
}
/**
* 行数据包处理
*/
private void handleRowPacket(final MySQLBackendConnection source,
byte[] data) {
ResponseHandler respHand = source.getRespHandler();
if (respHand != null) {
respHand.rowResponse(data, source);
} else {
closeNoHandler(source);
}
}
private void closeNoHandler(final MySQLBackendConnection source) {
if (!source.isClosedOrQuit()) {
source.close("no handler");
LOGGER.warn("no handler bind in this con " + this + " client:"
+ source);
}
}
/**
* 行数据包结束处理
*/
private void handleRowEofPacket(final MySQLBackendConnection source,
byte[] data) {
ResponseHandler responseHandler = source.getRespHandler();
if (responseHandler != null) {
responseHandler.rowEofResponse(data, source);
} else {
closeNoHandler(source);
}
}
private void processHandShakePacket(MySQLBackendConnection source,
byte[] data) {
// 设置握手数据包
HandshakePacket packet = new HandshakePacket();
packet.read(data);
source.setHandshake(packet);
source.setThreadId(packet.threadId);
// 设置字符集编码
int charsetIndex = (packet.serverCharsetIndex & 0xff);
String charset = CharsetUtil.getCharset(charsetIndex);
if (charset != null) {
source.setCharset(charset);
} else {
LOGGER.warn("Unknown charsetIndex:" + charsetIndex);
throw new RuntimeException("Unknown charsetIndex:" + charsetIndex);
}
}
private void auth323(MySQLBackendConnection source, byte packetId) {
// 发送323响应认证数据包
Reply323Packet r323 = new Reply323Packet();
r323.packetId = ++packetId;
String pass = source.getPassword();
if (pass != null && pass.length() > 0) {
byte[] seed = source.getHandshake().seed;
r323.seed = SecurityUtil.scramble323(pass, new String(seed))
.getBytes();
}
r323.write(source);
}
}