424 lines
8.9 KiB
C++
424 lines
8.9 KiB
C++
#include "examples/memcached/server/Session.h"
|
|
#include "examples/memcached/server/MemcacheServer.h"
|
|
|
|
#ifdef HAVE_TCMALLOC
|
|
#include <gperftools/malloc_extension.h>
|
|
#endif
|
|
|
|
using namespace muduo;
|
|
using namespace muduo::net;
|
|
|
|
static bool isBinaryProtocol(uint8_t firstByte)
|
|
{
|
|
return firstByte == 0x80;
|
|
}
|
|
|
|
const int kLongestKeySize = 250;
|
|
string Session::kLongestKey(kLongestKeySize, 'x');
|
|
|
|
template <typename InputIterator, typename Token>
|
|
bool Session::SpaceSeparator::operator()(InputIterator& next, InputIterator end, Token& tok)
|
|
{
|
|
while (next != end && *next == ' ')
|
|
++next;
|
|
if (next == end)
|
|
{
|
|
tok.clear();
|
|
return false;
|
|
}
|
|
InputIterator start(next);
|
|
const char* sp = static_cast<const char*>(memchr(start, ' ', end - start));
|
|
if (sp)
|
|
{
|
|
tok.set(start, static_cast<int>(sp - start));
|
|
next = sp;
|
|
}
|
|
else
|
|
{
|
|
tok.set(start, static_cast<int>(end - next));
|
|
next = end;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct Session::Reader
|
|
{
|
|
Reader(Tokenizer::iterator& beg, Tokenizer::iterator end)
|
|
: first_(beg),
|
|
last_(end)
|
|
{
|
|
}
|
|
|
|
template<typename T>
|
|
bool read(T* val)
|
|
{
|
|
if (first_ == last_)
|
|
return false;
|
|
char* end = NULL;
|
|
uint64_t x = strtoull((*first_).data(), &end, 10);
|
|
if (end == (*first_).end())
|
|
{
|
|
*val = static_cast<T>(x);
|
|
++first_;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
Tokenizer::iterator first_;
|
|
Tokenizer::iterator last_;;
|
|
};
|
|
|
|
void Session::onMessage(const muduo::net::TcpConnectionPtr& conn,
|
|
muduo::net::Buffer* buf,
|
|
muduo::Timestamp)
|
|
|
|
{
|
|
const size_t initialReadable = buf->readableBytes();
|
|
|
|
while (buf->readableBytes() > 0)
|
|
{
|
|
if (state_ == kNewCommand)
|
|
{
|
|
if (protocol_ == kAuto)
|
|
{
|
|
assert(bytesRead_ == 0);
|
|
protocol_ = isBinaryProtocol(buf->peek()[0]) ? kBinary : kAscii;
|
|
}
|
|
|
|
assert(protocol_ == kAscii || protocol_ == kBinary);
|
|
if (protocol_ == kBinary)
|
|
{
|
|
// FIXME
|
|
}
|
|
else // ASCII protocol
|
|
{
|
|
const char* crlf = buf->findCRLF();
|
|
if (crlf)
|
|
{
|
|
int len = static_cast<int>(crlf - buf->peek());
|
|
StringPiece request(buf->peek(), len);
|
|
if (processRequest(request))
|
|
{
|
|
resetRequest();
|
|
}
|
|
buf->retrieveUntil(crlf + 2);
|
|
}
|
|
else
|
|
{
|
|
if (buf->readableBytes() > 1024)
|
|
{
|
|
// FIXME: check for 'get' and 'gets'
|
|
conn_->shutdown();
|
|
// buf->retrieveAll() ???
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (state_ == kReceiveValue)
|
|
{
|
|
receiveValue(buf);
|
|
}
|
|
else if (state_ == kDiscardValue)
|
|
{
|
|
discardValue(buf);
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
bytesRead_ += initialReadable - buf->readableBytes();
|
|
}
|
|
|
|
void Session::receiveValue(muduo::net::Buffer* buf)
|
|
{
|
|
assert(currItem_.get());
|
|
assert(state_ == kReceiveValue);
|
|
// if (protocol_ == kBinary)
|
|
|
|
const size_t avail = std::min(buf->readableBytes(), currItem_->neededBytes());
|
|
assert(currItem_.unique());
|
|
currItem_->append(buf->peek(), avail);
|
|
buf->retrieve(avail);
|
|
if (currItem_->neededBytes() == 0)
|
|
{
|
|
if (currItem_->endsWithCRLF())
|
|
{
|
|
bool exists = false;
|
|
if (owner_->storeItem(currItem_, policy_, &exists))
|
|
{
|
|
reply("STORED\r\n");
|
|
}
|
|
else
|
|
{
|
|
if (policy_ == Item::kCas)
|
|
{
|
|
if (exists)
|
|
{
|
|
reply("EXISTS\r\n");
|
|
}
|
|
else
|
|
{
|
|
reply("NOT_FOUND\r\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reply("NOT_STORED\r\n");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reply("CLIENT_ERROR bad data chunk\r\n");
|
|
}
|
|
resetRequest();
|
|
state_ = kNewCommand;
|
|
}
|
|
}
|
|
|
|
void Session::discardValue(muduo::net::Buffer* buf)
|
|
{
|
|
assert(!currItem_);
|
|
assert(state_ == kDiscardValue);
|
|
if (buf->readableBytes() < bytesToDiscard_)
|
|
{
|
|
bytesToDiscard_ -= buf->readableBytes();
|
|
buf->retrieveAll();
|
|
}
|
|
else
|
|
{
|
|
buf->retrieve(bytesToDiscard_);
|
|
bytesToDiscard_ = 0;
|
|
resetRequest();
|
|
state_ = kNewCommand;
|
|
}
|
|
}
|
|
|
|
bool Session::processRequest(StringPiece request)
|
|
{
|
|
assert(command_.empty());
|
|
assert(!noreply_);
|
|
assert(policy_ == Item::kInvalid);
|
|
assert(!currItem_);
|
|
assert(bytesToDiscard_ == 0);
|
|
++requestsProcessed_;
|
|
|
|
// check 'noreply' at end of request line
|
|
if (request.size() >= 8)
|
|
{
|
|
StringPiece end(request.end() - 8, 8);
|
|
if (end == " noreply")
|
|
{
|
|
noreply_ = true;
|
|
request.remove_suffix(8);
|
|
}
|
|
}
|
|
|
|
SpaceSeparator sep;
|
|
Tokenizer tok(request.begin(), request.end(), sep);
|
|
Tokenizer::iterator beg = tok.begin();
|
|
if (beg == tok.end())
|
|
{
|
|
reply("ERROR\r\n");
|
|
return true;
|
|
}
|
|
(*beg).CopyToString(&command_);
|
|
++beg;
|
|
if (command_ == "set" || command_ == "add" || command_ == "replace"
|
|
|| command_ == "append" || command_ == "prepend" || command_ == "cas")
|
|
{
|
|
// this normally returns false
|
|
return doUpdate(beg, tok.end());
|
|
}
|
|
else if (command_ == "get" || command_ == "gets")
|
|
{
|
|
bool cas = command_ == "gets";
|
|
|
|
// FIXME: send multiple chunks with write complete callback.
|
|
while (beg != tok.end())
|
|
{
|
|
StringPiece key = *beg;
|
|
bool good = key.size() <= kLongestKeySize;
|
|
if (!good)
|
|
{
|
|
reply("CLIENT_ERROR bad command line format\r\n");
|
|
return true;
|
|
}
|
|
|
|
needle_->resetKey(key);
|
|
ConstItemPtr item = owner_->getItem(needle_);
|
|
++beg;
|
|
if (item)
|
|
{
|
|
item->output(&outputBuf_, cas);
|
|
}
|
|
}
|
|
outputBuf_.append("END\r\n");
|
|
|
|
if (conn_->outputBuffer()->writableBytes() > 65536 + outputBuf_.readableBytes())
|
|
{
|
|
LOG_DEBUG << "shrink output buffer from " << conn_->outputBuffer()->internalCapacity();
|
|
conn_->outputBuffer()->shrink(65536 + outputBuf_.readableBytes());
|
|
}
|
|
|
|
conn_->send(&outputBuf_);
|
|
}
|
|
else if (command_ == "delete")
|
|
{
|
|
doDelete(beg, tok.end());
|
|
}
|
|
else if (command_ == "version")
|
|
{
|
|
#ifdef HAVE_TCMALLOC
|
|
reply("VERSION 0.01 muduo with tcmalloc\r\n");
|
|
#else
|
|
reply("VERSION 0.01 muduo\r\n");
|
|
#endif
|
|
}
|
|
#ifdef HAVE_TCMALLOC
|
|
else if (command_ == "memstat")
|
|
{
|
|
char buf[1024*64];
|
|
MallocExtension::instance()->GetStats(buf, sizeof buf);
|
|
reply(buf);
|
|
}
|
|
#endif
|
|
else if (command_ == "quit")
|
|
{
|
|
conn_->shutdown();
|
|
}
|
|
else if (command_ == "shutdown")
|
|
{
|
|
// "ERROR: shutdown not enabled"
|
|
conn_->shutdown();
|
|
owner_->stop();
|
|
}
|
|
else
|
|
{
|
|
reply("ERROR\r\n");
|
|
LOG_INFO << "Unknown command: " << command_;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Session::resetRequest()
|
|
{
|
|
command_.clear();
|
|
noreply_ = false;
|
|
policy_ = Item::kInvalid;
|
|
currItem_.reset();
|
|
bytesToDiscard_ = 0;
|
|
}
|
|
|
|
void Session::reply(muduo::StringPiece msg)
|
|
{
|
|
if (!noreply_)
|
|
{
|
|
conn_->send(msg.data(), msg.size());
|
|
}
|
|
}
|
|
|
|
bool Session::doUpdate(Session::Tokenizer::iterator& beg, Session::Tokenizer::iterator end)
|
|
{
|
|
if (command_ == "set")
|
|
policy_ = Item::kSet;
|
|
else if (command_ == "add")
|
|
policy_ = Item::kAdd;
|
|
else if (command_ == "replace")
|
|
policy_ = Item::kReplace;
|
|
else if (command_ == "append")
|
|
policy_ = Item::kAppend;
|
|
else if (command_ == "prepend")
|
|
policy_ = Item::kPrepend;
|
|
else if (command_ == "cas")
|
|
policy_ = Item::kCas;
|
|
else
|
|
assert(false);
|
|
|
|
// FIXME: check (beg != end)
|
|
StringPiece key = (*beg);
|
|
++beg;
|
|
bool good = key.size() <= kLongestKeySize;
|
|
|
|
uint32_t flags = 0;
|
|
time_t exptime = 1;
|
|
int bytes = -1;
|
|
uint64_t cas = 0;
|
|
|
|
Reader r(beg, end);
|
|
good = good && r.read(&flags) && r.read(&exptime) && r.read(&bytes);
|
|
|
|
int rel_exptime = static_cast<int>(exptime);
|
|
if (exptime > 60*60*24*30)
|
|
{
|
|
rel_exptime = static_cast<int>(exptime - owner_->startTime());
|
|
if (rel_exptime < 1)
|
|
{
|
|
rel_exptime = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// rel_exptime = exptime + currentTime;
|
|
}
|
|
|
|
if (good && policy_ == Item::kCas)
|
|
{
|
|
good = r.read(&cas);
|
|
}
|
|
|
|
if (!good)
|
|
{
|
|
reply("CLIENT_ERROR bad command line format\r\n");
|
|
return true;
|
|
}
|
|
if (bytes > 1024*1024)
|
|
{
|
|
reply("SERVER_ERROR object too large for cache\r\n");
|
|
needle_->resetKey(key);
|
|
owner_->deleteItem(needle_);
|
|
bytesToDiscard_ = bytes + 2;
|
|
state_ = kDiscardValue;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
currItem_ = Item::makeItem(key, flags, rel_exptime, bytes + 2, cas);
|
|
state_ = kReceiveValue;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Session::doDelete(Session::Tokenizer::iterator& beg, Session::Tokenizer::iterator end)
|
|
{
|
|
assert(command_ == "delete");
|
|
// FIXME: check (beg != end)
|
|
StringPiece key = *beg;
|
|
bool good = key.size() <= kLongestKeySize;
|
|
++beg;
|
|
if (!good)
|
|
{
|
|
reply("CLIENT_ERROR bad command line format\r\n");
|
|
}
|
|
else if (beg != end && *beg != "0") // issue 108, old protocol
|
|
{
|
|
reply("CLIENT_ERROR bad command line format. Usage: delete <key> [noreply]\r\n");
|
|
}
|
|
else
|
|
{
|
|
needle_->resetKey(key);
|
|
if (owner_->deleteItem(needle_))
|
|
{
|
|
reply("DELETED\r\n");
|
|
}
|
|
else
|
|
{
|
|
reply("NOT_FOUND\r\n");
|
|
}
|
|
}
|
|
}
|