/* * Copyright 2015 Liu Huanting. * * 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 fm.liu.timo.mysql.connection; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import org.pmw.tinylog.Logger; import fm.liu.timo.backend.Source; import fm.liu.timo.config.Capabilities; import fm.liu.timo.config.ErrorCode; import fm.liu.timo.mysql.ByteUtil; import fm.liu.timo.mysql.SecurityUtil; import fm.liu.timo.mysql.Sync; import fm.liu.timo.mysql.handler.AuthenticatorHandler; import fm.liu.timo.mysql.packet.AuthPacket; import fm.liu.timo.mysql.packet.CommandPacket; import fm.liu.timo.mysql.packet.EOFPacket; import fm.liu.timo.mysql.packet.ErrorPacket; import fm.liu.timo.mysql.packet.HandshakePacket; import fm.liu.timo.mysql.packet.MySQLPacket; import fm.liu.timo.mysql.packet.OkPacket; import fm.liu.timo.net.NIOProcessor; import fm.liu.timo.net.connection.BackendConnection; import fm.liu.timo.net.connection.Variables; import fm.liu.timo.server.session.Session; import fm.liu.timo.server.session.TransactionSession; import fm.liu.timo.server.session.handler.ResultHandler; import fm.liu.timo.server.session.handler.SessionResultHandler; import fm.liu.timo.server.session.handler.SyncHandler; /** * @author Liu Huanting 2015年5月9日 */ public class MySQLConnection extends BackendConnection { private static final long CLIENT_FLAGS = initClientFlags(); private static long initClientFlags() { int flag = 0; flag |= Capabilities.CLIENT_LONG_PASSWORD; flag |= Capabilities.CLIENT_FOUND_ROWS; flag |= Capabilities.CLIENT_LONG_FLAG; flag |= Capabilities.CLIENT_CONNECT_WITH_DB; // flag |= Capabilities.CLIENT_NO_SCHEMA; // flag |= Capabilities.CLIENT_COMPRESS; flag |= Capabilities.CLIENT_ODBC; // flag |= Capabilities.CLIENT_LOCAL_FILES; flag |= Capabilities.CLIENT_IGNORE_SPACE; flag |= Capabilities.CLIENT_PROTOCOL_41; flag |= Capabilities.CLIENT_INTERACTIVE; // flag |= Capabilities.CLIENT_SSL; flag |= Capabilities.CLIENT_IGNORE_SIGPIPE; flag |= Capabilities.CLIENT_TRANSACTIONS; // flag |= Capabilities.CLIENT_RESERVED; flag |= Capabilities.CLIENT_SECURE_CONNECTION; // client extension // flag |= Capabilities.CLIENT_MULTI_STATEMENTS; flag |= Capabilities.CLIENT_MULTI_RESULTS; return flag; } private Source datasource; private long threadID; private final int datanodeID; private HandshakePacket handshake; private long clientFlags; private boolean isAuthenticated; private String username; private String password; private volatile String db = ""; protected volatile ResultHandler resultHandler; public MySQLConnection(SocketChannel channel, NIOProcessor processor, int datanodeID) { super(channel, processor); this.datanodeID = datanodeID; this.clientFlags = CLIENT_FLAGS; } public final void onRead(int got) { if (isClosed()) { return; } if (resultHandler != null) { decode(); } else { if (!isAuthenticated) { super.onRead(got); } else { this.close("no handler to deal with the data"); } } } private void decode() { ByteBuffer buffer = this.readBuffer; int offset = 0, position = buffer.position(), length; for (;;) { length = MySQLPacket.getPacketLength(buffer, offset); if (length == -1) { break; } if (position >= offset + length) { byte[] data = new byte[length]; buffer.position(offset); buffer.get(data, 0, length); buffer.position(position); dispose(data); offset += length; continue; } else { break; } } if (offset < position) { buffer.position(position); readBuffer = checkBuffer(buffer, offset, length); } else { buffer.clear(); } } public void auth() { AuthPacket packet = new AuthPacket(); packet.packetId = 1; packet.clientFlags = clientFlags; packet.maxPacketSize = MySQLPacket.MAX_PACKET_SIZE; packet.charsetIndex = variables.getCharsetIndex(); packet.user = username; try { packet.password = passwd(password, handshake); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e.getMessage()); } packet.database = db; packet.write(this); } private static byte[] passwd(String pass, HandshakePacket hs) throws NoSuchAlgorithmException { if (pass == null || pass.length() == 0) { return null; } byte[] passwd = pass.getBytes(); int sl1 = hs.seed.length; int sl2 = hs.restOfScrambleBuff.length; byte[] seed = new byte[sl1 + sl2]; System.arraycopy(hs.seed, 0, seed, 0, sl1); System.arraycopy(hs.restOfScrambleBuff, 0, seed, sl1, sl2); return SecurityUtil.scramble411(passwd, seed); } private volatile int resultStatus; private volatile byte[] header; private volatile List<byte[]> fields; public class ResponseStatus { public static final int RESULT_STATUS_INIT = 0; public static final int RESULT_STATUS_HEADER = 1; public static final int RESULT_STATUS_FIELD_EOF = 2; public static final int RESULT_STATUS_END = 3; } private void dispose(byte[] data) { byte reponsePacketType = data[4]; switch (resultStatus) { case ResponseStatus.RESULT_STATUS_INIT: switch (reponsePacketType) { case OkPacket.FIELD_COUNT: this.setState(State.borrowed); resultHandler.ok(data, this); break; case ErrorPacket.FIELD_COUNT: this.setState(State.borrowed); resultHandler.error(data, this); break; default: resultStatus = ResponseStatus.RESULT_STATUS_HEADER; header = data; fields = new ArrayList<byte[]>((int) ByteUtil.readLength(data, 4)); } break; case ResponseStatus.RESULT_STATUS_HEADER: switch (reponsePacketType) { case ErrorPacket.FIELD_COUNT: resultStatus = ResponseStatus.RESULT_STATUS_INIT; this.setState(State.borrowed); resultHandler.error(data, this); break; case EOFPacket.FIELD_COUNT: resultStatus = ResponseStatus.RESULT_STATUS_FIELD_EOF; resultHandler.field(header, fields, data, this); break; default: fields.add(data); } break; case ResponseStatus.RESULT_STATUS_FIELD_EOF: switch (reponsePacketType) { case ErrorPacket.FIELD_COUNT: resultStatus = ResponseStatus.RESULT_STATUS_INIT; this.setState(State.borrowed); resultHandler.error(data, this); break; case EOFPacket.FIELD_COUNT: resultStatus = ResponseStatus.RESULT_STATUS_INIT; this.setState(State.borrowed); resultHandler.eof(data, this); break; default: resultHandler.row(data, this); } break; default: throw new RuntimeException("unknown status when process MySQL Packet!"); } } public void setState(int state) { this.state = state; } public int getState() { return state; } private ByteBuffer checkBuffer(ByteBuffer buffer, int offset, int length) { if (length > buffer.capacity()) { ByteBuffer newBuffer = this.processor.getBufferPool().allocate(length); buffer.limit(buffer.position()); buffer.position(offset); newBuffer.put(buffer); return newBuffer; } if (offset > 0) { buffer.limit(buffer.position()); buffer.position(offset); buffer.compact(); } return buffer; } @Override public void error(int errCode, Throwable t) { switch (errCode) { case ErrorCode.ERR_HANDLE_DATA: break; case ErrorCode.ERR_PUT_WRITE_QUEUE: break; case ErrorCode.ERR_CONNECT_SOCKET: if (handler == null) { return; } if (resultHandler != null) { final ResultHandler temp = resultHandler; resultHandler = null; temp.close("connectionError"); } else if (handler instanceof AuthenticatorHandler) { AuthenticatorHandler theHandler = (AuthenticatorHandler) handler; theHandler.error(t); } break; } } @Override public void close(String reason) { if (resultHandler != null) { // 由线程池去执行关闭后的操作 final ResultHandler handler = resultHandler; resultHandler = null; this.processor.getExecutor().execute(() -> handler.close(null)); } if (!super.closed.compareAndSet(false, true)) { return; } // 如果已经认证过了,并且没有在执行SQL,发送quit命令 if (isAuthenticated && !this.isRunning()) { write(writeToBuffer(CommandPacket.QUIT, allocate())); write(allocate()); } datasource.remove(this); processor.remove(this); super.cleanup();// 立即关闭物理socket if (Logger.isDebugEnabled()) { Logger.debug("connection {} closed due to {}", this, reason); } } @Override public void handle(byte[] data) { handler.handle(data); } @Override public void onConnectFailed(Throwable e) { this.error(ErrorCode.ERR_CONNECT_SOCKET, e); } @Override public void query(String sql, ResultHandler handler) { ResultHandler _handler = handler; if (handler instanceof SessionResultHandler) { Session session = ((SessionResultHandler) handler).session; Variables var = session.getVariables(); String sync = null; if (session instanceof TransactionSession) { sync = variables.isSavepointChecked() ? null : ((TransactionSession) session).getSavepoint(); if (sync != null) { _handler = new SyncHandler(_handler, sql, () -> variables.setSavepointChecked(true)); sql = sync; } } final int charsetIndex = var.getCharsetIndex(); sync = variables.getCharsetIndex() != charsetIndex ? Sync.getCharsetCommandStr(charsetIndex) : null; if (sync != null) { _handler = new SyncHandler(_handler, sql, () -> variables.setCharsetIndex(charsetIndex)); sql = sync; } final int isolationLevel = var.getIsolationLevel(); sync = variables.getIsolationLevel() != isolationLevel ? Sync.getTxIsolationCommandStr(isolationLevel) : null; if (sync != null) { _handler = new SyncHandler(_handler, sql, () -> variables.setIsolationLevel(isolationLevel)); sql = sync; } final boolean autocommit = var.isAutocommit(); sync = variables.isAutocommit() != autocommit ? Sync.getAutoCommitCommandStr(autocommit) : null; if (sync != null) { _handler = new SyncHandler(_handler, sql, () -> variables.setAutocommit(autocommit)); sql = sync; } } if (this.isClosed()) { this.setResultHandler(null); handler.close("backend connection already closed!"); return; } this.setResultHandler(_handler); this.setState(State.running); CommandPacket packet = new CommandPacket(CommandPacket.COM_QUERY); packet.arg = sql.getBytes(); packet.write(this); variables.update(); } public void setResultHandler(ResultHandler handler) { this.resultHandler = handler; } public ResultHandler getResultHandler() { return resultHandler; } @Override public void release() { resultHandler = null; datasource.release(this); } public void setAuthenticated(boolean isAuthenticated) { this.isAuthenticated = isAuthenticated; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public Source getDatasource() { return datasource; } public void setDatasource(Source datasource) { this.datasource = datasource; } public HandshakePacket getHandshake() { return handshake; } public void setHandshake(HandshakePacket handshake) { this.handshake = handshake; } @Override public long getThreadID() { return threadID; } public void setThreadID(long id) { this.threadID = id; } public int getDatanodeID() { return datanodeID; } }