/*
* Copyright 1999-2012 Alibaba 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.alibaba.cobar.jdbc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.cobar.jdbc.packet.AuthPacket;
import com.alibaba.cobar.jdbc.packet.BinaryPacket;
import com.alibaba.cobar.jdbc.packet.CommandPacket;
import com.alibaba.cobar.jdbc.packet.EOFPacket;
import com.alibaba.cobar.jdbc.packet.ErrorPacket;
import com.alibaba.cobar.jdbc.packet.HandshakePacket;
import com.alibaba.cobar.jdbc.packet.OkPacket;
import com.alibaba.cobar.jdbc.packet.QuitPacket;
import com.alibaba.cobar.jdbc.packet.Reply323Packet;
import com.alibaba.cobar.jdbc.packet.ResultSetHeaderPacket;
import com.alibaba.cobar.jdbc.packet.RowDataPacket;
import com.alibaba.cobar.jdbc.util.Capabilities;
import com.alibaba.cobar.jdbc.util.ErrorPacketException;
import com.alibaba.cobar.jdbc.util.SecurityUtil;
import com.alibaba.cobar.jdbc.util.UnknownPacketException;
/**
* @author xianmao.hexm 2012-4-27
*/
public class UrlConnection {
private static final int RECV_BUFFER_SIZE = 1024;
private static final int SEND_BUFFER_SIZE = 1024;
private static final int INPUT_STREAM_BUFFER = 1024;
private static final int OUTPUT_STREAM_BUFFER = 1024;
private static final long MAX_PACKET_SIZE = 1024 * 1024 * 16;
private static final long CLIENT_FLAGS = getClientFlags();
private static final CommandPacket CLUSTER_CMD = new CommandPacket();
static {
CLUSTER_CMD.packetId = 0;
CLUSTER_CMD.command = 3;
CLUSTER_CMD.arg = "SHOW COBAR_CLUSTER".getBytes();
}
private String host;
private int port;
private String user;
private String password;
private String database;
private Socket socket;
private InputStream in;
private OutputStream out;
private int charsetIndex;
private AtomicBoolean isClosed;
public UrlConnection(String host, int port, String user, String password, String database) {
this.host = host;
this.port = port;
this.user = user;
this.password = password;
this.database = database;
this.isClosed = new AtomicBoolean(false);
}
public void connect(int timeout) throws Exception {
// 网络IO参数设置
socket = new Socket();
socket.setTcpNoDelay(true);
socket.setKeepAlive(true);
socket.setReceiveBufferSize(RECV_BUFFER_SIZE);
socket.setSendBufferSize(SEND_BUFFER_SIZE);
socket.connect(new InetSocketAddress(host, port), timeout);
in = new BufferedInputStream(socket.getInputStream(), INPUT_STREAM_BUFFER);
out = new BufferedOutputStream(socket.getOutputStream(), OUTPUT_STREAM_BUFFER);
// 读取握手数据包
BinaryPacket initPacket = new BinaryPacket();
initPacket.read(in);
HandshakePacket hsp = new HandshakePacket();
hsp.read(initPacket);
// 设置连接参数
this.charsetIndex = hsp.serverCharsetIndex & 0xff;
// 发送认证数据包
BinaryPacket bin = null;
try {
bin = auth411(hsp);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
switch (bin.value[0]) {
case OkPacket.FIELD_COUNT:
break;
case ErrorPacket.FIELD_COUNT:
ErrorPacket err = new ErrorPacket();
err.read(bin);
throw new ErrorPacketException(new String(err.message));
case EOFPacket.FIELD_COUNT:
auth323(bin.packetId, hsp.seed);
break;
default:
throw new UnknownPacketException(bin.toString());
}
}
/**
* 取得集群有效服务器列表
*/
public List<CobarNode> getServerList() throws IOException {
// 发送命令数据包
CLUSTER_CMD.write(out);
out.flush();
// 处理响应结果集
int fieldCount = 0;
BinaryPacket bin = new BinaryPacket();
bin.read(in);
switch (bin.value[0]) {
case ErrorPacket.FIELD_COUNT: {
ErrorPacket err = new ErrorPacket();
err.read(bin);
throw new ErrorPacketException(new String(err.message));
}
default:
ResultSetHeaderPacket rsh = new ResultSetHeaderPacket();
rsh.read(bin);
fieldCount = rsh.fieldCount;
}
for (;;) {
bin = new BinaryPacket();
bin.read(in);
switch (bin.value[0]) {
case ErrorPacket.FIELD_COUNT: {
ErrorPacket err = new ErrorPacket();
err.read(bin);
throw new ErrorPacketException(new String(err.message));
}
case EOFPacket.FIELD_COUNT: {
return getRowList(fieldCount);
}
default:
continue;
}
}
}
/**
* 关闭连接
*/
public void close() {
if (isClosed.compareAndSet(false, true)) {
try {
if (out != null) {
out.write(QuitPacket.QUIT);
out.flush();
}
} catch (IOException e) {
//ignore log
} finally {
try {
socket.close();
} catch (Throwable e) {
//ignore log
}
}
}
}
private List<CobarNode> getRowList(int fieldCount) throws IOException {
List<CobarNode> list = new LinkedList<CobarNode>();
BinaryPacket bin = null;
for (;;) {
bin = new BinaryPacket();
bin.read(in);
switch (bin.value[0]) {
case ErrorPacket.FIELD_COUNT: {
ErrorPacket err = new ErrorPacket();
err.read(bin);
throw new ErrorPacketException(new String(err.message));
}
case EOFPacket.FIELD_COUNT: {
return list;
}
default:
RowDataPacket row = new RowDataPacket(fieldCount);
row.read(bin);
String host = new String(row.fieldValues.get(0));
int weight = Integer.parseInt(new String(row.fieldValues.get(1)));
list.add(new CobarNode(host, weight));
}
}
}
/**
* 411协议认证
*/
private BinaryPacket auth411(HandshakePacket hsp) throws IOException, NoSuchAlgorithmException {
AuthPacket ap = new AuthPacket();
ap.packetId = 1;
ap.clientFlags = CLIENT_FLAGS;
ap.maxPacketSize = MAX_PACKET_SIZE;
ap.charsetIndex = charsetIndex;
ap.user = user;
String passwd = password;
if (passwd != null && passwd.length() > 0) {
byte[] password = passwd.getBytes();
byte[] seed = hsp.seed;
byte[] restOfScramble = hsp.restOfScrambleBuff;
byte[] authSeed = new byte[seed.length + restOfScramble.length];
System.arraycopy(seed, 0, authSeed, 0, seed.length);
System.arraycopy(restOfScramble, 0, authSeed, seed.length, restOfScramble.length);
ap.password = SecurityUtil.scramble411(password, authSeed);
}
ap.database = database;
ap.write(out);
out.flush();
BinaryPacket bin = new BinaryPacket();
bin.read(in);
return bin;
}
/**
* 323协议认证
*/
private void auth323(byte packetId, byte[] seed) throws IOException {
Reply323Packet r323 = new Reply323Packet();
r323.packetId = ++packetId;
String passwd = password;
if (passwd != null && passwd.length() > 0) {
r323.seed = SecurityUtil.scramble323(passwd, new String(seed)).getBytes();
}
r323.write(out);
out.flush();
BinaryPacket bin = new BinaryPacket();
bin.read(in);
switch (bin.value[0]) {
case OkPacket.FIELD_COUNT:
break;
case ErrorPacket.FIELD_COUNT:
ErrorPacket err = new ErrorPacket();
err.read(bin);
throw new ErrorPacketException(new String(err.message));
default:
throw new UnknownPacketException(bin.toString());
}
}
private static long getClientFlags() {
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;
}
}