blob: b389694650fa3f65554e22b733e9bec7943fb977 [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),
24 chunkSize_(0),
25 contentLength_(0),
26 httpBuf_(NULL),
27 httpBufPos_(0),
28 httpBufSize_(1024) {
29 init();
30}
31
32THttpClient::THttpClient(string host, int port, string path) :
33 host_(host),
34 path_(path),
35 readHeaders_(true),
36 chunked_(false),
37 chunkSize_(0),
38 contentLength_(0),
39 httpBuf_(NULL),
40 httpBufPos_(0),
41 httpBufSize_(1024) {
42 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
43 init();
44}
45
46void THttpClient::init() {
47 httpBuf_ = (char*)malloc(httpBufSize_+1);
48 if (httpBuf_ == NULL) {
49 throw TTransportException("Out of memory.");
50 }
Mark Slee44018142007-02-27 19:03:01 +000051 httpBuf_[httpBufPos_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000052}
53
54THttpClient::~THttpClient() {
55 if (httpBuf_ != NULL) {
56 free(httpBuf_);
57 }
58}
59
60uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
61 if (readBuffer_.available() == 0) {
62 readBuffer_.resetBuffer();
63 uint32_t got = readMoreData();
64 if (got == 0) {
65 return 0;
66 }
67 }
68 return readBuffer_.read(buf, len);
69}
70
71uint32_t THttpClient::readMoreData() {
72 // Get more data!
73 refill();
74
75 if (readHeaders_) {
76 readHeaders();
77 }
78
79 if (chunked_) {
80 return readChunked();
81 } else {
82 char* read;
Mark Slee44018142007-02-27 19:03:01 +000083 read = readContent(httpBuf_, contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +000084 shift(read);
85 return contentLength_;
86 }
87}
88
89uint32_t THttpClient::readChunked() {
90 uint32_t length = 0;
Mark Slee44018142007-02-27 19:03:01 +000091 char* nextLine = httpBuf_;
Mark Slee8a98e1b2007-02-27 05:16:23 +000092 while (true) {
Mark Slee44018142007-02-27 19:03:01 +000093 char* line = readLine(nextLine, &nextLine);
Mark Slee8a98e1b2007-02-27 05:16:23 +000094 uint32_t chunkSize = parseChunkSize(line);
95 if (chunkSize == 0) {
96 break;
97 }
98 // Read data content
99 nextLine = readContent(nextLine, chunkSize);
100 length += chunkSize;
101
102 // Read trailing CRLF after content
Mark Slee44018142007-02-27 19:03:01 +0000103 readLine(nextLine, &nextLine);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000104 }
105
106 // Read footer lines until a blank one appears
107 while (true) {
Mark Slee44018142007-02-27 19:03:01 +0000108 char* line = readLine(nextLine, &nextLine);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000109 if (strlen(line) == 0) {
110 break;
111 }
112 }
113
114 // Shift down whatever we have left in the buf
115 shift(nextLine);
116
117 return length;
118}
119
120uint32_t THttpClient::parseChunkSize(char* line) {
121 char* semi = strchr(line, ';');
122 if (semi != NULL) {
123 *semi = '\0';
124 }
Mark Slee44018142007-02-27 19:03:01 +0000125 int size = 0;
126 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000127 return (uint32_t)size;
128}
129
130char* THttpClient::readContent(char* pos, uint32_t size) {
131 uint32_t need = size;
132
133 while (need > 0) {
134 uint32_t avail = httpBufPos_ - (pos - httpBuf_);
135 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000136 // We have given all the data, reset position to head of the buffer
137 pos = shift(pos);
138 pos = refill();
139
140 // Now have available however much we read
141 avail = httpBufPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000142 }
143 uint32_t give = avail;
144 if (need < give) {
145 give = need;
146 }
147 readBuffer_.write((uint8_t*)pos, give);
148 pos += give;
149 need -= give;
150 }
151 return pos;
152}
Mark Slee44018142007-02-27 19:03:01 +0000153
154char* THttpClient::readLine(char* pos, char** next) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000155 while (true) {
156 char* eol = NULL;
157
158 // Note, the data we read could have ended right on the CRLF pair
159 if (pos != NULL) {
160 eol = strstr(pos, CRLF);
161 }
162
163 // No CRLF yet?
164 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000165 // Shift whatever we have now to front and refill
Mark Slee8a98e1b2007-02-27 05:16:23 +0000166 pos = shift(pos);
Mark Slee44018142007-02-27 19:03:01 +0000167 pos = refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000168 } else {
169 // Return pointer to next line
170 *eol = '\0';
Mark Slee44018142007-02-27 19:03:01 +0000171 *next = eol + CRLF_LEN;
172 return pos;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000173 }
174 }
175
176}
177
178char* THttpClient::shift(char* pos) {
179 if (pos != NULL && httpBufPos_ > (pos - httpBuf_)) {
180 // Shift down remaining data and read more
181 uint32_t length = httpBufPos_ - (pos - httpBuf_);
182 memmove(httpBuf_, pos, length);
183 httpBufPos_ = length;
184 } else {
185 httpBufPos_ = 0;
186 }
187 httpBuf_[httpBufPos_] = '\0';
188 return httpBuf_;
189}
190
Mark Slee44018142007-02-27 19:03:01 +0000191char* THttpClient::refill() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000192 uint32_t avail = httpBufSize_ - httpBufPos_;
193 if (avail <= (httpBufSize_ / 4)) {
194 httpBufSize_ *= 2;
Mark Slee44018142007-02-27 19:03:01 +0000195 httpBuf_ = (char*)realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000196 if (httpBuf_ == NULL) {
197 throw TTransportException("Out of memory.");
198 }
199 }
200
201 // Read more data
202 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufPos_), httpBufSize_-httpBufPos_);
203 httpBufPos_ += got;
204 httpBuf_[httpBufPos_] = '\0';
Mark Slee44018142007-02-27 19:03:01 +0000205
Mark Slee8a98e1b2007-02-27 05:16:23 +0000206 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000207 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000208 }
Mark Slee44018142007-02-27 19:03:01 +0000209
210 return httpBuf_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000211}
212
213void THttpClient::readHeaders() {
214 // Initialize headers state variables
215 contentLength_ = 0;
216 chunked_ = false;
217 chunkSize_ = 0;
218
219 // Control state flow
220 bool statusLine = true;
221 bool finished = false;
222
223 // Initialize local pos vars
224 char* nextLine = (char*)httpBuf_;
225
226 // Loop until headers are finished
227 while (true) {
Mark Slee44018142007-02-27 19:03:01 +0000228 char* line = readLine(nextLine, &nextLine);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000229
230 if (strlen(line) == 0) {
231 if (finished) {
232 readHeaders_ = false;
233 shift(nextLine);
234 return;
235 } else {
236 // Must have been an HTTP 100, keep going for another status line
237 statusLine = true;
238 }
239 } else {
240 if (statusLine) {
241 statusLine = false;
242 finished = parseStatusLine(line);
243 } else {
244 parseHeader(line);
245 }
246 }
247 }
248}
249
250bool THttpClient::parseStatusLine(char* status) {
251 char* http = status;
252
253 char* code = strchr(http, ' ');
254 *code = '\0';
255
256 while (*(code++) == ' ');
257
258 char* msg = strchr(code, ' ');
259 *msg = '\0';
260
261 if (strcmp(code, "200") == 0) {
262 // HTTP 200 = OK, we got the response
263 return true;
264 } else if (strcmp(code, "100") == 0) {
265 // HTTP 100 = continue, just keep reading
266 return false;
267 } else {
268 throw TTransportException(status);
269 }
270}
271
272void THttpClient::parseHeader(char* header) {
273 char* colon = strchr(header, ':');
274 if (colon == NULL) {
275 return;
276 }
277 uint32_t sz = colon - header;
278 char* value = colon+1;
279
280 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
281 if (strstr(value, "chunked") != NULL) {
282 chunked_ = true;
283 }
284 } else if (strncmp(header, "Content-Length", sz) == 0) {
285 chunked_ = false;
286 contentLength_ = atoi(value);
287 }
288}
289
290void THttpClient::write(const uint8_t* buf, uint32_t len) {
291 writeBuffer_.write(buf, len);
292}
293
294void THttpClient::flush() {
295 // Fetch the contents of the write buffer
296 uint8_t* buf;
297 uint32_t len;
298 writeBuffer_.getBuffer(&buf, &len);
299
300 // Construct the HTTP header
301 std::ostringstream h;
302 h <<
303 "POST " << path_ << " HTTP/1.1" << CRLF <<
304 "Host: " << host_ << CRLF <<
305 "Content-Type: application/x-thrift" << CRLF <<
306 "Content-Length: " << len << CRLF <<
307 "Accept: application/x-thrift" << CRLF <<
308 "User-Agent: C++/THttpClient" << CRLF <<
309 CRLF;
310 string header = h.str();
311
312 // Write the header, then the data, then flush
313 transport_->write((const uint8_t*)header.c_str(), header.size());
314 transport_->write(buf, len);
315 transport_->flush();
316
317 // Reset the buffer and header variables
318 writeBuffer_.resetBuffer();
319 readHeaders_ = true;
320}
321
322}}} // facebook::thrift::transport