blob: 7ee83fc32f22916f7eb87f2dee43c424d1a8047e [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
18 THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
19 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 }
51}
52
53THttpClient::~THttpClient() {
54 if (httpBuf_ != NULL) {
55 free(httpBuf_);
56 }
57}
58
59uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
60 if (readBuffer_.available() == 0) {
61 readBuffer_.resetBuffer();
62 uint32_t got = readMoreData();
63 if (got == 0) {
64 return 0;
65 }
66 }
67 return readBuffer_.read(buf, len);
68}
69
70uint32_t THttpClient::readMoreData() {
71 // Get more data!
72 refill();
73
74 if (readHeaders_) {
75 readHeaders();
76 }
77
78 if (chunked_) {
79 return readChunked();
80 } else {
81 char* read;
82 read = readContent((char*)httpBuf_, contentLength_);
83 shift(read);
84 return contentLength_;
85 }
86}
87
88uint32_t THttpClient::readChunked() {
89 uint32_t length = 0;
90 char* nextLine = (char*)httpBuf_;
91 while (true) {
92 char* line = nextLine;
93 nextLine = readLine(nextLine);
94 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
103 nextLine = readLine(nextLine);
104 }
105
106 // Read footer lines until a blank one appears
107 while (true) {
108 char* line = nextLine;
109 nextLine = readLine(nextLine);
110 if (strlen(line) == 0) {
111 break;
112 }
113 }
114
115 // Shift down whatever we have left in the buf
116 shift(nextLine);
117
118 return length;
119}
120
121uint32_t THttpClient::parseChunkSize(char* line) {
122 char* semi = strchr(line, ';');
123 if (semi != NULL) {
124 *semi = '\0';
125 }
126 int s;
127 int size;
128 s = sscanf(line, "%x", &size);
129 return (uint32_t)size;
130}
131
132char* THttpClient::readContent(char* pos, uint32_t size) {
133 uint32_t need = size;
134
135 while (need > 0) {
136 uint32_t avail = httpBufPos_ - (pos - httpBuf_);
137 if (avail == 0) {
138 refill();
139 }
140 uint32_t give = avail;
141 if (need < give) {
142 give = need;
143 }
144 readBuffer_.write((uint8_t*)pos, give);
145 pos += give;
146 need -= give;
147 }
148 return pos;
149}
150
151char* THttpClient::readLine(char* pos) {
152 while (true) {
153 char* eol = NULL;
154
155 // Note, the data we read could have ended right on the CRLF pair
156 if (pos != NULL) {
157 eol = strstr(pos, CRLF);
158 }
159
160 // No CRLF yet?
161 if (eol == NULL) {
162 // Shift whatever we have now to front
163 pos = shift(pos);
164 // Refill the buffer
165 refill();
166 } else {
167 // Return pointer to next line
168 *eol = '\0';
169 return eol + CRLF_LEN;
170 }
171 }
172
173}
174
175char* THttpClient::shift(char* pos) {
176 if (pos != NULL && httpBufPos_ > (pos - httpBuf_)) {
177 // Shift down remaining data and read more
178 uint32_t length = httpBufPos_ - (pos - httpBuf_);
179 memmove(httpBuf_, pos, length);
180 httpBufPos_ = length;
181 } else {
182 httpBufPos_ = 0;
183 }
184 httpBuf_[httpBufPos_] = '\0';
185 return httpBuf_;
186}
187
188void THttpClient::refill() {
189 uint32_t avail = httpBufSize_ - httpBufPos_;
190 if (avail <= (httpBufSize_ / 4)) {
191 httpBufSize_ *= 2;
192 httpBuf_ = (char*)realloc(httpBuf_, httpBufSize_);
193 if (httpBuf_ == NULL) {
194 throw TTransportException("Out of memory.");
195 }
196 }
197
198 // Read more data
199 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufPos_), httpBufSize_-httpBufPos_);
200 httpBufPos_ += got;
201 httpBuf_[httpBufPos_] = '\0';
202
203 if (got == 0) {
204 throw TTransportException("Could not finish reading HTTP headers");
205 }
206}
207
208void THttpClient::readHeaders() {
209 // Initialize headers state variables
210 contentLength_ = 0;
211 chunked_ = false;
212 chunkSize_ = 0;
213
214 // Control state flow
215 bool statusLine = true;
216 bool finished = false;
217
218 // Initialize local pos vars
219 char* nextLine = (char*)httpBuf_;
220
221 // Loop until headers are finished
222 while (true) {
223 char* line = nextLine;
224 nextLine = readLine(nextLine);
225
226 if (strlen(line) == 0) {
227 if (finished) {
228 readHeaders_ = false;
229 shift(nextLine);
230 return;
231 } else {
232 // Must have been an HTTP 100, keep going for another status line
233 statusLine = true;
234 }
235 } else {
236 if (statusLine) {
237 statusLine = false;
238 finished = parseStatusLine(line);
239 } else {
240 parseHeader(line);
241 }
242 }
243 }
244}
245
246bool THttpClient::parseStatusLine(char* status) {
247 char* http = status;
248
249 char* code = strchr(http, ' ');
250 *code = '\0';
251
252 while (*(code++) == ' ');
253
254 char* msg = strchr(code, ' ');
255 *msg = '\0';
256
257 if (strcmp(code, "200") == 0) {
258 // HTTP 200 = OK, we got the response
259 return true;
260 } else if (strcmp(code, "100") == 0) {
261 // HTTP 100 = continue, just keep reading
262 return false;
263 } else {
264 throw TTransportException(status);
265 }
266}
267
268void THttpClient::parseHeader(char* header) {
269 char* colon = strchr(header, ':');
270 if (colon == NULL) {
271 return;
272 }
273 uint32_t sz = colon - header;
274 char* value = colon+1;
275
276 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
277 if (strstr(value, "chunked") != NULL) {
278 chunked_ = true;
279 }
280 } else if (strncmp(header, "Content-Length", sz) == 0) {
281 chunked_ = false;
282 contentLength_ = atoi(value);
283 }
284}
285
286void THttpClient::write(const uint8_t* buf, uint32_t len) {
287 writeBuffer_.write(buf, len);
288}
289
290void THttpClient::flush() {
291 // Fetch the contents of the write buffer
292 uint8_t* buf;
293 uint32_t len;
294 writeBuffer_.getBuffer(&buf, &len);
295
296 // Construct the HTTP header
297 std::ostringstream h;
298 h <<
299 "POST " << path_ << " HTTP/1.1" << CRLF <<
300 "Host: " << host_ << CRLF <<
301 "Content-Type: application/x-thrift" << CRLF <<
302 "Content-Length: " << len << CRLF <<
303 "Accept: application/x-thrift" << CRLF <<
304 "User-Agent: C++/THttpClient" << CRLF <<
305 CRLF;
306 string header = h.str();
307
308 // Write the header, then the data, then flush
309 transport_->write((const uint8_t*)header.c_str(), header.size());
310 transport_->write(buf, len);
311 transport_->flush();
312
313 // Reset the buffer and header variables
314 writeBuffer_.resetBuffer();
315 readHeaders_ = true;
316}
317
318}}} // facebook::thrift::transport