blob: b6d9d9e726b2a3a50885b9345e6b5756b5b5eceb [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
Mark Slee8a98e1b2007-02-27 05:16:23 +00007#include "THttpClient.h"
8#include "TSocket.h"
9
10namespace facebook { namespace thrift { namespace transport {
11
12using namespace std;
13
14/**
15 * Http client implementation.
16 *
17 * @author Mark Slee <mcslee@facebook.com>
18 */
19
20// Yeah, yeah, hacky to put these here, I know.
21static const char* CRLF = "\r\n";
22static const int CRLF_LEN = 2;
23
Mark Sleea2c760b2007-02-27 05:18:07 +000024THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
Mark Slee8a98e1b2007-02-27 05:16:23 +000025 transport_(transport),
26 host_(host),
27 path_(path),
28 readHeaders_(true),
29 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000030 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000031 chunkSize_(0),
32 contentLength_(0),
33 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000034 httpPos_(0),
35 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000036 httpBufSize_(1024) {
37 init();
38}
39
40THttpClient::THttpClient(string host, int port, string path) :
41 host_(host),
42 path_(path),
43 readHeaders_(true),
44 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000045 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000046 chunkSize_(0),
47 contentLength_(0),
48 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000049 httpPos_(0),
50 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000051 httpBufSize_(1024) {
52 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
53 init();
54}
55
56void THttpClient::init() {
57 httpBuf_ = (char*)malloc(httpBufSize_+1);
58 if (httpBuf_ == NULL) {
59 throw TTransportException("Out of memory.");
60 }
Mark Slee2a22a882007-02-27 19:53:38 +000061 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000062}
63
64THttpClient::~THttpClient() {
65 if (httpBuf_ != NULL) {
66 free(httpBuf_);
67 }
68}
69
70uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
71 if (readBuffer_.available() == 0) {
72 readBuffer_.resetBuffer();
73 uint32_t got = readMoreData();
74 if (got == 0) {
75 return 0;
76 }
77 }
78 return readBuffer_.read(buf, len);
79}
80
Mark Slee2a22a882007-02-27 19:53:38 +000081void THttpClient::readEnd() {
82 // Read any pending chunked data (footers etc.)
83 if (chunked_) {
84 while (!chunkedDone_) {
85 readChunked();
86 }
87 }
88}
89
Mark Slee8a98e1b2007-02-27 05:16:23 +000090uint32_t THttpClient::readMoreData() {
91 // Get more data!
92 refill();
93
94 if (readHeaders_) {
95 readHeaders();
96 }
97
98 if (chunked_) {
99 return readChunked();
100 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000101 return readContent(contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000102 }
103}
104
105uint32_t THttpClient::readChunked() {
106 uint32_t length = 0;
Mark Slee2a22a882007-02-27 19:53:38 +0000107
108 char* line = readLine();
109 uint32_t chunkSize = parseChunkSize(line);
110 if (chunkSize == 0) {
111 readChunkedFooters();
112 } else {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000113 // Read data content
Mark Slee2a22a882007-02-27 19:53:38 +0000114 length += readContent(chunkSize);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000115 // Read trailing CRLF after content
Mark Slee2a22a882007-02-27 19:53:38 +0000116 readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000117 }
Mark Slee2a22a882007-02-27 19:53:38 +0000118 return length;
119}
Mark Slee8a98e1b2007-02-27 05:16:23 +0000120
Mark Slee2a22a882007-02-27 19:53:38 +0000121void THttpClient::readChunkedFooters() {
122 // End of data, read footer lines until a blank one appears
Mark Slee8a98e1b2007-02-27 05:16:23 +0000123 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000124 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000125 if (strlen(line) == 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000126 chunkedDone_ = true;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000127 break;
128 }
129 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000130}
131
132uint32_t THttpClient::parseChunkSize(char* line) {
133 char* semi = strchr(line, ';');
134 if (semi != NULL) {
135 *semi = '\0';
136 }
Mark Slee44018142007-02-27 19:03:01 +0000137 int size = 0;
138 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000139 return (uint32_t)size;
140}
141
Mark Slee2a22a882007-02-27 19:53:38 +0000142uint32_t THttpClient::readContent(uint32_t size) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000143 uint32_t need = size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000144 while (need > 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000145 uint32_t avail = httpBufLen_ - httpPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000146 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000147 // We have given all the data, reset position to head of the buffer
Mark Slee2a22a882007-02-27 19:53:38 +0000148 httpPos_ = 0;
149 httpBufLen_ = 0;
150 refill();
Mark Slee44018142007-02-27 19:03:01 +0000151
152 // Now have available however much we read
Mark Slee2a22a882007-02-27 19:53:38 +0000153 avail = httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000154 }
155 uint32_t give = avail;
156 if (need < give) {
157 give = need;
158 }
Mark Slee2a22a882007-02-27 19:53:38 +0000159 readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give);
160 httpPos_ += give;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000161 need -= give;
162 }
Mark Slee2a22a882007-02-27 19:53:38 +0000163 return size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000164}
Mark Slee44018142007-02-27 19:03:01 +0000165
Mark Slee2a22a882007-02-27 19:53:38 +0000166char* THttpClient::readLine() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000167 while (true) {
168 char* eol = NULL;
169
Mark Slee2a22a882007-02-27 19:53:38 +0000170 eol = strstr(httpBuf_+httpPos_, CRLF);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000171
172 // No CRLF yet?
173 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000174 // Shift whatever we have now to front and refill
Mark Slee2a22a882007-02-27 19:53:38 +0000175 shift();
176 refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000177 } else {
178 // Return pointer to next line
179 *eol = '\0';
Mark Slee2a22a882007-02-27 19:53:38 +0000180 char* line = httpBuf_+httpPos_;
181 httpPos_ = (eol-httpBuf_) + CRLF_LEN;
182 return line;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000183 }
184 }
185
186}
187
Mark Slee2a22a882007-02-27 19:53:38 +0000188void THttpClient::shift() {
189 if (httpBufLen_ > httpPos_) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000190 // Shift down remaining data and read more
Mark Slee2a22a882007-02-27 19:53:38 +0000191 uint32_t length = httpBufLen_ - httpPos_;
192 memmove(httpBuf_, httpBuf_+httpPos_, length);
193 httpBufLen_ = length;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000194 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000195 httpBufLen_ = 0;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000196 }
Mark Slee2a22a882007-02-27 19:53:38 +0000197 httpPos_ = 0;
198 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000199}
200
Mark Slee2a22a882007-02-27 19:53:38 +0000201void THttpClient::refill() {
202 uint32_t avail = httpBufSize_ - httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000203 if (avail <= (httpBufSize_ / 4)) {
204 httpBufSize_ *= 2;
Mark Slee44018142007-02-27 19:03:01 +0000205 httpBuf_ = (char*)realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000206 if (httpBuf_ == NULL) {
207 throw TTransportException("Out of memory.");
208 }
209 }
210
211 // Read more data
Mark Slee2a22a882007-02-27 19:53:38 +0000212 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_);
213 httpBufLen_ += got;
214 httpBuf_[httpBufLen_] = '\0';
Mark Slee44018142007-02-27 19:03:01 +0000215
Mark Slee8a98e1b2007-02-27 05:16:23 +0000216 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000217 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000218 }
219}
220
221void THttpClient::readHeaders() {
222 // Initialize headers state variables
223 contentLength_ = 0;
224 chunked_ = false;
Mark Slee2a22a882007-02-27 19:53:38 +0000225 chunkedDone_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000226 chunkSize_ = 0;
227
228 // Control state flow
229 bool statusLine = true;
230 bool finished = false;
231
Mark Slee8a98e1b2007-02-27 05:16:23 +0000232 // Loop until headers are finished
233 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000234 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000235
236 if (strlen(line) == 0) {
237 if (finished) {
238 readHeaders_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000239 return;
240 } else {
241 // Must have been an HTTP 100, keep going for another status line
242 statusLine = true;
243 }
244 } else {
245 if (statusLine) {
246 statusLine = false;
247 finished = parseStatusLine(line);
248 } else {
249 parseHeader(line);
250 }
251 }
252 }
253}
254
255bool THttpClient::parseStatusLine(char* status) {
256 char* http = status;
257
258 char* code = strchr(http, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000259 if (code == NULL) {
260 throw TTransportException(string("Bad Status: ") + status);
261 }
262
Mark Slee8a98e1b2007-02-27 05:16:23 +0000263 *code = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000264 while (*(code++) == ' ');
265
266 char* msg = strchr(code, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000267 if (msg == NULL) {
268 throw TTransportException(string("Bad Status: ") + status);
269 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000270 *msg = '\0';
271
272 if (strcmp(code, "200") == 0) {
273 // HTTP 200 = OK, we got the response
274 return true;
275 } else if (strcmp(code, "100") == 0) {
276 // HTTP 100 = continue, just keep reading
277 return false;
278 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000279 throw TTransportException(string("Bad Status: ") + status);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000280 }
281}
282
283void THttpClient::parseHeader(char* header) {
284 char* colon = strchr(header, ':');
285 if (colon == NULL) {
286 return;
287 }
288 uint32_t sz = colon - header;
289 char* value = colon+1;
290
291 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
292 if (strstr(value, "chunked") != NULL) {
293 chunked_ = true;
294 }
295 } else if (strncmp(header, "Content-Length", sz) == 0) {
296 chunked_ = false;
297 contentLength_ = atoi(value);
298 }
299}
300
301void THttpClient::write(const uint8_t* buf, uint32_t len) {
302 writeBuffer_.write(buf, len);
303}
304
305void THttpClient::flush() {
306 // Fetch the contents of the write buffer
307 uint8_t* buf;
308 uint32_t len;
309 writeBuffer_.getBuffer(&buf, &len);
310
311 // Construct the HTTP header
312 std::ostringstream h;
313 h <<
314 "POST " << path_ << " HTTP/1.1" << CRLF <<
315 "Host: " << host_ << CRLF <<
316 "Content-Type: application/x-thrift" << CRLF <<
317 "Content-Length: " << len << CRLF <<
318 "Accept: application/x-thrift" << CRLF <<
319 "User-Agent: C++/THttpClient" << CRLF <<
320 CRLF;
321 string header = h.str();
322
323 // Write the header, then the data, then flush
324 transport_->write((const uint8_t*)header.c_str(), header.size());
325 transport_->write(buf, len);
326 transport_->flush();
327
328 // Reset the buffer and header variables
329 writeBuffer_.resetBuffer();
330 readHeaders_ = true;
331}
332
333}}} // facebook::thrift::transport