blob: da0f13d064fdf6f0b5855a29ed9ec6b667e2b91b [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
T Jake Lucianib5e62212009-01-31 22:36:20 +000013namespace apache { namespace thrift { namespace transport {
Mark Slee8a98e1b2007-02-27 05:16:23 +000014
15using namespace std;
16
17/**
18 * Http client implementation.
19 *
Mark Slee8a98e1b2007-02-27 05:16:23 +000020 */
21
22// Yeah, yeah, hacky to put these here, I know.
23static const char* CRLF = "\r\n";
24static const int CRLF_LEN = 2;
25
Mark Sleea2c760b2007-02-27 05:18:07 +000026THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
Mark Slee8a98e1b2007-02-27 05:16:23 +000027 transport_(transport),
28 host_(host),
29 path_(path),
30 readHeaders_(true),
31 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000032 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000033 chunkSize_(0),
34 contentLength_(0),
35 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000036 httpPos_(0),
37 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000038 httpBufSize_(1024) {
39 init();
40}
41
42THttpClient::THttpClient(string host, int port, string path) :
43 host_(host),
44 path_(path),
45 readHeaders_(true),
46 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000047 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000048 chunkSize_(0),
49 contentLength_(0),
50 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000051 httpPos_(0),
52 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000053 httpBufSize_(1024) {
54 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
55 init();
56}
57
58void THttpClient::init() {
David Reissd7a16f42008-02-19 22:47:29 +000059 httpBuf_ = (char*)std::malloc(httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +000060 if (httpBuf_ == NULL) {
61 throw TTransportException("Out of memory.");
62 }
Mark Slee2a22a882007-02-27 19:53:38 +000063 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000064}
65
66THttpClient::~THttpClient() {
67 if (httpBuf_ != NULL) {
David Reissd7a16f42008-02-19 22:47:29 +000068 std::free(httpBuf_);
Mark Slee8a98e1b2007-02-27 05:16:23 +000069 }
70}
71
72uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
David Reiss28f298d2008-05-01 06:17:36 +000073 if (readBuffer_.available_read() == 0) {
Mark Slee8a98e1b2007-02-27 05:16:23 +000074 readBuffer_.resetBuffer();
75 uint32_t got = readMoreData();
76 if (got == 0) {
77 return 0;
78 }
79 }
80 return readBuffer_.read(buf, len);
81}
82
Mark Slee2a22a882007-02-27 19:53:38 +000083void THttpClient::readEnd() {
84 // Read any pending chunked data (footers etc.)
85 if (chunked_) {
86 while (!chunkedDone_) {
87 readChunked();
88 }
89 }
90}
91
Mark Slee8a98e1b2007-02-27 05:16:23 +000092uint32_t THttpClient::readMoreData() {
93 // Get more data!
94 refill();
95
96 if (readHeaders_) {
97 readHeaders();
98 }
99
100 if (chunked_) {
101 return readChunked();
102 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000103 return readContent(contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000104 }
105}
106
107uint32_t THttpClient::readChunked() {
108 uint32_t length = 0;
Mark Slee2a22a882007-02-27 19:53:38 +0000109
110 char* line = readLine();
111 uint32_t chunkSize = parseChunkSize(line);
112 if (chunkSize == 0) {
113 readChunkedFooters();
114 } else {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000115 // Read data content
Mark Slee2a22a882007-02-27 19:53:38 +0000116 length += readContent(chunkSize);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000117 // Read trailing CRLF after content
Mark Slee2a22a882007-02-27 19:53:38 +0000118 readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000119 }
Mark Slee2a22a882007-02-27 19:53:38 +0000120 return length;
121}
Mark Slee8a98e1b2007-02-27 05:16:23 +0000122
Mark Slee2a22a882007-02-27 19:53:38 +0000123void THttpClient::readChunkedFooters() {
124 // End of data, read footer lines until a blank one appears
Mark Slee8a98e1b2007-02-27 05:16:23 +0000125 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000126 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000127 if (strlen(line) == 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000128 chunkedDone_ = true;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000129 break;
130 }
131 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000132}
133
134uint32_t THttpClient::parseChunkSize(char* line) {
135 char* semi = strchr(line, ';');
136 if (semi != NULL) {
137 *semi = '\0';
138 }
Mark Slee44018142007-02-27 19:03:01 +0000139 int size = 0;
140 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000141 return (uint32_t)size;
142}
143
Mark Slee2a22a882007-02-27 19:53:38 +0000144uint32_t THttpClient::readContent(uint32_t size) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000145 uint32_t need = size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000146 while (need > 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000147 uint32_t avail = httpBufLen_ - httpPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000148 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000149 // We have given all the data, reset position to head of the buffer
Mark Slee2a22a882007-02-27 19:53:38 +0000150 httpPos_ = 0;
151 httpBufLen_ = 0;
152 refill();
David Reiss0c90f6f2008-02-06 22:18:40 +0000153
Mark Slee44018142007-02-27 19:03:01 +0000154 // Now have available however much we read
Mark Slee2a22a882007-02-27 19:53:38 +0000155 avail = httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000156 }
157 uint32_t give = avail;
158 if (need < give) {
159 give = need;
160 }
Mark Slee2a22a882007-02-27 19:53:38 +0000161 readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give);
162 httpPos_ += give;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000163 need -= give;
164 }
Mark Slee2a22a882007-02-27 19:53:38 +0000165 return size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000166}
David Reiss0c90f6f2008-02-06 22:18:40 +0000167
Mark Slee2a22a882007-02-27 19:53:38 +0000168char* THttpClient::readLine() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000169 while (true) {
170 char* eol = NULL;
171
Mark Slee2a22a882007-02-27 19:53:38 +0000172 eol = strstr(httpBuf_+httpPos_, CRLF);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000173
174 // No CRLF yet?
175 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000176 // Shift whatever we have now to front and refill
Mark Slee2a22a882007-02-27 19:53:38 +0000177 shift();
178 refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000179 } else {
180 // Return pointer to next line
181 *eol = '\0';
Mark Slee2a22a882007-02-27 19:53:38 +0000182 char* line = httpBuf_+httpPos_;
183 httpPos_ = (eol-httpBuf_) + CRLF_LEN;
184 return line;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000185 }
186 }
187
188}
189
Mark Slee2a22a882007-02-27 19:53:38 +0000190void THttpClient::shift() {
191 if (httpBufLen_ > httpPos_) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000192 // Shift down remaining data and read more
Mark Slee2a22a882007-02-27 19:53:38 +0000193 uint32_t length = httpBufLen_ - httpPos_;
194 memmove(httpBuf_, httpBuf_+httpPos_, length);
195 httpBufLen_ = length;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000196 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000197 httpBufLen_ = 0;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000198 }
Mark Slee2a22a882007-02-27 19:53:38 +0000199 httpPos_ = 0;
200 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000201}
202
Mark Slee2a22a882007-02-27 19:53:38 +0000203void THttpClient::refill() {
204 uint32_t avail = httpBufSize_ - httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000205 if (avail <= (httpBufSize_ / 4)) {
206 httpBufSize_ *= 2;
David Reissd7a16f42008-02-19 22:47:29 +0000207 httpBuf_ = (char*)std::realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000208 if (httpBuf_ == NULL) {
209 throw TTransportException("Out of memory.");
210 }
211 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000212
Mark Slee8a98e1b2007-02-27 05:16:23 +0000213 // Read more data
Mark Slee2a22a882007-02-27 19:53:38 +0000214 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_);
215 httpBufLen_ += got;
216 httpBuf_[httpBufLen_] = '\0';
David Reiss0c90f6f2008-02-06 22:18:40 +0000217
Mark Slee8a98e1b2007-02-27 05:16:23 +0000218 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000219 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000220 }
221}
222
223void THttpClient::readHeaders() {
224 // Initialize headers state variables
225 contentLength_ = 0;
226 chunked_ = false;
Mark Slee2a22a882007-02-27 19:53:38 +0000227 chunkedDone_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000228 chunkSize_ = 0;
229
230 // Control state flow
231 bool statusLine = true;
232 bool finished = false;
233
Mark Slee8a98e1b2007-02-27 05:16:23 +0000234 // Loop until headers are finished
235 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000236 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000237
238 if (strlen(line) == 0) {
239 if (finished) {
240 readHeaders_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000241 return;
242 } else {
243 // Must have been an HTTP 100, keep going for another status line
244 statusLine = true;
245 }
246 } else {
247 if (statusLine) {
248 statusLine = false;
249 finished = parseStatusLine(line);
250 } else {
251 parseHeader(line);
252 }
253 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000254 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000255}
256
257bool THttpClient::parseStatusLine(char* status) {
258 char* http = status;
259
260 char* code = strchr(http, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000261 if (code == NULL) {
262 throw TTransportException(string("Bad Status: ") + status);
263 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000264
Mark Slee8a98e1b2007-02-27 05:16:23 +0000265 *code = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000266 while (*(code++) == ' ');
267
268 char* msg = strchr(code, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000269 if (msg == NULL) {
270 throw TTransportException(string("Bad Status: ") + status);
271 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000272 *msg = '\0';
273
274 if (strcmp(code, "200") == 0) {
275 // HTTP 200 = OK, we got the response
276 return true;
277 } else if (strcmp(code, "100") == 0) {
278 // HTTP 100 = continue, just keep reading
279 return false;
280 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000281 throw TTransportException(string("Bad Status: ") + status);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000282 }
283}
284
285void THttpClient::parseHeader(char* header) {
286 char* colon = strchr(header, ':');
287 if (colon == NULL) {
288 return;
289 }
290 uint32_t sz = colon - header;
291 char* value = colon+1;
292
293 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
294 if (strstr(value, "chunked") != NULL) {
295 chunked_ = true;
296 }
297 } else if (strncmp(header, "Content-Length", sz) == 0) {
298 chunked_ = false;
299 contentLength_ = atoi(value);
300 }
301}
302
303void THttpClient::write(const uint8_t* buf, uint32_t len) {
304 writeBuffer_.write(buf, len);
305}
306
307void THttpClient::flush() {
308 // Fetch the contents of the write buffer
309 uint8_t* buf;
310 uint32_t len;
311 writeBuffer_.getBuffer(&buf, &len);
312
313 // Construct the HTTP header
314 std::ostringstream h;
315 h <<
316 "POST " << path_ << " HTTP/1.1" << CRLF <<
317 "Host: " << host_ << CRLF <<
318 "Content-Type: application/x-thrift" << CRLF <<
319 "Content-Length: " << len << CRLF <<
320 "Accept: application/x-thrift" << CRLF <<
321 "User-Agent: C++/THttpClient" << CRLF <<
322 CRLF;
323 string header = h.str();
324
325 // Write the header, then the data, then flush
326 transport_->write((const uint8_t*)header.c_str(), header.size());
327 transport_->write(buf, len);
328 transport_->flush();
329
330 // Reset the buffer and header variables
331 writeBuffer_.resetBuffer();
332 readHeaders_ = true;
333}
334
T Jake Lucianib5e62212009-01-31 22:36:20 +0000335}}} // apache::thrift::transport