blob: 59f233968efd4ebe7ea3e2ac7cf73d3a200e12ae [file] [log] [blame]
David Reissea2cba82009-03-30 21:35:00 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
Mark Slee9f0c6512007-02-28 23:58:26 +000019
David Reissd7a16f42008-02-19 22:47:29 +000020#include <cstdlib>
David Reisse39e9372008-05-01 05:52:48 +000021#include <sstream>
David Reissd7a16f42008-02-19 22:47:29 +000022
Mark Slee8a98e1b2007-02-27 05:16:23 +000023#include "THttpClient.h"
24#include "TSocket.h"
25
T Jake Lucianib5e62212009-01-31 22:36:20 +000026namespace apache { namespace thrift { namespace transport {
Mark Slee8a98e1b2007-02-27 05:16:23 +000027
28using namespace std;
29
30/**
31 * Http client implementation.
32 *
Mark Slee8a98e1b2007-02-27 05:16:23 +000033 */
34
35// Yeah, yeah, hacky to put these here, I know.
36static const char* CRLF = "\r\n";
37static const int CRLF_LEN = 2;
38
Mark Sleea2c760b2007-02-27 05:18:07 +000039THttpClient::THttpClient(boost::shared_ptr<TTransport> transport, string host, string path) :
Mark Slee8a98e1b2007-02-27 05:16:23 +000040 transport_(transport),
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 init();
53}
54
55THttpClient::THttpClient(string host, int port, string path) :
56 host_(host),
57 path_(path),
58 readHeaders_(true),
59 chunked_(false),
Mark Slee2a22a882007-02-27 19:53:38 +000060 chunkedDone_(false),
Mark Slee8a98e1b2007-02-27 05:16:23 +000061 chunkSize_(0),
62 contentLength_(0),
63 httpBuf_(NULL),
Mark Slee2a22a882007-02-27 19:53:38 +000064 httpPos_(0),
65 httpBufLen_(0),
Mark Slee8a98e1b2007-02-27 05:16:23 +000066 httpBufSize_(1024) {
67 transport_ = boost::shared_ptr<TTransport>(new TSocket(host, port));
68 init();
69}
70
71void THttpClient::init() {
David Reissd7a16f42008-02-19 22:47:29 +000072 httpBuf_ = (char*)std::malloc(httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +000073 if (httpBuf_ == NULL) {
74 throw TTransportException("Out of memory.");
75 }
Mark Slee2a22a882007-02-27 19:53:38 +000076 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +000077}
78
79THttpClient::~THttpClient() {
80 if (httpBuf_ != NULL) {
David Reissd7a16f42008-02-19 22:47:29 +000081 std::free(httpBuf_);
Mark Slee8a98e1b2007-02-27 05:16:23 +000082 }
83}
84
85uint32_t THttpClient::read(uint8_t* buf, uint32_t len) {
David Reiss28f298d2008-05-01 06:17:36 +000086 if (readBuffer_.available_read() == 0) {
Mark Slee8a98e1b2007-02-27 05:16:23 +000087 readBuffer_.resetBuffer();
88 uint32_t got = readMoreData();
89 if (got == 0) {
90 return 0;
91 }
92 }
93 return readBuffer_.read(buf, len);
94}
95
Mark Slee2a22a882007-02-27 19:53:38 +000096void THttpClient::readEnd() {
97 // Read any pending chunked data (footers etc.)
98 if (chunked_) {
99 while (!chunkedDone_) {
100 readChunked();
101 }
102 }
103}
104
Mark Slee8a98e1b2007-02-27 05:16:23 +0000105uint32_t THttpClient::readMoreData() {
106 // Get more data!
107 refill();
108
109 if (readHeaders_) {
110 readHeaders();
111 }
112
113 if (chunked_) {
114 return readChunked();
115 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000116 return readContent(contentLength_);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000117 }
118}
119
120uint32_t THttpClient::readChunked() {
121 uint32_t length = 0;
Mark Slee2a22a882007-02-27 19:53:38 +0000122
123 char* line = readLine();
124 uint32_t chunkSize = parseChunkSize(line);
125 if (chunkSize == 0) {
126 readChunkedFooters();
127 } else {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000128 // Read data content
Mark Slee2a22a882007-02-27 19:53:38 +0000129 length += readContent(chunkSize);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000130 // Read trailing CRLF after content
Mark Slee2a22a882007-02-27 19:53:38 +0000131 readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000132 }
Mark Slee2a22a882007-02-27 19:53:38 +0000133 return length;
134}
Mark Slee8a98e1b2007-02-27 05:16:23 +0000135
Mark Slee2a22a882007-02-27 19:53:38 +0000136void THttpClient::readChunkedFooters() {
137 // End of data, read footer lines until a blank one appears
Mark Slee8a98e1b2007-02-27 05:16:23 +0000138 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000139 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000140 if (strlen(line) == 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000141 chunkedDone_ = true;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000142 break;
143 }
144 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000145}
146
147uint32_t THttpClient::parseChunkSize(char* line) {
148 char* semi = strchr(line, ';');
149 if (semi != NULL) {
150 *semi = '\0';
151 }
Mark Slee44018142007-02-27 19:03:01 +0000152 int size = 0;
153 sscanf(line, "%x", &size);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000154 return (uint32_t)size;
155}
156
Mark Slee2a22a882007-02-27 19:53:38 +0000157uint32_t THttpClient::readContent(uint32_t size) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000158 uint32_t need = size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000159 while (need > 0) {
Mark Slee2a22a882007-02-27 19:53:38 +0000160 uint32_t avail = httpBufLen_ - httpPos_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000161 if (avail == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000162 // We have given all the data, reset position to head of the buffer
Mark Slee2a22a882007-02-27 19:53:38 +0000163 httpPos_ = 0;
164 httpBufLen_ = 0;
165 refill();
David Reiss0c90f6f2008-02-06 22:18:40 +0000166
Mark Slee44018142007-02-27 19:03:01 +0000167 // Now have available however much we read
Mark Slee2a22a882007-02-27 19:53:38 +0000168 avail = httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000169 }
170 uint32_t give = avail;
171 if (need < give) {
172 give = need;
173 }
Mark Slee2a22a882007-02-27 19:53:38 +0000174 readBuffer_.write((uint8_t*)(httpBuf_+httpPos_), give);
175 httpPos_ += give;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000176 need -= give;
177 }
Mark Slee2a22a882007-02-27 19:53:38 +0000178 return size;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000179}
David Reiss0c90f6f2008-02-06 22:18:40 +0000180
Mark Slee2a22a882007-02-27 19:53:38 +0000181char* THttpClient::readLine() {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000182 while (true) {
183 char* eol = NULL;
184
Mark Slee2a22a882007-02-27 19:53:38 +0000185 eol = strstr(httpBuf_+httpPos_, CRLF);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000186
187 // No CRLF yet?
188 if (eol == NULL) {
Mark Slee44018142007-02-27 19:03:01 +0000189 // Shift whatever we have now to front and refill
Mark Slee2a22a882007-02-27 19:53:38 +0000190 shift();
191 refill();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000192 } else {
193 // Return pointer to next line
194 *eol = '\0';
Mark Slee2a22a882007-02-27 19:53:38 +0000195 char* line = httpBuf_+httpPos_;
196 httpPos_ = (eol-httpBuf_) + CRLF_LEN;
197 return line;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000198 }
199 }
200
201}
202
Mark Slee2a22a882007-02-27 19:53:38 +0000203void THttpClient::shift() {
204 if (httpBufLen_ > httpPos_) {
Mark Slee8a98e1b2007-02-27 05:16:23 +0000205 // Shift down remaining data and read more
Mark Slee2a22a882007-02-27 19:53:38 +0000206 uint32_t length = httpBufLen_ - httpPos_;
207 memmove(httpBuf_, httpBuf_+httpPos_, length);
208 httpBufLen_ = length;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000209 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000210 httpBufLen_ = 0;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000211 }
Mark Slee2a22a882007-02-27 19:53:38 +0000212 httpPos_ = 0;
213 httpBuf_[httpBufLen_] = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000214}
215
Mark Slee2a22a882007-02-27 19:53:38 +0000216void THttpClient::refill() {
217 uint32_t avail = httpBufSize_ - httpBufLen_;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000218 if (avail <= (httpBufSize_ / 4)) {
219 httpBufSize_ *= 2;
David Reissd7a16f42008-02-19 22:47:29 +0000220 httpBuf_ = (char*)std::realloc(httpBuf_, httpBufSize_+1);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000221 if (httpBuf_ == NULL) {
222 throw TTransportException("Out of memory.");
223 }
224 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000225
Mark Slee8a98e1b2007-02-27 05:16:23 +0000226 // Read more data
Mark Slee2a22a882007-02-27 19:53:38 +0000227 uint32_t got = transport_->read((uint8_t*)(httpBuf_+httpBufLen_), httpBufSize_-httpBufLen_);
228 httpBufLen_ += got;
229 httpBuf_[httpBufLen_] = '\0';
David Reiss0c90f6f2008-02-06 22:18:40 +0000230
Mark Slee8a98e1b2007-02-27 05:16:23 +0000231 if (got == 0) {
Mark Slee44018142007-02-27 19:03:01 +0000232 throw TTransportException("Could not refill buffer");
Mark Slee8a98e1b2007-02-27 05:16:23 +0000233 }
234}
235
236void THttpClient::readHeaders() {
237 // Initialize headers state variables
238 contentLength_ = 0;
239 chunked_ = false;
Mark Slee2a22a882007-02-27 19:53:38 +0000240 chunkedDone_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000241 chunkSize_ = 0;
242
243 // Control state flow
244 bool statusLine = true;
245 bool finished = false;
246
Mark Slee8a98e1b2007-02-27 05:16:23 +0000247 // Loop until headers are finished
248 while (true) {
Mark Slee2a22a882007-02-27 19:53:38 +0000249 char* line = readLine();
Mark Slee8a98e1b2007-02-27 05:16:23 +0000250
251 if (strlen(line) == 0) {
252 if (finished) {
253 readHeaders_ = false;
Mark Slee8a98e1b2007-02-27 05:16:23 +0000254 return;
255 } else {
256 // Must have been an HTTP 100, keep going for another status line
257 statusLine = true;
258 }
259 } else {
260 if (statusLine) {
261 statusLine = false;
262 finished = parseStatusLine(line);
263 } else {
264 parseHeader(line);
265 }
266 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000267 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000268}
269
270bool THttpClient::parseStatusLine(char* status) {
271 char* http = status;
272
273 char* code = strchr(http, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000274 if (code == NULL) {
275 throw TTransportException(string("Bad Status: ") + status);
276 }
David Reiss0c90f6f2008-02-06 22:18:40 +0000277
Mark Slee8a98e1b2007-02-27 05:16:23 +0000278 *code = '\0';
Mark Slee8a98e1b2007-02-27 05:16:23 +0000279 while (*(code++) == ' ');
280
281 char* msg = strchr(code, ' ');
Mark Slee2a22a882007-02-27 19:53:38 +0000282 if (msg == NULL) {
283 throw TTransportException(string("Bad Status: ") + status);
284 }
Mark Slee8a98e1b2007-02-27 05:16:23 +0000285 *msg = '\0';
286
287 if (strcmp(code, "200") == 0) {
288 // HTTP 200 = OK, we got the response
289 return true;
290 } else if (strcmp(code, "100") == 0) {
291 // HTTP 100 = continue, just keep reading
292 return false;
293 } else {
Mark Slee2a22a882007-02-27 19:53:38 +0000294 throw TTransportException(string("Bad Status: ") + status);
Mark Slee8a98e1b2007-02-27 05:16:23 +0000295 }
296}
297
298void THttpClient::parseHeader(char* header) {
299 char* colon = strchr(header, ':');
300 if (colon == NULL) {
301 return;
302 }
303 uint32_t sz = colon - header;
304 char* value = colon+1;
305
306 if (strncmp(header, "Transfer-Encoding", sz) == 0) {
307 if (strstr(value, "chunked") != NULL) {
308 chunked_ = true;
309 }
310 } else if (strncmp(header, "Content-Length", sz) == 0) {
311 chunked_ = false;
312 contentLength_ = atoi(value);
313 }
314}
315
316void THttpClient::write(const uint8_t* buf, uint32_t len) {
317 writeBuffer_.write(buf, len);
318}
319
320void THttpClient::flush() {
321 // Fetch the contents of the write buffer
322 uint8_t* buf;
323 uint32_t len;
324 writeBuffer_.getBuffer(&buf, &len);
325
326 // Construct the HTTP header
327 std::ostringstream h;
328 h <<
329 "POST " << path_ << " HTTP/1.1" << CRLF <<
330 "Host: " << host_ << CRLF <<
331 "Content-Type: application/x-thrift" << CRLF <<
332 "Content-Length: " << len << CRLF <<
333 "Accept: application/x-thrift" << CRLF <<
334 "User-Agent: C++/THttpClient" << CRLF <<
335 CRLF;
336 string header = h.str();
337
338 // Write the header, then the data, then flush
339 transport_->write((const uint8_t*)header.c_str(), header.size());
340 transport_->write(buf, len);
341 transport_->flush();
342
343 // Reset the buffer and header variables
344 writeBuffer_.resetBuffer();
345 readHeaders_ = true;
346}
347
T Jake Lucianib5e62212009-01-31 22:36:20 +0000348}}} // apache::thrift::transport