blob: a29409322dc3e4d1ff11ebb1cfb1710a2fae86b7 [file] [log] [blame]
Mark Slee9f0c6512007-02-28 23:58:26 +00001// Copyright (c) 2006- Facebook
2// Distributed under the Thrift Software License
3//
4// See accompanying file LICENSE or visit the Thrift site at:
5// http://developers.facebook.com/thrift/
6
David Reissd7a16f42008-02-19 22:47:29 +00007#include <cstdlib>
David Reisse39e9372008-05-01 05:52:48 +00008#include <sstream>
David Reissd7a16f42008-02-19 22:47:29 +00009
Mark Slee8a98e1b2007-02-27 05:16:23 +000010#include "THttpClient.h"
11#include "TSocket.h"
12
David Reiss0c90f6f2008-02-06 22:18:40 +000013namespace facebook { namespace thrift { namespace transport {
Mark Slee8a98e1b2007-02-27 05:16:23 +000014
15using namespace std;
16
17/**
18 * Http client implementation.
19 *
20 * @author Mark Slee <mcslee@facebook.com>
21 */
22
23// Yeah, yeah, hacky to put these here, I know.
24static const char* CRLF = "\r\n";
25static const int CRLF_LEN = 2;
26
Mark Sleea2c760b2007-02-27 05:18:07 +000027THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
Mark Slee8a98e1b2007-02-27 05:16:23 +000028 transport_(transport),
29 host_(host),
30 path_(path),
31 readHeaders_(true),
32 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000033 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000034 chunkSize_(0),
35 contentLength_(0),
36 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000037 httpPos_(0),
38 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000039 httpBufSize_(1024) {
40 init();
41}
42
43THttpClient::THttpClient(string host, int port, string path) :
44 host_(host),
45 path_(path),
46 readHeaders_(true),
47 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000048 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000049 chunkSize_(0),
50 contentLength_(0),
51 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000052 httpPos_(0),
53 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000054 httpBufSize_(1024) {
55 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
56 init();
57}
58
59void THttpClient::init() {
David Reissd7a16f42008-02-19 22:47:29 +000060 httpBuf_ = (char*)std::malloc(httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +000061 if (httpBuf_ == NULL) {
62 throw TTransportException("Out of memory.");
63 }
Mark Slee2a22a882007-02-27 19:53:38 +000064 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000065}
66
67THttpClient::~THttpClient() {
68 if (httpBuf_ != NULL) {
David Reissd7a16f42008-02-19 22:47:29 +000069 std::free(httpBuf_);
Mark Slee8a98e1b2007-02-27 05:16:23 +000070 }
71}
72
73uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
74 if (readBuffer_.available() == 0) {
75 readBuffer_.resetBuffer();
76 uint32_t got = readMoreData();
77 if (got == 0) {
78 return 0;
79 }
80 }
81 return readBuffer_.read(buf, len);
82}
83
Mark Slee2a22a882007-02-27 19:53:38 +000084void THttpClient::readEnd() {
85 // Read any pending chunked data (footers etc.)
86 if (chunked_) {
87 while (!chunkedDone_) {
88 readChunked();
89 }
90 }
91}
92
Mark Slee8a98e1b2007-02-27 05:16:23 +000093uint32_t THttpClient::readMoreData() {
94 // Get more data!
95 refill();
96
97 if (readHeaders_) {
98 readHeaders();
99 }
100
101 if (chunked_) {
102 return readChunked();
103 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000104 return readContent(contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000105 }
106}
107
108uint32_t THttpClient::readChunked() {
109 uint32_t length = 0;
Mark Slee2a22a882007-02-27 19:53:38 +0000110
111 char* line = readLine();
112 uint32_t chunkSize = parseChunkSize(line);
113 if (chunkSize == 0) {
114 readChunkedFooters();
115 } else {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000116 // Read data content
Mark Slee2a22a882007-02-27 19:53:38 +0000117 length += readContent(chunkSize);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000118 // Read trailing CRLF after content
Mark Slee2a22a882007-02-27 19:53:38 +0000119 readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000120 }
Mark Slee2a22a882007-02-27 19:53:38 +0000121 return length;
122}
Mark Slee8a98e1b2007-02-27 05:16:23 +0000123
Mark Slee2a22a882007-02-27 19:53:38 +0000124void THttpClient::readChunkedFooters() {
125 // End of data, read footer lines until a blank one appears
Mark Slee8a98e1b2007-02-27 05:16:23 +0000126 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000127 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000128 if (strlen(line) == 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000129 chunkedDone_ = true;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000130 break;
131 }
132 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000133}
134
135uint32_t THttpClient::parseChunkSize(char* line) {
136 char* semi = strchr(line, ';');
137 if (semi != NULL) {
138 *semi = '\0';
139 }
Mark Slee44018142007-02-27 19:03:01 +0000140 int size = 0;
141 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000142 return (uint32_t)size;
143}
144
Mark Slee2a22a882007-02-27 19:53:38 +0000145uint32_t THttpClient::readContent(uint32_t size) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000146 uint32_t need = size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000147 while (need > 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000148 uint32_t avail = httpBufLen_ - httpPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000149 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000150 // We have given all the data, reset position to head of the buffer
Mark Slee2a22a882007-02-27 19:53:38 +0000151 httpPos_ = 0;
152 httpBufLen_ = 0;
153 refill();
David Reiss0c90f6f2008-02-06 22:18:40 +0000154
Mark Slee44018142007-02-27 19:03:01 +0000155 // Now have available however much we read
Mark Slee2a22a882007-02-27 19:53:38 +0000156 avail = httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000157 }
158 uint32_t give = avail;
159 if (need < give) {
160 give = need;
161 }
Mark Slee2a22a882007-02-27 19:53:38 +0000162 readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give);
163 httpPos_ += give;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000164 need -= give;
165 }
Mark Slee2a22a882007-02-27 19:53:38 +0000166 return size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000167}
David Reiss0c90f6f2008-02-06 22:18:40 +0000168
Mark Slee2a22a882007-02-27 19:53:38 +0000169char* THttpClient::readLine() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000170 while (true) {
171 char* eol = NULL;
172
Mark Slee2a22a882007-02-27 19:53:38 +0000173 eol = strstr(httpBuf_+httpPos_, CRLF);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000174
175 // No CRLF yet?
176 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000177 // Shift whatever we have now to front and refill
Mark Slee2a22a882007-02-27 19:53:38 +0000178 shift();
179 refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000180 } else {
181 // Return pointer to next line
182 *eol = '\0';
Mark Slee2a22a882007-02-27 19:53:38 +0000183 char* line = httpBuf_+httpPos_;
184 httpPos_ = (eol-httpBuf_) + CRLF_LEN;
185 return line;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000186 }
187 }
188
189}
190
Mark Slee2a22a882007-02-27 19:53:38 +0000191void THttpClient::shift() {
192 if (httpBufLen_ > httpPos_) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000193 // Shift down remaining data and read more
Mark Slee2a22a882007-02-27 19:53:38 +0000194 uint32_t length = httpBufLen_ - httpPos_;
195 memmove(httpBuf_, httpBuf_+httpPos_, length);
196 httpBufLen_ = length;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000197 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000198 httpBufLen_ = 0;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000199 }
Mark Slee2a22a882007-02-27 19:53:38 +0000200 httpPos_ = 0;
201 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000202}
203
Mark Slee2a22a882007-02-27 19:53:38 +0000204void THttpClient::refill() {
205 uint32_t avail = httpBufSize_ - httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000206 if (avail <= (httpBufSize_ / 4)) {
207 httpBufSize_ *= 2;
David Reissd7a16f42008-02-19 22:47:29 +0000208 httpBuf_ = (char*)std::realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000209 if (httpBuf_ == NULL) {
210 throw TTransportException("Out of memory.");
211 }
212 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000213
Mark Slee8a98e1b2007-02-27 05:16:23 +0000214 // Read more data
Mark Slee2a22a882007-02-27 19:53:38 +0000215 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_);
216 httpBufLen_ += got;
217 httpBuf_[httpBufLen_] = '\0';
David Reiss0c90f6f2008-02-06 22:18:40 +0000218
Mark Slee8a98e1b2007-02-27 05:16:23 +0000219 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000220 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000221 }
222}
223
224void THttpClient::readHeaders() {
225 // Initialize headers state variables
226 contentLength_ = 0;
227 chunked_ = false;
Mark Slee2a22a882007-02-27 19:53:38 +0000228 chunkedDone_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000229 chunkSize_ = 0;
230
231 // Control state flow
232 bool statusLine = true;
233 bool finished = false;
234
Mark Slee8a98e1b2007-02-27 05:16:23 +0000235 // Loop until headers are finished
236 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000237 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000238
239 if (strlen(line) == 0) {
240 if (finished) {
241 readHeaders_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000242 return;
243 } else {
244 // Must have been an HTTP 100, keep going for another status line
245 statusLine = true;
246 }
247 } else {
248 if (statusLine) {
249 statusLine = false;
250 finished = parseStatusLine(line);
251 } else {
252 parseHeader(line);
253 }
254 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000255 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000256}
257
258bool THttpClient::parseStatusLine(char* status) {
259 char* http = status;
260
261 char* code = strchr(http, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000262 if (code == NULL) {
263 throw TTransportException(string("Bad Status: ") + status);
264 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000265
Mark Slee8a98e1b2007-02-27 05:16:23 +0000266 *code = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000267 while (*(code++) == ' ');
268
269 char* msg = strchr(code, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000270 if (msg == NULL) {
271 throw TTransportException(string("Bad Status: ") + status);
272 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000273 *msg = '\0';
274
275 if (strcmp(code, "200") == 0) {
276 // HTTP 200 = OK, we got the response
277 return true;
278 } else if (strcmp(code, "100") == 0) {
279 // HTTP 100 = continue, just keep reading
280 return false;
281 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000282 throw TTransportException(string("Bad Status: ") + status);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000283 }
284}
285
286void THttpClient::parseHeader(char* header) {
287 char* colon = strchr(header, ':');
288 if (colon == NULL) {
289 return;
290 }
291 uint32_t sz = colon - header;
292 char* value = colon+1;
293
294 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
295 if (strstr(value, "chunked") != NULL) {
296 chunked_ = true;
297 }
298 } else if (strncmp(header, "Content-Length", sz) == 0) {
299 chunked_ = false;
300 contentLength_ = atoi(value);
301 }
302}
303
304void THttpClient::write(const uint8_t* buf, uint32_t len) {
305 writeBuffer_.write(buf, len);
306}
307
308void THttpClient::flush() {
309 // Fetch the contents of the write buffer
310 uint8_t* buf;
311 uint32_t len;
312 writeBuffer_.getBuffer(&buf, &len);
313
314 // Construct the HTTP header
315 std::ostringstream h;
316 h <<
317 "POST " << path_ << " HTTP/1.1" << CRLF <<
318 "Host: " << host_ << CRLF <<
319 "Content-Type: application/x-thrift" << CRLF <<
320 "Content-Length: " << len << CRLF <<
321 "Accept: application/x-thrift" << CRLF <<
322 "User-Agent: C++/THttpClient" << CRLF <<
323 CRLF;
324 string header = h.str();
325
326 // Write the header, then the data, then flush
327 transport_->write((const uint8_t*)header.c_str(), header.size());
328 transport_->write(buf, len);
329 transport_->flush();
330
331 // Reset the buffer and header variables
332 writeBuffer_.resetBuffer();
333 readHeaders_ = true;
334}
335
336}}} // facebook::thrift::transport