blob: c095d2aa3283090ea1df62e7cbd41004c133588c [file] [log] [blame]
Mark Slee8a98e1b2007-02-27 05:16:23 +00001#include "THttpClient.h"
2#include "TSocket.h"
3
4namespace facebook { namespace thrift { namespace transport {
5
6using namespace std;
7
8/**
9 * Http client implementation.
10 *
11 * @author Mark Slee <mcslee@facebook.com>
12 */
13
14// Yeah, yeah, hacky to put these here, I know.
15static const char* CRLF = "\r\n";
16static const int CRLF_LEN = 2;
17
Mark Sleea2c760b2007-02-27 05:18:07 +000018THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
Mark Slee8a98e1b2007-02-27 05:16:23 +000019 transport_(transport),
20 host_(host),
21 path_(path),
22 readHeaders_(true),
23 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000024 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000025 chunkSize_(0),
26 contentLength_(0),
27 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000028 httpPos_(0),
29 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000030 httpBufSize_(1024) {
31 init();
32}
33
34THttpClient::THttpClient(string host, int port, string path) :
35 host_(host),
36 path_(path),
37 readHeaders_(true),
38 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000039 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000040 chunkSize_(0),
41 contentLength_(0),
42 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000043 httpPos_(0),
44 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000045 httpBufSize_(1024) {
46 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
47 init();
48}
49
50void THttpClient::init() {
51 httpBuf_ = (char*)malloc(httpBufSize_+1);
52 if (httpBuf_ == NULL) {
53 throw TTransportException("Out of memory.");
54 }
Mark Slee2a22a882007-02-27 19:53:38 +000055 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000056}
57
58THttpClient::~THttpClient() {
59 if (httpBuf_ != NULL) {
60 free(httpBuf_);
61 }
62}
63
64uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
65 if (readBuffer_.available() == 0) {
66 readBuffer_.resetBuffer();
67 uint32_t got = readMoreData();
68 if (got == 0) {
69 return 0;
70 }
71 }
72 return readBuffer_.read(buf, len);
73}
74
Mark Slee2a22a882007-02-27 19:53:38 +000075void THttpClient::readEnd() {
76 // Read any pending chunked data (footers etc.)
77 if (chunked_) {
78 while (!chunkedDone_) {
79 readChunked();
80 }
81 }
82}
83
Mark Slee8a98e1b2007-02-27 05:16:23 +000084uint32_t THttpClient::readMoreData() {
85 // Get more data!
86 refill();
87
88 if (readHeaders_) {
89 readHeaders();
90 }
91
92 if (chunked_) {
93 return readChunked();
94 } else {
Mark Slee2a22a882007-02-27 19:53:38 +000095 return readContent(contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +000096 }
97}
98
99uint32_t THttpClient::readChunked() {
100 uint32_t length = 0;
Mark Slee2a22a882007-02-27 19:53:38 +0000101
102 char* line = readLine();
103 uint32_t chunkSize = parseChunkSize(line);
104 if (chunkSize == 0) {
105 readChunkedFooters();
106 } else {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000107 // Read data content
Mark Slee2a22a882007-02-27 19:53:38 +0000108 length += readContent(chunkSize);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000109 // Read trailing CRLF after content
Mark Slee2a22a882007-02-27 19:53:38 +0000110 readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000111 }
Mark Slee2a22a882007-02-27 19:53:38 +0000112 return length;
113}
Mark Slee8a98e1b2007-02-27 05:16:23 +0000114
Mark Slee2a22a882007-02-27 19:53:38 +0000115void THttpClient::readChunkedFooters() {
116 // End of data, read footer lines until a blank one appears
Mark Slee8a98e1b2007-02-27 05:16:23 +0000117 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000118 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000119 if (strlen(line) == 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000120 chunkedDone_ = true;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000121 break;
122 }
123 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000124}
125
126uint32_t THttpClient::parseChunkSize(char* line) {
127 char* semi = strchr(line, ';');
128 if (semi != NULL) {
129 *semi = '\0';
130 }
Mark Slee44018142007-02-27 19:03:01 +0000131 int size = 0;
132 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000133 return (uint32_t)size;
134}
135
Mark Slee2a22a882007-02-27 19:53:38 +0000136uint32_t THttpClient::readContent(uint32_t size) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000137 uint32_t need = size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000138 while (need > 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000139 uint32_t avail = httpBufLen_ - httpPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000140 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000141 // We have given all the data, reset position to head of the buffer
Mark Slee2a22a882007-02-27 19:53:38 +0000142 httpPos_ = 0;
143 httpBufLen_ = 0;
144 refill();
Mark Slee44018142007-02-27 19:03:01 +0000145
146 // Now have available however much we read
Mark Slee2a22a882007-02-27 19:53:38 +0000147 avail = httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000148 }
149 uint32_t give = avail;
150 if (need < give) {
151 give = need;
152 }
Mark Slee2a22a882007-02-27 19:53:38 +0000153 readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give);
154 httpPos_ += give;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000155 need -= give;
156 }
Mark Slee2a22a882007-02-27 19:53:38 +0000157 return size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000158}
Mark Slee44018142007-02-27 19:03:01 +0000159
Mark Slee2a22a882007-02-27 19:53:38 +0000160char* THttpClient::readLine() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000161 while (true) {
162 char* eol = NULL;
163
Mark Slee2a22a882007-02-27 19:53:38 +0000164 eol = strstr(httpBuf_+httpPos_, CRLF);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000165
166 // No CRLF yet?
167 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000168 // Shift whatever we have now to front and refill
Mark Slee2a22a882007-02-27 19:53:38 +0000169 shift();
170 refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000171 } else {
172 // Return pointer to next line
173 *eol = '\0';
Mark Slee2a22a882007-02-27 19:53:38 +0000174 char* line = httpBuf_+httpPos_;
175 httpPos_ = (eol-httpBuf_) + CRLF_LEN;
176 return line;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000177 }
178 }
179
180}
181
Mark Slee2a22a882007-02-27 19:53:38 +0000182void THttpClient::shift() {
183 if (httpBufLen_ > httpPos_) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000184 // Shift down remaining data and read more
Mark Slee2a22a882007-02-27 19:53:38 +0000185 uint32_t length = httpBufLen_ - httpPos_;
186 memmove(httpBuf_, httpBuf_+httpPos_, length);
187 httpBufLen_ = length;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000188 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000189 httpBufLen_ = 0;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000190 }
Mark Slee2a22a882007-02-27 19:53:38 +0000191 httpPos_ = 0;
192 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000193}
194
Mark Slee2a22a882007-02-27 19:53:38 +0000195void THttpClient::refill() {
196 uint32_t avail = httpBufSize_ - httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000197 if (avail <= (httpBufSize_ / 4)) {
198 httpBufSize_ *= 2;
Mark Slee44018142007-02-27 19:03:01 +0000199 httpBuf_ = (char*)realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000200 if (httpBuf_ == NULL) {
201 throw TTransportException("Out of memory.");
202 }
203 }
204
205 // Read more data
Mark Slee2a22a882007-02-27 19:53:38 +0000206 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_);
207 httpBufLen_ += got;
208 httpBuf_[httpBufLen_] = '\0';
Mark Slee44018142007-02-27 19:03:01 +0000209
Mark Slee8a98e1b2007-02-27 05:16:23 +0000210 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000211 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000212 }
213}
214
215void THttpClient::readHeaders() {
216 // Initialize headers state variables
217 contentLength_ = 0;
218 chunked_ = false;
Mark Slee2a22a882007-02-27 19:53:38 +0000219 chunkedDone_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000220 chunkSize_ = 0;
221
222 // Control state flow
223 bool statusLine = true;
224 bool finished = false;
225
Mark Slee8a98e1b2007-02-27 05:16:23 +0000226 // Loop until headers are finished
227 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000228 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000229
230 if (strlen(line) == 0) {
231 if (finished) {
232 readHeaders_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000233 return;
234 } else {
235 // Must have been an HTTP 100, keep going for another status line
236 statusLine = true;
237 }
238 } else {
239 if (statusLine) {
240 statusLine = false;
241 finished = parseStatusLine(line);
242 } else {
243 parseHeader(line);
244 }
245 }
246 }
247}
248
249bool THttpClient::parseStatusLine(char* status) {
250 char* http = status;
251
252 char* code = strchr(http, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000253 if (code == NULL) {
254 throw TTransportException(string("Bad Status: ") + status);
255 }
256
Mark Slee8a98e1b2007-02-27 05:16:23 +0000257 *code = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000258 while (*(code++) == ' ');
259
260 char* msg = strchr(code, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000261 if (msg == NULL) {
262 throw TTransportException(string("Bad Status: ") + status);
263 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000264 *msg = '\0';
265
266 if (strcmp(code, "200") == 0) {
267 // HTTP 200 = OK, we got the response
268 return true;
269 } else if (strcmp(code, "100") == 0) {
270 // HTTP 100 = continue, just keep reading
271 return false;
272 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000273 throw TTransportException(string("Bad Status: ") + status);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000274 }
275}
276
277void THttpClient::parseHeader(char* header) {
278 char* colon = strchr(header, ':');
279 if (colon == NULL) {
280 return;
281 }
282 uint32_t sz = colon - header;
283 char* value = colon+1;
284
285 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
286 if (strstr(value, "chunked") != NULL) {
287 chunked_ = true;
288 }
289 } else if (strncmp(header, "Content-Length", sz) == 0) {
290 chunked_ = false;
291 contentLength_ = atoi(value);
292 }
293}
294
295void THttpClient::write(const uint8_t* buf, uint32_t len) {
296 writeBuffer_.write(buf, len);
297}
298
299void THttpClient::flush() {
300 // Fetch the contents of the write buffer
301 uint8_t* buf;
302 uint32_t len;
303 writeBuffer_.getBuffer(&buf, &len);
304
305 // Construct the HTTP header
306 std::ostringstream h;
307 h <<
308 "POST " << path_ << " HTTP/1.1" << CRLF <<
309 "Host: " << host_ << CRLF <<
310 "Content-Type: application/x-thrift" << CRLF <<
311 "Content-Length: " << len << CRLF <<
312 "Accept: application/x-thrift" << CRLF <<
313 "User-Agent: C++/THttpClient" << CRLF <<
314 CRLF;
315 string header = h.str();
316
317 // Write the header, then the data, then flush
318 transport_->write((const uint8_t*)header.c_str(), header.size());
319 transport_->write(buf, len);
320 transport_->flush();
321
322 // Reset the buffer and header variables
323 writeBuffer_.resetBuffer();
324 readHeaders_ = true;
325}
326
327}}} // facebook::thrift::transport