diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..86cb75e --- /dev/null +++ b/Makefile @@ -0,0 +1,205 @@ +CC = gcc +CXX = g++ +CFLAGS = -pipe -Wall -W -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -mtune=generic -fno-strict-aliasing +CXXFLAGS = -std=c++0x -pipe -Wall -W -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -mtune=generic -fno-strict-aliasing +INCPATH = -I./src +LINK = g++ +LFLAGS = +LIBS = $(SUBLIBS) /usr/local/lib/libevent.a /usr/local/lib/libevent_pthreads.a -lrt -pthread /usr/local/lib/libjemalloc.a +####### Output directory + +OBJECTS_DIR = tmp/ + +####### Files + +HEADERS = src/eventloop.h \ + src/util/logger.h \ + src/util/objectpool.h \ + src/util/vector.h \ + src/util/string.h \ + src/util/queue.h \ + src/util/hash.h \ + src/redisproto.h \ + src/redisproxy.h \ + src/redisservant.h \ + src/command.h \ + src/util/tcpsocket.h \ + src/util/tcpserver.h \ + src/util/thread.h \ + src/tinyxml/tinystr.h \ + src/tinyxml/tinyxml.h \ + src/redis-proxy-config.h \ + src/util/iobuffer.h \ + src/redis-servant-select.h \ + src/redisservantgroup.h \ + src/util/locker.h \ + src/monitor.h \ + src/top-key.h \ + src/non-portable.h \ + src/proxymanager.h \ + src/cmdhandler.h + +SOURCES = src/eventloop.cpp \ + src/util/logger.cpp \ + src/main.cpp \ + src/util/hash.cpp \ + src/redisproto.cpp \ + src/redisproxy.cpp \ + src/redisservant.cpp \ + src/command.cpp \ + src/util/tcpsocket.cpp \ + src/util/tcpserver.cpp \ + src/util/thread.cpp \ + src/tinyxml/tinystr.cpp \ + src/tinyxml/tinyxml.cpp \ + src/tinyxml/tinyxmlerror.cpp \ + src/tinyxml/tinyxmlparser.cpp \ + src/redis-proxy-config.cpp \ + src/util/iobuffer.cpp \ + src/redis-servant-select.cpp \ + src/redisservantgroup.cpp \ + src/util/locker.cpp \ + src/monitor.cpp \ + src/top-key.cpp \ + src/non-portable.cpp \ + src/cmdhandler.cpp + +OBJECTS = tmp/eventloop.o \ + tmp/logger.o \ + tmp/main.o \ + tmp/hash.o \ + tmp/redisproto.o \ + tmp/redisproxy.o \ + tmp/redisservant.o \ + tmp/command.o \ + tmp/tcpsocket.o \ + tmp/tcpserver.o \ + tmp/thread.o \ + tmp/tinystr.o \ + tmp/tinyxml.o \ + tmp/tinyxmlerror.o \ + tmp/tinyxmlparser.o \ + tmp/redis-proxy-config.o \ + tmp/iobuffer.o \ + tmp/redis-servant-select.o \ + tmp/redisservantgroup.o \ + tmp/locker.o \ + tmp/monitor.o \ + tmp/top-key.o \ + tmp/non-portable.o \ + tmp/proxymanager.o \ + tmp/cmdhandler.o + + +DESTDIR = +TARGET = onecache + +first: all +####### Implicit rules + +.SUFFIXES: .c .o .cpp .cc .cxx .C + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cc.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cxx.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.C.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.c.o: + $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $< + +####### Build rules + +all: Makefile $(TARGET) + +$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC) + $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(OBJCOMP) $(LIBS) + + +clean: + rm -f $(OBJECTS) + rm -f *.core + + +####### Compile + +tmp/eventloop.o: src/eventloop.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/eventloop.o src/eventloop.cpp + +tmp/logger.o: src/util/logger.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/logger.o src/util/logger.cpp + +tmp/main.o: src/main.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/main.o src/main.cpp + +tmp/hash.o: src/util/hash.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/hash.o src/util/hash.cpp + +tmp/redisproto.o: src/redisproto.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redisproto.o src/redisproto.cpp + +tmp/redisproxy.o: src/redisproxy.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redisproxy.o src/redisproxy.cpp + +tmp/redisservant.o: src/redisservant.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redisservant.o src/redisservant.cpp + +tmp/command.o: src/command.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/command.o src/command.cpp + +tmp/tcpsocket.o: src/util/tcpsocket.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tcpsocket.o src/util/tcpsocket.cpp + +tmp/tcpserver.o: src/util/tcpserver.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tcpserver.o src/util/tcpserver.cpp + +tmp/thread.o: src/util/thread.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/thread.o src/util/thread.cpp + +tmp/tinystr.o: src/tinyxml/tinystr.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tinystr.o src/tinyxml/tinystr.cpp + +tmp/tinyxml.o: src/tinyxml/tinyxml.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tinyxml.o src/tinyxml/tinyxml.cpp + +tmp/tinyxmlerror.o: src/tinyxml/tinyxmlerror.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tinyxmlerror.o src/tinyxml/tinyxmlerror.cpp + +tmp/tinyxmlparser.o: src/tinyxml/tinyxmlparser.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/tinyxmlparser.o src/tinyxml/tinyxmlparser.cpp + +tmp/redis-proxy-config.o: src/redis-proxy-config.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redis-proxy-config.o src/redis-proxy-config.cpp + +tmp/iobuffer.o: src/util/iobuffer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/iobuffer.o src/util/iobuffer.cpp + +tmp/redisservantgroup.o: src/redisservantgroup.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redisservantgroup.o src/redisservantgroup.cpp + +tmp/redis-servant-select.o: src/redis-servant-select.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/redis-servant-select.o src/redis-servant-select.cpp + +tmp/locker.o: src/util/locker.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/locker.o src/util/locker.cpp + +tmp/monitor.o: src/monitor.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/monitor.o src/monitor.cpp + +tmp/top-key.o: src/top-key.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/top-key.o src/top-key.cpp + +tmp/non-portable.o: src/non-portable.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/non-portable.o src/non-portable.cpp + +tmp/proxymanager.o: src/proxymanager.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/proxymanager.o src/proxymanager.cpp + +tmp/cmdhandler.o: src/cmdhandler.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tmp/cmdhandler.o src/cmdhandler.cpp diff --git a/src/cmdhandler.cpp b/src/cmdhandler.cpp new file mode 100644 index 0000000..d526694 --- /dev/null +++ b/src/cmdhandler.cpp @@ -0,0 +1,417 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "cmdhandler.h" +#include "redisproxy.h" +#include "redisservant.h" +#include "redis-proxy-config.h" + +struct MGetCommandContext +{ + int keyCount; + int returnCount; + ClientPacket* subs[1024]; + ClientPacket* packet; +}; + +struct MSetCommandContext +{ + int keyvalCount; + int succeedCount; + ClientPacket* packet; +}; + +struct DelCommandContext +{ + int keyCount; + int returnCount; + int integer; + ClientPacket* subs[1024]; + ClientPacket* packet; +}; + +void onGetPacketFinished(ClientPacket*, void* arg) +{ + MGetCommandContext* mgetcontext = (MGetCommandContext*)arg; + ++mgetcontext->returnCount; + if (mgetcontext->returnCount == mgetcontext->keyCount) { + for (int i = 0; i < mgetcontext->keyCount; ++i) { + mgetcontext->packet->sendBuff.append(mgetcontext->subs[i]->sendBuff); + delete mgetcontext->subs[i]; + } + mgetcontext->packet->setFinishedState(ClientPacket::RequestFinished); + delete mgetcontext; + } +} + +void onSetPacketFinished(ClientPacket* packet, void* arg) +{ + MSetCommandContext* msetcontext = (MSetCommandContext*)arg; + ++msetcontext->succeedCount; + if (msetcontext->succeedCount == msetcontext->keyvalCount) { + msetcontext->packet->sendBuff.append("+OK\r\n"); + msetcontext->packet->setFinishedState(ClientPacket::RequestFinished); + delete msetcontext; + } + delete packet; +} + +void onDelPacketFinished(ClientPacket* packet, void* arg) +{ + DelCommandContext* delcontext = (DelCommandContext*)arg; + ++delcontext->returnCount; + delcontext->integer += packet->sendParseResult.integer; + if (delcontext->returnCount == delcontext->keyCount) { + delcontext->packet->sendBuff.appendFormatString(":%d\r\n", delcontext->integer); + delcontext->packet->setFinishedState(ClientPacket::RequestFinished); + delete delcontext; + } + delete packet; +} + + +void onStandardKeyCommand(ClientPacket* packet, void*) +{ + char* key = packet->recvParseResult.tokens[1].s; + int len = packet->recvParseResult.tokens[1].len; + packet->proxy()->handleClientPacket(key, len, packet); +} + +void onMGetCommand(ClientPacket* packet, void*) +{ + RedisProtoParseResult& r = packet->recvParseResult; + int keyCount = r.tokenCount - 1; + if (keyCount <= 0) { + packet->setFinishedState(ClientPacket::WrongNumberOfArguments); + return; + } + + if (keyCount == 1) { + char* key = r.tokens[0].s; + int len = r.tokens[0].len; + packet->proxy()->handleClientPacket(key, len, packet); + } else { + MGetCommandContext* mgetcontext = new MGetCommandContext; + mgetcontext->keyCount = keyCount; + mgetcontext->returnCount = 0; + mgetcontext->packet = packet; + packet->sendBuff.appendFormatString("*%d\r\n", keyCount); + for (int i = 1; i < r.tokenCount; ++i) { + char* key = r.tokens[i].s; + int len = r.tokens[i].len; + + ClientPacket* get = new ClientPacket; + get->eventLoop = packet->eventLoop; + get->commandType = RedisCommand::GET; + get->finished_func = onGetPacketFinished; + get->finished_arg = mgetcontext; + get->recvBuff.appendFormatString("*2\r\n$3\r\nGET\r\n$%d\r\n", len); + get->recvBuff.append(key, len); + get->recvBuff.append("\r\n"); + mgetcontext->subs[i-1] = get; + get->parseRecvBuffer(); + packet->proxy()->handleClientPacket(key, len, get); + } + } +} + +void onMSetCommand(ClientPacket* packet, void*) +{ + RedisProtoParseResult& r = packet->recvParseResult; + int keyvalCount = (r.tokenCount - 1) / 2; + if (keyvalCount <= 0) { + packet->setFinishedState(ClientPacket::WrongNumberOfArguments); + return; + } + + if (keyvalCount == 1) { + char* key = r.tokens[0].s; + int len = r.tokens[0].len; + packet->proxy()->handleClientPacket(key, len, packet); + } else { + MSetCommandContext* msetcontext = new MSetCommandContext; + msetcontext->keyvalCount = keyvalCount; + msetcontext->succeedCount = 0; + msetcontext->packet = packet; + + for (int i = 1; i < r.tokenCount; i += 2) { + char* key = r.tokens[i].s; + int len = r.tokens[i].len; + + char* val = r.tokens[i+1].s; + int vallen = r.tokens[i+1].len; + + ClientPacket* set = new ClientPacket; + set->finished_func = onSetPacketFinished; + set->finished_arg = msetcontext; + set->eventLoop = packet->eventLoop; + set->commandType = RedisCommand::SET; + set->recvBuff.appendFormatString("*3\r\n$3\r\nSET\r\n$%d\r\n", len); + set->recvBuff.append(key, len); + set->recvBuff.append("\r\n"); + set->recvBuff.appendFormatString("$%d\r\n", vallen); + set->recvBuff.append(val, vallen); + set->recvBuff.append("\r\n"); + set->parseRecvBuffer(); + packet->proxy()->handleClientPacket(key, len, set); + } + } +} + +void onDelCommand(ClientPacket* packet, void*) +{ + RedisProtoParseResult& r = packet->recvParseResult; + int keyCount = r.tokenCount - 1; + if (keyCount <= 0) { + packet->setFinishedState(ClientPacket::WrongNumberOfArguments); + return; + } + if (keyCount == 1) { + char* key = r.tokens[0].s; + int len = r.tokens[0].len; + packet->proxy()->handleClientPacket(key, len, packet); + } else { + DelCommandContext* delcontext = new DelCommandContext; + delcontext->keyCount = keyCount; + delcontext->returnCount = 0; + delcontext->integer = 0; + delcontext->packet = packet; + for (int i = 1; i < r.tokenCount; ++i) { + char* key = r.tokens[i].s; + int len = r.tokens[i].len; + + ClientPacket* del = new ClientPacket; + del->eventLoop = packet->eventLoop; + del->finished_func = onDelPacketFinished; + del->finished_arg = delcontext; + del->commandType = RedisCommand::DEL; + del->eventLoop = packet->eventLoop; + del->recvBuff.appendFormatString("*2\r\n$3\r\nDEL\r\n$%d\r\n", len); + del->recvBuff.append(key, len); + del->recvBuff.append("\r\n"); + del->parseRecvBuffer(); + packet->proxy()->handleClientPacket(key, len, del); + } + } +} + +void onPingCommand(ClientPacket* packet, void*) +{ + packet->sendBuff.append("+PONG\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void onShowCommand(ClientPacket* packet, void*) +{ + IOBuffer& reply = packet->sendBuff; + reply.append("+ *** Support the command ***\n"); + reply.append("[Redis Command]\n"); + + IOBuffer redis; + IOBuffer oneCache; + + const std::list& seq = RedisCommandTable::instance()->commands(); + std::list::const_iterator it = seq.cbegin(); + for (; it != seq.cend(); ++it) { + RedisCommand* cmd = *it; + if (cmd->type >= 0 && cmd->type < RedisCommand::CMD_COUNT) { + redis.appendFormatString("%s ", cmd->name); + } else { + oneCache.appendFormatString("%s ", cmd->name); + } + } + + reply.append(redis); + reply.append("\n\n"); + reply.append("[OneCache Command]\n"); + reply.append(oneCache); + reply.append("\n\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void onHashMapping(ClientPacket* packet, void*) +{ + RedisProtoParseResult& request = packet->recvParseResult; + if (request.tokenCount != 3) { + packet->sendBuff.append("+Usage:\nHASHMAPPING [hash value] [group name]\n\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + + RedisProxy* proxy = packet->proxy(); + char hashValueStr[1024] = {0}; + char groupName[1024] = {0}; + strncpy(hashValueStr, request.tokens[1].s, request.tokens[1].len); + strncpy(groupName, request.tokens[2].s, request.tokens[2].len); + + int hashValue = atoi(hashValueStr); + RedisServantGroup* group = proxy->group(groupName); + if (!group) { + packet->sendBuff.append("-Group is not exists\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + + if (proxy->setGroupMappingValue(hashValue, group)) { + packet->sendBuff.append("+OK\r\n"); + CRedisProxyCfg::instance()->saveProxyLastState(proxy); + } else { + packet->sendBuff.append("-Invalid hash value\r\n"); + } + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void onAddKeyMapping(ClientPacket* packet, void*) +{ + RedisProtoParseResult& request = packet->recvParseResult; + if (request.tokenCount <= 2) { + packet->sendBuff.append("+Usage:\nADDKEYMAPPING [group name] [key1] [key2]...\n\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + + RedisProxy* proxy = packet->proxy(); + + char groupName[1024] = {0}; + strncpy(groupName, request.tokens[1].s, request.tokens[1].len); + RedisServantGroup* group = proxy->group(groupName); + if (!group) { + packet->sendBuff.append("-Group is not exists\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + + for (int i = 2; i < request.tokenCount; ++i) { + const char* key = request.tokens[i].s; + int keyLen = request.tokens[i].len; + proxy->addGroupKeyMapping(key, keyLen, group); + } + + packet->sendBuff.append("+OK\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + CRedisProxyCfg::instance()->saveProxyLastState(proxy); +} + +void onDelKeyMapping(ClientPacket* packet, void*) +{ + RedisProtoParseResult& request = packet->recvParseResult; + if (request.tokenCount <= 1) { + packet->sendBuff.append("+Usage:\nDELKEYMAPPING [key1] [key2]...\n\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + + RedisProxy* proxy = packet->proxy(); + for (int i = 1; i < request.tokenCount; ++i) { + const char* key = request.tokens[i].s; + int keyLen = request.tokens[i].len; + proxy->removeGroupKeyMapping(key, keyLen); + } + + packet->sendBuff.append("+OK\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + CRedisProxyCfg::instance()->saveProxyLastState(proxy); +} + +void onShowMapping(ClientPacket* packet, void*) +{ + RedisProxy* proxy = packet->proxy(); + packet->sendBuff.append("+\n"); + + packet->sendBuff.append("[HASH MAPPING]\n"); + packet->sendBuff.appendFormatString("%-15s %-15s\n", "HASH_VALUE", "GROUP_NAME"); + for (int i = 0; i < proxy->maxHashValue(); ++i) { + packet->sendBuff.appendFormatString("%-15d %-15s\n", + i, proxy->hashForGroup(i)->groupName()); + } + packet->sendBuff.append("\n"); + packet->sendBuff.append("[KEY MAPPING]\n"); + packet->sendBuff.appendFormatString("%-4s %-15s KEYS\n", "ID", "NAME"); + + StringMap& keyMapping = proxy->keyMapping(); + for (int j = 0; j < proxy->groupCount(); ++j) { + RedisServantGroup* group = proxy->group(j); + packet->sendBuff.appendFormatString("%-4d %-15s ", group->groupId(), group->groupName()); + + StringMap::iterator it = keyMapping.begin(); + for (; it != keyMapping.end(); ++it) { + if (it->second == group) { + String key = it->first; + packet->sendBuff.append(key.data(), key.length()); + packet->sendBuff.append(" "); + } + } + packet->sendBuff.append("\n"); + } + + packet->sendBuff.append("\n\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void onPoolInfo(ClientPacket* packet, void*) +{ + RedisProxy* proxy = packet->proxy(); + IOBuffer& sendbuf = packet->sendBuff; + sendbuf.append("+", 1); + sendbuf.appendFormatString("%-10s %-20s %-8s %-10s %-12s\n", + "GROUP", "HOST", "ACTIVE", "UNACTIVE", "POOLSIZE"); + for (int i = 0; i < proxy->groupCount(); ++i) { + RedisServantGroup* group = proxy->group(i); + for (int m = 0; m < group->masterCount(); ++m) { + RedisServant* servant = group->master(m); + RedisConnectionPool* pool = servant->connectionPool(); + char buf[64]; + sprintf(buf, "%s:%d", servant->redisAddress().ip(), servant->redisAddress().port()); + sendbuf.appendFormatString("%-10s %-20s %-8d %-10d %-12d\n", + group->groupName(), + buf, + pool->activeConnectionNums(), + pool->unActiveConnectionNums(), + pool->capacity()); + } + for (int s = 0; s < group->slaveCount(); ++s) { + RedisServant* servant = group->slave(s); + RedisConnectionPool* pool = servant->connectionPool(); + char buf[64]; + sprintf(buf, "%s:%d", servant->redisAddress().ip(), servant->redisAddress().port()); + sendbuf.appendFormatString("%-10s %-20s %-8d %-10d %-12d\n", + group->groupName(), + buf, + pool->activeConnectionNums(), + pool->unActiveConnectionNums(), + pool->capacity()); + } + } + sendbuf.append("\r\n", 2); + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void onShutDown(ClientPacket* packet, void*) +{ + RedisProtoParseResult& request = packet->recvParseResult; + if (request.tokenCount == 1) { + exit(0); + } else if (request.tokenCount == 2) { + if (strncasecmp(request.tokens[1].s, "FORCE", request.tokens[1].len) == 0) { + exit(APP_EXIT_KEY); + } else { + exit(0); + } + } +} diff --git a/src/cmdhandler.h b/src/cmdhandler.h new file mode 100644 index 0000000..4b318f2 --- /dev/null +++ b/src/cmdhandler.h @@ -0,0 +1,49 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef CMDHANDLER_H +#define CMDHANDLER_H + +#include "command.h" + +void onStandardKeyCommand(ClientPacket*, void*); + +void onDelCommand(ClientPacket*, void*); + +void onPingCommand(ClientPacket*, void*); + +void onShowCommand(ClientPacket*, void*); + +void onMGetCommand(ClientPacket*, void*); + +void onMSetCommand(ClientPacket*, void*); + +void onHashMapping(ClientPacket* packet, void*); + +void onAddKeyMapping(ClientPacket* packet, void*); + +void onDelKeyMapping(ClientPacket* packet, void*); + +void onShowMapping(ClientPacket* packet, void*); + +void onPoolInfo(ClientPacket* packet, void*); + +void onShutDown(ClientPacket* packet, void*); + +#endif diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 index 0000000..cdcd18b --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,185 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "cmdhandler.h" +#include "redisproxy.h" +#include "command.h" + +static RedisCommand _redisCommand[] = { + {"APPEND", 6, RedisCommand::APPEND, onStandardKeyCommand, NULL}, + {"BITCOUNT", 8, RedisCommand::BITCOUNT, onStandardKeyCommand, NULL}, + {"BITPOS", 6, RedisCommand::BITPOS, onStandardKeyCommand, NULL}, + {"DUMP", 4, RedisCommand::DUMP, onStandardKeyCommand, NULL}, + {"DEL", 3, RedisCommand::DEL, onDelCommand, NULL}, + {"DECR", 4, RedisCommand::DECR, onStandardKeyCommand, NULL}, + {"DECRBY", 6, RedisCommand::DECRBY, onStandardKeyCommand, NULL}, + {"EXPIREAT", 8, RedisCommand::EXPIREAT, onStandardKeyCommand, NULL}, + {"EXISTS", 6, RedisCommand::EXISTS, onStandardKeyCommand, NULL}, + {"EXPIRE", 6, RedisCommand::EXPIRE, onStandardKeyCommand, NULL}, + {"GET", 3, RedisCommand::GET, onStandardKeyCommand, NULL}, + {"GETBIT", 6, RedisCommand::GETBIT, onStandardKeyCommand, NULL}, + {"GETRANGE", 8, RedisCommand::GETRANGE, onStandardKeyCommand, NULL}, + {"GETSET", 6, RedisCommand::GETSET, onStandardKeyCommand, NULL}, + {"HSET", 4, RedisCommand::HSET, onStandardKeyCommand, NULL}, + {"HSETNX", 6, RedisCommand::HSETNX, onStandardKeyCommand, NULL}, + {"HMSET", 5, RedisCommand::HMSET, onStandardKeyCommand, NULL}, + {"HGET", 4, RedisCommand::HGET, onStandardKeyCommand, NULL}, + {"HMGET", 5, RedisCommand::HMGET, onStandardKeyCommand, NULL}, + {"HINCRBY", 7, RedisCommand::HINCRBY, onStandardKeyCommand, NULL}, + {"HEXISTS", 7, RedisCommand::HEXISTS, onStandardKeyCommand, NULL}, + {"HLEN", 4, RedisCommand::HLEN, onStandardKeyCommand, NULL}, + {"HDEL", 4, RedisCommand::HDEL, onStandardKeyCommand, NULL}, + {"HKEYS", 5, RedisCommand::HKEYS, onStandardKeyCommand, NULL}, + {"HVALS", 5, RedisCommand::HVALS, onStandardKeyCommand, NULL}, + {"HGETALL", 7, RedisCommand::HGETALL, onStandardKeyCommand, NULL}, + {"HINCRBYFLOAT", 12, RedisCommand::HINCRBYFLOAT, onStandardKeyCommand, NULL}, + {"INCR", 4, RedisCommand::INCR, onStandardKeyCommand, NULL}, + {"INCRBY", 6, RedisCommand::INCRBY, onStandardKeyCommand, NULL}, + {"INCRBYFLOAT", 11, RedisCommand::INCRBYFLOAT, onStandardKeyCommand, NULL}, + + {"LPUSH", 5, RedisCommand::LPUSH, onStandardKeyCommand, NULL}, + {"LPUSHX", 6, RedisCommand::LPUSHX, onStandardKeyCommand, NULL}, + {"LPOP", 4, RedisCommand::LPOP, onStandardKeyCommand, NULL}, + {"LRANGE", 6, RedisCommand::LRANGE, onStandardKeyCommand, NULL}, + {"LREM", 4, RedisCommand::LREM, onStandardKeyCommand, NULL}, + {"LINDEX", 5, RedisCommand::LINDEX, onStandardKeyCommand, NULL}, + {"LINSERT", 7, RedisCommand::LINSERT, onStandardKeyCommand, NULL}, + {"LLEN", 4, RedisCommand::LLEN, onStandardKeyCommand, NULL}, + {"LSET", 4, RedisCommand::LSET, onStandardKeyCommand, NULL}, + {"LTRIM", 5, RedisCommand::LTRIM, onStandardKeyCommand, NULL}, + + {"MGET", 4, RedisCommand::MGET, onMGetCommand, NULL}, + {"MSET", 4, RedisCommand::MSET, onMSetCommand, NULL}, + + {"PSETEX", 6, RedisCommand::PSETEX, onStandardKeyCommand, NULL}, + {"PERSIST", 7, RedisCommand::PERSIST, onStandardKeyCommand, NULL}, + {"PEXPIRE", 7, RedisCommand::PEXPIRE, onStandardKeyCommand, NULL}, + {"PEXPIREAT", 9, RedisCommand::PEXPIREAT, onStandardKeyCommand, NULL}, + {"PTTL", 4, RedisCommand::PTTL, onStandardKeyCommand, NULL}, + {"PING", 4, RedisCommand::PING, onPingCommand, NULL}, + + {"RESTORE", 7, RedisCommand::RESTORE, onStandardKeyCommand, NULL}, + {"RPOP", 4, RedisCommand::RPOP, onStandardKeyCommand, NULL}, + {"RPUSH", 5, RedisCommand::RPUSH, onStandardKeyCommand, NULL}, + {"RPUSHX", 6, RedisCommand::RPUSHX, onStandardKeyCommand, NULL}, + + {"SADD", 4, RedisCommand::SADD, onStandardKeyCommand, NULL}, + {"SMEMBERS", 8, RedisCommand::SMEMBERS, onStandardKeyCommand, NULL}, + {"SREM", 4, RedisCommand::SREM, onStandardKeyCommand, NULL}, + {"SPOP", 4, RedisCommand::SPOP, onStandardKeyCommand, NULL}, + {"SCARD", 5, RedisCommand::SCARD, onStandardKeyCommand, NULL}, + {"SISMEMBER", 9, RedisCommand::SISMEMBER, onStandardKeyCommand, NULL}, + {"SRANDMEMBER", 11, RedisCommand::SRANDMEMBER, onStandardKeyCommand, NULL}, + + {"SETBIT", 6, RedisCommand::SETBIT, onStandardKeyCommand, NULL}, + {"SETRANGE", 8, RedisCommand::SETRANGE, onStandardKeyCommand, NULL}, + {"STRLEN", 6, RedisCommand::STRLEN, onStandardKeyCommand, NULL}, + {"SET", 3, RedisCommand::SET, onStandardKeyCommand, NULL}, + {"SETEX", 5, RedisCommand::SETEX, onStandardKeyCommand, NULL}, + {"SETNX", 5, RedisCommand::SETNX, onStandardKeyCommand, NULL}, + + {"TTL", 3, RedisCommand::TTL, onStandardKeyCommand, NULL}, + {"TYPE", 4, RedisCommand::TYPE, onStandardKeyCommand, NULL}, + + {"ZADD", 4, RedisCommand::ZADD, onStandardKeyCommand, NULL}, + {"ZRANGE", 6, RedisCommand::ZRANGE, onStandardKeyCommand, NULL}, + {"ZREM", 4, RedisCommand::ZREM, onStandardKeyCommand, NULL}, + {"ZINCRBY", 7, RedisCommand::ZINCRBY, onStandardKeyCommand, NULL}, + {"ZRANK", 5, RedisCommand::ZRANK, onStandardKeyCommand, NULL}, + {"ZREVRANK", 8, RedisCommand::ZREVRANK, onStandardKeyCommand, NULL}, + {"ZREVRANGE", 9, RedisCommand::ZREVRANGE, onStandardKeyCommand, NULL}, + {"ZRANGEBYSCORE", 13, RedisCommand::ZRANGEBYSCORE, onStandardKeyCommand, NULL}, + {"ZCOUNT", 6, RedisCommand::ZCOUNT, onStandardKeyCommand, NULL}, + {"ZCARD", 5, RedisCommand::ZCARD, onStandardKeyCommand, NULL}, + {"ZREMRANGEBYRANK", 15, RedisCommand::ZREMRANGEBYRANK, onStandardKeyCommand, NULL}, + {"ZREMRANGEBYSCORE", 16, RedisCommand::ZREMRANGEBYSCORE, onStandardKeyCommand, NULL}, + + {"SHOWCMD", 7, -1, onShowCommand, NULL} +}; + +const char *RedisCommand::commandName(int type) +{ + if (type >= 0 && type < RedisCommand::CMD_COUNT) { + return _redisCommand[type].name; + } + return ""; +} + +RedisCommandTable::RedisCommandTable(void) +{ + registerCommand(_redisCommand, sizeof(_redisCommand) / sizeof(RedisCommand)); +} + +RedisCommandTable::~RedisCommandTable(void) +{ + std::list::iterator it = m_cmds.begin(); + for (; it != m_cmds.end(); ++it) { + delete *it; + } +} + +RedisCommandTable* RedisCommandTable::instance(void) +{ + static RedisCommandTable* table = NULL; + if (!table) { + table = new RedisCommandTable; + } + return table; +} + +int RedisCommandTable::registerCommand(RedisCommand *cmd, int cnt) +{ + int succeed = 0; + for (int i = 0; i < cnt; ++i) { + char* name = cmd[i].name; + int len = cmd[i].len; + + for (int j = 0; j < len; ++j) { + if (name[j] >= 'a' && name[j] <= 'z') { + name[j] += ('A' - 'a'); + } + } + + RedisCommand* val = new RedisCommand(cmd[i]); + m_cmds.push_back(val); + m_cmdMap.insert(StringMap::value_type(String(name, len, true), val)); + ++succeed; + } + return succeed; +} + +void RedisCommandTable::execCommand(char *cmd, int len, ClientPacket *packet) +{ + //to upper + for (int i = 0; i < len; ++i) { + if (cmd[i] >= 'a' && cmd[i] <= 'z') { + cmd[i] += ('A' - 'a'); + } + } + + StringMap::iterator it = m_cmdMap.find(String(cmd, len)); + if (it != m_cmdMap.end()) { + RedisCommand* command = it->second; + packet->commandType = command->type; + command->proc(packet, command->arg); + } else { + packet->setFinishedState(ClientPacket::ProtoNotSupport); + } +} diff --git a/src/command.h b/src/command.h new file mode 100644 index 0000000..9bb4bbb --- /dev/null +++ b/src/command.h @@ -0,0 +1,84 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef COMMAND_H +#define COMMAND_H + +#include + +#include "util/hash.h" + +class ClientPacket; +class RedisCommand +{ +public: + enum Type { + APPEND, + BITCOUNT, BITPOS, + DUMP,DEL, DECR, DECRBY, + EXPIREAT, EXISTS, EXPIRE, + GET, GETBIT, GETRANGE, GETSET, + HSET, HSETNX, HMSET, HGET, HMGET, HINCRBY, + HEXISTS, HLEN, HDEL, HKEYS, HVALS, HGETALL, HINCRBYFLOAT, + INCR, INCRBY, INCRBYFLOAT, + LPUSH, LPUSHX, LPOP, LRANGE, LREM, LINDEX, LINSERT, LLEN, LSET, LTRIM, + MGET, MSET, + PSETEX, PERSIST, PEXPIRE, PEXPIREAT, PTTL, PING, + RESTORE, RPOP, RPUSH, RPUSHX, + SADD, SMEMBERS, SREM, SPOP, SCARD, SISMEMBER, SRANDMEMBER, + SETBIT, SETRANGE, STRLEN, SET, SETEX, SETNX, + TTL, TYPE, + ZADD, ZRANGE, ZREM, ZINCRBY, ZRANK, ZREVRANK, ZREVRANGE, + ZRANGEBYSCORE, ZCOUNT, ZCARD, ZREMRANGEBYRANK, ZREMRANGEBYSCORE + }; + enum { + CMD_COUNT = ZREMRANGEBYSCORE+1 + }; + +public: + static const char* commandName(int type); + +public: + char name[32]; + int len; + int type; + void (*proc)(ClientPacket*, void*); + void* arg; +}; + +class RedisCommandTable +{ +private: + RedisCommandTable(void); + ~RedisCommandTable(void); + +public: + static RedisCommandTable* instance(void); + + int registerCommand(RedisCommand* cmd, int cnt); + void execCommand(char* cmd, int len, ClientPacket* packet); + + const std::list& commands(void) const { return m_cmds; } + +private: + StringMap m_cmdMap; + std::list m_cmds; +}; + +#endif diff --git a/src/eventloop.cpp b/src/eventloop.cpp new file mode 100644 index 0000000..5a4feaa --- /dev/null +++ b/src/eventloop.cpp @@ -0,0 +1,188 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "eventloop.h" + +Event::Event(void) +{ +} + +Event::~Event(void) +{ +} + +void Event::set(EventLoop *loop, evutil_socket_t sock, short flags, event_callback_fn fn, void *arg) +{ + event_assign(&m_event, loop->m_event_loop, sock, flags, fn, arg); +} + +void Event::setTimer(EventLoop *loop, event_callback_fn fn, void *arg) +{ + evtimer_assign(&m_event, loop->m_event_loop, fn, arg); +} + +void Event::active(int timeout) +{ + if (timeout != -1) { + timeval val; + val.tv_sec = (timeout / 1000); + val.tv_usec = (timeout - val.tv_sec * 1000) * 1000; + event_add(&m_event, &val); + } else { + event_add(&m_event, NULL); + } +} + +void Event::remove(void) +{ + event_del(&m_event); +} + + + +EventLoop::EventLoop(void) +{ + static bool b = false; + if (!b) { +#ifdef WIN32 + evthread_use_windows_threads(); +#else + evthread_use_pthreads(); +#endif + b = true; + } + m_event_loop = event_base_new(); +} + +EventLoop::~EventLoop(void) +{ + if (m_event_loop) { + event_base_free(m_event_loop); + } +} + +void EventLoop::exec(void) +{ + if (m_event_loop) { + if (event_base_dispatch(m_event_loop) < 0) { + Logger::log(Logger::Error, "EventLoop::exec: event_base_dispatch() failed"); + } + } +} + +void EventLoop::exit(int timeout) +{ + if (m_event_loop) { + timeval* p = NULL; + timeval val; + if (timeout != -1) { + val.tv_sec = (timeout / 1000); + val.tv_usec = (timeout - val.tv_sec * 1000) * 1000; + p = &val; + } + if (event_base_loopexit(m_event_loop, p) < 0) { + Logger::log(Logger::Error, "EventLoop::exit: event_base_loopexit() failed"); + } + } +} + + + +EventLoopThread::EventLoopThread(void) +{ +} + +EventLoopThread::~EventLoopThread(void) +{ +} + +void EventLoopThread::exit(void) +{ + m_timeout.remove(); + m_loop.exit(); +} + +void emptyCallBack(evutil_socket_t, short, void*) +{ +} + +void EventLoopThread::run(void) +{ + m_timeout.set(&m_loop, -1, EV_PERSIST | EV_TIMEOUT, emptyCallBack, this); + m_timeout.active(60000); + m_loop.exec(); +} + + +EventLoopThreadPool::EventLoopThreadPool(void) +{ + m_size = 0; + m_threads = NULL; +} + +EventLoopThreadPool::~EventLoopThreadPool(void) +{ + stop(); +} + +void EventLoopThreadPool::start(int size) +{ + stop(); + + m_size = size; + if (m_size <= 0 || m_size > MaxThreadCount) { + Logger::log(Logger::Warning, "EventLoopThreadPool::start: size out of range. use default size"); + m_size = DefaultThreadCount; + } + + Logger::log(Logger::Message, "Create the thread pool..."); + m_threads = new EventLoopThread[m_size]; + for (int i = 0; i < m_size; ++i) { + m_threads[i].start(); + } + + Thread::sleep(100); + Logger::log(Logger::Message, "Thread pool created. size: %d", m_size); +} + +void EventLoopThreadPool::stop(void) +{ + if (m_threads) { + Logger::log(Logger::Message, "Destroy thread pool..."); + for (int i = 0; i < m_size; ++i) { + m_threads[i].exit(); + } + delete []m_threads; + m_threads = NULL; + m_size = 0; + Logger::log(Logger::Message, "Thread pool destroyed"); + } +} + +EventLoopThread *EventLoopThreadPool::thread(int index) const +{ + if ((index >= 0) && (index < m_size)) { + return &m_threads[index]; + } + return NULL; +} + + + diff --git a/src/eventloop.h b/src/eventloop.h new file mode 100644 index 0000000..1b72e8f --- /dev/null +++ b/src/eventloop.h @@ -0,0 +1,114 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef EVENTLOOPPOOL_H +#define EVENTLOOPPOOL_H + +#include +#include +#include +#include + +#include "util/thread.h" + +class EventLoop; +class Event +{ +public: + Event(void); + ~Event(void); + + //Set event param + void set(EventLoop* loop, evutil_socket_t sock, short flags, event_callback_fn fn, void* arg); + + //Set timeout event + void setTimer(EventLoop* loop, event_callback_fn fn, void* arg); + + //Active + void active(int timeout_msec = -1); + + //Remove event from event loop + void remove(void); + +private: + event m_event; + friend class EventLoop; +}; + + +class EventLoop +{ +public: + EventLoop(void); + ~EventLoop(void); + + void exec(void); + void exit(int timeout = -1); + +private: + event_base* m_event_loop; + friend class Event; + EventLoop(const EventLoop&); + EventLoop& operator=(const EventLoop&); +}; + + +class EventLoopThread : public Thread +{ +public: + EventLoopThread(void); + ~EventLoopThread(void); + + EventLoop* eventLoop(void) { return &m_loop; } + void exit(void); + +protected: + virtual void run(void); + +public: + Event m_timeout; + EventLoop m_loop; +}; + + +class EventLoopThreadPool +{ +public: + enum { + DefaultThreadCount = 4, + MaxThreadCount = 32 + }; + + EventLoopThreadPool(void); + ~EventLoopThreadPool(void); + + void start(int size = DefaultThreadCount); + void stop(void); + + int size(void) const { return m_size; } + EventLoopThread* thread(int index) const; + +private: + int m_size; + EventLoopThread* m_threads; + EventLoopThreadPool(const EventLoopThreadPool&); + EventLoopThreadPool& operator=(const EventLoopThreadPool&); +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..502d339 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,196 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include + +#include "util/logger.h" +#include "redisproxy.h" +#include "non-portable.h" +#include "redis-proxy-config.h" + +#include "monitor.h" + +RedisProxy* currentProxy = NULL; +void handler(int) +{ + if (currentProxy) { + currentProxy->stop(); + } +} + +void setupSignal(void) +{ +#ifndef WIN32 + struct sigaction sig; + + sig.sa_handler = handler; + sigemptyset(&sig.sa_mask); + sig.sa_flags = 0; + + sigaction(SIGTERM, &sig, NULL); + sigaction(SIGINT, &sig, NULL); + + signal(SIGPIPE, SIG_IGN); +#endif +} + +void startOneCache(void) +{ + CRedisProxyCfg* cfg = CRedisProxyCfg::instance(); + + setupSignal(); + + RedisProxy proxy; + currentProxy = &proxy; + + EventLoopThreadPool pool; + pool.start(cfg->threadNum()); + proxy.setEventLoopThreadPool(&pool); + + FileLogger fileLogger; + if (fileLogger.setFileName(cfg->logFile())) { + Logger::log(Logger::Message, "Using the log file(%s) output", fileLogger.fileName()); + Logger::setDefaultLogger(&fileLogger); + } + + const SVipInfo* vipInfo = cfg->vipInfo(); + proxy.setVipName(vipInfo->if_alias_name); + proxy.setVipAddress(vipInfo->vip_address); + proxy.setVipEnabled(vipInfo->enable); + + const SHashInfo* sHashInfo = cfg->hashInfo(); + proxy.setMaxHashValue(sHashInfo->hash_value_max); + + const GroupOption* groupOption = cfg->groupOption(); + proxy.setGroupRetryTime(groupOption->group_retry_time); + proxy.setAutoEjectGroupEnabled(groupOption->auto_eject_group); + proxy.setEjectAfterRestoreEnabled(groupOption->eject_after_restore); + + for (int i = 0; i < cfg->groupCnt(); ++i) { + const CGroupInfo* info = cfg->group(i); + + RedisServantGroup* group = new RedisServantGroup; + RedisServantGroupPolicy* policy = RedisServantGroupPolicy::createPolicy(info->groupPolicy()); + group->setGroupName(info->groupName()); + group->setPolicy(policy); + + const HostInfoList& hostList = info->hosts(); + HostInfoList::const_iterator itHost = hostList.begin(); + for (; itHost != hostList.end(); ++itHost) { + const CHostInfo& hostInfo = (*itHost); + RedisServant* servant = new RedisServant; + RedisServant::Option opt; + strcpy(opt.name, hostInfo.get_hostName().c_str()); + opt.poolSize = hostInfo.get_connectionNum(); + opt.reconnInterval = groupOption->backend_retry_interval; + opt.maxReconnCount = groupOption->backend_retry_limit; + servant->setOption(opt); + servant->setRedisAddress(HostAddress(hostInfo.get_ip().c_str(), hostInfo.get_port())); + servant->setEventLoop(proxy.eventLoop()); + if (hostInfo.get_master()) { + group->addMasterRedisServant(servant); + } else { + group->addSlaveRedisServant(servant); + } + } + group->setEnabled(true); + proxy.addRedisGroup(group); + for (int i = info->hashMin(); i <= info->hashMax(); ++i) { + proxy.setGroupMappingValue(i, group); + } + + for (int i = 0; i < cfg->hashMapCnt(); ++i) { + const CHashMapping* mapping = cfg->hashMapping(i); + RedisServantGroup* group = proxy.group(mapping->group_name); + proxy.setGroupMappingValue(mapping->hash_value, group); + } + + for (int i = 0; i < cfg->keyMapCnt(); ++i) { + const CKeyMapping* mapping = cfg->keyMapping(i); + RedisServantGroup* group = proxy.group(mapping->group_name); + if (group != NULL) { + proxy.addGroupKeyMapping(mapping->key, strlen(mapping->key), group); + } + } + } + + CProxyMonitor monitor; + proxy.setMonitor(&monitor); + + int port = cfg->port(); + if (port <= 0) { + port = RedisProxy::DefaultPort; + } + proxy.run(HostAddress(port)); +} + +int main(int argc, char** argv) +{ + printf("%s %s, Copyright 2015 by OneXSoft\n\n", APP_NAME, APP_VERSION); + + const char* cfgFile = "onecache.xml"; + if (argc == 1) { + Logger::log(Logger::Message, "No config file specified. using the default config(%s)", cfgFile); + } if (argc >= 2) { + cfgFile = argv[1]; + } + + CRedisProxyCfg* cfg = CRedisProxyCfg::instance(); + if (!cfg->loadCfg(cfgFile)) { + Logger::log(Logger::Error, "Failed to read config file"); + return 1; + } + + const char* errInfo; + if (!CRedisProxyCfgChecker::isValid(cfg, errInfo)) { + Logger::log(Logger::Error, "Invalid configuration: %s", errInfo); + return 1; + } + + //Background + if (cfg->daemonize()) { + NonPortable::daemonize(); + } + + //Guard + if (cfg->guard()) { + NonPortable::guard(startOneCache, APP_EXIT_KEY); + } else { + startOneCache(); + } + + return 0; +} + + + + + + + + + + + + + + + + + diff --git a/src/monitor.cpp b/src/monitor.cpp new file mode 100644 index 0000000..1b1ed25 --- /dev/null +++ b/src/monitor.cpp @@ -0,0 +1,531 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "monitor.h" +#include + +#define STATUS "STATUS" +#define OUTPUTSTATUS "OUTPUTSTATUS" +#define TOPKEY "TOPKEY" +#define TOPVALUE "TOPVALUE" + +CCommandRecorder::CCommandRecorder() { + for (int i = 0; i < RedisCommand::CMD_COUNT; i++) { + m_cmdCnt[i] = 0LL; + } +} + +CCommandRecorder::~CCommandRecorder(){} + +void CCommandRecorder::addCnt(int commandType) { + if (commandType >= 0 && commandType <= RedisCommand::CMD_COUNT) + ++m_cmdCnt[commandType]; +} + +void CTimming::timmingBegin() { + begin_time = time(NULL); +} + +void CTimming::timmingEnd() { + end_time = time(NULL); +} + +char* CTimming::toString(time_t t) +{ + static char buf[50]; + memset(buf, 0, 50); + struct tm* current_time = localtime(&t); + sprintf(buf,"%d-%d-%d %02d:%02d:%02d", + current_time->tm_year + 1900, + current_time->tm_mon + 1, + current_time->tm_mday, + current_time->tm_hour, + current_time->tm_min, + current_time->tm_sec); + return buf; +} + +char* CTimming::secondToString(int sec) +{ + static char buf[50]; + memset(buf, 0, 50); + int hour,min; + hour = sec / 3600; + min = (sec % 3600) / 60; + sec = (sec % 3600) % 60; + sprintf(buf, "%02d:%02d:%02d\n", hour, min, sec); + return buf; +} + +time_t CTimming::beginTime() { + return begin_time; +} + +time_t CTimming::endTime() { + return end_time; +} + + +CByteCounter::CByteCounter() { + for (int i = 0; i < 1024; i++) { + gb_array[i] = 0; + mb_array[i] = 0; + kb_array[i] = 0; + } +} + +CByteCounter::~CByteCounter() {} + +void CByteCounter::addBytes(int bytes) { + if (bytes >= GBSIZE) { + ++gb_array[bytes / GBSIZE]; + } else if (bytes >= MBSIZE) { + ++mb_array[bytes / MBSIZE]; + } else if (bytes >= KBSIZE) { + ++kb_array[bytes / KBSIZE]; + } +} + +int CByteCounter::queryKBRange(int left, int right) +{ + int cnt = 0; + for (int i = left; i <= right; ++i) { + cnt += kb_array[i]; + } + return cnt; +} + +int CByteCounter::queryMBRange(int left, int right) +{ + int cnt = 0; + for (int i = left; i <= right; ++i) { + cnt += mb_array[i]; + } + return cnt; +} + +int CByteCounter::queryGBRange(int left, int right) +{ + int cnt = 0; + for (int i = left; i <= right; ++i) { + cnt += gb_array[i]; + } + return cnt; +} + + +SClientRecorder::SClientRecorder() { + request_num = 0; + connect_num = 0; +} + +SClientRecorder::~SClientRecorder() {} + + +CRedisRecorder::~CRedisRecorder() {} + +CRedisRecorder::CRedisRecorder() { + m_active = false; + m_AllRequestTimes = 0LL; + m_requestSuccTimes = 0LL; + m_requestFailTimes = 0LL; + m_allReplySize = 0LL; + m_allRequestSize = 0LL; +} + +CProxyMonitor::CProxyMonitor(){ + m_topKeyEnable = false; +} + +CProxyMonitor::~CProxyMonitor(){} + + +void statusProc(ClientPacket* packet, void* arg) { + IOBuffer& iobuffer = packet->sendBuff; + CProxyMonitor* monitor = (CProxyMonitor*)arg; + iobuffer.append("+"); + CFormatMonitorToIoBuf c(&iobuffer); + CShowMonitor::showMonitorToIobuf(c, *monitor); + + iobuffer.append("\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + +void outPutStatusProc(ClientPacket* packet, void* arg) { + IOBuffer& iobuffer = packet->sendBuff; + CProxyMonitor* monitor = (CProxyMonitor*)arg; + CFormatMonitorToIoBuf c(&iobuffer); + if (!CShowMonitor::showMonitorToFile("onecache_monitor.log", "w", c, *monitor)) { + iobuffer.clear(); + iobuffer.append("+load file failed\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return ; + } + iobuffer.clear(); + iobuffer.append("+output file ok\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + + +void topValueProc(ClientPacket* packet, void* arg) { + IOBuffer& iobuffer = packet->sendBuff; + CProxyMonitor* monitor = (CProxyMonitor*)arg; + iobuffer.append("+"); + RedisProtoParseResult& requestParseResult = packet->recvParseResult; + if (requestParseResult.tokenCount < 2) { + // default + CFormatMonitorToIoBuf c(&iobuffer ,20); + c.formatTopValueToIoBuf(*monitor); + iobuffer.append("\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + Token& token = requestParseResult.tokens[1]; + char buf[20]; + strncpy(buf, token.s, token.len); + int topNum = atoi(buf); + CFormatMonitorToIoBuf c(&iobuffer ,topNum); + c.formatTopValueToIoBuf(*monitor); + iobuffer.append("\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + + +void topKeyProc(ClientPacket* packet, void* arg) { + IOBuffer& iobuffer = packet->sendBuff; + CProxyMonitor* monitor = (CProxyMonitor*)arg; + iobuffer.append("+"); + RedisProtoParseResult& requestParseResult = packet->recvParseResult; + if (requestParseResult.tokenCount < 2) { + // default + CFormatMonitorToIoBuf c(&iobuffer ,20); + c.formatTopKeyToIoBuf(*monitor); + iobuffer.append("\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); + return; + } + Token& token = requestParseResult.tokens[1]; + char buf[20]; + strncpy(buf, token.s, token.len); + int topNum = atoi(buf); + CFormatMonitorToIoBuf c(&iobuffer ,topNum); + c.formatTopKeyToIoBuf(*monitor); + iobuffer.append("\r\n"); + packet->setFinishedState(ClientPacket::RequestFinished); +} + + +void CProxyMonitor::proxyStarted(RedisProxy* proxy) { + m_proxyBeginTime.timmingBegin(); + m_redisProxy = proxy; + + // Register command + RedisCommand status; + strcpy(status.name, STATUS); + status.len = strlen(STATUS); + status.type = -1; + status.proc = statusProc; + status.arg = this; + RedisCommandTable::instance()->registerCommand(&status, 1); + + RedisCommand outPutStatus; + strcpy(outPutStatus.name, OUTPUTSTATUS); + outPutStatus.len = strlen(OUTPUTSTATUS); + outPutStatus.type = -1; + outPutStatus.proc = outPutStatusProc; + outPutStatus.arg = this; + RedisCommandTable::instance()->registerCommand(&outPutStatus, 1); + + // top key + RedisCommand topKey; + strcpy(topKey.name, TOPKEY); + topKey.len = strlen(TOPKEY); + topKey.type = -1; + topKey.proc = topKeyProc; + topKey.arg = this; + RedisCommandTable::instance()->registerCommand(&topKey, 1); + + // top value + RedisCommand topValue; + strcpy(topValue.name, TOPVALUE); + topValue.len = strlen(TOPVALUE); + topValue.type = -1; + topValue.proc = topValueProc; + topValue.arg = this; + RedisCommandTable::instance()->registerCommand(&topValue, 1); + + m_topKeyEnable = CRedisProxyCfg::instance()->topKeyEnable(); + if (m_topKeyEnable) { + m_topKeyRecorderThread.start(); + } +} + +char* CIpUtil::int2ipstr (int ip) { + static char buf[50]; + sprintf (buf, "%u.%u.%u.%u", + (unsigned char) * ((char *) &ip + 0), + (unsigned char )* ((char *) &ip + 1), + (unsigned char )* ((char *) &ip + 2), + (unsigned char) * ((char *) &ip + 3)); + return buf; +} + +void CProxyMonitor::clientConnected(ClientPacket* packet) { + int ip = packet->clientAddress._sockaddr()->sin_addr.s_addr; + SClientRecorder& recorder = m_clientRecMap[ip]; + recorder.m_clientIp = ip; + ++recorder.connect_num; + recorder.connectInfo.timmingBegin(); +} + +void CProxyMonitor::clientDisconnected(ClientPacket* ) { +} + + +void CProxyMonitor::replyClientFinished(ClientPacket* packet) { + int replySize = packet->sendBuff.size(); + if (m_topKeyEnable) { + KeyStrValueSize keyInfo; + char* key = packet->recvParseResult.tokens[1].s; + int len = packet->recvParseResult.tokens[1].len; + if (len > 0) { + keyInfo.key = new char[len + 1]; + strncpy(keyInfo.key, key, len); + keyInfo.key[len] = '\0'; + keyInfo.valueSize = replySize; + m_topKeyLock.lock(); + m_topKeyRecorderThread.keyToList(keyInfo); // 2015/9/18 10:28:46 + m_topKeyLock.unlock(); + } + } + + int ip = packet->clientAddress._sockaddr()->sin_addr.s_addr; + // add client info + m_clientLock.lock(); + SClientRecorder& clientRecorder = m_clientRecMap[ip]; + ++clientRecorder.request_num; + clientRecorder.m_clientIp = ip; + clientRecorder.commandRecorder.addCnt(packet->commandType); + clientRecorder.valueInfo.addBytes(replySize); + m_clientLock.unlock(); + + RedisServant* pServant = packet->requestServant; + if (pServant == NULL) { + return; + } + + // add backend info + m_backendLock.lock(); + CRedisRecorder& redisRecorder = m_redisRecMap[pServant]; + redisRecorder.valueInfo.addBytes(replySize); + ++redisRecorder.m_AllRequestTimes; + redisRecorder.addAllReplySize(replySize); + redisRecorder.addAllRequestSize(packet->recvBuff.size()); + redisRecorder.commandRecorder.addCnt(packet->commandType); + m_backendLock.unlock(); +} + + + +CFormatMonitorToIoBuf::CFormatMonitorToIoBuf(IOBuffer* pIobuf, int topKeyCnt) { + m_iobuf = pIobuf; + m_topKeyCnt = topKeyCnt; +} + +void CFormatMonitorToIoBuf::formatOneServant( + const char* groupName, + const char* str, + CProxyMonitor::RedisRecorderMap* mapRedisRec, + RedisServant* servant) +{ + m_iobuf->appendFormatString("%-10s%17s",groupName, servant->redisAddress().ip()); + m_iobuf->appendFormatString("%10d%11d%6s%8s", + servant->redisAddress().port(), + servant->option().poolSize, + str, servant->isActived()?"Y":"N"); + + CProxyMonitor::RedisRecorderMap::iterator itMap = mapRedisRec->find(servant); + if (itMap != mapRedisRec->end()) { + m_iobuf->appendFormatString("%20lld", itMap->second.allRequestTimes()); + if (itMap->second.allRequestSize() > (1024*1024*1024)) { + m_iobuf->appendFormatString("%17.2fGB", itMap->second.allRequestSize()/(1024*1024*(1024.0))); + } + else if (itMap->second.allRequestSize() > (1024*1024)) { + m_iobuf->appendFormatString("%17.2fMB", itMap->second.allRequestSize()/(1024*(1024.0))); + } + else { + m_iobuf->appendFormatString("%17.2fKB", itMap->second.allRequestSize()/(1024.0)); + } + if (itMap->second.allReplySize() > (1024*1024*1024)) { + m_iobuf->appendFormatString("%16.2fGB", itMap->second.allReplySize()/(1024*1024*(1024.0))); + } + else if (itMap->second.allReplySize() > (1024*1024)) { + m_iobuf->appendFormatString("%16.2fMB", itMap->second.allReplySize()/(1024*(1024.0))); + } + else { + m_iobuf->appendFormatString("%16.2fKB", itMap->second.allReplySize()/(1024.0)); + } + CByteCounter& valueInfo = itMap->second.valueInfo; + m_iobuf->appendFormatString("%14d", valueInfo.queryKBRange(1, 1023)); + m_iobuf->appendFormatString("%12d", valueInfo.queryMBRange(1, 1023)); + bool bSet = false; + for (int i = 0; i < RedisCommand::CMD_COUNT; i++) { + unsigned long long cnt = itMap->second.commandRecorder.commandCount(i); + if (cnt > 0) { + m_iobuf->appendFormatString(" [%s]:%ld ", RedisCommand::commandName(i), cnt); + bSet = true; + } + } + if (!bSet) { + m_iobuf->appendFormatString("%7s","NULL\n"); + } + m_iobuf->append("\n"); + return; + } else { + m_iobuf->appendFormatString("%20d%17dKB%16dKB%14d%12d%7s\n",0, 0, 0, 0, 0,"NULL"); + return; + } +} + +void CFormatMonitorToIoBuf::formatTopValueToIoBuf(CProxyMonitor& proxyMonirot) { + CTopKeyRecorderThread* topKeyRec = proxyMonirot.topKeyRecorder(); + m_topKeySorter.sortTopKeyByValueSize(*topKeyRec); + m_iobuf->appendFormatString("%-120s%s\n","[KEY]", "[VALUESIZE]"); + + vector >::iterator it = m_topKeySorter.m_sortVectorKeyCnt->begin(); + int i = 0; + for (; it != m_topKeySorter.m_sortVectorKeyCnt->end(); ++it) { + if (i++ < m_topKeyCnt) { + unsigned long valueSize = (it->second)->valueSize; + if (valueSize > (1024*1024*1024)) { + m_iobuf->appendFormatString("%-120s%-0.2fGB\n", it->first, valueSize/(1024*1024*(1024.0))); + } + else if (valueSize > (1024*1024)) { + m_iobuf->appendFormatString("%-120s%-0.3fMB\n", it->first, valueSize/(1024*(1024.0))); + } + else { + m_iobuf->appendFormatString("%-120s%-0.3fKB\n", it->first, valueSize/(1024.0)); + } + } + } +} + +void CFormatMonitorToIoBuf::formatTopKeyToIoBuf(CProxyMonitor& proxyMonirot) { + CTopKeyRecorderThread* topKeyRec = proxyMonirot.topKeyRecorder(); + m_topKeySorter.sortTopKeyByCnt(*topKeyRec); + m_iobuf->appendFormatString("%-120s%s\n","[KEY]", "[COUNT]"); + + vector >::iterator it = m_topKeySorter.m_sortVectorKeyCnt->begin(); + int i = 0; + for (; it != m_topKeySorter.m_sortVectorKeyCnt->end(); ++it) { + if (i++ < m_topKeyCnt) { + m_iobuf->appendFormatString("%-120s%-17d\n", it->first, (it->second)->keyCnt); + } + } +} + + +void CFormatMonitorToIoBuf::formatServants(CProxyMonitor& proxyMonirot, RedisServantGroup* group) { + CProxyMonitor::RedisRecorderMap* mapRedisRec = proxyMonirot.redisRecortMap(); + const char* groupName = group->groupName(); + for (int i = 0; i < group->masterCount(); i++) { + formatOneServant(groupName, "Y", mapRedisRec, group->master(i)); + } + for (int i = 0; i < group->slaveCount(); i++) { + formatOneServant(groupName, "N", mapRedisRec, group->slave(i)); + } +} + + +void CFormatMonitorToIoBuf::formatProxyToIoBuf(CProxyMonitor& proxyMonirot) { + m_iobuf->append("[OneCache]\n"); + m_iobuf->appendFormatString("Version=%s\n", APP_VERSION); + m_iobuf->appendFormatString("Port=%d\n",proxyMonirot.redisProxy()->address().port()); + m_iobuf->appendFormatString("StartTime=%s\n", CTimming::toString(proxyMonirot.proxyBeginTime())); + time_t now = time(NULL); + time_t upTime = now - proxyMonirot.proxyBeginTime(); + m_iobuf->appendFormatString("UpTime=%s", CTimming::secondToString((int)upTime)); + RedisProxy* proxy = proxyMonirot.redisProxy(); + m_iobuf->appendFormatString("GroupCount=%d\n", proxy->groupCount()); + m_iobuf->appendFormatString("VipEnabled=%s\n", proxy->vipEnabled()?"Yes":"No"); + m_iobuf->appendFormatString("TopKeyEnabled=%s\n", CRedisProxyCfg::instance()->topKeyEnable()?"Yes":"No"); + if (proxyMonirot.redisProxy()->vipEnabled()) { + m_iobuf->appendFormatString("VipName=%s\n", proxy->vipName()); + m_iobuf->appendFormatString("VipAddress=%s\n", proxy->vipAddress()); + } + m_iobuf->append("\n[Backends]\n"); + int groupCnt = proxy->groupCount(); + m_iobuf->append("[GROUP] [IP] [PORT] [CONNPOOL] [MASTER] [ACTIVE] [REQUESTS] [RECV SIZE] [SEND SIZE] [SEND>1KB] [SEND>1MB] [COMMANDS]\n"); + for (int i = 0; i < groupCnt; i++) { + RedisServantGroup* group = proxy->group(i); + formatServants(proxyMonirot, group); + } + m_iobuf->append("\n"); +} + +void CFormatMonitorToIoBuf::formatClientsToIoBuf(CProxyMonitor& proxyMonirot) { + CProxyMonitor::ClientRecorderMap* cliRecMap = proxyMonirot.clientRecordMap(); + CProxyMonitor::ClientRecorderMap::iterator itCliMap = cliRecMap->begin(); + m_iobuf->append("[Clients]\n[NUM] [IP] [CONNECTS] [REQUESTS] [RECV>1KB] [RECV>1MB] [LAST CONNECT] [COMMANDS]\n"); + for (long i = 1; itCliMap != cliRecMap->end(); ++itCliMap, ++i) { + SClientRecorder& client = (itCliMap)->second; + m_iobuf->appendFormatString("%-8ld%16s%13ld%12ld %11d %11d %19s", + i, + CIpUtil::int2ipstr(client.clientIp()), + client.connectNum(), + client.requestNum(), + client.valueInfo.queryKBRange(1, 1023), + client.valueInfo.queryMBRange(1, 1023), + CTimming::toString(client.connectInfo.beginTime())); + bool bSet = false; + for (int i = 0; i < RedisCommand::CMD_COUNT; i++) { + unsigned long long cnt = client.commandRecorder.commandCount(i); + if (cnt > 0) { + m_iobuf->appendFormatString(" [%s]:%ld ", RedisCommand::commandName(i), cnt); + bSet = true; + } + } + if (!bSet) { + m_iobuf->appendFormatString("%8s","NULL"); + } + m_iobuf->append("\n"); + } +} + +void CShowMonitor::showMonitorToIobuf(CFormatMonitorToIoBuf& formatMonitor,CProxyMonitor& monitor) { + formatMonitor.formatProxyToIoBuf(monitor); + formatMonitor.formatClientsToIoBuf(monitor); +} + +bool CShowMonitor::showMonitorToFile( + const char* path, + const char* mode, + CFormatMonitorToIoBuf& formatMonitor, + CProxyMonitor& monitor) { + if ((formatMonitor.m_pfile = CFileOperate::openFile(path, mode))==NULL) { + return false; + } + formatMonitor.formatProxyToIoBuf(monitor); + formatMonitor.formatClientsToIoBuf(monitor); + formatMonitor.m_iobuf->append("\0", 1); + CFileOperate::formatString2File(formatMonitor.m_iobuf->data(), formatMonitor.m_pfile); + fclose(formatMonitor.m_pfile); + return true; +} + diff --git a/src/monitor.h b/src/monitor.h new file mode 100644 index 0000000..183ae7b --- /dev/null +++ b/src/monitor.h @@ -0,0 +1,203 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef ONE_PROXY_CACHE_MONITOR +#define ONE_PROXY_CACHE_MONITOR + +#include +#include +#include + +#include "redisservant.h" +#include "redisproxy.h" +#include "top-key.h" +#include "redis-proxy-config.h" + +class CCommandRecorder { +public: + CCommandRecorder(); + ~CCommandRecorder(); + inline void addCnt(int commandType); + inline unsigned long long commandCount(int cmdType) const + { return m_cmdCnt[cmdType]; } + + void reset(){ } +private: + unsigned long long m_cmdCnt[RedisCommand::CMD_COUNT]; +}; + + +class CByteCounter +{ +public: + enum { + KBSIZE = 1024, + MBSIZE = 1048576, + GBSIZE = 1073741824 + }; + CByteCounter(); + ~CByteCounter(); + + void addBytes(int bytes); + int queryKBRange(int left, int right); + int queryMBRange(int left, int right); + int queryGBRange(int left, int right); +private: + int gb_array[1024]; + int mb_array[1024]; + int kb_array[1024]; +}; + + +class CTimming { +public: + void timmingBegin(); + void timmingEnd(); + static char* toString(time_t t); + static char* secondToString(int sec); + time_t beginTime(); + time_t endTime(); +private: + time_t begin_time; + time_t end_time; +}; + + +class CIpUtil { +public: + static char* int2ipstr (int ip); +}; + +class SClientRecorder +{ +public: + SClientRecorder(); + ~SClientRecorder(); + void reset(){} + + inline int clientIp() {return m_clientIp;} + inline long requestNum() {return request_num;} + inline long connectNum() {return connect_num;} +public: + int m_clientIp; + long request_num; + long connect_num; + CCommandRecorder commandRecorder; + CByteCounter valueInfo; + CTimming connectInfo; + friend class CProxyMonitor; +}; + +class CRedisRecorder +{ +public: + CRedisRecorder(); + ~CRedisRecorder(); + void reset(){} + inline unsigned long long allRequestTimes(){ return m_AllRequestTimes; } + inline unsigned long long requestSuccTimes(){ return m_requestSuccTimes; } + inline unsigned long long requestFailTimes(){ return m_requestFailTimes; } + inline unsigned long long allRequestSize(){ return m_allRequestSize; } + inline unsigned long long allReplySize(){ return m_allReplySize; } + + inline void addAllReplySize(unsigned long size) { m_allReplySize += size;} + inline void addAllRequestSize(unsigned long size) { m_allRequestSize += size;} + +public: + bool m_active; + unsigned long long m_AllRequestTimes; + unsigned long long m_requestSuccTimes; + unsigned long long m_requestFailTimes; + unsigned long long m_allRequestSize; + unsigned long long m_allReplySize; + CCommandRecorder commandRecorder; + CByteCounter valueInfo; +}; + +class CProxyMonitor : public Monitor +{ +public: + CProxyMonitor(); + ~CProxyMonitor(); + typedef std::map RedisRecorderMap; + typedef std::map ClientRecorderMap; + + virtual void proxyStarted(RedisProxy*); + virtual void clientConnected(ClientPacket*); + virtual void clientDisconnected(ClientPacket*); + virtual void replyClientFinished(ClientPacket*); +public: + time_t proxyBeginTime() { return m_proxyBeginTime.beginTime(); } + RedisRecorderMap* redisRecortMap() { return &m_redisRecMap; } + ClientRecorderMap* clientRecordMap() { return &m_clientRecMap; } + RedisProxy* redisProxy() { return m_redisProxy; } + CTopKeyRecorderThread* topKeyRecorder() { return &m_topKeyRecorderThread; } +public: + bool m_topKeyEnable; +private: + // for client + ClientRecorderMap m_clientRecMap; + // for redis backend + RedisRecorderMap m_redisRecMap; + // for proxy self + CTimming m_proxyBeginTime; + RedisProxy* m_redisProxy; + SpinLocker m_clientLock; + SpinLocker m_backendLock; + SpinLocker m_topKeyLock; + + CTopKeyRecorderThread m_topKeyRecorderThread; +}; + + +class CFileOperate { +public: + static FILE* openFile(const char* path, const char* mode) { + return fopen(path, mode); + } + static void formatString2File(const char* string, FILE* f){ fprintf(f,"%s",string); } +}; + +class CFormatMonitorToIoBuf { +public: + CFormatMonitorToIoBuf(IOBuffer* pIobuf, int topKeyCnt = 20); + ~CFormatMonitorToIoBuf(){} + void formatProxyToIoBuf(CProxyMonitor& proxyMonirot); + void formatClientsToIoBuf(CProxyMonitor& proxyMonirot); + + void formatTopKeyToIoBuf(CProxyMonitor& proxyMonirot); + void formatTopValueToIoBuf(CProxyMonitor& proxyMonirot); + + IOBuffer* m_iobuf; + FILE* m_pfile; + CTopKeySorter m_topKeySorter; + int m_topKeyCnt; +private: + void formatServants(CProxyMonitor& m, RedisServantGroup* p); + void formatOneServant(const char* p, const char* str, CProxyMonitor::RedisRecorderMap* mapRedisRec, RedisServant* servant); +}; + +class CShowMonitor { +public: + static void showMonitorToIobuf(CFormatMonitorToIoBuf& formatMonitor, CProxyMonitor& monitor); + static bool showMonitorToFile(const char* p, const char* mode, CFormatMonitorToIoBuf& f, CProxyMonitor& m); +}; + + +#endif diff --git a/src/non-portable.cpp b/src/non-portable.cpp new file mode 100644 index 0000000..15fb7a3 --- /dev/null +++ b/src/non-portable.cpp @@ -0,0 +1,371 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "non-portable.h" + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct ARP_PACKET +{ + unsigned char dest_mac[6]; + unsigned char src_mac[6]; + unsigned short type; + unsigned short hw_type; + unsigned short pro_type; + unsigned char hw_len; + unsigned char pro_len; + unsigned short op; + unsigned char from_mac[6]; + unsigned char from_ip[4]; + unsigned char to_mac[6]; + unsigned char to_ip[4]; +}; + +int send_arp_pack(int ifndx, char *address, char *mac) +{ + int err=0; + int sfd = 0; + char buf[256]; + struct ARP_PACKET *ah = (struct ARP_PACKET *)buf; + struct sockaddr_ll dest; + int dlen = sizeof(dest); + + memset(buf,0,256); + sfd = socket(PF_PACKET,SOCK_RAW, htons(ETH_P_RARP)); + if (sfd >= 0) + { + memset(&dest,0, sizeof(dest)); + dest.sll_family = AF_PACKET; + dest.sll_halen = ETH_ALEN; + dest.sll_ifindex = ifndx; + memcpy(dest.sll_addr, mac, ETH_ALEN); + + ah->type = htons(ETHERTYPE_ARP); + memset(ah->dest_mac, 0xff, 6); + memcpy(ah->src_mac, mac, 6); + ah->hw_type = htons(ARPHRD_ETHER); + ah->pro_type = htons(ETH_P_IP); + ah->hw_len = ETH_ALEN; + ah->pro_len = 4; + ah->op = htons(ARPOP_REPLY); + memcpy(ah->from_mac, mac, 6); + memcpy(ah->from_ip, address, 4); + memset(ah->to_ip, 0x00, 4); + memset(ah->to_mac, 0xff, 6); + + err = sendto(sfd, buf, sizeof(*ah), 0, (struct sockaddr *)&dest, dlen); + close(sfd); + if (err == sizeof(*ah)) return 1; + } + return 0; +} + +int NonPortable::setVipAddress(const char *ifname, const char *address, int isdel) +{ + int i = 0; + int err = 0; + int sfd = 0; + struct ifreq ifr; + char arpcmd[512]; + + int addr_mask = 0; + struct sockaddr_in brdaddr; + struct sockaddr_in netmask; + + int ifindex = 0; + struct sockaddr_in ipaddr; + struct sockaddr macaddr; + + struct sockaddr_in *sin = NULL; + + sfd = socket (AF_INET, SOCK_STREAM, 0); + if (sfd < 0) return 0; + + i=0; + while(ifname[i] && ifname[i] != ':') i++; + if (ifname[i] == ':') + { + memset(&ifr, 0, sizeof (ifr)); + memcpy(ifr.ifr_name, ifname, i); + ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + addr_mask = 0; + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + if (ioctl (sfd, SIOCGIFBRDADDR, &ifr) >= 0) + { + memcpy(&brdaddr, &ifr.ifr_broadaddr, sizeof(struct sockaddr_in)); + addr_mask = addr_mask | 0x01; + } + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + if (ioctl (sfd, SIOCGIFNETMASK, &ifr) >= 0) + { + memcpy(&netmask, &ifr.ifr_netmask, sizeof(struct sockaddr_in)); + addr_mask = addr_mask | 0x02; + } + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + if (ioctl (sfd, SIOCGIFHWADDR, &ifr) >= 0) + { + memcpy(&macaddr, &ifr.ifr_hwaddr, sizeof(struct sockaddr_in)); + addr_mask = addr_mask | 0x04; + } + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + if (ioctl (sfd, SIOCGIFINDEX, &ifr) >= 0) + { + ifindex = ifr.ifr_ifindex; + } + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + if (ioctl (sfd, SIOCGIFADDR, &ifr) >= 0) + { + memcpy(&ipaddr, &ifr.ifr_addr, sizeof(struct sockaddr_in)); + addr_mask = addr_mask | 0x08; + } + } + + memset(arpcmd, 0, 512); + sprintf(arpcmd, "/sbin/arping -A -c 1 -I %s %s > /dev/null 2>&1",ifr.ifr_name, address); + + memset(&ifr, 0, sizeof (ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + ioctl (sfd, SIOCSIFFLAGS, &ifr); + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + + if (isdel) + { + close(sfd); + return 0; + } + + sin = (struct sockaddr_in *) &ifr.ifr_addr; + sin->sin_family = AF_INET; + err = inet_aton(address, &sin->sin_addr); + netmask.sin_addr.s_addr = 0xffffffff; + if (err) + { + err = ioctl (sfd, SIOCSIFADDR, &ifr); + if (err < 0) + { + close(sfd); + return 0; + } + + if (addr_mask & 0x1) + { + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + memcpy(&ifr.ifr_broadaddr, &brdaddr, sizeof(struct sockaddr_in)); + ioctl (sfd, SIOCSIFBRDADDR, &ifr); + } + if (addr_mask & 0x2) + { + memset(&ifr.ifr_ifru, 0, sizeof(ifr.ifr_ifru)); + memcpy(&ifr.ifr_netmask, &netmask, sizeof(struct sockaddr_in)); + ioctl (sfd, SIOCSIFNETMASK, &ifr); + } + close(sfd); + sin->sin_family = AF_INET; + err = inet_aton(address, &sin->sin_addr); + if (!send_arp_pack(ifindex, (char*)&sin->sin_addr.s_addr, macaddr.sa_data)) { + err = system(arpcmd); + } + return 1; + } + close(sfd); + return 0; +} +#else +int NonPortable::setVipAddress(const char*, const char*, int) +{ + return 0; +} +#endif + + + +socket_t NonPortable::createUnixSocketFile(const char* file) +{ +#ifndef WIN32 + socket_t server_sockfd = -1; + server_sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); + + sockaddr_un server_addr; + server_addr.sun_family = AF_UNIX; + strcpy(server_addr.sun_path, file); + + if (::bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) { + TcpSocket::close(server_sockfd); + return -1; + } + + if (::listen(server_sockfd, 128) != 0) { + TcpSocket::close(server_sockfd); + return -1; + } + return server_sockfd; +#else + (void)file; + return -1; +#endif +} + + + +void NonPortable::guard(void (*driverFunc)(), int exit_key) +{ +#ifdef WIN32 + (void)exit_key; + driverFunc(); +#else + do { + pid_t pid = fork(); + if (0 == pid) { + driverFunc(); + exit(0); + } else if (pid > 0) { + int status; + pid_t pidWait = waitpid(pid, &status, 0); + if (pidWait < 0) { + Logger::log(Logger::Error, "waitpid() failed"); + exit(0); + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == exit_key) { + exit(0); + } + } + Logger::log(Logger::Message, "Restart the program...", status); + } else { + Logger::log(Logger::Error, "fork() failed"); + exit(1); + } + } while (true); +#endif +} + +int NonPortable::daemonize(void) +{ +#ifndef WIN32 + int status; + pid_t pid, sid; + int fd; + + char appdir[1024]; + char* appdirptr = getcwd(appdir, 1024); + + pid = fork(); + switch (pid) { + case -1: + Logger::log(Logger::Error, "fork() failed: %s", strerror(errno)); + return 1; + + case 0: + break; + + default: + exit(0); + } + + sid = setsid(); + if (sid < 0) { + Logger::log(Logger::Error, "setsid() failed: %s", strerror(errno)); + return 1; + } + + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) { + Logger::log(Logger::Error, "signal(SIGHUP, SIG_IGN) failed: %s", strerror(errno)); + return 1; + } + + pid = fork(); + switch (pid) { + case -1: + Logger::log(Logger::Error, "fork() failed: %s", strerror(errno)); + return 1; + + case 0: + break; + + default: + exit(0); + } + + + umask(0); + + fd = open("/dev/null", O_RDWR); + if (fd < 0) { + Logger::log(Logger::Error, "open(\"/dev/null\") failed: %s", strerror(errno)); + return 1; + } + + status = dup2(fd, STDIN_FILENO); + if (status < 0) { + Logger::log(Logger::Error, "dup2(%d, STDIN) failed: %s", fd, strerror(errno)); + close(fd); + return 1; + } + + status = dup2(fd, STDOUT_FILENO); + if (status < 0) { + Logger::log(Logger::Error, "dup2(%d, STDOUT) failed: %s", fd, strerror(errno)); + close(fd); + return 1; + } + + status = dup2(fd, STDERR_FILENO); + if (status < 0) { + Logger::log(Logger::Error, "dup2(%d, STDERR) failed: %s", fd, strerror(errno)); + close(fd); + return 1; + } + + if (fd > STDERR_FILENO) { + status = close(fd); + if (status < 0) { + Logger::log(Logger::Error, "close(%d) failed: %s", fd, strerror(errno)); + return 1; + } + } + + if (chdir(appdirptr) != -1) { + Logger::log(Logger::Error, "chdir() failed"); + } +#endif + return 0; +} diff --git a/src/non-portable.h b/src/non-portable.h new file mode 100644 index 0000000..5a92833 --- /dev/null +++ b/src/non-portable.h @@ -0,0 +1,41 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef NONPORTABLE_H +#define NONPORTABLE_H + +#include "util/tcpsocket.h" + +namespace NonPortable { + +//guard +void guard(void (*driverFunc)(), int exit_key); + +//daemon +int daemonize(void); + +//create unix socket file +socket_t createUnixSocketFile(const char* file); + +//vip address +int setVipAddress(const char *ifname, const char *address, int isdel); + +} + +#endif // NONPORTABLE_H diff --git a/src/proxymanager.cpp b/src/proxymanager.cpp new file mode 100644 index 0000000..a4464a6 --- /dev/null +++ b/src/proxymanager.cpp @@ -0,0 +1,141 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "proxymanager.h" +#include "redisproxy.h" + +ProxyManager::ProxyManager(void) +{ + m_proxy = NULL; +} + +ProxyManager::~ProxyManager(void) +{ + GroupInfoMap::iterator it = m_groupInfoMap.begin(); + for (; it != m_groupInfoMap.end(); ++it) { + delete it->second; + } +} + +void ProxyManager::setGroupTTL(RedisServantGroup* group, int seconds, bool restore) +{ + GroupInfo* info = NULL; + GroupInfoMap::iterator it = m_groupInfoMap.find(group); + if (it == m_groupInfoMap.end()) { + info = new GroupInfo; + info->group = group; + info->restore = restore; + info->manager = this; + m_groupInfoMap[group] = info; + } else { + info = it->second; + } + + if (!info->can_ttl) { + return; + } + + //Update group old hash values + info->oldHashValues.clear(); + for (int i = 0; i < m_proxy->maxHashValue(); ++i) { + if (m_proxy->hashForGroup(i) == group) { + info->oldHashValues.push_back(i); + } + } + + Logger::log(Logger::Message, "set group '%s' TTL=%d second(s)", group->groupName(), seconds); + info->ev.setTimer(m_proxy->eventLoop(), onSetGroupTTL, info); + info->ev.active(seconds * 1000); + info->can_ttl = false; +} + +void ProxyManager::removeGroup(RedisServantGroup* group) +{ + std::vector groupOldHashValue; + std::set otherGroups; + int maxHashValue = m_proxy->maxHashValue(); + for (int i = 0; i < maxHashValue; ++i) { + RedisServantGroup* mp = m_proxy->hashForGroup(i); + if (mp == group) { + groupOldHashValue.push_back(i); + m_proxy->setGroupMappingValue(i, NULL); + } else { + otherGroups.insert(mp); + } + } + + if (!groupOldHashValue.empty()) { + Logger::log(Logger::Message, "Group '%s' removed", group->groupName()); + } + + if (otherGroups.empty() || groupOldHashValue.empty()) { + return; + } + + std::vector vec(otherGroups.size()); + std::set::const_iterator itGroup = otherGroups.cbegin(); + for (int i = 0; itGroup != otherGroups.cend(); ++itGroup, ++i) { + vec[i] = *itGroup; + } + + std::vector::const_iterator itHash = groupOldHashValue.cbegin(); + for (int i = 0; itHash != groupOldHashValue.cend(); ++itHash, ++i) { + int hashval = *itHash; + RedisServantGroup* group = vec[i % vec.size()]; + m_proxy->setGroupMappingValue(hashval, group); + } +} + +void ProxyManager::onSetGroupTTL(int, short, void* arg) +{ + GroupInfo* info = (GroupInfo*)arg; + Logger::log(Logger::Message, "TTL: Remove group '%s'...", info->group->groupName()); + RedisServantGroup* group = info->group; + if (group->isEnabled()) { + Logger::log(Logger::Message, "TTL: Group '%s' is actived. Has been cancelled to remove", + info->group->groupName()); + info->can_ttl = true; + return; + } + + info->manager->removeGroup(group); + if (info->restore) { + Logger::log(Logger::Message, "When the group '%s' becomes active state before will restore", + info->group->groupName()); + info->ev.setTimer(info->manager->proxy()->eventLoop(), onRestoreGroup, info); + info->ev.active(500); + } +} + +void ProxyManager::onRestoreGroup(int, short, void* arg) +{ + GroupInfo* info = (GroupInfo*)arg; + RedisServantGroup* group = info->group; + if (group->isEnabled()) { + Logger::log(Logger::Message, "Group '%s' has been restored", info->group->groupName()); + std::vector::iterator it = info->oldHashValues.begin(); + for (; it != info->oldHashValues.end(); ++it) { + info->manager->proxy()->setGroupMappingValue(*it, group); + } + info->can_ttl = true; + } else { + info->ev.active(500); + } +} diff --git a/src/proxymanager.h b/src/proxymanager.h new file mode 100644 index 0000000..e83a76b --- /dev/null +++ b/src/proxymanager.h @@ -0,0 +1,75 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef PROXYMANAGER_H +#define PROXYMANAGER_H + +#include +#include +#include + +#include "eventloop.h" + +class RedisProxy; +class RedisServantGroup; +class ProxyManager; + +struct GroupInfo +{ + GroupInfo(void) { + manager = NULL; + group = NULL; + can_ttl = true; + restore = false; + } + + ProxyManager* manager; + RedisServantGroup* group; + bool can_ttl; + Event ev; + bool restore; + std::vector oldHashValues; +}; + + +class ProxyManager +{ +public: + ProxyManager(void); + ~ProxyManager(void); + + void setProxy(RedisProxy* p) { m_proxy = p; } + RedisProxy* proxy(void) const { return m_proxy; } + + void setGroupTTL(RedisServantGroup* group, int seconds, bool restore); + +private: + void removeGroup(RedisServantGroup* group); + static void onSetGroupTTL(int, short, void*); + static void onRestoreGroup(int, short, void*); + +private: + typedef std::map GroupInfoMap; + GroupInfoMap m_groupInfoMap; + RedisProxy* m_proxy; +}; + +#endif + + diff --git a/src/redis-proxy-config.cpp b/src/redis-proxy-config.cpp new file mode 100644 index 0000000..24be18c --- /dev/null +++ b/src/redis-proxy-config.cpp @@ -0,0 +1,712 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include + +#include "redis-proxy-config.h" +#include "redis-servant-select.h" + +#ifdef WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + + +COperateXml::COperateXml() { + m_docPointer = new TiXmlDocument; +} + +COperateXml::~COperateXml() { + delete m_docPointer; +} + +bool COperateXml::xml_open(const char* xml_path) { + if (!m_docPointer->LoadFile(xml_path)) { + return false; + } + m_rootElement = m_docPointer->RootElement(); + return true; +} + +void COperateXml::xml_print() { + m_docPointer->Print(); +} + +const TiXmlElement* COperateXml::get_rootElement() { + return m_rootElement; +} + +// host info +CHostInfo::CHostInfo() { + ip = ""; + host_name = ""; + port = 0; + master = false; + priority = 0; + policy = 0; +} +CHostInfo::~CHostInfo(){} +string CHostInfo::get_ip()const +{ + return ip; +} + +string CHostInfo::get_hostName()const +{ + return host_name; +} +int CHostInfo::get_port()const { return port;} +bool CHostInfo::get_master()const { return master;} +int CHostInfo::get_policy()const { return policy;} +int CHostInfo::get_priority()const { return priority;} +int CHostInfo::get_connectionNum()const { return connection_num;} + +void CHostInfo::set_ip(string& s) { ip = s;} +void CHostInfo::set_hostName(string& s) { host_name = s;} +void CHostInfo::set_port(int p) { port = p;} +void CHostInfo::set_master(bool m) { master = m;} +void CHostInfo::set_policy(int p) { policy = p;} +void CHostInfo::set_priority(int p) { priority = p;} +void CHostInfo::set_connectionNum(int p) { connection_num = p;} + +CGroupInfo::CGroupInfo() +{ + memset(m_groupName, '\0', sizeof(m_groupName)); + memset(m_groupPolicy, '\0', sizeof(m_groupPolicy)); +} + +CGroupInfo::~CGroupInfo() {} + +CRedisProxyCfg::CRedisProxyCfg() { + m_operateXmlPointer = new COperateXml; + m_hashInfo.hash_value_max = 0; + m_threadNum = 0; + m_port = 0; + memset(m_vip.if_alias_name, '\0', sizeof(m_vip.if_alias_name)); + memset(m_vip.vip_address, '\0', sizeof(m_vip.vip_address)); + m_vip.enable = false; + memset(m_logFile, '\0', sizeof(m_logFile)); + m_daemonize = false; + m_guard = false; + m_topKeyEnable = false; + m_hashMappingList = new HashMappingList; + m_keyMappingList = new KeyMappingList; + m_groupInfo = new GroupInfoList; +} + +CRedisProxyCfg::~CRedisProxyCfg() { + if(NULL != m_operateXmlPointer) delete m_operateXmlPointer; + delete m_hashMappingList; + delete m_keyMappingList; + delete m_groupInfo; +} + +CRedisProxyCfg *CRedisProxyCfg::instance() +{ + static CRedisProxyCfg* cfg = NULL; + if (cfg == NULL) { + cfg = new CRedisProxyCfg; + } + return cfg; +} + +void CRedisProxyCfg::set_groupName(CGroupInfo& group, const char* name) { + int size = strlen(name); + memcpy(group.m_groupName, name, size+1); +} + +void CRedisProxyCfg::set_hashMin(CGroupInfo& group, int num) { + group.m_hashMin = num; +} + +void CRedisProxyCfg::set_hashMax(CGroupInfo& group, int num) { + group.m_hashMax = num; +} + +void CRedisProxyCfg::addHost(CGroupInfo& group, CHostInfo& p) { + group.m_hosts.push_back(p); +} + +void CRedisProxyCfg::set_groupAttribute(TiXmlAttribute *groupAttr, CGroupInfo& pGroup) { + for (; groupAttr != NULL; groupAttr = groupAttr->Next()) { + const char* name = groupAttr->Name(); + const char* value = groupAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "hash_min")) { + set_hashMin(pGroup, atoi(value)); + continue; + } + if (0 == strcasecmp(name, "hash_max")) { + set_hashMax(pGroup, atoi(value)); + continue; + } + if (0 == strcasecmp(name, "name")) { + set_groupName(pGroup, value); + continue; + } + if (0 == strcasecmp(name, "policy")) { + memcpy(pGroup.m_groupPolicy, value, strlen(value)+1); + continue; + } + } +} + +void CRedisProxyCfg::set_hostAttribute(TiXmlAttribute *addrAttr, CHostInfo& pHostInfo) { + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "host_name")) { + string s = value; + pHostInfo.set_hostName(s); + continue; + } + if (0 == strcasecmp(name, "ip")) { + string s = value; + pHostInfo.set_ip(s); + continue; + } + if (0 == strcasecmp(name, "connection_num")) { + pHostInfo.set_connectionNum(atoi(value)); + continue; + } + if (0 == strcasecmp(name, "port")) { + pHostInfo.set_port(atoi(value)); + continue; + } + if (0 == strcasecmp(name, "policy")) { + pHostInfo.set_policy(atoi(value)); + continue; + } + if (0 == strcasecmp(name, "priority")) { + pHostInfo.set_priority(atoi(value)); + continue; + } + if (0 == strcasecmp(name, "master")) { + pHostInfo.set_master((atoi(value) != 0)); + continue; + } + } + +} + +void CRedisProxyCfg::set_hostEle(TiXmlElement* hostContactEle, CHostInfo& hostInfo) { + for (; hostContactEle != NULL; hostContactEle = hostContactEle->NextSiblingElement()) { + const char* strValue; + strValue = hostContactEle->Value(); + if (strValue == NULL) strValue = ""; + + const char* strText; + strText = hostContactEle->GetText(); + if (strText == 0) strText = ""; + + if (0 == strcasecmp(strValue, "ip")) { + string s = strText; + hostInfo.set_ip(s); + continue; + } + if (0 == strcasecmp(strValue, "connection_num")) { + if (hostContactEle->GetText() != NULL) + hostInfo.set_connectionNum(atoi(strText)); + continue; + } + if (0 == strcasecmp(strValue, "port")) { + hostInfo.set_port(atoi(strText)); + continue; + } + if (0 == strcasecmp(strValue, "policy")) { + hostInfo.set_policy(atoi(strText)); + continue; + } + if (0 == strcasecmp(strValue, "host_name")) { + string s = strText; + hostInfo.set_hostName(s); + continue; + } + if (0 == strcasecmp(strValue, "priority")) { + hostInfo.set_priority(atoi(strText)); + continue; + } + if (0 == strcasecmp(strValue, "master")) { + hostInfo.set_master((atoi(strText) != 0)); + continue; + } + } +} + +void CRedisProxyCfg::getRootAttr(const TiXmlElement* pRootNode) { + TiXmlAttribute *addrAttr = (TiXmlAttribute *)pRootNode->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "thread_num")) { + m_threadNum = atoi(value); + continue; + } + if (0 == strcasecmp(name, "port")) { + m_port = atoi(value); + continue; + } + if (0 == strcasecmp(name, "hash_value_max")) { + m_hashInfo.hash_value_max = atoi(value); + continue; + } + if (0 == strcasecmp(name, "log_file")) { + strcpy(m_logFile, value); + continue; + } + if (0 == strcasecmp(name, "daemonize")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0 ) { + m_daemonize = true; + } + continue; + } + if (0 == strcasecmp(name, "guard")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0) { + m_guard = true; + } + continue; + } + } +} + +void CRedisProxyCfg::getVipAttr(const TiXmlElement* vidNode) { + TiXmlAttribute *addrAttr = (TiXmlAttribute *)vidNode->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "if_alias_name")) { + strcpy(m_vip.if_alias_name, value); + continue; + } + if (0 == strcasecmp(name, "vip_address")) { + strcpy(m_vip.vip_address, value); + continue; + } + if (0 == strcasecmp(name, "enable")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0 ) { + m_vip.enable = true; + } + } + } +} + +void CRedisProxyCfg::setHashMappingNode(TiXmlElement* pNode) { + TiXmlElement* pNext = pNode->FirstChildElement(); + for (; pNext != NULL; pNext = pNext->NextSiblingElement()) { + CHashMapping hashMap; + if (0 == strcasecmp(pNext->Value(), "hash")) { + TiXmlAttribute *addrAttr = pNext->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "value")) { + hashMap.hash_value = atoi(value); + continue; + } + if (0 == strcasecmp(name, "group_name")) { + strcpy(hashMap.group_name, value); + } + } + m_hashMappingList->push_back(hashMap); + } + } +} + + +void CRedisProxyCfg::setGroupOption(const TiXmlElement* vidNode) { + TiXmlAttribute *addrAttr = (TiXmlAttribute *)vidNode->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "backend_retry_interval")) { + m_groupOption.backend_retry_interval = atoi(value); + continue; + } + if (0 == strcasecmp(name, "backend_retry_limit")) { + m_groupOption.backend_retry_limit = atoi(value); + continue; + } + if (0 == strcasecmp(name, "group_retry_time")) { + m_groupOption.group_retry_time = atoi(value); + continue; + } + + if (0 == strcasecmp(name, "auto_eject_group")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0 ) { + m_groupOption.auto_eject_group = true; + continue; + } + } + + if (0 == strcasecmp(name, "eject_after_restore")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0 ) { + m_groupOption.eject_after_restore = true; + } + } + } +} + + + +void CRedisProxyCfg::setKeyMappingNode(TiXmlElement* pNode) { + TiXmlElement* pNext = pNode->FirstChildElement(); + for (; pNext != NULL; pNext = pNext->NextSiblingElement()) { + CKeyMapping hashMap; + if (0 == strcasecmp(pNext->Value(), "key")) { + TiXmlAttribute *addrAttr = pNext->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (value == NULL) value = ""; + if (0 == strcasecmp(name, "key_name")) { + strcpy(hashMap.key, value); + continue; + } + if (0 == strcasecmp(name, "group_name")) { + strcpy(hashMap.group_name, value); + } + } + m_keyMappingList->push_back(hashMap); + } + } +} + +void CRedisProxyCfg::getGroupNode(TiXmlElement* pNode) { + CGroupInfo groupTmp; + set_groupAttribute(pNode->FirstAttribute(), groupTmp); + TiXmlElement* pNext = pNode->FirstChildElement(); + for (; pNext != NULL; pNext = pNext->NextSiblingElement()) { + if (0 == strcasecmp(pNext->Value(), "hash_min")) { + if (NULL == pNext->GetText()) { + set_hashMin(groupTmp, 0); + continue; + } + set_hashMin(groupTmp, atoi(pNext->GetText())); + continue; + } + if (0 == strcasecmp(pNext->Value(), "hash_max")) { + if (NULL == pNext->GetText()) { + set_hashMax(groupTmp, 0); + continue; + } + set_hashMax(groupTmp, atoi(pNext->GetText())); + continue; + } + if (0 == strcasecmp(pNext->Value(), "name")) { + set_groupName(groupTmp, pNext->GetText()); + continue; + } + + if (0 == strcasecmp(pNext->Value(), "host")) { + CHostInfo hostInfo; + set_hostAttribute(pNext->FirstAttribute(), hostInfo); + TiXmlElement* hostContactEle = (TiXmlElement* )pNext->FirstChildElement(); + set_hostEle(hostContactEle, hostInfo); + addHost(groupTmp, hostInfo); + } + } + m_groupInfo->push_back(groupTmp); +} + +bool GetNodePointerByName( + TiXmlElement* pRootEle, + const std::string &strNodeName, + TiXmlElement* &Node) +{ + if (strNodeName==pRootEle->Value()) { + Node = pRootEle; + return true; + } + TiXmlElement* pEle = pRootEle; + for (pEle = pRootEle->FirstChildElement(); pEle; pEle = pEle->NextSiblingElement()) { + if(GetNodePointerByName(pEle,strNodeName,Node)) + return true; + } + + return false; +} + + +bool CRedisProxyCfg::saveProxyLastState(RedisProxy* proxy) { + TiXmlElement* pRootNode = (TiXmlElement*)m_operateXmlPointer->get_rootElement(); + string nodeHash = "hash_mapping"; + TiXmlElement* pOldHashMap; + if (GetNodePointerByName(pRootNode, nodeHash, pOldHashMap)) { + pRootNode->RemoveChild(pOldHashMap); + } + TiXmlElement hashMappingNode("hash_mapping"); + for (int i = 0; i < proxy->maxHashValue(); ++i) { + TiXmlElement hashNode("hash"); + hashNode.SetAttribute("value", i); + hashNode.SetAttribute("group_name", proxy->hashForGroup(i)->groupName()); + hashMappingNode.InsertEndChild(hashNode); + } + pRootNode->InsertEndChild(hashMappingNode); + + // key mapping + string nodeKey = "key_mapping"; + TiXmlElement* pKey; + if (GetNodePointerByName(pRootNode, nodeKey, pKey)) { + pRootNode->RemoveChild(pKey); + } + TiXmlElement keyMappingNode("key_mapping"); + + StringMap& keyMapping = proxy->keyMapping(); + StringMap::iterator it = keyMapping.begin(); + for (; it != keyMapping.end(); ++it) { + String key = it->first; + TiXmlElement keyNode("key"); + keyNode.SetAttribute("key_name", key.data()); + RedisServantGroup* group = it->second; + if (group != NULL) { + keyNode.SetAttribute("group_name", group->groupName()); + } + keyMappingNode.InsertEndChild(keyNode); + } + pRootNode->InsertEndChild(keyMappingNode); + + // add time + time_t now = time(NULL); + char fileName[512]; + struct tm* current_time = localtime(&now); + sprintf(fileName,"onecache%d%02d%02d%02d.xml", + current_time->tm_year + 1900, + current_time->tm_mon + 1, + current_time->tm_mday, + current_time->tm_hour); + + bool ok = m_operateXmlPointer->m_docPointer->SaveFile(fileName); + return ok; +} + + +bool CRedisProxyCfg::loadCfg(const char* file) { + if (!m_operateXmlPointer->xml_open(file)) return false; + + const TiXmlElement* pRootNode = m_operateXmlPointer->get_rootElement(); + getRootAttr(pRootNode); + TiXmlElement* pNode = (TiXmlElement*)pRootNode->FirstChildElement(); + for (; pNode != NULL; pNode = pNode->NextSiblingElement()) { + if (0 == strcasecmp(pNode->Value(), "thread_num")) { + if (pNode->GetText() != NULL) m_threadNum = atoi(pNode->GetText()); + continue; + } + if (0 == strcasecmp(pNode->Value(), "port")) { + if (pNode->GetText() != NULL) m_port = atoi(pNode->GetText()); + continue; + } + if (0 == strcasecmp(pNode->Value(), "vip")) { + getVipAttr(pNode); + continue; + } + + if (0 == strcasecmp(pNode->Value(), "top_key")) { + TiXmlAttribute *addrAttr = (TiXmlAttribute *)pNode->FirstAttribute(); + for (; addrAttr != NULL; addrAttr = addrAttr->Next()) { + const char* name = addrAttr->Name(); + const char* value = addrAttr->Value(); + if (0 == strcasecmp(name, "enable")) { + if(strcasecmp(value, "0") != 0 && strcasecmp(value, "") != 0 ) { + m_topKeyEnable = true; + } + } + } + continue; + } + + if (0 == strcasecmp(pNode->Value(), "hash")) { + TiXmlElement* pNext = pNode->FirstChildElement(); + if (NULL == pNext) continue; + for (; pNext != NULL; pNext = pNext->NextSiblingElement()) { + if (0 == strcasecmp(pNext->Value(), "hash_value_max")) { + if (pNext->GetText() == NULL) { + m_hashInfo.hash_value_max = 0; + continue; + } + m_hashInfo.hash_value_max = atoi(pNext->GetText()); + } + } + continue; + } + if (0 == strcasecmp(pNode->Value(), "group")) { + getGroupNode(pNode); + continue; + } + if (0 == strcasecmp(pNode->Value(), "hash_mapping")) { + setHashMappingNode(pNode); + continue; + } + if (0 == strcasecmp(pNode->Value(), "group_option")) { + setGroupOption(pNode); + continue; + } + if (0 == strcasecmp(pNode->Value(), "key_mapping")) { + setKeyMappingNode(pNode); + continue; + } + } + + return true; +} + + +bool CRedisProxyCfgChecker::isValid(CRedisProxyCfg* pCfg, const char*& errMsg) +{ + const int hash_value_max = pCfg->hashInfo()->hash_value_max; + if (hash_value_max > REDIS_PROXY_HASH_MAX) { + errMsg = "hash_value_max is not greater than 1024"; + return false; + } + + int port = pCfg->port(); + if (port <= 0 || port > 65535) { + errMsg = "onecache's port is invalid"; + return false; + } + + int thread_num = pCfg->threadNum(); + if (thread_num <= 0) { + errMsg = "onecache's thread_num is invalid"; + return false; + } + + bool barray[REDIS_PROXY_HASH_MAX] = {0}; + string groupNameBuf[512]; + int groupCnt_ = pCfg->groupCnt(); + for (int i = 0; i < groupCnt_; ++i) { + const CGroupInfo* group = pCfg->group(i); + if (0 == strlen(group->groupName())) { + errMsg = "group name cannot be empty"; + return false; + } + bool empty = false; + if (0 == strlen(group->groupPolicy())) { + empty = true; + } + if (!empty) { + if (0 != strcasecmp(group->groupPolicy(), POLICY_READ_BALANCE) || + 0 != strcasecmp(group->groupPolicy(), POLICY_MASTER_ONLY)) + { + errMsg = "group's policy is wrong, it should be read_balance or master_only"; + return false; + } + } + + groupNameBuf[i] = group->groupName(); + for (int j = 0; j < i; ++j) { + if (groupNameBuf[i] == groupNameBuf[j]) { + errMsg = "having the same group name"; + return false; + } + } + + if (group->hashMin() > group->hashMax()) { + errMsg = "hash_min > hash_max"; + return false; + } + for (int j = group->hashMin(); j <= group->hashMax(); ++j) { + if (j < 0 || j > REDIS_PROXY_HASH_MAX) { + errMsg = "hash value is invalid"; + return false; + } + if (j >= hash_value_max) { + errMsg = "hash value is out of range"; + return false; + } + + if (barray[j]) { + errMsg = "hash range error"; + return false; + } + barray[j] = true; + } + } + for (int i = 0; i < hash_value_max; ++i) { + if (!barray[i]) { + errMsg = "hash values are not complete"; + return false; + } + } + + const GroupOption* groupOp = pCfg->groupOption(); + if (groupOp->backend_retry_interval <= 0) { + errMsg = "backend_retry_interval invalid"; + return false; + } + + if (groupOp->backend_retry_limit <= 0) { + errMsg = "backend_retry_limit invalid"; + return false; + } + + if (groupOp->auto_eject_group) { + if (groupOp->group_retry_time <= 0) { + errMsg = "group_retry_time invalid"; + return false; + } + } + + int hashMapCnt = pCfg->hashMapCnt(); + for (int i = 0; i < hashMapCnt; ++i) { + const CHashMapping* p = pCfg->hashMapping(i); + if (p->hash_value < 0) { + errMsg = "hash_mapping's value < 0"; + return false; + } + bool exist = false; + for (int j = 0; j < groupCnt_; ++j) { + if (groupNameBuf[j] == p->group_name) { + exist = true; + break; + } + } + if (!exist) { + errMsg = "hash_mapping's group name doesn't exist"; + return false; + } + } + + int keyMapCnt = pCfg->keyMapCnt(); + for (int i = 0; i < keyMapCnt; ++i) { + const CKeyMapping* p = pCfg->keyMapping(i); + std::string groupName = p->group_name; + bool exist = false; + for (int j = 0; j < groupCnt_; ++j) { + if (groupNameBuf[j] == groupName) { + exist = true; + break; + } + } + if (!exist) { + errMsg = "key_mapping's group name doesn't exist"; + return false; + } + } + + return true; +} + + + + diff --git a/src/redis-proxy-config.h b/src/redis-proxy-config.h new file mode 100644 index 0000000..5f78a9f --- /dev/null +++ b/src/redis-proxy-config.h @@ -0,0 +1,237 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef REDIS_PROXY_CONFIG +#define REDIS_PROXY_CONFIG +#include +#include +#include +#include +#include + +#include "tinyxml/tinyxml.h" +#include "tinyxml/tinystr.h" +#include "util/logger.h" +#include "redisproxy.h" + +using namespace std; + +typedef class COperateXml +{ +public: + COperateXml(); + ~COperateXml(); + + bool xml_open(const char* xml_path); + + virtual void xml_print(); + const TiXmlElement* get_rootElement(); +public: + // xml file operate pointer + TiXmlDocument* m_docPointer; +private: + TiXmlElement* m_rootElement; +private: + COperateXml(const COperateXml&); + COperateXml& operator =(const COperateXml&); +}COperateXml; + + +// host info +class CHostInfo { +public: + CHostInfo(); + ~CHostInfo(); + string get_ip()const; + string get_hostName()const; + int get_port()const; + bool get_master()const; + int get_policy()const; + int get_priority()const; + int get_connectionNum()const; + + void set_ip(string& s); + void set_hostName(string& s); + void set_port(int p) ; + void set_master(bool m); + void set_policy(int p); + void set_priority(int p); + void set_connectionNum(int p); +private: + string ip; + string host_name; + int port; + bool master; + int priority; + int policy; + int connection_num; +}; +typedef std::vector HostInfoList; + +struct SHashInfo { + // int hash_type; + int hash_value_max; +}; + +struct SVipInfo { + char if_alias_name[50]; + char vip_address[30]; + bool enable; +}; + + +class CHashMapping { +public: + CHashMapping() { + hash_value = 0; + memset(group_name, '\0', sizeof(group_name)); + } + int hash_value; + char group_name[512]; +}; +typedef std::vector HashMappingList; + +class CKeyMapping { +public: + CKeyMapping() { + memset(key, '\0', sizeof(key)); + memset(group_name, '\0', sizeof(group_name)); + } + char key[512]; + char group_name[512]; +}; +typedef std::vector KeyMappingList; + + +class CGroupInfo +{ +public: + CGroupInfo(); + ~CGroupInfo(); + const char* groupName() const { return m_groupName; } + const char* groupPolicy() const { return m_groupPolicy; } + int hashMin()const { return m_hashMin; } + int hashMax()const { return m_hashMax; } + const HostInfoList& hosts() const { return m_hosts; } + void setGroupPolicy(const char* p) { + strcpy(m_groupPolicy, p); + m_groupPolicy[strlen(p) + 1] = '\0'; + } +private: + char m_groupName[128]; + char m_groupPolicy[128]; + int m_hashMin; + int m_hashMax; + HostInfoList m_hosts; + friend class CRedisProxyCfg; +}; +typedef std::vector GroupInfoList; + + +struct GroupOption { + GroupOption(){ + backend_retry_interval = 1; + backend_retry_limit = 100; + group_retry_time = 30; + auto_eject_group = false; + eject_after_restore = false; + } + int backend_retry_interval; + int backend_retry_limit; + int group_retry_time; + bool auto_eject_group; + bool eject_after_restore; +}; + + + +// read the config +class CRedisProxyCfg +{ +private: + CRedisProxyCfg(); + ~CRedisProxyCfg(); + +public: + static CRedisProxyCfg* instance(); + + bool loadCfg(const char* xml_path); + bool saveProxyLastState(RedisProxy* proxy); + + int groupCnt()const {return m_groupInfo->size();} + const CGroupInfo* group(int index)const {return &(*m_groupInfo)[index];} + const SHashInfo* hashInfo()const {return &m_hashInfo;} + const GroupOption* groupOption()const {return &m_groupOption;} + const SVipInfo* vipInfo()const {return &m_vip;} + int threadNum()const {return m_threadNum;} + int port() const {return m_port;} + const char* logFile(){ return m_logFile; } + bool daemonize() { return m_daemonize;} + bool guard() { return m_guard;} + bool topKeyEnable() { return m_topKeyEnable;} + + int hashMapCnt(){ return m_hashMappingList->size();} + int keyMapCnt(){ return m_keyMappingList->size();} + const CHashMapping* hashMapping(int index)const {return &(*m_hashMappingList)[index];} + const CKeyMapping* keyMapping(int index)const {return &(*m_keyMappingList)[index];} +private: + COperateXml* m_operateXmlPointer; + GroupInfoList* m_groupInfo; + HashMappingList* m_hashMappingList; + KeyMappingList* m_keyMappingList; + SHashInfo m_hashInfo; + SVipInfo m_vip; + int m_threadNum; + int m_port; + char m_logFile[512]; + bool m_daemonize; + bool m_guard; + bool m_topKeyEnable; + GroupOption m_groupOption; +private: + void set_groupName(CGroupInfo& group, const char* name); + void set_hashMin(CGroupInfo& group, int num); + void set_hashMax(CGroupInfo& group, int num); + void addHost(CGroupInfo& group, CHostInfo& h); + + void set_groupAttribute(TiXmlAttribute* groupAttr, CGroupInfo& group); + void set_hostAttribute(TiXmlAttribute* addrAttr, CHostInfo& pHostInfo); + void set_hostEle(TiXmlElement* hostContactEle, CHostInfo& hostInfo); + void getRootAttr(const TiXmlElement* pRootNode); + void getVipAttr(const TiXmlElement* vidNode); + void getGroupNode(TiXmlElement* pNode); + void setHashMappingNode(TiXmlElement* pNode); + void setKeyMappingNode(TiXmlElement* pNode); + void setGroupOption(const TiXmlElement* pNode); +private: + CRedisProxyCfg(const CRedisProxyCfg&); + CRedisProxyCfg& operator =(const CRedisProxyCfg&); +}; + +// check the cfg is valid or not +class CRedisProxyCfgChecker +{ +public: + enum {REDIS_PROXY_HASH_MAX = 1024}; + CRedisProxyCfgChecker(); + ~CRedisProxyCfgChecker(); + static bool isValid(CRedisProxyCfg* pCfg, const char*& err); +}; + +#endif diff --git a/src/redis-servant-select.cpp b/src/redis-servant-select.cpp new file mode 100644 index 0000000..db467ce --- /dev/null +++ b/src/redis-servant-select.cpp @@ -0,0 +1,161 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "command.h" +#include "redisproxy.h" +#include "redisservant.h" + +#include "redis-servant-select.h" + + +ServantSelect::ServantSelect(void) +{ m_masterCallNum = 0; + m_slaveCallNum = 0; +} + +ServantSelect::~ServantSelect() +{ + +} + + +RedisServant* ServantSelect::selectMaster(RedisServantGroup* group) { + RedisServant* servant = oneMaster(group); + if (servant != NULL) return servant; + return oneSlave(group); +} + +RedisServant* ServantSelect::selectSlave(RedisServantGroup* group) { + RedisServant* servant = oneSlave(group); + if (servant != NULL) return servant; + return oneMaster(group); +} + + +RedisServant* ServantSelect::oneMaster(RedisServantGroup* group) { + int masterCount = group->masterCount(); + unsigned int callNum = m_masterCallNum; + for (int i = 0; i < masterCount; ++i) { + RedisServant* servant = group->master(callNum % masterCount); + if (servant->isActived()) + return servant; + callNum++; + } + m_masterCallNum = callNum; + return NULL; +} + +RedisServant* ServantSelect::oneSlave(RedisServantGroup* group) { + int slaveCount = group->slaveCount(); + unsigned int callNum = m_slaveCallNum; + for (int i = 0; i < slaveCount; ++i) { + RedisServant* servant = group->slave(callNum % slaveCount); + if (servant->isActived()) { + ++m_slaveCallNum; + return servant; + } + ++callNum; + } + m_slaveCallNum = callNum; + return NULL; +} + + +static int CommandType[RedisCommand::CMD_COUNT] = {0}; +void initCommandType() +{ + for (int i = 0; i < RedisCommand::CMD_COUNT; ++i) { + // DUMP DEL DECR DECRBY EXPIREAT EXISTS EXPIRE GETSET HSET HSETNX HMSET HINCRBY + // HDEL HINCRBYFLOAT INCR INCRBY INCRBYFLOAT LPUSH LPUSHX LPOP LREM + // LINSERT LSET LTRIM MSET PSETEX PERSIST PEXPIRE + // PEXPIREAT PTTL PING RESTORE RPOP RPUSH RPUSHX SADD SREM + // SPOP SETBIT SETRANGE SET SETEX + // SETNX TTL ZADD ZREM ZINCRBY ZREMRANGEBYRANK ZREMRANGEBYSCORE + if (RedisCommand::APPEND == i || RedisCommand::BITPOS == i + || RedisCommand::DUMP == i || RedisCommand::DEL == i + || RedisCommand::DECR == i || RedisCommand::DECRBY == i + || RedisCommand::EXPIREAT == i || RedisCommand::EXISTS == i + || RedisCommand::EXPIRE == i || RedisCommand::GETSET == i + || RedisCommand::HSET == i || RedisCommand::HSETNX == i + || RedisCommand::HMSET == i || RedisCommand::HINCRBY == i + || RedisCommand::HDEL == i || RedisCommand::HINCRBYFLOAT == i + || RedisCommand::INCR == i || RedisCommand::INCRBY == i + || RedisCommand::INCRBYFLOAT == i || RedisCommand::LPUSH == i + || RedisCommand::LPUSHX == i || RedisCommand::LPOP == i + || RedisCommand::LREM == i || RedisCommand::LINSERT == i + || RedisCommand::LSET == i || RedisCommand::LTRIM == i + || RedisCommand::MSET == i || RedisCommand::PSETEX == i + || RedisCommand::PERSIST == i || RedisCommand::PEXPIRE == i + || RedisCommand::PEXPIREAT == i || RedisCommand::PTTL == i + || RedisCommand::PING == i || RedisCommand::RESTORE == i + || RedisCommand::RPOP == i || RedisCommand::RPUSH == i + || RedisCommand::RPUSHX == i || RedisCommand::SADD == i + || RedisCommand::SREM == i || RedisCommand::SPOP == i + || RedisCommand::SETBIT == i || RedisCommand::SETRANGE == i + || RedisCommand::SET == i || RedisCommand::SETEX == i + || RedisCommand::SETNX == i || RedisCommand::TTL == i + || RedisCommand::ZADD == i || RedisCommand::ZREM == i + || RedisCommand::ZINCRBY == i || RedisCommand::ZREMRANGEBYRANK == i + || RedisCommand::ZREMRANGEBYSCORE == i) + { + CommandType[i] = 1; + } else { + CommandType[i] = 0; + } + } +} + +class CommandTypeInit { +public: + CommandTypeInit() { + initCommandType(); + } +}; + +static CommandTypeInit init; + +ReadBalancePolicy::ReadBalancePolicy(void) { + m_readCnt = 0; +} + +ReadBalancePolicy::~ReadBalancePolicy(void) {} + + +RedisServant* ReadBalancePolicy::selectServant(RedisServantGroup* group, ClientPacket* packet) +{ + if (packet->commandType < 0 || packet->commandType >= RedisCommand::CMD_COUNT) { + return m_servantSelect.selectMaster(group); + } + if (1 == CommandType[packet->commandType]) { + return m_servantSelect.selectMaster(group); + } + + if (0 == (++m_readCnt % 2)){ + return m_servantSelect.selectSlave(group); + } + return m_servantSelect.selectMaster(group); +} + +RedisServant* MasterOnlyPolicy::selectServant(RedisServantGroup* group, ClientPacket*) +{ + return m_servantSelect.selectMaster(group); +} + + + diff --git a/src/redis-servant-select.h b/src/redis-servant-select.h new file mode 100644 index 0000000..50d8271 --- /dev/null +++ b/src/redis-servant-select.h @@ -0,0 +1,70 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef SELECTSERVANT_H +#define SELECTSERVANT_H + +#include "redisservantgroup.h" + +#define POLICY_READ_BALANCE "read_balance" +#define POLICY_MASTER_ONLY "master_only" + +class ServantSelect +{ +public: + ServantSelect(void); + ~ServantSelect(void); + RedisServant* selectMaster(RedisServantGroup* g); + RedisServant* selectSlave(RedisServantGroup* g); +private: + RedisServant* oneMaster(RedisServantGroup* g); + RedisServant* oneSlave(RedisServantGroup* g); +private: + unsigned int m_masterCallNum; + unsigned int m_slaveCallNum; +}; + + +class ReadBalancePolicy : public RedisServantGroupPolicy +{ +public: + ReadBalancePolicy(void); + ~ReadBalancePolicy(void); + virtual RedisServant* selectServant(RedisServantGroup* g, ClientPacket* p); +private: + ServantSelect m_servantSelect; + unsigned int m_readCnt; +}; + + +class MasterOnlyPolicy : public RedisServantGroupPolicy +{ +public: + MasterOnlyPolicy(void){} + ~MasterOnlyPolicy(void){} + virtual RedisServant* selectServant(RedisServantGroup* g, ClientPacket* p); +private: + ServantSelect m_servantSelect; +}; + + + +#endif + + diff --git a/src/redisproto.cpp b/src/redisproto.cpp new file mode 100644 index 0000000..649af4f --- /dev/null +++ b/src/redisproto.cpp @@ -0,0 +1,316 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include + +#include "redisproto.h" + +#define READ_AGAIN -2 +#define READ_ERROR -1 + +static int readTextLine(char* s, int len, int stringlen) +{ + if (len <= stringlen) { + return READ_AGAIN; + } + + if (len - stringlen == 1) { + if (s[stringlen] == '\r') { + return READ_AGAIN; + } else { + return READ_ERROR; + } + } + + if (len - stringlen >= 2) { + if (s[stringlen] == '\r' && s[stringlen+1] == '\n') { + return stringlen + 2; + } else { + return READ_ERROR; + } + } + return READ_ERROR; +} + +static int readTextEndByCRLF(char* s, int len, int* stringlen) +{ + int pos = 0; + *stringlen = 0; + while (pos < len && s[pos] != '\r') { + ++(*stringlen); + ++pos; + } + if (*stringlen == 0) { + return READ_ERROR; + } + + if (s[pos] != '\r') { + return READ_AGAIN; + } else { + if (pos < len) { + ++pos; + return (s[pos] == '\n') ? pos + 1 : READ_ERROR; + } + } + return READ_AGAIN; +} + + +static int readNumberLine(char* s, int len, int* num) +{ + int pos = 0; + *num = 0; + int signed_num = 1; + while (pos < len && s[pos] != '\r') { + if (s[pos] == '-') { + signed_num = -1; + } else if (s[pos] == '+') { + signed_num = 1; + } else if (s[pos] >= '0' && s[pos] <= '9') { + *num = (*num * 10) + (s[pos] - '0'); + } else { + return READ_ERROR; + } + ++pos; + } + *num *= signed_num; + + if (s[pos] != '\r') { + return READ_AGAIN; + } else { + if (pos < len) { + ++pos; + return (s[pos] == '\n') ? pos + 1 : READ_ERROR; + } + } + return READ_AGAIN; +} + +static int readBulk(char* s, int len, Token* tok) +{ + int pos = 0; + if (s[pos++] != '$') { + return READ_ERROR; + } + + if (pos < len) { + char* str = NULL; + int stringlen = 0; + int ret = readNumberLine(s + pos, len - pos, &stringlen); + if (ret < 0) { + return ret; + } + + pos += ret; + if (pos == len) { + if (stringlen <= 0) { + return pos; + } else { + return READ_AGAIN; + } + } + + str = s + pos; + ret = readTextLine(s + pos, len - pos, stringlen); + if (ret < 0) { + return ret; + } + pos += ret; + tok->s = str; + tok->len = stringlen; + return pos; + } else { + return READ_AGAIN; + } +} + +static int readMultiBulk(char* s, int len, Token* toks, int* cnt) +{ + int pos = 0; + if (s[pos++] != '*') { + return READ_ERROR; + } + int lines = 0; + int argc = 0; + int ret = 0; + if (pos < len) { + ret = readNumberLine(s + pos, len - pos, &argc); + if (ret < 0) { + return ret; + } + pos += ret; + while (pos < len && (lines != argc)) { + Token* tok = toks + lines; + ret = readBulk(s + pos, len - pos, tok); + if (ret < 0) { + return ret; + } + ++lines; + pos += ret; + } + if (lines != argc) { + return READ_AGAIN; + } + *cnt = lines; + return pos; + } else { + return READ_AGAIN; + } +} + +static int readStatus(char* s, int len, Token* tok) +{ + int pos = 0; + if (s[pos++] != '+') { + return READ_ERROR; + } + + if (pos < len) { + char* str = s + pos; + int stringlen = 0; + int ret = readTextEndByCRLF(s + pos, len - pos, &stringlen); + if (ret < 0) { + return ret; + } + tok->s = str; + tok->len = stringlen; + pos += ret; + return pos; + } else { + return READ_AGAIN; + } +} + +static int readError(char* s, int len, Token* tok) +{ + int pos = 0; + if (s[pos++] != '-') { + return READ_ERROR; + } + + if (pos < len) { + int stringlen = 0; + char* str = s + pos; + int ret = readTextEndByCRLF(s + pos, len - pos, &stringlen); + if (ret < 0) { + return ret; + } + tok->s = str; + tok->len = stringlen; + pos += ret; + return pos; + } else { + return READ_AGAIN; + } +} + +static int readInteger(char* s, int len, int* num) +{ + int pos = 0; + if (s[pos++] != ':') { + return READ_ERROR; + } + if (pos < len) { + int ret = readNumberLine(s + pos, len - pos, num); + if (ret < 0) { + return ret; + } + pos += ret; + return pos; + } else { + return READ_AGAIN; + } +} + + + +RedisProto::RedisProto(void) +{ +} + +RedisProto::~RedisProto(void) +{ +} + +void RedisProto::outputProtoString(const char* s, int len) +{ + char buff[10240]; + int offs = 0; + for (int i = 0; i < len; ++i) { + switch (s[i]) { + case '\r': + buff[offs++]='\\'; + buff[offs++] = 'r'; + break; + case '\n': + buff[offs++]='\\'; + buff[offs++] = 'n'; + break; + default: + buff[offs++] = s[i]; + break; + } + buff[offs] = '\0'; + } + printf("%s\n", buff); +} + +RedisProto::ParseState RedisProto::parse(char *s, int len, RedisProtoParseResult *result) +{ + int ret = 0; + switch (s[0]) { + case '+': + result->type = RedisProtoParseResult::Status; + ret = readStatus(s, len, &(result->tokens[0])); + break; + case '-': + result->type = RedisProtoParseResult::Error; + ret = readError(s, len, &(result->tokens[0])); + break; + case ':': + result->type = RedisProtoParseResult::Integer; + ret = readInteger(s, len, &result->integer); + break; + case '$': + result->type = RedisProtoParseResult::Bulk; + ret = readBulk(s, len, &(result->tokens[0])); + break; + case '*': + result->type = RedisProtoParseResult::MultiBulk; + ret = readMultiBulk(s, len, result->tokens, &result->tokenCount); + break; + default: + result->type = RedisProtoParseResult::Unknown; + ret = READ_ERROR; + break; + } + + switch (ret) { + case READ_AGAIN: + return ProtoIncomplete; + case READ_ERROR: + return ProtoError; + default: + result->protoBuff = s; + result->protoBuffLen = ret; + return ProtoOK; + } +} + + diff --git a/src/redisproto.h b/src/redisproto.h new file mode 100644 index 0000000..49dced3 --- /dev/null +++ b/src/redisproto.h @@ -0,0 +1,75 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef REDISPROTO_H +#define REDISPROTO_H + +struct Token { + char* s; + int len; +}; + +class RedisProtoParseResult +{ +public: + enum { MaxToken = 1024 }; + enum Type { + Unknown = 0, //????? + Status, //"+" + Error, //"-" + Integer, //":" + Bulk, //"$" + MultiBulk //"*" + }; + RedisProtoParseResult(void) { reset(); } + ~RedisProtoParseResult(void) {} + + void reset(void) { + protoBuff = 0; + protoBuffLen = 0; + type = Unknown; + integer = 0; + tokenCount = 0; + } + + char* protoBuff; + int protoBuffLen; + int type; + int integer; + Token tokens[MaxToken]; + int tokenCount; +}; + +class RedisProto +{ +public: + enum ParseState { + ProtoOK = 0, + ProtoError = -1, + ProtoIncomplete = -2 + }; + + RedisProto(void); + ~RedisProto(void); + + static void outputProtoString(const char* s, int len); + static ParseState parse(char* s, int len, RedisProtoParseResult* result); +}; + +#endif diff --git a/src/redisproxy.cpp b/src/redisproxy.cpp new file mode 100644 index 0000000..e16b0f4 --- /dev/null +++ b/src/redisproxy.cpp @@ -0,0 +1,384 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "command.h" +#include "cmdhandler.h" +#include "non-portable.h" +#include "redisservant.h" +#include "redisproxy.h" +#include "redis-proxy-config.h" + +ClientPacket::ClientPacket(void) +{ + commandType = -1; + recvBufferOffset = 0; + sendBufferOffset = 0; + finishedState = 0; + sendToRedisBytes = 0; + requestServant = NULL; + redisSocket = NULL; + finished_func = defaultFinishedHandler; +} + +ClientPacket::~ClientPacket(void) +{ +} + +void ClientPacket::setFinishedState(ClientPacket::State state) +{ + finishedState = state; + finished_func(this, finished_arg); +} + +RedisProto::ParseState ClientPacket::parseRecvBuffer(void) +{ + recvParseResult.reset(); + RedisProto::ParseState state = RedisProto::parse(recvBuff.data() + recvBufferOffset, + recvBuff.size() - recvBufferOffset, + &recvParseResult); + if (state == RedisProto::ProtoOK) { + recvBufferOffset += recvParseResult.protoBuffLen; + } + return state; +} + +RedisProto::ParseState ClientPacket::parseSendBuffer(void) +{ + sendParseResult.reset(); + RedisProto::ParseState state = RedisProto::parse(sendBuff.data() + sendBufferOffset, + sendBuff.size() - sendBufferOffset, + &sendParseResult); + if (state == RedisProto::ProtoOK) { + sendBufferOffset += sendParseResult.protoBuffLen; + } + return state; +} + +void ClientPacket::defaultFinishedHandler(ClientPacket *packet, void *) +{ + switch (packet->finishedState) { + case ClientPacket::Unknown: + packet->sendBuff.append("-Unknown state\r\n"); + break; + case ClientPacket::ProtoError: + packet->sendBuff.append("-Proto error\r\n"); + break; + case ClientPacket::ProtoNotSupport: + packet->sendBuff.append("-Proto not support\r\n"); + break; + case ClientPacket::WrongNumberOfArguments: + packet->sendBuff.append("-Wrong number of arguments\r\n"); + break; + case ClientPacket::RequestError: + packet->sendBuff.append("-Request error\r\n"); + break; + case ClientPacket::RequestFinished: + break; + default: + break; + } + packet->server->writeReply(packet); +} + + + +static Monitor dummy; +RedisProxy::RedisProxy(void) +{ + m_monitor = &dummy; + m_hashFunc = hashForBytes; + m_maxHashValue = DefaultMaxHashValue; + for (int i = 0; i < MaxHashValue; ++i) { + m_hashMapping[i] = NULL; + } + m_vipAddress[0] = 0; + m_vipName[0] = 0; + m_vipEnabled = false; + m_groupRetryTime = 30; + m_autoEjectGroup = false; + m_ejectAfterRestoreEnabled = false; + m_threadPoolRefCount = 0; + m_proxyManager.setProxy(this); +} + +RedisProxy::~RedisProxy(void) +{ + for (int i = 0; i < m_groups.size(); ++i) { + delete m_groups.at(i); + } +} + +bool RedisProxy::run(const HostAddress& addr) +{ + if (isRunning()) { + return false; + } + + if (m_vipEnabled) { + TcpSocket sock = TcpSocket::createTcpSocket(); + Logger::log(Logger::Message, "connect to vip address(%s:%d)...", m_vipAddress, addr.port()); + if (!sock.connect(HostAddress(m_vipAddress, addr.port()))) { + Logger::log(Logger::Message, "set VIP [%s,%s]...", m_vipName, m_vipAddress); + int ret = NonPortable::setVipAddress(m_vipName, m_vipAddress, 0); + Logger::log(Logger::Message, "set_vip_address return %d", ret); + } else { + m_vipSocket = sock; + m_vipEvent.set(eventLoop(), sock.socket(), EV_READ, vipHandler, this); + m_vipEvent.active(); + } + } + + + m_monitor->proxyStarted(this); + Logger::log(Logger::Message, "Start the %s on port %d", APP_NAME, addr.port()); + + RedisCommand cmds[] = { + {"HASHMAPPING", 11, -1, onHashMapping, NULL}, + {"ADDKEYMAPPING", 13, -1, onAddKeyMapping, NULL}, + {"DELKEYMAPPING", 13, -1, onDelKeyMapping, NULL}, + {"SHOWMAPPING", 11, -1, onShowMapping, NULL}, + {"POOLINFO", 8, -1, onPoolInfo, NULL}, + {"SHUTDOWN", 8, -1, onShutDown, this} + }; + RedisCommandTable::instance()->registerCommand(cmds, sizeof(cmds)/sizeof(RedisCommand)); + + return TcpServer::run(addr); +} + +void RedisProxy::stop(void) +{ + TcpServer::stop(); + if (m_vipEnabled) { + if (!m_vipSocket.isNull()) { + Logger::log(Logger::Message, "delete vip..."); + int ret = NonPortable::setVipAddress(m_vipName, m_vipAddress, 1); + Logger::log(Logger::Message, "delete vip return %d", ret); + + m_vipEvent.remove(); + m_vipSocket.close(); + } + } + Logger::log(Logger::Message, "%s has stopped", APP_NAME); +} + +void RedisProxy::addRedisGroup(RedisServantGroup *group) +{ + if (group) { + group->setGroupId(m_groups.size()); + m_groups.append(group); + } +} + +bool RedisProxy::setGroupMappingValue(int hashValue, RedisServantGroup *group) +{ + if (hashValue >= 0 && hashValue < MaxHashValue) { + m_hashMapping[hashValue] = group; + return true; + } + return false; +} + +RedisServantGroup *RedisProxy::hashForGroup(int hashValue) const +{ + if (hashValue >= 0 && hashValue < m_maxHashValue) { + return m_hashMapping[hashValue]; + } + return NULL; +} + +RedisServantGroup *RedisProxy::group(const char *name) const +{ + for (int i = 0; i < groupCount(); ++i) { + RedisServantGroup* p = group(i); + if (strcmp(name, p->groupName()) == 0) { + return p; + } + } + return NULL; +} + +RedisServantGroup *RedisProxy::mapToGroup(const char* key, int len) +{ + if (!m_keyMapping.empty()) { + String _key(key, len, false); + StringMap::iterator it = m_keyMapping.find(_key); + if (it != m_keyMapping.end()) { + return it->second; + } + } + + unsigned int hash_val = m_hashFunc(key, len); + unsigned int idx = hash_val % m_maxHashValue; + return m_hashMapping[idx]; +} + +void RedisProxy::handleClientPacket(const char *key, int len, ClientPacket *packet) +{ + RedisServantGroup* group = mapToGroup(key, len); + if (!group) { + packet->setFinishedState(ClientPacket::RequestError); + return; + } + RedisServant* servant = group->findUsableServant(packet); + if (servant) { + servant->handle(packet); + } else { + if (m_autoEjectGroup) { + m_groupMutex.lock(); + m_proxyManager.setGroupTTL(group, m_groupRetryTime, m_ejectAfterRestoreEnabled); + m_groupMutex.unlock(); + } + packet->setFinishedState(ClientPacket::RequestError); + } +} + +bool RedisProxy::addGroupKeyMapping(const char *key, int len, RedisServantGroup *group) +{ + if (key && len > 0 && group) { + m_keyMapping.insert(StringMap::value_type(String(key, len, true), group)); + return true; + } + return false; +} + +void RedisProxy::removeGroupKeyMapping(const char *key, int len) +{ + if (key && len > 0) { + m_keyMapping.erase(String(key, len)); + } +} + + +Context *RedisProxy::createContextObject(void) +{ + ClientPacket* packet = new ClientPacket; + + EventLoop* loop; + if (m_eventLoopThreadPool) { + int threadCount = m_eventLoopThreadPool->size(); + EventLoopThread* loopThread = m_eventLoopThreadPool->thread(m_threadPoolRefCount % threadCount); + ++m_threadPoolRefCount; + loop = loopThread->eventLoop(); + } else { + loop = eventLoop(); + } + + packet->eventLoop = loop; + return packet; +} + +void RedisProxy::destroyContextObject(Context *c) +{ + delete c; +} + +void RedisProxy::closeConnection(Context* c) +{ + ClientPacket* packet = (ClientPacket*)c; + m_monitor->clientDisconnected(packet); + TcpServer::closeConnection(c); +} + +void RedisProxy::clientConnected(Context* c) +{ + ClientPacket* packet = (ClientPacket*)c; + m_monitor->clientConnected(packet); +} + +TcpServer::ReadStatus RedisProxy::readingRequest(Context *c) +{ + ClientPacket* packet = (ClientPacket*)c; + switch (packet->parseRecvBuffer()) { + case RedisProto::ProtoError: + return ReadError; + case RedisProto::ProtoIncomplete: + return ReadIncomplete; + case RedisProto::ProtoOK: + return ReadFinished; + default: + return ReadError; + } +} + +void RedisProxy::readRequestFinished(Context *c) +{ + ClientPacket* packet = (ClientPacket*)c; + RedisProtoParseResult& r = packet->recvParseResult; + char* cmd = r.tokens[0].s; + int len = r.tokens[0].len; + + RedisCommandTable* cmdtable = RedisCommandTable::instance(); + cmdtable->execCommand(cmd, len, packet); +} + +void RedisProxy::writeReply(Context *c) +{ + ClientPacket* packet = (ClientPacket*)c; + if (!packet->isRecvParseEnd()) { + switch (packet->parseRecvBuffer()) { + case RedisProto::ProtoError: + closeConnection(c); + break; + case RedisProto::ProtoIncomplete: + waitRequest(c); + break; + case RedisProto::ProtoOK: + readRequestFinished(c); + break; + default: + break; + } + } else { + TcpServer::writeReply(c); + } +} + +void RedisProxy::writeReplyFinished(Context *c) +{ + ClientPacket* packet = (ClientPacket*)c; + m_monitor->replyClientFinished(packet); + packet->finishedState = ClientPacket::Unknown; + packet->commandType = -1; + packet->sendBuff.clear(); + packet->recvBuff.clear(); + packet->sendBytes = 0; + packet->recvBytes = 0; + packet->sendToRedisBytes = 0; + packet->requestServant = NULL; + packet->redisSocket = NULL; + packet->recvBufferOffset = 0; + packet->sendBufferOffset = 0; + packet->sendParseResult.reset(); + packet->recvParseResult.reset(); + waitRequest(c); +} + +void RedisProxy::vipHandler(socket_t sock, short, void* arg) +{ + char buff[64]; + RedisProxy* proxy = (RedisProxy*)arg; + int ret = ::recv(sock, buff, 64, 0); + if (ret == 0) { + Logger::log(Logger::Message, "disconnected from VIP. change vip address..."); + int ret = NonPortable::setVipAddress(proxy->m_vipName, proxy->m_vipAddress, 0); + Logger::log(Logger::Message, "set_vip_address return %d", ret); + proxy->m_vipSocket.close(); + } +} diff --git a/src/redisproxy.h b/src/redisproxy.h new file mode 100644 index 0000000..94d5b4e --- /dev/null +++ b/src/redisproxy.h @@ -0,0 +1,182 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef REDISPROXY_H +#define REDISPROXY_H + +//Application version +#define APP_VERSION "3.0" +#define APP_NAME "OneCache" +#define APP_EXIT_KEY 10 + +#include "util/tcpserver.h" +#include "util/locker.h" + +#include "command.h" +#include "redisproto.h" +#include "redisservantgroup.h" +#include "proxymanager.h" + +class RedisConnection; +class RedisServant; +class RedisProxy; +class ClientPacket : public Context +{ +public: + enum State { + Unknown = 0, + ProtoError = 1, + ProtoNotSupport = 2, + WrongNumberOfArguments = 3, + RequestError = 4, + RequestFinished = 5 + }; + + ClientPacket(void); + ~ClientPacket(void); + + void setFinishedState(State state); + RedisProxy* proxy(void) const { return (RedisProxy*)server; } + RedisProto::ParseState parseRecvBuffer(void); + RedisProto::ParseState parseSendBuffer(void); + bool isRecvParseEnd(void) const + { return (recvBufferOffset == recvBuff.size()); } + + static void defaultFinishedHandler(ClientPacket *packet, void*); + + int finishedState; //Finished state + void* finished_arg; //Finished function arg + void (*finished_func)(ClientPacket*, void*); //Finished notify function + int commandType; //Current command type + int recvBufferOffset; //Current request buffer offset + int sendBufferOffset; //Current reply buffer offset + RedisProtoParseResult recvParseResult; //Request parse result + RedisProtoParseResult sendParseResult; //Reply parse result + int sendToRedisBytes; //Send to redis bytes + RedisServant* requestServant; //Object of request + RedisConnection* redisSocket; //Redis socket +}; + +class Monitor +{ +public: + Monitor(void) {} + virtual ~Monitor(void) {} + + virtual void proxyStarted(RedisProxy*) {} + virtual void clientConnected(ClientPacket*) {} + virtual void clientDisconnected(ClientPacket*) {} + virtual void replyClientFinished(ClientPacket*) {} +}; + +class RedisProxy : public TcpServer +{ +public: + enum { + DefaultPort = 8221, + + MaxHashValue = 1024, + DefaultMaxHashValue = 128, + }; + + RedisProxy(void); + ~RedisProxy(void); + +public: + void setEventLoopThreadPool(EventLoopThreadPool* pool) + { m_eventLoopThreadPool = pool; } + + EventLoopThreadPool* eventLoopThreadPool(void) + { return m_eventLoopThreadPool; } + + bool vipEnabled(void) const { return m_vipEnabled; } + const char* vipName(void) const { return m_vipName; } + const char* vipAddress(void) const { return m_vipAddress; } + void setVipName(const char* name) { strcpy(m_vipName, name); } + void setVipAddress(const char* address) { strcpy(m_vipAddress, address); } + void setVipEnabled(bool b) { m_vipEnabled = b; } + void setGroupRetryTime(int seconds) { m_groupRetryTime = seconds; } + void setAutoEjectGroupEnabled(bool b) { m_autoEjectGroup = b; } + void setEjectAfterRestoreEnabled(bool b) + { m_ejectAfterRestoreEnabled = b; } + + void setMonitor(Monitor* monitor) { m_monitor = monitor; } + Monitor* monitor(void) const { return m_monitor; } + + bool run(const HostAddress &addr); + void stop(void); + + void addRedisGroup(RedisServantGroup* group); + bool setGroupMappingValue(int hashValue, RedisServantGroup* group); + void setHashFunction(HashFunc func) { m_hashFunc = func; } + void setMaxHashValue(int value) { m_maxHashValue = value; } + + HashFunc hashFunction(void) const { return m_hashFunc; } + int maxHashValue(void) const { return m_maxHashValue; } + RedisServantGroup* hashForGroup(int hashValue) const; + + int groupCount(void) const { return m_groups.size(); } + RedisServantGroup* group(int index) const { return m_groups.at(index); } + RedisServantGroup* group(const char* name) const; + RedisServantGroup* mapToGroup(const char* key, int len); + void handleClientPacket(const char* key, int len, ClientPacket* packet); + + bool addGroupKeyMapping(const char* key, int len, RedisServantGroup* group); + void removeGroupKeyMapping(const char* key, int len); + + StringMap& keyMapping(void) { return m_keyMapping; } + + virtual Context* createContextObject(void); + virtual void destroyContextObject(Context* c); + virtual void closeConnection(Context* c); + virtual void clientConnected(Context* c); + virtual ReadStatus readingRequest(Context* c); + virtual void readRequestFinished(Context* c); + virtual void writeReply(Context* c); + virtual void writeReplyFinished(Context* c); + +private: + static void vipHandler(socket_t, short, void*); + +private: + Monitor* m_monitor; + HashFunc m_hashFunc; + int m_maxHashValue; + RedisServantGroup* m_hashMapping[MaxHashValue]; + Vector m_groups; + TcpSocket m_vipSocket; + char m_vipName[256]; + char m_vipAddress[256]; + Event m_vipEvent; + bool m_vipEnabled; + int m_groupRetryTime; + bool m_autoEjectGroup; + bool m_ejectAfterRestoreEnabled; + StringMap m_keyMapping; + unsigned int m_threadPoolRefCount; + EventLoopThreadPool* m_eventLoopThreadPool; + Mutex m_groupMutex; + ProxyManager m_proxyManager; + +private: + RedisProxy(const RedisProxy&); + RedisProxy& operator =(const RedisProxy&); +}; + +#endif diff --git a/src/redisservant.cpp b/src/redisservant.cpp new file mode 100644 index 0000000..b38988c --- /dev/null +++ b/src/redisservant.cpp @@ -0,0 +1,371 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "redisproxy.h" +#include "redisservant.h" + +RedisConnection::RedisConnection(void) +{ +} + +RedisConnection::~RedisConnection(void) +{ + disconnect(); +} + +bool RedisConnection::connect(const HostAddress& addr) +{ + timeval defaultVal; + socketlen_t len = sizeof(timeval); + TcpSocket sock = TcpSocket::createTcpSocket(); + if (sock.isNull()) { + Logger::log(Logger::Error, "RedisConnection::connect: %s", strerror(errno)); + return false; + } + + sock.option(SOL_SOCKET, SO_SNDTIMEO, (char*)&defaultVal, &len); + + timeval sndTimeout = {1, 0}; + sock.setOption(SOL_SOCKET, SO_SNDTIMEO, (char*)&sndTimeout, sizeof(timeval)); + if (!sock.connect(addr)) { + Logger::log(Logger::Error, "RedisConnection::connect: %s", strerror(errno)); + sock.close(); + return false; + } + + sock.setOption(SOL_SOCKET, SO_SNDTIMEO, (char*)&defaultVal, sizeof(timeval)); + sock.setNonBlocking(); + sock.setNoDelay(); + sock.setKeepAlive(); + m_socket = sock; + return true; +} + +void RedisConnection::disconnect(void) +{ + m_socket.close(); +} + + + + +RedisConnectionPool::RedisConnectionPool(void) +{ + m_activeConnNums = 0; + m_capacity = 0; +} + +RedisConnectionPool::~RedisConnectionPool(void) +{ + close(); +} + +bool RedisConnectionPool::open(const HostAddress& addr, int capacity) +{ + close(); + Logger::log(Logger::Message, "Create connection pool (%s:%d)...", + addr.ip(), addr.port()); + + if (capacity <= 0) { + Logger::log(Logger::Error, "Create failed: capacity parameter error"); + return false; + } + + m_redisAddress = addr; + m_capacity = capacity; + + for (int i = 0; i < m_capacity; ++i) { + RedisConnection* sock = new RedisConnection; + if (!sock->connect(addr)) { + delete sock; + close(); + return false; + } else { + m_pool.push_back(sock); + } + } + Logger::log(Logger::Message, "Creating successful. nums: %d", m_pool.size()); + return true; +} + +RedisConnection *RedisConnectionPool::select(void) +{ + m_locker.lock(); + RedisConnection* sock = m_pool.pop_back(NULL); + if (sock) { + ++m_activeConnNums; + } else { + if ((m_pool.size() + m_activeConnNums) < m_capacity) { + sock = new RedisConnection; + if (!sock->connect(m_redisAddress)) { + delete sock; + sock = NULL; + } else { + ++m_activeConnNums; + } + } + } + m_locker.unlock(); + return sock; +} + +void RedisConnectionPool::unSelect(RedisConnection *sock) +{ + m_locker.lock(); + m_pool.push_back(sock); + --m_activeConnNums; + m_locker.unlock(); +} + +bool RedisConnectionPool::repairSocket(RedisConnection *sock) +{ + sock->disconnect(); + return sock->connect(m_redisAddress); +} + +void RedisConnectionPool::free(RedisConnection *sock) +{ + delete sock; + m_locker.lock(); + --m_activeConnNums; + m_locker.unlock(); +} + +void RedisConnectionPool::close(void) +{ + m_locker.lock(); + while (1) { + RedisConnection* sock = m_pool.pop_back(NULL); + if (sock != NULL) { + delete sock; + } else { + break; + } + } + m_locker.unlock(); +} + + + +RedisServant::RedisServant(void) +{ + m_loop = NULL; + m_reconnCount = 0; + m_actived = false; + m_reconnectEnabled = true; +} + +RedisServant::~RedisServant(void) +{ + stop(); + + if (m_connListener.isActived()) { + m_connListener.disconnect(); + m_connEvent.remove(); + } +} + +bool RedisServant::start(void) +{ + if (m_actived) { + return true; + } + + if (!m_connPool.open(m_redisAddress, m_option.poolSize)) { + return false; + } + + if (m_connListener.connect(m_redisAddress)) { + m_connEvent.set(m_loop, m_connListener.m_socket.socket(), EV_READ, onDisconnected, this); + m_connEvent.active(); + } + m_actived = true; + return true; +} + +void RedisServant::stop(void) +{ + m_locker.lock(); + m_connPool.close(); + while (1) { + ClientPacket* packet = m_requests.take(NULL); + if (packet != NULL) { + packet->setFinishedState(ClientPacket::RequestError); + } else { + break; + } + } + m_actived = false; + m_locker.unlock(); +} + + +void RedisServant::handle(ClientPacket* packet) +{ + packet->requestServant = this; + RedisConnection* sock = m_connPool.select(); + if (sock == NULL) { + m_locker.lock(); + if (m_actived) { + m_requests.append(packet); + m_locker.unlock(); + } else { + m_locker.unlock(); + packet->setFinishedState(ClientPacket::RequestError); + } + } else { + packet->redisSocket = sock; + onSendRequest(sock->m_socket.socket(), 0, packet); + } +} + +void RedisServant::onRedisSocketUseCompleted(RedisConnection* sock) +{ + m_locker.lock(); + ClientPacket* packet = m_requests.take(NULL); + m_locker.unlock(); + if (!packet) { + m_connPool.unSelect(sock); + } else { + packet->redisSocket = sock; + onSendRequest(sock->m_socket.socket(), 0, packet); + } +} + +void RedisServant::onReconnect(socket_t, short, void* arg) +{ + RedisServant* servant = (RedisServant*)arg; + if (!servant->m_reconnectEnabled) { + return; + } + + RedisServant::Option opt = servant->option(); + if (servant->m_reconnCount >= opt.maxReconnCount) { + Logger::log(Logger::Message, "Stop the reconnection"); + return; + } + + ++servant->m_reconnCount; + Logger::log(Logger::Message, "(%d) Reconnect to redis...", servant->m_reconnCount); + if (!servant->start()) { + servant->m_connEvent.setTimer(servant->m_loop, onReconnect, servant); + servant->m_connEvent.active(opt.reconnInterval * 1000); + Logger::log(Logger::Message, "After %d second(s) reconnection...", opt.reconnInterval); + } else { + servant->m_reconnCount = 0; + } +} + +void RedisServant::onDisconnected(socket_t sock, short, void *arg) +{ + char buff[32]; + int len = recv(sock, buff, sizeof(buff), 0); + if (len == 0) { + RedisServant* servant = (RedisServant*)arg; + servant->stop(); + + Logger::log(Logger::Warning, "Redis (%s:%d) disconnected", + servant->redisAddress().ip(), + servant->redisAddress().port()); + + servant->m_connListener.disconnect(); + onReconnect(0, 0, servant); + } +} + +void RedisServant::onSendRequest(socket_t sock, short, void *arg) +{ + ClientPacket* packet = (ClientPacket*)arg; + RedisConnection* redisSocket = packet->redisSocket; + RedisServant* redisServant = packet->requestServant; + + char* sendBuff = packet->recvParseResult.protoBuff + packet->sendToRedisBytes; + int sendSize = packet->recvParseResult.protoBuffLen - packet->sendToRedisBytes; + + TcpSocket socket(sock); + int ret = socket.nonblocking_send(sendBuff, sendSize); + switch (ret) { + default: + packet->sendToRedisBytes += ret; + if (packet->sendToRedisBytes != packet->recvParseResult.protoBuffLen) { + onSendRequest(sock, 0, packet); + } else { + packet->_event.set(packet->eventLoop, sock, EV_READ, onRecvReply, packet); + packet->_event.active(); + } + break; + case TcpSocket::IOAgain: + packet->_event.set(packet->eventLoop, sock, EV_WRITE, onSendRequest, packet); + packet->_event.active(); + break; + case TcpSocket::IOError: + if (!redisServant->m_connPool.repairSocket(redisSocket)) { + redisServant->m_connPool.free(redisSocket); + } else { + redisServant->onRedisSocketUseCompleted(redisSocket); + } + packet->setFinishedState(ClientPacket::RequestError); + break; + } +} + +void RedisServant::onRecvReply(socket_t sock, short, void *arg) +{ + ClientPacket* packet = (ClientPacket*)arg; + RedisConnection* redisSocket = packet->redisSocket; + RedisServant* redisServant = packet->requestServant; + IOBuffer& sendbuf = packet->sendBuff; + IOBuffer::DirectCopy cp = sendbuf.beginCopy(); + TcpSocket socket(sock); + int ret = socket.nonblocking_recv(cp.address, cp.maxsize); + switch (ret) { + default: + sendbuf.endCopy(ret); + switch (packet->parseSendBuffer()) { + case RedisProto::ProtoError: + redisServant->onRedisSocketUseCompleted(redisSocket); + packet->setFinishedState(ClientPacket::RequestError); + break; + case RedisProto::ProtoIncomplete: + onRecvReply(sock, 0, packet); + break; + case RedisProto::ProtoOK: + redisServant->onRedisSocketUseCompleted(redisSocket); + packet->setFinishedState(ClientPacket::RequestFinished); + break; + default: + break; + } + break; + case TcpSocket::IOAgain: + packet->_event.set(packet->eventLoop, sock, EV_READ, onRecvReply, packet); + packet->_event.active(); + break; + case TcpSocket::IOError: + if (!redisServant->m_connPool.repairSocket(redisSocket)) { + redisServant->m_connPool.free(redisSocket); + } else { + redisServant->onRedisSocketUseCompleted(redisSocket); + } + packet->setFinishedState(ClientPacket::RequestError); + break; + } +} + diff --git a/src/redisservant.h b/src/redisservant.h new file mode 100644 index 0000000..bc7f156 --- /dev/null +++ b/src/redisservant.h @@ -0,0 +1,142 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef REDISSERVANT_H +#define REDISSERVANT_H + +#include "util/vector.h" +#include "util/queue.h" +#include "util/locker.h" +#include "util/tcpsocket.h" + +#include "eventloop.h" + +class ClientPacket; +class RedisConnection +{ +public: + RedisConnection(void); + ~RedisConnection(void); + + bool connect(const HostAddress& addr); + bool isActived(void) const { return !m_socket.isNull(); } + void disconnect(void); + +private: + TcpSocket m_socket; + friend class RedisConnectionPool; + friend class RedisServant; +}; + + +class RedisConnectionPool +{ +public: + RedisConnectionPool(void); + ~RedisConnectionPool(void); + + const HostAddress& redisAddress(void) const { return m_redisAddress; } + int capacity(void) const { return m_capacity; } + int activeConnectionNums(void) const { return m_activeConnNums; } + int unActiveConnectionNums(void) const { return m_pool.size(); } + + bool open(const HostAddress& addr, int capacity); + RedisConnection* select(void); + void unSelect(RedisConnection* sock); + bool repairSocket(RedisConnection* sock); + void free(RedisConnection* sock); + void close(void); + +private: + HostAddress m_redisAddress; + SpinLocker m_locker; + int m_capacity; + int m_activeConnNums; + Vector m_pool; +}; + +class RedisServant +{ +public: + struct Option { + Option(void) { + name[0] = 0; + maxReconnCount = 100; + reconnInterval = 1; + poolSize = 50; + } + ~Option(void) {} + + char name[64]; + int reconnInterval; + int maxReconnCount; + int poolSize; + }; + + RedisServant(void); + ~RedisServant(void); + +public: + void setRedisAddress(const HostAddress& addr) { m_redisAddress = addr; } + const HostAddress& redisAddress(void) const { return m_redisAddress; } + + void setOption(const Option& opt) { m_option = opt; } + Option option(void) const { return m_option; } + + void setReconnectEnabled(bool b) { m_reconnectEnabled = b; } + bool reconnectEnabled(void) const { return m_reconnectEnabled; } + + void setEventLoop(EventLoop* loop) { m_loop = loop; } + EventLoop* eventLoop(void) const { return m_loop; } + + RedisConnectionPool* connectionPool(void) const + { return (RedisConnectionPool*)&m_connPool; } + + bool isActived(void) const { return m_actived; } + bool start(void); + void stop(void); + + void handle(ClientPacket* packet); + +private: + void onRedisSocketUseCompleted(RedisConnection* sock); + static void onDisconnected(socket_t sock, short, void* arg); + static void onReconnect(socket_t sock, short, void* arg); + static void onSendRequest(socket_t sock, short, void* arg); + static void onRecvReply(socket_t sock, short, void* arg); + +private: + HostAddress m_redisAddress; + int m_reconnCount; + Event m_connEvent; + RedisConnection m_connListener; + EventLoop* m_loop; + Option m_option; + Queue m_requests; + SpinLocker m_locker; + bool m_actived; + bool m_reconnectEnabled; + RedisConnectionPool m_connPool; + +private: + RedisServant(const RedisServant&); + RedisServant& operator =(const RedisServant&); +}; + +#endif diff --git a/src/redisservantgroup.cpp b/src/redisservantgroup.cpp new file mode 100644 index 0000000..7349698 --- /dev/null +++ b/src/redisservantgroup.cpp @@ -0,0 +1,154 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "util/logger.h" +#include "redisproxy.h" +#include "redisservant.h" +#include "redisservantgroup.h" +#include "redis-servant-select.h" + + +RedisServantGroupPolicy::RedisServantGroupPolicy(void) +{ +} + +RedisServantGroupPolicy::~RedisServantGroupPolicy(void) +{ +} + +RedisServant *RedisServantGroupPolicy::selectServant(RedisServantGroup*, ClientPacket*) +{ + return NULL; +} + +RedisServantGroupPolicy *RedisServantGroupPolicy::createPolicy(const char *name) +{ + if (name == NULL) { + return new MasterOnlyPolicy; + } + if (strcmp(name, POLICY_MASTER_ONLY) == 0) { + return new MasterOnlyPolicy; + } else if (strcmp(name, POLICY_READ_BALANCE) == 0) { + return new ReadBalancePolicy; + } else { + return new MasterOnlyPolicy; + } +} + + + + +RedisServantGroup::RedisServantGroup(void) +{ + m_groupId = -1; + m_masterCount = 0; + m_slaveCount = 0; + m_policy = NULL; +} + +RedisServantGroup::~RedisServantGroup(void) +{ + for (int i = 0; i < masterCount(); ++i) { + delete master(i); + } + + for (int i = 0; i < slaveCount(); ++i) { + delete slave(i); + } + + if (m_policy) { + delete m_policy; + } +} + +void RedisServantGroup::setGroupName(const char *name) +{ + if (name) { + strcpy(m_name, name); + } +} + +void RedisServantGroup::setPolicy(RedisServantGroupPolicy *policy) +{ + if (policy) { + if (m_policy) { + delete m_policy; + } + m_policy = policy; + } +} + +void RedisServantGroup::addMasterRedisServant(RedisServant *servant) +{ + if (servant) { + if (m_masterCount >= MaxServantCount) { + return; + } + m_master[m_masterCount] = servant; + ++m_masterCount; + } +} + +void RedisServantGroup::addSlaveRedisServant(RedisServant *servant) +{ + if (servant) { + if (m_slaveCount >= MaxServantCount) { + return; + } + m_slaver[m_slaveCount] = servant; + ++m_slaveCount; + } +} + +void RedisServantGroup::setEnabled(bool b) +{ + for (int i = 0; i < m_masterCount; ++i) { + if (b) { + m_master[i]->start(); + } else { + m_master[i]->stop(); + } + } + + for (int i = 0; i < m_slaveCount; ++i) { + if (b) { + m_slaver[i]->start(); + } else { + m_slaver[i]->stop(); + } + } +} + +bool RedisServantGroup::isEnabled(void) const +{ + for (int i = 0; i < m_masterCount; ++i) { + if (m_master[i]->isActived()) { + return true; + } + } + + for (int i = 0; i < m_slaveCount; ++i) { + if (m_slaver[i]->isActived()) { + return true; + } + } + + return false; +} + diff --git a/src/redisservantgroup.h b/src/redisservantgroup.h new file mode 100644 index 0000000..df28bed --- /dev/null +++ b/src/redisservantgroup.h @@ -0,0 +1,88 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef REDISSERVANTGROUP_H +#define REDISSERVANTGROUP_H + +class ClientPacket; +class RedisServant; +class RedisServantGroup; + +class RedisServantGroupPolicy +{ +public: + RedisServantGroupPolicy(void); + virtual ~RedisServantGroupPolicy(void); + + virtual RedisServant* selectServant(RedisServantGroup* group, ClientPacket* packet); + + static RedisServantGroupPolicy* createPolicy(const char* name); +}; + + +class RedisServantGroup +{ +public: + enum { + MaxServantCount = 1024, + }; + + RedisServantGroup(void); + ~RedisServantGroup(void); + + void setGroupId(int id) { m_groupId = id; } + int groupId(void) const { return m_groupId; } + + void setGroupName(const char* name); + const char* groupName(void) const { return m_name; } + + void setPolicy(RedisServantGroupPolicy* policy); + RedisServantGroupPolicy* policy(void) const { return m_policy; } + + void addMasterRedisServant(RedisServant* servant); + void addSlaveRedisServant(RedisServant* servant); + + RedisServant* master(int index) const { return m_master[index]; } + RedisServant* slave(int index) const { return m_slaver[index]; } + + int masterCount(void) const { return m_masterCount; } + int slaveCount(void) const { return m_slaveCount; } + + void setEnabled(bool b); + bool isEnabled(void) const; + + RedisServant* findUsableServant(ClientPacket* packet) + { return m_policy->selectServant(this, packet); } + +private: + int m_groupId; + char m_name[256]; + int m_masterCount; + int m_slaveCount; + RedisServant* m_master[MaxServantCount]; + RedisServant* m_slaver[MaxServantCount]; + RedisServantGroupPolicy* m_policy; + +private: + RedisServantGroup(const RedisServantGroup&); + RedisServantGroup& operator =(const RedisServantGroup&); +}; + + +#endif diff --git a/src/tinyxml/tinystr.cpp b/src/tinyxml/tinystr.cpp new file mode 100644 index 0000000..0665768 --- /dev/null +++ b/src/tinyxml/tinystr.cpp @@ -0,0 +1,111 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TIXML_USE_STL + +#include "tinystr.h" + +// Error value for find primitive +const TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1); + + +// Null rep. +TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\0' } }; + + +void TiXmlString::reserve (size_type cap) +{ + if (cap > capacity()) + { + TiXmlString tmp; + tmp.init(length(), cap); + memcpy(tmp.start(), data(), length()); + swap(tmp); + } +} + + +TiXmlString& TiXmlString::assign(const char* str, size_type len) +{ + size_type cap = capacity(); + if (len > cap || cap > 3*(len + 8)) + { + TiXmlString tmp; + tmp.init(len); + memcpy(tmp.start(), str, len); + swap(tmp); + } + else + { + memmove(start(), str, len); + set_size(len); + } + return *this; +} + + +TiXmlString& TiXmlString::append(const char* str, size_type len) +{ + size_type newsize = length() + len; + if (newsize > capacity()) + { + reserve (newsize + capacity()); + } + memmove(finish(), str, len); + set_size(newsize); + return *this; +} + + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b) +{ + TiXmlString tmp; + tmp.reserve(a.length() + b.length()); + tmp += a; + tmp += b; + return tmp; +} + +TiXmlString operator + (const TiXmlString & a, const char* b) +{ + TiXmlString tmp; + TiXmlString::size_type b_len = static_cast( strlen(b) ); + tmp.reserve(a.length() + b_len); + tmp += a; + tmp.append(b, b_len); + return tmp; +} + +TiXmlString operator + (const char* a, const TiXmlString & b) +{ + TiXmlString tmp; + TiXmlString::size_type a_len = static_cast( strlen(a) ); + tmp.reserve(a_len + b.length()); + tmp.append(a, a_len); + tmp += b; + return tmp; +} + + +#endif // TIXML_USE_STL diff --git a/src/tinyxml/tinystr.h b/src/tinyxml/tinystr.h new file mode 100644 index 0000000..89cca33 --- /dev/null +++ b/src/tinyxml/tinystr.h @@ -0,0 +1,305 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TIXML_USE_STL + +#ifndef TIXML_STRING_INCLUDED +#define TIXML_STRING_INCLUDED + +#include +#include + +/* The support for explicit isn't that universal, and it isn't really + required - it is used to check that the TiXmlString class isn't incorrectly + used. Be nice to old compilers and macro it here: +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + #define TIXML_EXPLICIT explicit +#elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + #define TIXML_EXPLICIT explicit +#else + #define TIXML_EXPLICIT +#endif + + +/* + TiXmlString is an emulation of a subset of the std::string template. + Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. + Only the member functions relevant to the TinyXML project have been implemented. + The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase + a string and there's no more room, we allocate a buffer twice as big as we need. +*/ +class TiXmlString +{ + public : + // The size type used + typedef size_t size_type; + + // Error value for find primitive + static const size_type npos; // = -1; + + + // TiXmlString empty constructor + TiXmlString () : rep_(&nullrep_) + { + } + + // TiXmlString copy constructor + TiXmlString ( const TiXmlString & copy) : rep_(0) + { + init(copy.length()); + memcpy(start(), copy.data(), length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0) + { + init( static_cast( strlen(copy) )); + memcpy(start(), copy, length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0) + { + init(len); + memcpy(start(), str, len); + } + + // TiXmlString destructor + ~TiXmlString () + { + quit(); + } + + TiXmlString& operator = (const char * copy) + { + return assign( copy, (size_type)strlen(copy)); + } + + TiXmlString& operator = (const TiXmlString & copy) + { + return assign(copy.start(), copy.length()); + } + + + // += operator. Maps to append + TiXmlString& operator += (const char * suffix) + { + return append(suffix, static_cast( strlen(suffix) )); + } + + // += operator. Maps to append + TiXmlString& operator += (char single) + { + return append(&single, 1); + } + + // += operator. Maps to append + TiXmlString& operator += (const TiXmlString & suffix) + { + return append(suffix.data(), suffix.length()); + } + + + // Convert a TiXmlString into a null-terminated char * + const char * c_str () const { return rep_->str; } + + // Convert a TiXmlString into a char * (need not be null terminated). + const char * data () const { return rep_->str; } + + // Return the length of a TiXmlString + size_type length () const { return rep_->size; } + + // Alias for length() + size_type size () const { return rep_->size; } + + // Checks if a TiXmlString is empty + bool empty () const { return rep_->size == 0; } + + // Return capacity of string + size_type capacity () const { return rep_->capacity; } + + + // single char extraction + const char& at (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // [] operator + char& operator [] (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // find a char in a string. Return TiXmlString::npos if not found + size_type find (char lookup) const + { + return find(lookup, 0); + } + + // find a char in a string from an offset. Return TiXmlString::npos if not found + size_type find (char tofind, size_type offset) const + { + if (offset >= length()) return npos; + + for (const char* p = c_str() + offset; *p != '\0'; ++p) + { + if (*p == tofind) return static_cast< size_type >( p - c_str() ); + } + return npos; + } + + void clear () + { + //Lee: + //The original was just too strange, though correct: + // TiXmlString().swap(*this); + //Instead use the quit & re-init: + quit(); + init(0,0); + } + + /* Function to reserve a big amount of data when we know we'll need it. Be aware that this + function DOES NOT clear the content of the TiXmlString if any exists. + */ + void reserve (size_type cap); + + TiXmlString& assign (const char* str, size_type len); + + TiXmlString& append (const char* str, size_type len); + + void swap (TiXmlString& other) + { + Rep* r = rep_; + rep_ = other.rep_; + other.rep_ = r; + } + + private: + + void init(size_type sz) { init(sz, sz); } + void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; } + char* start() const { return rep_->str; } + char* finish() const { return rep_->str + rep_->size; } + + struct Rep + { + size_type size, capacity; + char str[1]; + }; + + void init(size_type sz, size_type cap) + { + if (cap) + { + // Lee: the original form: + // rep_ = static_cast(operator new(sizeof(Rep) + cap)); + // doesn't work in some cases of new being overloaded. Switching + // to the normal allocation, although use an 'int' for systems + // that are overly picky about structure alignment. + const size_type bytesNeeded = sizeof(Rep) + cap; + const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); + rep_ = reinterpret_cast( new int[ intsNeeded ] ); + + rep_->str[ rep_->size = sz ] = '\0'; + rep_->capacity = cap; + } + else + { + rep_ = &nullrep_; + } + } + + void quit() + { + if (rep_ != &nullrep_) + { + // The rep_ is really an array of ints. (see the allocator, above). + // Cast it back before delete, so the compiler won't incorrectly call destructors. + delete [] ( reinterpret_cast( rep_ ) ); + } + } + + Rep * rep_; + static Rep nullrep_; + +} ; + + +inline bool operator == (const TiXmlString & a, const TiXmlString & b) +{ + return ( a.length() == b.length() ) // optimization on some platforms + && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare +} +inline bool operator < (const TiXmlString & a, const TiXmlString & b) +{ + return strcmp(a.c_str(), b.c_str()) < 0; +} + +inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); } +inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; } +inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); } +inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); } + +inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; } +inline bool operator == (const char* a, const TiXmlString & b) { return b == a; } +inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); } +inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); } + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b); +TiXmlString operator + (const TiXmlString & a, const char* b); +TiXmlString operator + (const char* a, const TiXmlString & b); + + +/* + TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. + Only the operators that we need for TinyXML have been developped. +*/ +class TiXmlOutStream : public TiXmlString +{ +public : + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const TiXmlString & in) + { + *this += in; + return *this; + } + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const char * in) + { + *this += in; + return *this; + } + +} ; + +#endif // TIXML_STRING_INCLUDED +#endif // TIXML_USE_STL diff --git a/src/tinyxml/tinyxml.cpp b/src/tinyxml/tinyxml.cpp new file mode 100644 index 0000000..9c161df --- /dev/null +++ b/src/tinyxml/tinyxml.cpp @@ -0,0 +1,1886 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include + +#ifdef TIXML_USE_STL +#include +#include +#endif + +#include "tinyxml.h" + +FILE* TiXmlFOpen( const char* filename, const char* mode ); + +bool TiXmlBase::condenseWhiteSpace = true; + +// Microsoft compiler security +FILE* TiXmlFOpen( const char* filename, const char* mode ) +{ + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + FILE* fp = 0; + errno_t err = fopen_s( &fp, filename, mode ); + if ( !err && fp ) + return fp; + return 0; + #else + return fopen( filename, mode ); + #endif +} + +void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) +{ + int i=0; + + while( i<(int)str.length() ) + { + unsigned char c = (unsigned char) str[i]; + + if ( c == '&' + && i < ( (int)str.length() - 2 ) + && str[i+1] == '#' + && str[i+2] == 'x' ) + { + // Hexadecimal character reference. + // Pass through unchanged. + // © -- copyright symbol, for example. + // + // The -1 is a bug fix from Rob Laveaux. It keeps + // an overflow from happening if there is no ';'. + // There are actually 2 ways to exit this loop - + // while fails (error case) and break (semicolon found). + // However, there is no mechanism (currently) for + // this function to return an error. + while ( i<(int)str.length()-1 ) + { + outString->append( str.c_str() + i, 1 ); + ++i; + if ( str[i] == ';' ) + break; + } + } + else if ( c == '&' ) + { + outString->append( entity[0].str, entity[0].strLength ); + ++i; + } + else if ( c == '<' ) + { + outString->append( entity[1].str, entity[1].strLength ); + ++i; + } + else if ( c == '>' ) + { + outString->append( entity[2].str, entity[2].strLength ); + ++i; + } + else if ( c == '\"' ) + { + outString->append( entity[3].str, entity[3].strLength ); + ++i; + } + else if ( c == '\'' ) + { + outString->append( entity[4].str, entity[4].strLength ); + ++i; + } + else if ( c < 32 ) + { + // Easy pass at non-alpha/numeric/symbol + // Below 32 is symbolic. + char buf[ 32 ]; + + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); + #else + sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); + #endif + + //*ME: warning C4267: convert 'size_t' to 'int' + //*ME: Int-Cast to make compiler happy ... + outString->append( buf, (int)strlen( buf ) ); + ++i; + } + else + { + //char realc = (char) c; + //outString->append( &realc, 1 ); + *outString += (char) c; // somewhat more efficient function call. + ++i; + } + } +} + + +TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() +{ + parent = 0; + type = _type; + firstChild = 0; + lastChild = 0; + prev = 0; + next = 0; +} + + +TiXmlNode::~TiXmlNode() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } +} + + +void TiXmlNode::CopyTo( TiXmlNode* target ) const +{ + target->SetValue (value.c_str() ); + target->userData = userData; + target->location = location; +} + + +void TiXmlNode::Clear() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } + + firstChild = 0; + lastChild = 0; +} + + +TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) +{ + assert( node->parent == 0 || node->parent == this ); + assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); + + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + delete node; + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + node->parent = this; + + node->prev = lastChild; + node->next = 0; + + if ( lastChild ) + lastChild->next = node; + else + firstChild = node; // it was an empty list. + + lastChild = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) +{ + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + + return LinkEndChild( node ); +} + + +TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) +{ + if ( !beforeThis || beforeThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->next = beforeThis; + node->prev = beforeThis->prev; + if ( beforeThis->prev ) + { + beforeThis->prev->next = node; + } + else + { + assert( firstChild == beforeThis ); + firstChild = node; + } + beforeThis->prev = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) +{ + if ( !afterThis || afterThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->prev = afterThis; + node->next = afterThis->next; + if ( afterThis->next ) + { + afterThis->next->prev = node; + } + else + { + assert( lastChild == afterThis ); + lastChild = node; + } + afterThis->next = node; + return node; +} + + +TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) +{ + if ( !replaceThis ) + return 0; + + if ( replaceThis->parent != this ) + return 0; + + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = withThis.Clone(); + if ( !node ) + return 0; + + node->next = replaceThis->next; + node->prev = replaceThis->prev; + + if ( replaceThis->next ) + replaceThis->next->prev = node; + else + lastChild = node; + + if ( replaceThis->prev ) + replaceThis->prev->next = node; + else + firstChild = node; + + delete replaceThis; + node->parent = this; + return node; +} + + +bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) +{ + if ( !removeThis ) { + return false; + } + + if ( removeThis->parent != this ) + { + assert( 0 ); + return false; + } + + if ( removeThis->next ) + removeThis->next->prev = removeThis->prev; + else + lastChild = removeThis->prev; + + if ( removeThis->prev ) + removeThis->prev->next = removeThis->next; + else + firstChild = removeThis->next; + + delete removeThis; + return true; +} + +const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = firstChild; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = lastChild; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild(); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling(); + } +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild( val ); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling( val ); + } +} + + +const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = next; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = prev; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +void TiXmlElement::RemoveAttribute( const char * name ) +{ + #ifdef TIXML_USE_STL + TIXML_STRING str( name ); + TiXmlAttribute* node = attributeSet.Find( str ); + #else + TiXmlAttribute* node = attributeSet.Find( name ); + #endif + if ( node ) + { + attributeSet.Remove( node ); + delete node; + } +} + +const TiXmlElement* TiXmlNode::FirstChildElement() const +{ + const TiXmlNode* node; + + for ( node = FirstChild(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = FirstChild( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement() const +{ + const TiXmlNode* node; + + for ( node = NextSibling(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = NextSibling( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlDocument* TiXmlNode::GetDocument() const +{ + const TiXmlNode* node; + + for( node = this; node; node = node->parent ) + { + if ( node->ToDocument() ) + return node->ToDocument(); + } + return 0; +} + + +TiXmlElement::TiXmlElement (const char * _value) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} + + +#ifdef TIXML_USE_STL +TiXmlElement::TiXmlElement( const std::string& _value ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} +#endif + + +TiXmlElement::TiXmlElement( const TiXmlElement& copy) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + copy.CopyTo( this ); +} + + +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) +{ + ClearThis(); + base.CopyTo( this ); + return *this; +} + + +TiXmlElement::~TiXmlElement() +{ + ClearThis(); +} + + +void TiXmlElement::ClearThis() +{ + Clear(); + while( attributeSet.First() ) + { + TiXmlAttribute* node = attributeSet.First(); + attributeSet.Remove( node ); + delete node; + } +} + + +const char* TiXmlElement::Attribute( const char* name ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + return node->Value(); + return 0; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return &attrib->ValueStr(); + return 0; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} +#endif + + +int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} + + +int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int ival = 0; + int result = node->QueryIntValue( &ival ); + *value = (unsigned)ival; + return result; +} + + +int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int result = TIXML_WRONG_TYPE; + if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = true; + result = TIXML_SUCCESS; + } + else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = false; + result = TIXML_SUCCESS; + } + return result; +} + + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} +#endif + + +int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} +#endif + + +void TiXmlElement::SetAttribute( const char * name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} +#endif + + +void TiXmlElement::SetDoubleAttribute( const char * name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} +#endif + + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); + if ( attrib ) { + attrib->SetValue( cvalue ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); + } +} +#endif + + +void TiXmlElement::Print( FILE* cfile, int depth ) const +{ + int i; + assert( cfile ); + for ( i=0; iNext() ) + { + fprintf( cfile, " " ); + attrib->Print( cfile, depth ); + } + + // There are 3 different formatting approaches: + // 1) An element without children is printed as a node + // 2) An element with only a text child is printed as text + // 3) An element with children is printed on multiple lines. + TiXmlNode* node; + if ( !firstChild ) + { + fprintf( cfile, " />" ); + } + else if ( firstChild == lastChild && firstChild->ToText() ) + { + fprintf( cfile, ">" ); + firstChild->Print( cfile, depth + 1 ); + fprintf( cfile, "", value.c_str() ); + } + else + { + fprintf( cfile, ">" ); + + for ( node = firstChild; node; node=node->NextSibling() ) + { + if ( !node->ToText() ) + { + fprintf( cfile, "\n" ); + } + node->Print( cfile, depth+1 ); + } + fprintf( cfile, "\n" ); + for( i=0; i", value.c_str() ); + } +} + + +void TiXmlElement::CopyTo( TiXmlElement* target ) const +{ + // superclass: + TiXmlNode::CopyTo( target ); + + // Element class: + // Clone the attributes, then clone the children. + const TiXmlAttribute* attribute = 0; + for( attribute = attributeSet.First(); + attribute; + attribute = attribute->Next() ) + { + target->SetAttribute( attribute->Name(), attribute->Value() ); + } + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + +bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, attributeSet.First() ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +TiXmlNode* TiXmlElement::Clone() const +{ + TiXmlElement* clone = new TiXmlElement( Value() ); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +const char* TiXmlElement::GetText() const +{ + const TiXmlNode* child = this->FirstChild(); + if ( child ) { + const TiXmlText* childText = child->ToText(); + if ( childText ) { + return childText->Value(); + } + } + return 0; +} + + +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + ClearError(); +} + +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} + + +#ifdef TIXML_USE_STL +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} +#endif + + +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) +{ + return LoadFile( Value(), encoding ); +} + + +bool TiXmlDocument::SaveFile() const +{ + return SaveFile( Value() ); +} + +bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) +{ + TIXML_STRING filename( _filename ); + value = filename; + + // reading in binary mode so that tinyxml can normalize the EOL + FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + + if ( file ) + { + bool result = LoadFile( file, encoding ); + fclose( file ); + return result; + } + else + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +} + +bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +{ + if ( !file ) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = 0; + fseek( file, 0, SEEK_END ); + length = ftell( file ); + fseek( file, 0, SEEK_SET ); + + // Strange case, but good to handle up front. + if ( length <= 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // + // + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new char[ length+1 ]; + buf[0] = 0; + + if ( fread( buf, length, 1, file ) != 1 ) { + delete [] buf; + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; + + buf[length] = 0; + while( *p ) { + assert( p < (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; + } + } + else { + *q++ = *p++; + } + } + assert( q <= (buf+length) ); + *q = 0; + + Parse( buf, 0, encoding ); + + delete [] buf; + return !Error(); +} + + +bool TiXmlDocument::SaveFile( const char * filename ) const +{ + // The old c stuff lives on... + FILE* fp = TiXmlFOpen( filename, "w" ); + if ( fp ) + { + bool result = SaveFile( fp ); + fclose( fp ); + return result; + } + return false; +} + + +bool TiXmlDocument::SaveFile( FILE* fp ) const +{ + if ( useMicrosoftBOM ) + { + const unsigned char TIXML_UTF_LEAD_0 = 0xefU; + const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; + const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + fputc( TIXML_UTF_LEAD_0, fp ); + fputc( TIXML_UTF_LEAD_1, fp ); + fputc( TIXML_UTF_LEAD_2, fp ); + } + Print( fp, 0 ); + return (ferror(fp) == 0); +} + + +void TiXmlDocument::CopyTo( TiXmlDocument* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->error = error; + target->errorId = errorId; + target->errorDesc = errorDesc; + target->tabsize = tabsize; + target->errorLocation = errorLocation; + target->useMicrosoftBOM = useMicrosoftBOM; + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + + +TiXmlNode* TiXmlDocument::Clone() const +{ + TiXmlDocument* clone = new TiXmlDocument(); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlDocument::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + node->Print( cfile, depth ); + fprintf( cfile, "\n" ); + } +} + + +bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +const TiXmlAttribute* TiXmlAttribute::Next() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} + +/* +TiXmlAttribute* TiXmlAttribute::Next() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} +*/ + +const TiXmlAttribute* TiXmlAttribute::Previous() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} + +/* +TiXmlAttribute* TiXmlAttribute::Previous() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} +*/ + +void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + TIXML_STRING n, v; + + EncodeString( name, &n ); + EncodeString( value, &v ); + + if (value.find ('\"') == TIXML_STRING::npos) { + if ( cfile ) { + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; + } + } + else { + if ( cfile ) { + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; + } + } +} + + +int TiXmlAttribute::QueryIntValue( int* ival ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +int TiXmlAttribute::QueryDoubleValue( double* dval ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +void TiXmlAttribute::SetIntValue( int _value ) +{ + char buf [64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); + #else + sprintf (buf, "%d", _value); + #endif + SetValue (buf); +} + +void TiXmlAttribute::SetDoubleValue( double _value ) +{ + char buf [256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); + #else + sprintf (buf, "%g", _value); + #endif + SetValue (buf); +} + +int TiXmlAttribute::IntValue() const +{ + return atoi (value.c_str ()); +} + +double TiXmlAttribute::DoubleValue() const +{ + return atof (value.c_str ()); +} + + +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) +{ + Clear(); + base.CopyTo( this ); + return *this; +} + + +void TiXmlComment::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlComment::CopyTo( TiXmlComment* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlComment::Clone() const +{ + TiXmlComment* clone = new TiXmlComment(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlText::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + if ( cdata ) + { + int i; + fprintf( cfile, "\n" ); + for ( i=0; i\n", value.c_str() ); // unformatted output + } + else + { + TIXML_STRING buffer; + EncodeString( value, &buffer ); + fprintf( cfile, "%s", buffer.c_str() ); + } +} + + +void TiXmlText::CopyTo( TiXmlText* target ) const +{ + TiXmlNode::CopyTo( target ); + target->cdata = cdata; +} + + +bool TiXmlText::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlText::Clone() const +{ + TiXmlText* clone = 0; + clone = new TiXmlText( "" ); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlDeclaration::TiXmlDeclaration( const char * _version, + const char * _encoding, + const char * _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} + + +#ifdef TIXML_USE_STL +TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} +#endif + + +TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + copy.CopyTo( this ); +} + + +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + if ( cfile ) fprintf( cfile, "" ); + if ( str ) (*str) += "?>"; +} + + +void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->version = version; + target->encoding = encoding; + target->standalone = standalone; +} + + +bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlDeclaration::Clone() const +{ + TiXmlDeclaration* clone = new TiXmlDeclaration(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlUnknown::Print( FILE* cfile, int depth ) const +{ + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlUnknown::Clone() const +{ + TiXmlUnknown* clone = new TiXmlUnknown(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlAttributeSet::TiXmlAttributeSet() +{ + sentinel.next = &sentinel; + sentinel.prev = &sentinel; +} + + +TiXmlAttributeSet::~TiXmlAttributeSet() +{ + assert( sentinel.next == &sentinel ); + assert( sentinel.prev == &sentinel ); +} + + +void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) +{ + #ifdef TIXML_USE_STL + assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. + #else + assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. + #endif + + addMe->next = &sentinel; + addMe->prev = sentinel.prev; + + sentinel.prev->next = addMe; + sentinel.prev = addMe; +} + +void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) +{ + TiXmlAttribute* node; + + for( node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node == removeMe ) + { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; + return; + } + } + assert( 0 ); // we tried to remove a non-linked attribute. +} + + +#ifdef TIXML_USE_STL +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} +#endif + + +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} + + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} + + +#ifdef TIXML_USE_STL +std::istream& operator>> (std::istream & in, TiXmlNode & base) +{ + TIXML_STRING tag; + tag.reserve( 8 * 1000 ); + base.StreamIn( &in, &tag ); + + base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); + return in; +} +#endif + + +#ifdef TIXML_USE_STL +std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out << printer.Str(); + + return out; +} + + +std::string& operator<< (std::string& out, const TiXmlNode& base ) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out.append( printer.Str() ); + + return out; +} +#endif + + +TiXmlHandle TiXmlHandle::FirstChild() const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement() const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild(); + for ( i=0; + child && iNextSibling(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild( value ); + for ( i=0; + child && iNextSibling( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement(); + for ( i=0; + child && iNextSiblingElement(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement( value ); + for ( i=0; + child && iNextSiblingElement( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) +{ + DoIndent(); + buffer += "<"; + buffer += element.Value(); + + for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) + { + buffer += " "; + attrib->Print( 0, 0, &buffer ); + } + + if ( !element.FirstChild() ) + { + buffer += " />"; + DoLineBreak(); + } + else + { + buffer += ">"; + if ( element.FirstChild()->ToText() + && element.LastChild() == element.FirstChild() + && element.FirstChild()->ToText()->CDATA() == false ) + { + simpleTextPrint = true; + // no DoLineBreak()! + } + else + { + DoLineBreak(); + } + } + ++depth; + return true; +} + + +bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) +{ + --depth; + if ( !element.FirstChild() ) + { + // nothing. + } + else + { + if ( simpleTextPrint ) + { + simpleTextPrint = false; + } + else + { + DoIndent(); + } + buffer += ""; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlText& text ) +{ + if ( text.CDATA() ) + { + DoIndent(); + buffer += ""; + DoLineBreak(); + } + else if ( simpleTextPrint ) + { + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + } + else + { + DoIndent(); + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) +{ + DoIndent(); + declaration.Print( 0, 0, &buffer ); + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlComment& comment ) +{ + DoIndent(); + buffer += ""; + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) +{ + DoIndent(); + buffer += "<"; + buffer += unknown.Value(); + buffer += ">"; + DoLineBreak(); + return true; +} + diff --git a/src/tinyxml/tinyxml.h b/src/tinyxml/tinyxml.h new file mode 100644 index 0000000..a3589e5 --- /dev/null +++ b/src/tinyxml/tinyxml.h @@ -0,0 +1,1805 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#ifdef TIXML_USE_STL + #include + #include + #include + #define TIXML_STRING std::string +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its sibilings will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknown node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; + +const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; + +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + assert( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + assert( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + TiXmlBase( const TiXmlBase& ); // not implemented. + void operator=( const TiXmlBase& base ); // not allowed. + + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; + +private: + TiXmlNode( const TiXmlNode& ); // not implemented. + void operator=( const TiXmlNode& base ); // not allowed. +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif + + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + /// Construct an element. + TiXmlElement (const char * in_value); + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlElement( const std::string& _value ); + #endif + + TiXmlElement( const TiXmlElement& ); + + TiXmlElement& operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + */ + const char* Attribute( const char* name ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an integer, + the integer value will be put in the return 'i', if 'i' + is non-null. + */ + const char* Attribute( const char* name, int* i ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an double, + the double value will be put in the return 'd', if 'd' + is non-null. + */ + const char* Attribute( const char* name, double* d ) const; + + /** QueryIntAttribute examines the attribute - it is an alternative to the + Attribute() method with richer error checking. + If the attribute is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. If the attribute + does not exist, then TIXML_NO_ATTRIBUTE is returned. + */ + int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). + int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; + /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). + Note that '1', 'true', or 'yes' are considered true, while '0', 'false' + and 'no' are considered false. + */ + int QueryBoolAttribute( const char* name, bool* _value ) const; + /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). + int QueryDoubleAttribute( const char* name, double* _value ) const; + /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). + int QueryFloatAttribute( const char* name, float* _value ) const { + double d; + int result = QueryDoubleAttribute( name, &d ); + if ( result == TIXML_SUCCESS ) { + *_value = (float)d; + } + return result; + } + + #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types that contain spaces. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char* name, const char * _value ); + + #ifdef TIXML_USE_STL + const std::string* Attribute( const std::string& name ) const; + const std::string* Attribute( const std::string& name, int* i ) const; + const std::string* Attribute( const std::string& name, double* d ) const; + int QueryIntAttribute( const std::string& name, int* _value ) const; + int QueryDoubleAttribute( const std::string& name, double* _value ) const; + + /// STL std::string form. + void SetAttribute( const std::string& name, const std::string& _value ); + ///< STL std::string form. + void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char * name, int value ); + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetDoubleAttribute( const char * name, double value ); + + /** Deletes an attribute with the given name. + */ + void RemoveAttribute( const char * name ); + #ifdef TIXML_USE_STL + void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. + #endif + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + TiXmlComment& operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { Print( stdout, 0 ); } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const; + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i +#include + +#include "tinyxml.h" + +//#define DEBUG_PARSER +#if defined( DEBUG_PARSER ) +# if defined( DEBUG ) && defined( _MSC_VER ) +# include +# define TIXML_LOG OutputDebugString +# else +# define TIXML_LOG printf +# endif +#endif + +// Note tha "PutString" hardcodes the same list. This +// is less flexible than it appears. Changing the entries +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = +{ + { "&", 5, '&' }, + { "<", 4, '<' }, + { ">", 4, '>' }, + { """, 6, '\"' }, + { "'", 6, '\'' } +}; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// Including the basic of this table, which determines the #bytes in the +// sequence from the lead byte. 1 placed for invalid sequences -- +// although the result will be junk, pass it through as much as possible. +// Beware of the non-characters in UTF-8: +// ef bb bf (Microsoft "lead bytes") +// ef bf be +// ef bf bf + +const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +const int TiXmlBase::utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte + 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; + + +void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalpha( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalpha( anyByte ); +// } +} + + +/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalnum( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalnum( anyByte ); +// } +} + + +class TiXmlParsingData +{ + friend class TiXmlDocument; + public: + void Stamp( const char* now, TiXmlEncoding encoding ); + + const TiXmlCursor& Cursor() const { return cursor; } + + private: + // Only used by the document! + TiXmlParsingData( const char* start, int _tabsize, int row, int col ) + { + assert( start ); + stamp = start; + tabsize = _tabsize; + cursor.row = row; + cursor.col = col; + } + + TiXmlCursor cursor; + const char* stamp; + int tabsize; +}; + + +void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) +{ + assert( now ); + + // Do nothing if the tabsize is 0. + if ( tabsize < 1 ) + { + return; + } + + // Get the current row, column. + int row = cursor.row; + int col = cursor.col; + const char* p = stamp; + assert( p ); + + while ( p < now ) + { + // Treat p as unsigned, so we have a happy compiler. + const unsigned char* pU = (const unsigned char*)p; + + // Code contributed by Fletcher Dunn: (modified by lee) + switch (*pU) { + case 0: + // We *should* never get here, but in case we do, don't + // advance past the terminating null character, ever + return; + + case '\r': + // bump down to the next line + ++row; + col = 0; + // Eat the character + ++p; + + // Check for \r\n sequence, and treat this as a single character + if (*p == '\n') { + ++p; + } + break; + + case '\n': + // bump down to the next line + ++row; + col = 0; + + // Eat the character + ++p; + + // Check for \n\r sequence, and treat this as a single + // character. (Yes, this bizarre thing does occur still + // on some arcane platforms...) + if (*p == '\r') { + ++p; + } + break; + + case '\t': + // Eat the character + ++p; + + // Skip to next tab stop + col = (col / tabsize + 1) * tabsize; + break; + + case TIXML_UTF_LEAD_0: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( *(p+1) && *(p+2) ) + { + // In these cases, don't advance the column. These are + // 0-width spaces. + if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) + p += 3; + else + { p +=3; ++col; } // A normal character. + } + } + else + { + ++p; + ++col; + } + break; + + default: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // Eat the 1 to 4 byte utf8 character. + int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; + if ( step == 0 ) + step = 1; // Error case from bad encoding, but handle gracefully. + p += step; + + // Just advance one column, of course. + ++col; + } + else + { + ++p; + ++col; + } + break; + } + } + cursor.row = row; + cursor.col = col; + assert( cursor.row >= -1 ); + assert( cursor.col >= -1 ); + stamp = p; + assert( stamp ); +} + + +const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) +{ + if ( !p || !*p ) + { + return 0; + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + while ( *p ) + { + const unsigned char* pU = (const unsigned char*)p; + + // Skip the stupid Microsoft UTF-8 Byte order marks + if ( *(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+2)==TIXML_UTF_LEAD_2 ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbeU ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbfU ) + { + p += 3; + continue; + } + + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. + ++p; + else + break; + } + } + else + { + while ( *p && IsWhiteSpace( *p ) ) + ++p; + } + + return p; +} + +#ifdef TIXML_USE_STL +/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) +{ + for( ;; ) + { + if ( !in->good() ) return false; + + int c = in->peek(); + // At this scope, we can't get to a document. So fail silently. + if ( !IsWhiteSpace( c ) || c <= 0 ) + return true; + + *tag += (char) in->get(); + } +} + +/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) +{ + //assert( character > 0 && character < 128 ); // else it won't work in utf-8 + while ( in->good() ) + { + int c = in->peek(); + if ( c == character ) + return true; + if ( c <= 0 ) // Silent failure: can't get document at this scope + return false; + + in->get(); + *tag += (char) c; + } + return false; +} +#endif + +// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The +// "assign" optimization removes over 10% of the execution time. +// +const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) +{ + // Oddly, not supported on some comilers, + //name->clear(); + // So use this: + *name = ""; + assert( p ); + + // Names start with letters or underscores. + // Of course, in unicode, tinyxml has no idea what a letter *is*. The + // algorithm is generous. + // + // After that, they can be letters, underscores, numbers, + // hyphens, or colons. (Colons are valid ony for namespaces, + // but tinyxml can't tell namespaces from names.) + if ( p && *p + && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) + { + const char* start = p; + while( p && *p + && ( IsAlphaNum( (unsigned char ) *p, encoding ) + || *p == '_' + || *p == '-' + || *p == '.' + || *p == ':' ) ) + { + //(*name) += *p; // expensive + ++p; + } + if ( p-start > 0 ) { + name->assign( start, p-start ); + } + return p; + } + return 0; +} + +const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) +{ + // Presume an entity, and pull it out. + TIXML_STRING ent; + int i; + *length = 0; + + if ( *(p+1) && *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + } + else + { + *value = (char)ucs; + *length = 1; + } + return p + delta + 1; + } + + // Now try to match it. + for( i=0; iappend( cArr, len ); + } + } + else + { + bool whitespace = false; + + // Remove leading white space: + p = SkipWhiteSpace( p, encoding ); + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) ) + { + if ( *p == '\r' || *p == '\n' ) + { + whitespace = true; + ++p; + } + else if ( IsWhiteSpace( *p ) ) + { + whitespace = true; + ++p; + } + else + { + // If we've found whitespace, add it before the + // new character. Any whitespace just becomes a space. + if ( whitespace ) + { + (*text) += ' '; + whitespace = false; + } + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + if ( len == 1 ) + (*text) += cArr[0]; // more efficient + else + text->append( cArr, len ); + } + } + } + if ( p && *p ) + p += strlen( endTag ); + return ( p && *p ) ? p : 0; +} + +#ifdef TIXML_USE_STL + +void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + // The basic issue with a document is that we don't know what we're + // streaming. Read something presumed to be a tag (and hope), then + // identify it, and call the appropriate stream method on the tag. + // + // This "pre-streaming" will never read the closing ">" so the + // sub-tag can orient itself. + + if ( !StreamTo( in, '<', tag ) ) + { + SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + while ( in->good() ) + { + int tagIndex = (int) tag->length(); + while ( in->good() && in->peek() != '>' ) + { + int c = in->get(); + if ( c <= 0 ) + { + SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + break; + } + (*tag) += (char) c; + } + + if ( in->good() ) + { + // We now have something we presume to be a node of + // some sort. Identify it, and call the node to + // continue streaming. + TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); + + if ( node ) + { + node->StreamIn( in, tag ); + bool isElement = node->ToElement() != 0; + delete node; + node = 0; + + // If this is the root element, we're done. Parsing will be + // done by the >> operator. + if ( isElement ) + { + return; + } + } + else + { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + } + } + // We should have returned sooner. + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); +} + +#endif + +const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) +{ + ClearError(); + + // Parse away, at the document level. Since a document + // contains nothing but other tags, most of what happens + // here is skipping white space. + if ( !p || !*p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + // Note that, for a document, this needs to come + // before the while space skip, so that parsing + // starts from the pointer we are given. + location.Clear(); + if ( prevData ) + { + location.row = prevData->cursor.row; + location.col = prevData->cursor.col; + } + else + { + location.row = 0; + location.col = 0; + } + TiXmlParsingData data( p, TabSize(), location.row, location.col ); + location = data.Cursor(); + + if ( encoding == TIXML_ENCODING_UNKNOWN ) + { + // Check for the Microsoft UTF-8 lead bytes. + const unsigned char* pU = (const unsigned char*)p; + if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 + && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 + && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) + { + encoding = TIXML_ENCODING_UTF8; + useMicrosoftBOM = true; + } + } + + p = SkipWhiteSpace( p, encoding ); + if ( !p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + while ( p && *p ) + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, &data, encoding ); + LinkEndChild( node ); + } + else + { + break; + } + + // Did we get encoding info? + if ( encoding == TIXML_ENCODING_UNKNOWN + && node->ToDeclaration() ) + { + TiXmlDeclaration* dec = node->ToDeclaration(); + const char* enc = dec->Encoding(); + assert( enc ); + + if ( *enc == 0 ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice + else + encoding = TIXML_ENCODING_LEGACY; + } + + p = SkipWhiteSpace( p, encoding ); + } + + // Was this empty? + if ( !firstChild ) { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); + return 0; + } + + // All is well. + return p; +} + +void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + // The first error in a chain is more accurate - don't set again! + if ( error ) + return; + + assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); + error = true; + errorId = err; + errorDesc = errorString[ errorId ]; + + errorLocation.Clear(); + if ( pError && data ) + { + data->Stamp( pError, encoding ); + errorLocation = data->Cursor(); + } +} + + +TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) +{ + TiXmlNode* returnNode = 0; + + p = SkipWhiteSpace( p, encoding ); + if( !p || !*p || *p != '<' ) + { + return 0; + } + + p = SkipWhiteSpace( p, encoding ); + + if ( !p || !*p ) + { + return 0; + } + + // What is this thing? + // - Elements start with a letter or underscore, but xml is reserved. + // - Comments: "; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // [ 1475201 ] TinyXML parses entities in comments + // Oops - ReadText doesn't work, because we don't want to parse the entities. + // p = ReadText( p, &value, false, endTag, false, encoding ); + // + // from the XML spec: + /* + [Definition: Comments may appear anywhere in a document outside other markup; in addition, + they may appear within the document type declaration at places allowed by the grammar. + They are not part of the document's character data; an XML processor MAY, but need not, + make it possible for an application to retrieve the text of comments. For compatibility, + the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity + references MUST NOT be recognized within comments. + + An example of a comment: + + + */ + + value = ""; + // Keep all the white space. + while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) + { + value.append( p, 1 ); + ++p; + } + if ( p && *p ) + p += strlen( endTag ); + + return p; +} + + +const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) return 0; + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + // Read the name, the '=' and the value. + const char* pErr = p; + p = ReadName( p, &name, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p || *p != '=' ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + ++p; // skip '=' + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + const char* end; + const char SINGLE_QUOTE = '\''; + const char DOUBLE_QUOTE = '\"'; + + if ( *p == SINGLE_QUOTE ) + { + ++p; + end = "\'"; // single quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else if ( *p == DOUBLE_QUOTE ) + { + ++p; + end = "\""; // double quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else + { + // All attribute values should be in single or double quotes. + // But this is such a common error that the parser will try + // its best, even without them. + value = ""; + while ( p && *p // existence + && !IsWhiteSpace( *p ) // whitespace + && *p != '/' && *p != '>' ) // tag end + { + if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { + // [ 1451649 ] Attribute values with trailing quotes not handled correctly + // We did not have an opening quote but seem to have a + // closing one. Give up and throw an error. + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + value += *p; + ++p; + } + } + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->peek(); + if ( !cdata && (c == '<' ) ) + { + return; + } + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + in->get(); // "commits" the peek made above + + if ( cdata && c == '>' && tag->size() >= 3 ) { + size_t len = tag->size(); + if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { + // terminator of cdata. + return; + } + } + } +} +#endif + +const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + value = ""; + TiXmlDocument* document = GetDocument(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + const char* const startTag = ""; + + if ( cdata || StringEqual( p, startTag, false, encoding ) ) + { + cdata = true; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // Keep all the white space, ignore the encoding, etc. + while ( p && *p + && !StringEqual( p, endTag, false, encoding ) + ) + { + value += *p; + ++p; + } + + TIXML_STRING dummy; + p = ReadText( p, &dummy, false, endTag, false, encoding ); + return p; + } + else + { + bool ignoreWhite = true; + + const char* end = "<"; + p = ReadText( p, &value, ignoreWhite, end, false, encoding ); + if ( p && *p ) + return p-1; // don't truncate the '<' + return 0; + } +} + +#ifdef TIXML_USE_STL +void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + +const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) +{ + p = SkipWhiteSpace( p, _encoding ); + // Find the beginning, find the end, and look for + // the stuff in-between. + TiXmlDocument* document = GetDocument(); + if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); + return 0; + } + if ( data ) + { + data->Stamp( p, _encoding ); + location = data->Cursor(); + } + p += 5; + + version = ""; + encoding = ""; + standalone = ""; + + while ( p && *p ) + { + if ( *p == '>' ) + { + ++p; + return p; + } + + p = SkipWhiteSpace( p, _encoding ); + if ( StringEqual( p, "version", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + version = attrib.Value(); + } + else if ( StringEqual( p, "encoding", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + encoding = attrib.Value(); + } + else if ( StringEqual( p, "standalone", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + standalone = attrib.Value(); + } + else + { + // Read over whatever it is. + while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) + ++p; + } + } + return 0; +} + +bool TiXmlText::Blank() const +{ + for ( unsigned i=0; i& x, \ + const pair& y) { + return (x.second)->keyCnt > (y.second)->keyCnt; +} + +// group by value size +static bool cmpValueSize(const pair& x, \ + const pair& y) { + return (x.second)->valueSize > (y.second)->valueSize; +} + +CTopKeySorter::CTopKeySorter() { + m_sortVectorKeyCnt = new vector >; +} + +CTopKeySorter::~CTopKeySorter() { + if (m_sortVectorKeyCnt != NULL) { + delete m_sortVectorKeyCnt; + m_sortVectorKeyCnt = NULL; + } +} + +void CTopKeySorter::sortTopKeyByValueSize(CTopKeyRecorderThread& keyRecorder) { + keyRecorder.m_lock.lock(); + m_sortVectorKeyCnt->insert(m_sortVectorKeyCnt->begin(), + keyRecorder.m_keyCntMap.begin(), + keyRecorder.m_keyCntMap.end()); + keyRecorder.m_lock.unlock(); + sort(m_sortVectorKeyCnt->begin(), m_sortVectorKeyCnt->end(), cmpValueSize); +} + +void CTopKeySorter::sortTopKeyByCnt(CTopKeyRecorderThread& keyRecorder) { + keyRecorder.m_lock.lock(); + m_sortVectorKeyCnt->insert(m_sortVectorKeyCnt->begin(), + keyRecorder.m_keyCntMap.begin(), + keyRecorder.m_keyCntMap.end()); + keyRecorder.m_lock.unlock(); + sort(m_sortVectorKeyCnt->begin(), m_sortVectorKeyCnt->end(), cmp); +} + + +void CTopKeyRecorderThread::delListMap() { + int midSize = m_maxKeyCnt - 500; + KeyCntList::iterator it = m_keyCntList.begin(); + KeyCntList::iterator itDel = m_keyCntList.end(); + const unsigned int endCnt = (--m_keyCntList.end())->keyCnt; + for (int i = 0; i < midSize; ++i) { + ++it; + } + bool del = false; + while (it != m_keyCntList.end()) { + if (it->keyCnt < endCnt) { + KeyCntMap::iterator itMap = m_keyCntMap.end(); + itMap = minCntMap(it); + if (itMap != m_keyCntMap.end()) { + delete[] (itMap->first); + m_keyCntMap.erase(itMap); + it = m_keyCntList.erase(it); + } + del = true; + } else { + ++it; + } + } + + if (!del) { + it = --m_keyCntList.end(); + KeyCntMap::iterator itMap = m_keyCntMap.end(); + itMap = minCntMap(it); + if (itMap != m_keyCntMap.end()) { + delete[] (itMap->first); + m_keyCntMap.erase(itMap); + m_keyCntList.erase(it); + } + } +} + +CTopKeyRecorderThread::KeyCntMap::iterator CTopKeyRecorderThread::minCntMap(KeyCntList::iterator itListDel){ + KeyCntMap::iterator itMap = m_keyCntMap.begin(); + for (; itMap != m_keyCntMap.end(); ++itMap) { + if (itListDel == itMap->second) + return itMap; + } + return itMap; +} + +void CTopKeyRecorderThread::keyToList(KeyStrValueSize& keyInfo) { + if (!m_processTopKey) { + m_allKeys.push(keyInfo); + return; + } + delete[] keyInfo.key; +} + +void CTopKeyRecorderThread::keyRecorder(KeyStrValueSize& keyInfo) { + KeyCntMap::iterator itMap = m_keyCntMap.find(keyInfo.key); + KeyCntValueSize node; + if (itMap == m_keyCntMap.end()) { + m_lock.lock(); + // new key + if (m_keyCntList.size() >= m_maxKeyCnt) { + delListMap(); + } + node.keyCnt = 1; + node.valueSize = keyInfo.valueSize; + + m_keyCntList.push_front(node); + m_keyCntMap[keyInfo.key] = m_keyCntList.begin(); + m_lock.unlock(); + } else { + m_lock.lock(); + // already exist + KeyCntList::iterator listIt = itMap->second; + node = *listIt; + m_keyCntList.erase(listIt); + node.keyCnt++; + node.valueSize += keyInfo.valueSize; + + m_keyCntList.push_front(node); + itMap->second = m_keyCntList.begin(); + delete[] keyInfo.key; + m_lock.unlock(); + } +} + + + +void CTopKeyRecorderThread::run() { + while(true) { + Thread::sleep(50); + m_processTopKey = true; + while (!this->m_allKeys.empty()) { + KeyStrValueSize node = this->m_allKeys.front(); + this->m_allKeys.pop(); + this->keyRecorder(node); + } + this->m_processTopKey = false; + } +} + +void CTopKeyRecorderThread::onExit() +{ + +} diff --git a/src/top-key.h b/src/top-key.h new file mode 100644 index 0000000..2fe2c5c --- /dev/null +++ b/src/top-key.h @@ -0,0 +1,117 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef ONE_CACHE_TOPKEY +#define ONE_CACHE_TOPKEY + +#include +#include +#include +#include +#include +#include + +#include "util/thread.h" +#include "util/locker.h" +using namespace std; + + + +struct hash_func { + unsigned int operator()(const char *str) const { + unsigned int hash = 0; + unsigned int x = 0; + while (*str) { + hash = (hash << 4) + (*str++); + if ((x = hash & 0xF0000000L) != 0) { + hash ^= (x >> 24); + hash &= ~x; + } + } + return (hash & 0x7FFFFFFF); + } +}; + +struct cmp_fun { + bool operator()(const char* str1, const char* str2) const { + return (strcmp(str1, str2) == 0); + } +}; + +class KeyStrValueSize { +public: + char* key; + unsigned long valueSize; +}; + + +class KeyCntValueSize { +public: + KeyCntValueSize(){ + valueSize = 0LL; + keyCnt = 0LL; + } + unsigned long valueSize; + unsigned long keyCnt; +}; + +class CTopKeyRecorderThread : public Thread +{ +public: + typedef list KeyCntList; + typedef queue KeyList; + typedef unordered_map KeyCntMap; + CTopKeyRecorderThread(int cnt = 5000000) { + m_processTopKey = false; + m_maxKeyCnt = cnt; + } + ~CTopKeyRecorderThread(){ + } + + void keyRecorder(KeyStrValueSize&); + void keyToList(KeyStrValueSize&); + +protected: + virtual void run(void); + virtual void onExit(void); + +public: + void delListMap(); + KeyCntMap::iterator minCntMap(KeyCntList::iterator); + bool m_processTopKey; + KeyList m_allKeys; + unsigned int m_maxKeyCnt; + KeyCntList m_keyCntList; + KeyCntMap m_keyCntMap; + Mutex m_lock; + friend class CTopKeySorter; +}; + + +class CTopKeySorter { +public: + CTopKeySorter(); + ~CTopKeySorter(); + void sortTopKeyByCnt(CTopKeyRecorderThread&); + void sortTopKeyByValueSize(CTopKeyRecorderThread&); + + vector >* m_sortVectorKeyCnt; +}; + +#endif diff --git a/src/util/hash.cpp b/src/util/hash.cpp new file mode 100644 index 0000000..907d544 --- /dev/null +++ b/src/util/hash.cpp @@ -0,0 +1,70 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + + +#include "util/hash.h" + +unsigned int hashForBytes(const char *key, int len) +{ + /* + int seed = 131; + unsigned int hash = 0; + const char* c = key; + int i = 0; + while (i++ < len) { + hash = hash * seed + (*c++); + } + return (hash & 0x7FFFFFFF); + */ + + const unsigned int m = 0x5bd1e995; + const int r = 24; + const int seed = 131; + + unsigned int h = seed ^ len; + + const unsigned char *data = (const unsigned char *)key; + + while(len >= 4) { + unsigned int k = *(unsigned int*)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + switch(len) { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; h *= m; + }; + + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return (unsigned int)h; +} diff --git a/src/util/hash.h b/src/util/hash.h new file mode 100644 index 0000000..10a0fc4 --- /dev/null +++ b/src/util/hash.h @@ -0,0 +1,45 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef HASH_H +#define HASH_H + +#include + +#include "util/string.h" +#include "util/vector.h" + +typedef unsigned int (*HashFunc)(const char* s, int len); +unsigned int hashForBytes(const char *key, int len); + +struct StringHashFunc { + unsigned int operator()(const String& str) const { + return hashForBytes(str.data(), str.length()); + } +}; + +#define HashTable std::unordered_map + +template +class StringMap : public HashTable +{ +public: +}; + +#endif diff --git a/src/util/iobuffer.cpp b/src/util/iobuffer.cpp new file mode 100644 index 0000000..b572d23 --- /dev/null +++ b/src/util/iobuffer.cpp @@ -0,0 +1,150 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include +#include +#include + +#include "util/string.h" +#include "iobuffer.h" + +IOBuffer::IOBuffer(void) +{ + m_capacity = sizeof(m_data); + m_offset = 0; + m_ptr = m_data; +} + +IOBuffer::IOBuffer(const IOBuffer &rhs) +{ + *this = rhs; +} + +IOBuffer::~IOBuffer(void) +{ + clear(); +} + +IOBuffer &IOBuffer::operator=(const IOBuffer &rhs) +{ + if (this != &rhs) { + clear(); + + m_capacity = rhs.m_capacity; + m_offset = rhs.m_offset; + if (m_offset > ChunkSize) { + m_ptr = new char[m_capacity]; + } + memcpy(m_ptr, rhs.m_ptr, m_offset); + } + return *this; +} + +void IOBuffer::reserve(int size) +{ + if (size <= m_capacity) { + return; + } + + int append_chunk_cnt = size / ChunkSize + 1; + appendChunk(append_chunk_cnt); +} + +void IOBuffer::appendFormatString(const char *format, ...) +{ + if (!format) { + return; + } + + char buffer[10240]; + va_list marker; + va_start(marker, format); + int len = vsprintf(buffer, format, marker); + va_end(marker); + + append(buffer, len); +} + +void IOBuffer::append(const char *data, int size) +{ + if (size == -1) { + size = strlen(data); + } + + int need_size = m_offset + size; + if (need_size > m_capacity) { + int append_chunk_cnt = (need_size - m_capacity) / ChunkSize + 1; + appendChunk(append_chunk_cnt); + append(data, size); + } else { + memcpy(m_ptr + m_offset, data, size); + m_offset += size; + } +} + +void IOBuffer::append(const IOBuffer &rhs) +{ + append(rhs.data(), rhs.size()); +} + +void IOBuffer::clear(void) +{ + if (m_capacity > ChunkSize) { + delete []m_ptr; + } + m_capacity = sizeof(m_data); + m_offset = 0; + m_ptr = m_data; +} + +IOBuffer::DirectCopy IOBuffer::beginCopy(void) +{ + int freeSize = m_capacity - m_offset; + if (freeSize < (ChunkSize * 0.01)) { + appendChunk(1); + freeSize = m_capacity - m_offset; + } + DirectCopy cp; + cp.address = m_ptr + m_offset; + cp.maxsize = freeSize; + return cp; +} + +void IOBuffer::endCopy(int cpsize) +{ + if (cpsize > 0 && cpsize <= (m_capacity - m_offset)) { + m_offset += cpsize; + } +} + +void IOBuffer::appendChunk(int nums) +{ + if (nums <= 0) { + return; + } + int new_size = m_capacity + nums * ChunkSize; + char* tmp = new char[new_size]; + memcpy(tmp, m_ptr, m_offset); + if (m_capacity > ChunkSize) { + delete []m_ptr; + } + m_ptr = tmp; + m_capacity = new_size; +} + diff --git a/src/util/iobuffer.h b/src/util/iobuffer.h new file mode 100644 index 0000000..ab30c9e --- /dev/null +++ b/src/util/iobuffer.h @@ -0,0 +1,65 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef IOBUFFER_H +#define IOBUFFER_H + +class IOBuffer +{ +public: + struct DirectCopy { + char* address; + int maxsize; + }; + + enum { ChunkSize = 1024 * 64 }; + IOBuffer(void); + IOBuffer(const IOBuffer& rhs); + ~IOBuffer(void); + IOBuffer& operator=(const IOBuffer& rhs); + + void reserve(int size); + + void appendFormatString(const char* format, ...); + void append(const char* data, int size = -1); + void append(const IOBuffer& rhs); + void clear(void); + + char* data(void) { return m_ptr; } + const char* data(void) const { return m_ptr; } + int size(void) const { return m_offset; } + + bool isEmpty(void) const { return (m_offset == 0); } + + //Fast copy + DirectCopy beginCopy(void); + void endCopy(int cpsize); + +private: + void appendChunk(int nums); + +private: + int m_capacity; + int m_offset; + char m_data[ChunkSize]; + char* m_ptr; +}; + + +#endif diff --git a/src/util/locker.cpp b/src/util/locker.cpp new file mode 100644 index 0000000..ed9dca8 --- /dev/null +++ b/src/util/locker.cpp @@ -0,0 +1,94 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifdef WIN32 +#include +#endif + +#include "locker.h" + +Mutex::Mutex(void) +{ +#ifdef WIN32 + m_hMutex = ::CreateMutexA(NULL, FALSE, NULL); +#else + pthread_mutex_init(&m_mutex, NULL); +#endif +} + +Mutex::~Mutex(void) +{ +#ifdef WIN32 + ::CloseHandle(m_hMutex); +#else + pthread_mutex_destroy(&m_mutex); +#endif +} + +void Mutex::lock(void) +{ +#ifdef WIN32 + ::WaitForSingleObject(m_hMutex, INFINITE); +#else + pthread_mutex_lock(&m_mutex); +#endif +} + +void Mutex::unlock(void) +{ +#ifdef WIN32 + ::ReleaseMutex(m_hMutex); +#else + pthread_mutex_unlock(&m_mutex); +#endif +} + + +SpinLocker::SpinLocker(void) +{ +#ifndef WIN32 + pthread_spin_init(&m_spinlock, PTHREAD_PROCESS_SHARED); +#endif +} + +SpinLocker::~SpinLocker(void) +{ +#ifndef WIN32 + pthread_spin_destroy(&m_spinlock); +#endif +} + +void SpinLocker::lock(void) +{ +#ifdef WIN32 + m_mutex.lock(); +#else + pthread_spin_lock(&m_spinlock); +#endif +} + +void SpinLocker::unlock(void) +{ +#ifdef WIN32 + m_mutex.unlock(); +#else + pthread_spin_unlock(&m_spinlock); +#endif +} + diff --git a/src/util/locker.h b/src/util/locker.h new file mode 100644 index 0000000..fb5c349 --- /dev/null +++ b/src/util/locker.h @@ -0,0 +1,62 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef LOCKER_H +#define LOCKER_H + +#ifdef WIN32 +typedef void* HANDLE; +#else +#include +#endif + +class Mutex +{ +public: + Mutex(void); + ~Mutex(void); + + void lock(void); + void unlock(void); + +#ifdef WIN32 + HANDLE m_hMutex; +#else + pthread_mutex_t m_mutex; +#endif +}; + +class SpinLocker +{ +public: + SpinLocker(void); + ~SpinLocker(void); + + void lock(void); + void unlock(void); + +private: +#ifndef WIN32 + pthread_spinlock_t m_spinlock; +#else + Mutex m_mutex; +#endif +}; + +#endif diff --git a/src/util/logger.cpp b/src/util/logger.cpp new file mode 100644 index 0000000..badd92c --- /dev/null +++ b/src/util/logger.cpp @@ -0,0 +1,124 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include +#include +#include + +#include "util/string.h" +#include "logger.h" + +static Logger _stdoutput; +Logger* _defaultLogger = &_stdoutput; + +static const char* msg_type_text[] = +{ + "Message", + "Warning", + "Error" +}; + +Logger::Logger(void) +{ +} + +Logger::~Logger(void) +{ +} + +void Logger::output(Logger::MsgType type, const char *msg) +{ + time_t t = time(NULL); + tm* lt = localtime(&t); + + printf("[%d-%02d-%02d %02d:%02d:%02d] %s: %s\n", + lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, + lt->tm_hour, lt->tm_min, lt->tm_sec, msg_type_text[type], msg); +} + +void Logger::log(Logger::MsgType type, const char *format, ...) +{ + if (!format || !_defaultLogger) { + return; + } + + char buffer[10240]; + va_list marker; + va_start(marker, format); + vsprintf(buffer, format, marker); + va_end(marker); + + _defaultLogger->output(type, buffer); +} + +Logger *Logger::defaultLogger(void) +{ + return _defaultLogger; +} + +void Logger::setDefaultLogger(Logger *logger) +{ + _defaultLogger = logger; +} + + + +FileLogger::FileLogger(void) +{ + m_fp = NULL; + memset(m_fileName, 0, sizeof(m_fileName)); +} + +FileLogger::FileLogger(const char *fileName) +{ + setFileName(fileName); +} + +FileLogger::~FileLogger(void) +{ + if (m_fp) { + fclose(m_fp); + } +} + +bool FileLogger::setFileName(const char *fileName) +{ + FILE* fp = fopen(fileName, "a+"); + if (!fp) { + return false; + } + + strcpy(m_fileName, fileName); + if (m_fp) { + fclose(m_fp); + } + m_fp = fp; + return true; +} + +void FileLogger::output(Logger::MsgType type, const char *msg) +{ + time_t t = time(NULL); + tm* lt = localtime(&t); + + fprintf(m_fp, "[%d-%02d-%02d %02d:%02d:%02d] %s: %s\n", + lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, + lt->tm_hour, lt->tm_min, lt->tm_sec, msg_type_text[type], msg); + fflush(m_fp); +} diff --git a/src/util/logger.h b/src/util/logger.h new file mode 100644 index 0000000..d728b18 --- /dev/null +++ b/src/util/logger.h @@ -0,0 +1,72 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef LOGGER_H +#define LOGGER_H + +#include + +class Logger +{ +public: + enum MsgType { + Message = 0, + Warning = 1, + Error = 2 + }; + + Logger(void); + virtual ~Logger(void); + + //Output handler + virtual void output(MsgType type, const char* msg); + + //Output function + static void log(MsgType type, const char* format, ...); + + //Default logger + static Logger* defaultLogger(void); + + //Set Default logger + static void setDefaultLogger(Logger* logger); +}; + + + +class FileLogger : public Logger +{ +public: + FileLogger(void); + FileLogger(const char* fileName); + ~FileLogger(void); + + bool setFileName(const char* fileName); + const char* fileName(void) const + { return m_fileName; } + + virtual void output(MsgType type, const char *msg); + +private: + FILE* m_fp; + char m_fileName[512]; +}; + + + +#endif diff --git a/src/util/objectpool.h b/src/util/objectpool.h new file mode 100644 index 0000000..ad844c2 --- /dev/null +++ b/src/util/objectpool.h @@ -0,0 +1,71 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef OBJECTPOOL_H +#define OBJECTPOOL_H + +#include + +#include "util/vector.h" + +template +class ObjectPool +{ +public: + enum { ObjectSize = sizeof(T) }; + + ObjectPool(void) {} + ~ObjectPool(void) { clear(); } + +public: + T* alloc(void) { + char* ptr = m_pool.pop_back(0); + if (ptr == 0) { + ptr = new char[ObjectSize]; + } + if (ptr) { + return new (ptr) T; + } + return 0; + } + + void free(T* object) { + char* ptr = (char*)object; + if (ptr) { + object->~T(); + m_pool.push_back(ptr); + } + } + + void clear(void) { + for (int i = 0; i < m_pool.size(); ++i) { + delete []m_pool.at(i); + } + m_pool.clear(); + } + +private: + Vector m_pool; + +private: + ObjectPool(const ObjectPool&); + ObjectPool& operator=(const ObjectPool&); +}; + +#endif diff --git a/src/util/queue.h b/src/util/queue.h new file mode 100644 index 0000000..1c6a025 --- /dev/null +++ b/src/util/queue.h @@ -0,0 +1,86 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef QUEUE_H +#define QUEUE_H + +#include "util/objectpool.h" + +template +class Queue +{ +public: + struct Node { + T item; + Node* next; + }; + + Queue(void) : m_entry(NULL), m_tail(NULL) {} + ~Queue(void) { clear(); } + + void append(const T& object) { + if (m_tail) { + m_tail->next = m_objectPool.alloc(); + if (m_tail->next) { + m_tail->next->item = object; + m_tail = m_tail->next; + m_tail->next = NULL; + } + } else { + m_entry = m_objectPool.alloc(); + m_entry->item = object; + m_entry->next = NULL; + m_tail = m_entry; + } + } + + T take(const T& defaultVal) { + Node* node = m_entry; + if (!node) { + return defaultVal; + } + T ret = node->item; + m_entry = m_entry->next; + if (m_entry == NULL) { + m_tail = NULL; + } + m_objectPool.free(node); + return ret; + } + + void clear(void) { + for (Node* node = m_entry; node != NULL;) { + Node* next = node->next; + m_objectPool.free(node); + node = next; + } + m_entry = NULL; + m_tail = NULL; + } + + bool isEmpty(void) const + { return (m_entry == 0); } + +private: + Node* m_entry; + Node* m_tail; + ObjectPool m_objectPool; +}; + +#endif diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 0000000..0fcf998 --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,94 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef STRING_H +#define STRING_H + +#include + +#ifdef WIN32 +#pragma warning(disable: 4996) + +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + +class String +{ +public: + String(void) : m_str(NULL), m_len(0), m_dup(false) {} + String(const String& rhs) : m_str(NULL), m_len(0), m_dup(false) { *this = rhs; } + String(const char* s, int len = -1, bool dup = false) { + if (len == -1) { + len = strlen(s); + } + m_str = (char*)s; + m_len = len; + m_dup = dup; + if (m_dup) { + char* p = new char[m_len+1]; + strncpy(p, m_str, m_len); + p[len] = 0; + m_str = p; + } + } + String& operator=(const String& rhs) { + if (this != &rhs) { + if (m_dup) { + delete []m_str; + } + m_str = rhs.m_str; + m_len = rhs.m_len; + m_dup = rhs.m_dup; + if (m_dup) { + char* p = new char[m_len+1]; + strncpy(p, m_str, m_len); + p[m_len] = 0; + m_str = p; + } + } + return *this; + } + + ~String(void) { + if (m_str && m_dup) { + delete []m_str; + } + } + + friend bool operator ==(const String& lhs, const String& rhs) { + if (lhs.m_len != rhs.m_len) { + return false; + } + return (strncmp(lhs.m_str, rhs.m_str, lhs.m_len) == 0); + } + friend bool operator !=(const String& lhs, const String& rhs) { + return !(lhs == rhs); + } + + inline const char* data(void) const { return m_str; } + inline int length(void) const { return m_len; } + +private: + char* m_str; + unsigned int m_len : 31; + unsigned int m_dup : 1; +}; + +#endif diff --git a/src/util/tcpserver.cpp b/src/util/tcpserver.cpp new file mode 100644 index 0000000..e4a6861 --- /dev/null +++ b/src/util/tcpserver.cpp @@ -0,0 +1,219 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "logger.h" + +#include "tcpserver.h" + +void onReadClientHandler(socket_t, short, void* arg) +{ + Context* c = (Context*)arg; + IOBuffer* buf = &c->recvBuff; + IOBuffer::DirectCopy cp = buf->beginCopy(); + int ret = c->clientSocket.nonblocking_recv(cp.address, cp.maxsize); + switch (ret) { + case TcpSocket::IOAgain: + c->server->waitRequest(c); + break; + case TcpSocket::IOError: + c->server->closeConnection(c); + break; + default: + buf->endCopy(ret); + c->recvBytes += ret; + switch (c->server->readingRequest(c)) { + case TcpServer::ReadFinished: + c->server->readRequestFinished(c); + break; + case TcpServer::ReadIncomplete: + c->server->waitRequest(c); + break; + case TcpServer::ReadError: + c->server->closeConnection(c); + break; + } + break; + } +} + +void onWriteClientHandler(socket_t, short, void* arg) +{ + Context* c = (Context*)arg; + char* data = c->sendBuff.data() + c->sendBytes; + int size = c->sendBuff.size() - c->sendBytes; + int ret = c->clientSocket.nonblocking_send(data, size); + switch (ret) { + case TcpSocket::IOAgain: + c->_event.set(c->eventLoop, c->clientSocket.socket(), EV_WRITE, onWriteClientHandler, c); + c->_event.active(); + break; + case TcpSocket::IOError: + c->server->closeConnection(c); + break; + default: + c->sendBytes += ret; + if (c->sendBytes != c->sendBuff.size()) { + onWriteClientHandler(0, 0, c); + } else { + c->server->writeReplyFinished(c); + } + break; + } +} + + +void TcpServer::onAcceptHandler(evutil_socket_t sock, short, void* arg) +{ + sockaddr_in clientAddr; + socketlen_t len = sizeof(sockaddr_in); + socket_t clisock = accept(sock, (sockaddr*)&clientAddr, &len); + TcpSocket socket(clisock); + if (socket.isNull()) { + return; + } + + socket.setKeepAlive(); + socket.setNonBlocking(); + socket.setNoDelay(); + + TcpServer* srv = (TcpServer*)arg; + Context* c = srv->createContextObject(); + if (c != NULL) { + c->clientSocket = socket; + c->clientAddress = HostAddress(clientAddr); + c->server = srv; + if (c->eventLoop == NULL) { + c->eventLoop = srv->eventLoop(); + } + srv->clientConnected(c); + srv->waitRequest(c); + } else { + socket.close(); + } +} + + + +TcpServer::TcpServer(void) +{ +} + +TcpServer::~TcpServer(void) +{ + stop(); +} + +bool TcpServer::run(const HostAddress& addr) +{ + if (isRunning()) { + Logger::log(Logger::Error, "TcpServer::run: server is already running"); + return false; + } + + TcpSocket tcpSocket = TcpSocket::createTcpSocket(); + if (tcpSocket.isNull()) { + Logger::log(Logger::Error, "TcpServer::run: %s", strerror(errno)); + return false; + } + + tcpSocket.setReuseaddr(); + tcpSocket.setNoDelay(); + tcpSocket.setNonBlocking(); + + if (!tcpSocket.bind(addr)) { + Logger::log(Logger::Error, "TcpServer::run: bind failed at port %d: %s", + addr.port(), strerror(errno)); + tcpSocket.close(); + return false; + } + + if (!tcpSocket.listen(128)) { + Logger::log(Logger::Error, "TcpServer::run: listen failed at port %d: %s", + addr.port(), strerror(errno)); + tcpSocket.close(); + return false; + } + + m_listener.set(&m_loop, tcpSocket.socket(), EV_READ | EV_PERSIST, onAcceptHandler, this); + m_listener.active(); + + m_socket = tcpSocket; + m_addr = addr; + + m_loop.exec(); + return true; +} + +bool TcpServer::isRunning(void) const +{ + return !m_socket.isNull(); +} + +void TcpServer::stop(void) +{ + if (isRunning()) { + m_listener.remove(); + m_socket.close(); + m_loop.exit(); + } +} + +Context *TcpServer::createContextObject(void) +{ + return new Context; +} + +void TcpServer::destroyContextObject(Context *c) +{ + delete c; +} + +void TcpServer::closeConnection(Context *c) +{ + c->clientSocket.close(); + destroyContextObject(c); +} + +void TcpServer::clientConnected(Context*) +{ +} + +void TcpServer::waitRequest(Context *c) +{ + c->_event.set(c->eventLoop, c->clientSocket.socket(), EV_READ, onReadClientHandler, c); + c->_event.active(); +} + +TcpServer::ReadStatus TcpServer::readingRequest(Context*) +{ + return ReadFinished; +} + +void TcpServer::readRequestFinished(Context*) +{ +} + +void TcpServer::writeReply(Context* c) +{ + onWriteClientHandler(0, 0, c); +} + +void TcpServer::writeReplyFinished(Context*) +{ +} diff --git a/src/util/tcpserver.h b/src/util/tcpserver.h new file mode 100644 index 0000000..1e14337 --- /dev/null +++ b/src/util/tcpserver.h @@ -0,0 +1,95 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include "iobuffer.h" +#include "eventloop.h" +#include "tcpsocket.h" + +class TcpServer; +class Context +{ +public: + Context(void) { + server = NULL; + sendBytes = 0; + recvBytes = 0; + eventLoop = NULL; + } + + virtual ~Context(void) {} + + TcpSocket clientSocket; //Client socket + HostAddress clientAddress; //Client address + TcpServer* server; //The Connected server + IOBuffer sendBuff; //Send buffer + IOBuffer recvBuff; //Recv buffer + int sendBytes; //Current send bytes + int recvBytes; //Current recv bytes + EventLoop* eventLoop; //Use the event loop + Event _event; //Read/Write event +}; + + +class TcpServer +{ +public: + enum ReadStatus { + ReadFinished = 0, + ReadIncomplete = 1, + ReadError = 2 + }; + + TcpServer(void); + virtual ~TcpServer(void); + + EventLoop* eventLoop(void) { return &m_loop; } + + const HostAddress& address(void) const { return m_addr; } + bool run(const HostAddress& addr); + bool isRunning(void) const; + void stop(void); + + virtual Context* createContextObject(void); + virtual void destroyContextObject(Context* c); + virtual void closeConnection(Context* c); + virtual void clientConnected(Context* c); + virtual void waitRequest(Context* c); + virtual ReadStatus readingRequest(Context* c); + virtual void readRequestFinished(Context* c); + virtual void writeReply(Context* c); + virtual void writeReplyFinished(Context* c); + +protected: + static void onAcceptHandler(evutil_socket_t sock, short, void* arg); + +private: + HostAddress m_addr; + Event m_listener; + EventLoop m_loop; + TcpSocket m_socket; + +private: + TcpServer(const TcpServer&); + TcpServer& operator=(const TcpServer&); +}; + +#endif diff --git a/src/util/tcpsocket.cpp b/src/util/tcpsocket.cpp new file mode 100644 index 0000000..ef7d69f --- /dev/null +++ b/src/util/tcpsocket.cpp @@ -0,0 +1,314 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include "tcpsocket.h" + +#ifdef WIN32 +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "shell32.lib") +#pragma comment(lib, "advapi32.lib") +#define SOCK_EAGAIN WSAEWOULDBLOCK +#define SOCKET_ERRNO WSAGetLastError() +#else +#define closesocket close +#define SOCK_EAGAIN EAGAIN +#define SOCKET_ERRNO (errno) +#endif + + +class EnvInitializer +{ +public: + EnvInitializer(void) + { +#ifdef WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + } + + ~EnvInitializer(void) + { +#ifdef WIN32 + WSACleanup(); +#endif + } +}; + +static EnvInitializer envinit; + + + +HostAddress::HostAddress(int port) +{ + m_addr.sin_family = AF_INET; + m_addr.sin_port = htons(port); + m_addr.sin_addr.s_addr = 0; +} + +HostAddress::HostAddress(const char *ip, int port) +{ + m_addr.sin_family = AF_INET; + m_addr.sin_port = htons(port); + m_addr.sin_addr.s_addr = inet_addr(ip); +} + +HostAddress::HostAddress(const sockaddr_in &addr) +{ + m_addr = addr; +} + +HostAddress::~HostAddress(void) +{ +} + +const char *HostAddress::ip(void) const +{ + char* s = inet_ntoa(m_addr.sin_addr); + strcpy(m_ipBuff, s); + return m_ipBuff; +} + +int HostAddress::port(void) const +{ + return ntohs(m_addr.sin_port); +} + + + + +TcpSocket::TcpSocket(socket_t sock) +{ + m_socket = sock; +} + +TcpSocket::~TcpSocket(void) +{ +} + +TcpSocket TcpSocket::createTcpSocket(void) +{ + socket_t sock = ::socket(AF_INET, SOCK_STREAM, 0); + return TcpSocket(sock); +} + +bool TcpSocket::bind(const HostAddress& addr) +{ + if (::bind(m_socket, (sockaddr*)addr._sockaddr(), sizeof(sockaddr_in)) != 0) { + return false; + } + return true; +} + +bool TcpSocket::listen(int backlog) +{ + if (::listen(m_socket, backlog) != 0) { + return false; + } + return true; +} + +int TcpSocket::option(int level, int name, char *val, socketlen_t* vallen) +{ + return ::getsockopt(m_socket, level, name, val, vallen); +} + +int TcpSocket::setOption(int level, int name, char *val, socketlen_t vallen) +{ + return ::setsockopt(m_socket, level, name, val, vallen); +} + +bool TcpSocket::setNonBlocking(void) +{ +#ifdef WIN32 + u_long nonblocking = 1; + if (ioctlsocket(m_socket, FIONBIO, &nonblocking) == SOCKET_ERROR) { + return false; + } +#else + int flags; + if ((flags = fcntl(m_socket, F_GETFL, NULL)) < 0) { + return false; + } + if (fcntl(m_socket, F_SETFL, flags | O_NONBLOCK) == -1) { + return false; + } +#endif + return true; +} + +bool TcpSocket::setReuseaddr(void) +{ + int reuse; + socketlen_t len; + + reuse = 1; + len = sizeof(reuse); + + return (setOption(SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, len) == 0); +} + +bool TcpSocket::setNoDelay(void) +{ + int nodelay; + socketlen_t len; + + nodelay = 1; + len = sizeof(nodelay); + + return (setOption(IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, len) == 0); +} + +bool TcpSocket::setKeepAlive(void) +{ + int val = 1; + return (setOption(SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(val)) == 0); +} + +bool TcpSocket::setSendBufferSize(int size) +{ + socketlen_t len; + len = sizeof(size); + return (setOption(SOL_SOCKET, SO_SNDBUF, (char*)&size, len) == 0); +} + +bool TcpSocket::setRecvBufferSize(int size) +{ + socketlen_t len; + len = sizeof(size); + return (setOption(SOL_SOCKET, SO_RCVBUF, (char*)&size, len) == 0); +} + +bool TcpSocket::setSendTimeout(int msec) +{ + timeval val; + val.tv_sec = (msec / 1000); + val.tv_usec = (msec - val.tv_sec * 1000) * 1000; + + return (setOption(SOL_SOCKET, SO_SNDTIMEO,(char*)&val,sizeof(val)) == 0); +} + +bool TcpSocket::setRecvTimeout(int msec) +{ + timeval val; + val.tv_sec = (msec / 1000); + val.tv_usec = (msec - val.tv_sec * 1000) * 1000; + + return (setOption(SOL_SOCKET, SO_RCVTIMEO,(char*)&val,sizeof(val)) == 0); +} + +int TcpSocket::sendBufferSize(void) +{ + int status, size; + socketlen_t len; + + size = 0; + len = sizeof(size); + + status = option(SOL_SOCKET, SO_SNDBUF, (char*)&size, &len); + if (status < 0) { + return -1; + } + + return size; +} + +int TcpSocket::recvBufferSize(void) +{ + int status, size; + socketlen_t len; + + size = 0; + len = sizeof(size); + + status = option(SOL_SOCKET, SO_RCVBUF, (char*)&size, &len); + if (status < 0) { + return -1; + } + + return size; +} + +bool TcpSocket::connect(const HostAddress &addr) +{ + if (::connect(m_socket, (sockaddr*)addr._sockaddr(), sizeof(sockaddr_in)) != 0) { + return false; + } + + return true; +} + +int TcpSocket::nonblocking_send(const char *buff, int size, int flag) +{ + int ret = ::send(m_socket, buff, size, flag); + if (ret > 0) { + return ret; + } else if (ret == -1) { + switch(SOCKET_ERRNO) { + case SOCK_EAGAIN: + return IOAgain; + default: + return IOError; + } + } else { + return IOError; + } +} + +int TcpSocket::nonblocking_recv(char *buff, int size, int flag) +{ + int ret = ::recv(m_socket, buff, size, flag); + if (ret > 0) { + return ret; + } else if (ret == 0) { + return IOError; + } else if (ret == -1) { + switch(SOCKET_ERRNO) { + case SOCK_EAGAIN: + return IOAgain; + default: + return IOError; + } + } else { + return IOError; + } +} + +void TcpSocket::close(void) +{ + if (m_socket != -1) { + TcpSocket::close(m_socket); + m_socket = -1; + } +} + +bool TcpSocket::isNull(void) const +{ + return (m_socket < 0); +} + +void TcpSocket::close(socket_t sock) +{ +#ifdef WIN32 + ::closesocket(sock); +#else + ::close(sock); +#endif +} + diff --git a/src/util/tcpsocket.h b/src/util/tcpsocket.h new file mode 100644 index 0000000..a2794ae --- /dev/null +++ b/src/util/tcpsocket.h @@ -0,0 +1,119 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef SOCKET_H +#define SOCKET_H + +#ifdef WIN32 +#include +#include +typedef intptr_t socket_t; +typedef int socketlen_t; +#else +#include +#include +#include +#include +#include +#include +#include +#include +typedef int socket_t; +typedef socklen_t socketlen_t; +#endif + +#include "util/string.h" + +class HostAddress +{ +public: + HostAddress(int port = 0); + HostAddress(const char* ip, int port); + HostAddress(const sockaddr_in& addr); + ~HostAddress(void); + + const char* ip(void) const; + int port(void) const; + + sockaddr_in* _sockaddr(void) const + { return (sockaddr_in*)&m_addr; } + +private: + mutable char m_ipBuff[32]; + sockaddr_in m_addr; +}; + +class TcpSocket +{ +public: + enum { + IOAgain = -1, + IOError = -2 + }; + + TcpSocket(socket_t sock = -1); + ~TcpSocket(void); + + static TcpSocket createTcpSocket(void); + + socket_t socket(void) const { return m_socket; } + + bool bind(const HostAddress& addr); + bool listen(int backlog); + + int option(int level, int name, char* val, socketlen_t* vallen); + int setOption(int level, int name, char* val, socketlen_t vallen); + + bool setNonBlocking(void); + bool setReuseaddr(void); + bool setNoDelay(void); + bool setKeepAlive(void); + bool setSendBufferSize(int size); + bool setRecvBufferSize(int size); + bool setSendTimeout(int msec); + bool setRecvTimeout(int msec); + + int sendBufferSize(void); + int recvBufferSize(void); + + bool connect(const HostAddress& addr); + + //Blocking + int send(const char *buf, int len, int flags = 0) { + return ::send(m_socket, buf, len, flags); + } + int recv(char *buf, int len, int flags = 0) { + return ::recv(m_socket, buf, len, flags); + } + + //NonBlocking + int nonblocking_send(const char* buff, int size, int flag = 0); + int nonblocking_recv(char* buff, int size, int flag = 0); + + void close(void); + bool isNull(void) const; + + static void close(socket_t sock); + +private: + socket_t m_socket; +}; + + +#endif diff --git a/src/util/thread.cpp b/src/util/thread.cpp new file mode 100644 index 0000000..5f99596 --- /dev/null +++ b/src/util/thread.cpp @@ -0,0 +1,177 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#include + +#ifdef WIN32 +#include +#else +#include +#include +#endif + +#include "thread.h" + +class ThreadPrivate +{ +public: + ThreadPrivate(Thread* thread) : + m_isRunning(false), + m_thread(thread) + {} + virtual ~ThreadPrivate(void) { + if (m_isRunning) { + terminate(); + } + } + + virtual void start(void) {} + virtual void terminate(void) {} + bool isRunning(void) const { return m_isRunning; } + + bool m_isRunning; + Thread* m_thread; +}; + +#ifdef WIN32 +class WinThread : public ThreadPrivate +{ +public: + WinThread(Thread* thread) : + ThreadPrivate(thread), + m_tHandle(INVALID_HANDLE_VALUE) + {} + + ~WinThread(void) {} + + virtual void start(void) + { + m_tHandle = ::CreateThread(NULL, 0, + (LPTHREAD_START_ROUTINE)WinThreadEntry, + (void*)m_thread, + NULL, + NULL); + } + + virtual void terminate(void) + { + ::TerminateThread(m_tHandle, 0); + m_isRunning = false; + m_tHandle = INVALID_HANDLE_VALUE; + } + + static DWORD WINAPI WinThreadEntry(LPVOID lp) + { + Thread* thread = (Thread*)lp; + thread->m_priv->m_isRunning = true; + thread->run(); + thread->m_priv->m_isRunning = false; + return 0; + } +private: + HANDLE m_tHandle; +}; + +#else + +class UnixThread : public ThreadPrivate +{ +public: + UnixThread(Thread* thread) : + ThreadPrivate(thread) + {} + ~UnixThread(void) {} + + virtual void start(void) { + pthread_create(&m_thread_id, NULL, UnixThreadEntry, m_thread); + } + + virtual void terminate(void) { + pthread_cancel(m_thread_id); + m_isRunning = false; + } + + static void* UnixThreadEntry(void* lp) + { + Thread* thread = (Thread*)lp; + thread->m_priv->m_isRunning = true; + thread->run(); + thread->m_priv->m_isRunning = false; + return NULL; + } + +private: + pthread_t m_thread_id; +}; + +#endif + +Thread::Thread(void) : m_priv(NULL) +{ +#ifdef WIN32 + m_priv = new WinThread(this); +#else + m_priv = new UnixThread(this); +#endif +} + +Thread::~Thread(void) +{ + delete m_priv; +} + +void Thread::start(void) +{ + if (!isRunning()) { + m_priv->start(); + } +} + +void Thread::terminate(void) +{ + if (isRunning()) { + m_priv->terminate(); + } +} + +bool Thread::isRunning(void) const +{ + return m_priv->isRunning(); +} + +void Thread::sleep(int msec) +{ +#ifdef WIN32 + Sleep(msec); +#else + usleep(msec * 1000); +#endif +} + +Thread::tid_t Thread::currentThreadId(void) +{ +#ifdef WIN32 + return (tid_t)::GetCurrentThreadId(); +#else + pthread_t tid = pthread_self(); + tid_t thread_id = 0; + memcpy(&thread_id, &tid, sizeof(tid_t)); + return thread_id; +#endif +} diff --git a/src/util/thread.h b/src/util/thread.h new file mode 100644 index 0000000..d822889 --- /dev/null +++ b/src/util/thread.h @@ -0,0 +1,52 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + + +#ifndef THREAD_H +#define THREAD_H + +class ThreadPrivate; +class Thread +{ +public: + typedef unsigned long long tid_t; + + Thread(void); + virtual ~Thread(void); + + void start(void); + void terminate(void); + bool isRunning(void) const; + + static void sleep(int msec); + static tid_t currentThreadId(void); + +protected: + virtual void run(void) = 0; + +private: + ThreadPrivate* m_priv; + + friend class WinThread; + friend class UnixThread; + Thread(const Thread& rhs); + Thread& operator=(const Thread& rhs); +}; + +#endif diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 0000000..c5d6267 --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,102 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + +#ifndef VECTOR_H +#define VECTOR_H + +template +class Vector +{ +public: + enum { ChunkSize = 64 }; + Vector(void) { + buff = 0; + curcnt = 0; + capacity = 0; + resize(ChunkSize); + } + + Vector(int n) { + resize(n); + } + + ~Vector(void) { + delete []buff; + } + + void append(const T& item) { + if (curcnt == capacity) { + int new_capacity = capacity+ChunkSize; + resize(new_capacity); + } + buff[curcnt] = item; + ++curcnt; + } + + T* data(void) { return buff; } + const T* data(void) const { return buff; } + int size(void) const { return curcnt; } + + bool isEmpty(void) const { return (curcnt == 0); } + + T& at(int n) { return buff[n]; } + const T& at(int n) const { return buff[n]; } + + void push_back(const T& item) { append(item); } + T pop_back(const T& defaultVal) { + if (curcnt == 0) { + return defaultVal; + } + return buff[--curcnt]; + } + + void resize(int n) { + if (n <= 0) { + return; + } + + T* p = new T[n]; + int copy = (n < curcnt) ? n : curcnt; + for (int i = 0; i < copy; ++i) { + p[i] = buff[i]; + } + if (buff) { + delete []buff; + } + buff = p; + capacity = n; + } + + void clear(void) { + if (buff) { + delete []buff; + } + buff = 0; + curcnt = 0; + capacity = 0; + resize(ChunkSize); + } + +private: + T* buff; + int curcnt; + int capacity; +}; + +#endif