From fa4b8b5b0b1dea2cef01868192b526e7e5f5a555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20BORDIER?= Date: Sat, 3 Jan 2009 22:55:39 +0100 Subject: [PATCH] * Split THttpClient in THttpTransport interface and THttpClient / THttpServer transports, we're now able to have C++ thrift service talking json over http for ajax ! :) --- lib/cpp/src/transport/THttpServer.cpp | 95 +++++++++ lib/cpp/src/transport/THttpServer.h | 57 ++++++ lib/cpp/src/transport/THttpTransport.cpp | 240 +++++++++++++++++++++++ lib/cpp/src/transport/THttpTransport.h | 92 +++++++++ 4 files changed, 484 insertions(+) create mode 100644 lib/cpp/src/transport/THttpServer.cpp create mode 100644 lib/cpp/src/transport/THttpServer.h create mode 100644 lib/cpp/src/transport/THttpTransport.cpp create mode 100644 lib/cpp/src/transport/THttpTransport.h diff --git a/lib/cpp/src/transport/THttpServer.cpp b/lib/cpp/src/transport/THttpServer.cpp new file mode 100644 index 000000000..7fe7a6a81 --- /dev/null +++ b/lib/cpp/src/transport/THttpServer.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "THttpServer.h" +#include "TSocket.h" + +namespace facebook { namespace thrift { namespace transport { + +using namespace std; + +/** + * Http server transport implementation. + * + * @author Jeremie BORDIER + */ + +THttpServer::THttpServer(boost::shared_ptr transport) : + THttpTransport(transport) { +} + +THttpServer::~THttpServer() {} + +void THttpServer::parseHeader(char* header) { + char* colon = strchr(header, ':'); + if (colon == NULL) { + return; + } + uint32_t sz = colon - header; + char* value = colon+1; + + if (strncmp(header, "Transfer-Encoding", sz) == 0) { + if (strstr(value, "chunked") != NULL) { + chunked_ = true; + } + } else if (strncmp(header, "Content-Length", sz) == 0) { + chunked_ = false; + contentLength_ = atoi(value); + } +} + +bool THttpServer::parseStatusLine(char* status) { + char* method = status; + + char* path = strchr(method, ' '); + if (path == NULL) { + throw TTransportException(string("Bad Status: ") + status); + } + + *path = '\0'; + while (*(++path) == ' '); + + char* http = strchr(path, ' '); + if (http == NULL) { + throw TTransportException(string("Bad Status: ") + status); + } + *http = '\0'; + + if (strcmp(method, "POST") == 0) { + // POST method ok, looking for content. + return true; + } + throw TTransportException(string("Bad Status (unsupported method): ") + status); +} + +void THttpServer::flush() { + // Fetch the contents of the write buffer + uint8_t* buf; + uint32_t len; + writeBuffer_.getBuffer(&buf, &len); + + // Construct the HTTP header + std::ostringstream h; + h << + "HTTP/1.1 200 Ok" << CRLF << + "Date: Fri, 02 Jan 2009 19:09:44 GMT" << CRLF << + "Server: Thrift/0.1" << CRLF << + "Content-Type: application/x-thrift" << CRLF << + "Content-Length: " << len << CRLF << + "Connection: Keep-Alive" << CRLF << + CRLF; + string header = h.str(); + + // Write the header, then the data, then flush + transport_->write((const uint8_t*)header.c_str(), header.size()); + transport_->write(buf, len); + transport_->flush(); +// printf(">>>\n%s%s\n", header.c_str(), buf); + + // Reset the buffer and header variables + writeBuffer_.resetBuffer(); + readHeaders_ = true; +} + +}}} // facebook::thrift::transport diff --git a/lib/cpp/src/transport/THttpServer.h b/lib/cpp/src/transport/THttpServer.h new file mode 100644 index 000000000..2100d7571 --- /dev/null +++ b/lib/cpp/src/transport/THttpServer.h @@ -0,0 +1,57 @@ +// Copyright (c) 2006- Facebook +// Distributed under the Thrift Software License +// +// See accompanying file LICENSE or visit the Thrift site at: +// http://developers.facebook.com/thrift/ + +#ifndef _THRIFT_TRANSPORT_THTTPSERVER_H_ +#define _THRIFT_TRANSPORT_THTTPSERVER_H_ 1 + +#include + +namespace facebook { namespace thrift { namespace transport { + +/** + * HTTP server transport implementation. + * + * @author Jeremie BORDIER + */ +class THttpServer : public THttpTransport { + public: + THttpServer(boost::shared_ptr transport); + + virtual ~THttpServer(); + + virtual void flush(); + + protected: + + void readHeaders(); + virtual void parseHeader(char* header); + virtual bool parseStatusLine(char* status); + +}; + +/** + * Wraps a transport into HTTP protocol + * + * @author Jeremie BORDIER + */ +class THttpServerTransportFactory : public TTransportFactory { + public: + THttpServerTransportFactory() {} + + virtual ~THttpServerTransportFactory() {} + + /** + * Wraps the transport into a buffered one. + */ + virtual boost::shared_ptr getTransport(boost::shared_ptr trans) { + return boost::shared_ptr(new THttpServer(trans)); + } + +}; + +}}} // facebook::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_THTTPSERVER_H_ diff --git a/lib/cpp/src/transport/THttpTransport.cpp b/lib/cpp/src/transport/THttpTransport.cpp new file mode 100644 index 000000000..1ca34d518 --- /dev/null +++ b/lib/cpp/src/transport/THttpTransport.cpp @@ -0,0 +1,240 @@ +#include "THttpTransport.h" + +/** + * Http client implementation. + * + * @author Jeremie BORDIER + * and Mark Slee + */ + +namespace facebook { namespace thrift { namespace transport { + +using namespace std; + +// Yeah, yeah, hacky to put these here, I know. +const char* THttpTransport::CRLF = "\r\n"; +const int THttpTransport::CRLF_LEN = 2; + +THttpTransport::THttpTransport(boost::shared_ptr transport) : + transport_(transport), + readHeaders_(true), + chunked_(false), + chunkedDone_(false), + chunkSize_(0), + contentLength_(0), + httpBuf_(NULL), + httpPos_(0), + httpBufLen_(0), + httpBufSize_(1024) { + init(); +} + +void THttpTransport::init() { + httpBuf_ = (char*)std::malloc(httpBufSize_+1); + if (httpBuf_ == NULL) { + throw TTransportException("Out of memory."); + } + httpBuf_[httpBufLen_] = '\0'; +} + +THttpTransport::~THttpTransport() { + if (httpBuf_ != NULL) { + std::free(httpBuf_); + } +} + +uint32_t THttpTransport::read(uint8_t* buf, uint32_t len) { + if (readBuffer_.available_read() == 0) { + readBuffer_.resetBuffer(); + uint32_t got = readMoreData(); + if (got == 0) { + return 0; + } + } + return readBuffer_.read(buf, len); +} + +void THttpTransport::readEnd() { + // Read any pending chunked data (footers etc.) + if (chunked_) { + while (!chunkedDone_) { + readChunked(); + } + } +} + +uint32_t THttpTransport::readMoreData() { + uint32_t size; + + // Get more data! + refill(); + + if (readHeaders_) { + readHeaders(); + } + + if (chunked_) { + size = readChunked(); + } else { + size = readContent(contentLength_); + } + readHeaders_ = true; + return size; +} + +uint32_t THttpTransport::readChunked() { + uint32_t length = 0; + + char* line = readLine(); + uint32_t chunkSize = parseChunkSize(line); + if (chunkSize == 0) { + readChunkedFooters(); + } else { + // Read data content + length += readContent(chunkSize); + // Read trailing CRLF after content + readLine(); + } + return length; +} + +void THttpTransport::readChunkedFooters() { + // End of data, read footer lines until a blank one appears + while (true) { + char* line = readLine(); + if (strlen(line) == 0) { + chunkedDone_ = true; + break; + } + } +} + +uint32_t THttpTransport::parseChunkSize(char* line) { + char* semi = strchr(line, ';'); + if (semi != NULL) { + *semi = '\0'; + } + int size = 0; + sscanf(line, "%x", &size); + return (uint32_t)size; +} + +uint32_t THttpTransport::readContent(uint32_t size) { + uint32_t need = size; + while (need > 0) { + uint32_t avail = httpBufLen_ - httpPos_; + if (avail == 0) { + // We have given all the data, reset position to head of the buffer + httpPos_ = 0; + httpBufLen_ = 0; + refill(); + + // Now have available however much we read + avail = httpBufLen_; + } + uint32_t give = avail; + if (need < give) { + give = need; + } + readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give); + httpPos_ += give; + need -= give; + } + return size; +} + +char* THttpTransport::readLine() { + while (true) { + char* eol = NULL; + + eol = strstr(httpBuf_+httpPos_, CRLF); + + // No CRLF yet? + if (eol == NULL) { + // Shift whatever we have now to front and refill + shift(); + refill(); + } else { + // Return pointer to next line + *eol = '\0'; + char* line = httpBuf_+httpPos_; + httpPos_ = (eol-httpBuf_) + CRLF_LEN; + return line; + } + } + +} + +void THttpTransport::shift() { + if (httpBufLen_ > httpPos_) { + // Shift down remaining data and read more + uint32_t length = httpBufLen_ - httpPos_; + memmove(httpBuf_, httpBuf_+httpPos_, length); + httpBufLen_ = length; + } else { + httpBufLen_ = 0; + } + httpPos_ = 0; + httpBuf_[httpBufLen_] = '\0'; +} + +void THttpTransport::refill() { + uint32_t avail = httpBufSize_ - httpBufLen_; + if (avail <= (httpBufSize_ / 4)) { + httpBufSize_ *= 2; + httpBuf_ = (char*)std::realloc(httpBuf_, httpBufSize_+1); + if (httpBuf_ == NULL) { + throw TTransportException("Out of memory."); + } + } + + // Read more data + uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_); + httpBufLen_ += got; + httpBuf_[httpBufLen_] = '\0'; +// printf("<<<\n%s\n", httpBuf_); + + if (got == 0) { + throw TTransportException("Could not refill buffer"); + } +} + +void THttpTransport::readHeaders() { + // Initialize headers state variables + contentLength_ = 0; + chunked_ = false; + chunkedDone_ = false; + chunkSize_ = 0; + + // Control state flow + bool statusLine = true; + bool finished = false; + + // Loop until headers are finished + while (true) { + char* line = readLine(); + + if (strlen(line) == 0) { + if (finished) { + readHeaders_ = false; + return; + } else { + // Must have been an HTTP 100, keep going for another status line + statusLine = true; + } + } else { + if (statusLine) { + statusLine = false; + finished = parseStatusLine(line); + } else { + parseHeader(line); + } + } + } +} + +void THttpTransport::write(const uint8_t* buf, uint32_t len) { + writeBuffer_.write(buf, len); +} + +}}} diff --git a/lib/cpp/src/transport/THttpTransport.h b/lib/cpp/src/transport/THttpTransport.h new file mode 100644 index 000000000..9c70e641a --- /dev/null +++ b/lib/cpp/src/transport/THttpTransport.h @@ -0,0 +1,92 @@ +#ifndef _THRIFT_TRANSPORT_THTTPTRANSPORT_H_ +#define _THRIFT_TRANSPORT_THTTPTRANSPORT_H_ 1 + +#include + +namespace facebook { namespace thrift { namespace transport { + +/** + * HTTP Client/Server transport common base (interface), based on + * Mark Slee's THttpClient implementation. + * + * His old comments are still valid: This was irritating + * to write, but the alternatives in C++ land are daunting. Linking CURL + * requires 23 dynamic libraries last time I checked (WTF?!?). All we have + * here is a VERY basic HTTP/1.1 client which supports HTTP 100 Continue, + * chunked transfer encoding, keepalive, etc. Tested against Apache. + * + * @author Jeremie BORDIER , Mark Slee + */ +class THttpTransport : public TTransport { + public: + THttpTransport(boost::shared_ptr transport); + + virtual ~THttpTransport(); + + void open() { + transport_->open(); + } + + bool isOpen() { + return transport_->isOpen(); + } + + bool peek() { + return transport_->peek(); + } + + void close() { + transport_->close(); + } + + uint32_t read(uint8_t* buf, uint32_t len); + + void readEnd(); + + void write(const uint8_t* buf, uint32_t len); + + virtual void flush() = 0; + + protected: + + boost::shared_ptr transport_; + + TMemoryBuffer writeBuffer_; + TMemoryBuffer readBuffer_; + + bool readHeaders_; + bool chunked_; + bool chunkedDone_; + uint32_t chunkSize_; + uint32_t contentLength_; + + char* httpBuf_; + uint32_t httpPos_; + uint32_t httpBufLen_; + uint32_t httpBufSize_; + + virtual void init(); + + uint32_t readMoreData(); + char* readLine(); + + void readHeaders(); + virtual void parseHeader(char* header) = 0; + virtual bool parseStatusLine(char* status) = 0; + + uint32_t readChunked(); + void readChunkedFooters(); + uint32_t parseChunkSize(char* line); + + uint32_t readContent(uint32_t size); + + void refill(); + void shift(); + + static const char* CRLF; + static const int CRLF_LEN; +}; + +}}} // facebook::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_THTTPCLIENT_H_