Skip to content

Commit

Permalink
* Split THttpClient in THttpTransport interface and THttpClient / THt…
Browse files Browse the repository at this point in the history
…tpServer transports, we're now able to have C++ thrift service talking json over http for ajax ! :)
  • Loading branch information
Jérémie BORDIER committed Jan 3, 2009
1 parent 49651ab commit fa4b8b5
Show file tree
Hide file tree
Showing 4 changed files with 484 additions and 0 deletions.
95 changes: 95 additions & 0 deletions lib/cpp/src/transport/THttpServer.cpp
@@ -0,0 +1,95 @@
#include <cstdlib>
#include <sstream>
#include <iostream>

#include "THttpServer.h"
#include "TSocket.h"

namespace facebook { namespace thrift { namespace transport {

using namespace std;

/**
* Http server transport implementation.
*
* @author Jeremie BORDIER <jeremie.bordier@gmail.com>
*/

THttpServer::THttpServer(boost::shared_ptr<TTransport> 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
57 changes: 57 additions & 0 deletions 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 <transport/THttpTransport.h>

namespace facebook { namespace thrift { namespace transport {

/**
* HTTP server transport implementation.
*
* @author Jeremie BORDIER <jeremie.bordier@gmail.com>
*/
class THttpServer : public THttpTransport {
public:
THttpServer(boost::shared_ptr<TTransport> 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 <jeremie.bordier@gmail.com>
*/
class THttpServerTransportFactory : public TTransportFactory {
public:
THttpServerTransportFactory() {}

virtual ~THttpServerTransportFactory() {}

/**
* Wraps the transport into a buffered one.
*/
virtual boost::shared_ptr<TTransport> getTransport(boost::shared_ptr<TTransport> trans) {
return boost::shared_ptr<TTransport>(new THttpServer(trans));
}

};

}}} // facebook::thrift::transport

#endif // #ifndef _THRIFT_TRANSPORT_THTTPSERVER_H_
240 changes: 240 additions & 0 deletions lib/cpp/src/transport/THttpTransport.cpp
@@ -0,0 +1,240 @@
#include "THttpTransport.h"

/**
* Http client implementation.
*
* @author Jeremie BORDIER <jeremie.bordier@gmail.com>
* and Mark Slee <mcslee@facebook.com>
*/

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<TTransport> 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);
}

}}}

0 comments on commit fa4b8b5

Please sign in to comment.