TDME2  1.9.200
HTTPClient.cpp
Go to the documentation of this file.
2 
3 #include <iomanip>
4 #include <memory>
5 #include <sstream>
6 #include <string>
7 #include <unordered_map>
8 #include <vector>
9 
10 #include <tdme/tdme.h>
16 #include <tdme/utilities/Base64.h>
18 #include <tdme/utilities/Console.h>
20 #include <tdme/utilities/Integer.h>
23 
24 using std::hex;
25 using std::make_unique;
26 using std::nouppercase;
27 using std::ostringstream;
28 using std::setw;
29 using std::string;
30 using std::stringstream;
31 using std::to_string;
32 using std::unique_ptr;
33 using std::unordered_map;
34 using std::uppercase;
35 using std::vector;
36 
49 
51 
52 const string HTTPClient::HTTP_METHOD_GET = "GET";
53 const string HTTPClient::HTTP_METHOD_HEAD = "HEAD";
54 const string HTTPClient::HTTP_METHOD_POST = "POST";
55 const string HTTPClient::HTTP_METHOD_PUT = "PUT";
56 const string HTTPClient::HTTP_METHOD_DELETE = "DELETE";
57 
58 string HTTPClient::urlEncode(const string &value) {
59  // TODO: put me into utilities
60  // see: https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
61  ostringstream escaped;
62  escaped.fill('0');
63  escaped << hex;
64 
65  for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
66  string::value_type c = (*i);
67 
68  // Keep alphanumeric and other accepted characters intact
69  if (Character::isAlphaNumeric(c) == true || c == '-' || c == '_' || c == '.' || c == '~') {
70  escaped << c;
71  continue;
72  }
73 
74  // Any other characters are percent-encoded
75  escaped << uppercase;
76  escaped << '%' << setw(2) << int((unsigned char) c);
77  escaped << nouppercase;
78  }
79 
80  return escaped.str();
81 }
82 
83 string HTTPClient::createHTTPRequestHeaders(const string& hostname, const string& relativeUrl, const string& body) {
84  string query;
85  for (const auto& [parameterName, parameterValue]: getParameters) {
86  if (query.empty() == true) query+= "?"; else query+="&";
87  query+= urlEncode(parameterName) + "=" + urlEncode(parameterValue);
88  }
89  auto request =
90  string(method + " " + relativeUrl + query + " HTTP/1.1\r\n") +
91  string("User-Agent: tdme2-httpclient\r\n") +
92  string("Accept-Charset: UTF-8\r\n") +
93  string("Host: " + hostname + "\r\n") +
94  string("Connection: close\r\n");
95  if (username.empty() == false || password.empty() == false) {
96  string base64Pass;
97  Base64::encode(username + ":" + password, base64Pass);
98  request+= "Authorization: Basic " + base64Pass + "\r\n";
99  }
100  if (contentType.size() > 0) {
101  request+=
102  string("Content-Type: " + contentType + "\r\n");
103  }
104  for (const auto& [headerName, headerValue]: headers) {
105  request+= headerName + ": " + headerValue + "\r\n";
106  }
108  string _body;
109  if (postParameters.size() > 0) {
110  for (const auto& [parameterName, parameterValue]: postParameters) {
111  if (_body.size() >= 0) _body+="&";
112  _body+= urlEncode(parameterName) + "=" + urlEncode(parameterValue);
113  }
114  } else {
115  _body = body;
116  }
117  request+= "Content-Length: " + to_string(_body.size()) + "\r\n\r\n";
118  request+= _body;
119  } else {
120  request+= "\r\n";
121  }
122  return request;
123 }
124 
125 void HTTPClient::parseHTTPResponseHeaders(stringstream& rawResponse) {
126  int headerIdx = 0;
127  string statusHeader;
128  string line;
129  char lastChar = -1;
130  char currentChar;
131  while (rawResponse.eof() == false) {
132  rawResponse.get(currentChar);
133  if (lastChar == '\r' && currentChar == '\n') {
134  if (line.empty() == false) {
135  if (headerIdx == 0) {
136  statusHeader = line;
137  headerIdx++;
138  } else {
139  auto headerNameValueSeparator = StringTools::indexOf(line, ':');
140  responseHeaders[StringTools::trim(StringTools::substring(line, 0, headerNameValueSeparator))] =
141  StringTools::trim(StringTools::substring(line, headerNameValueSeparator + 1));
142  }
143  } else {
144  break;
145  }
146  line.clear();
147  } else
148  if (currentChar != '\r' && currentChar != '\n') {
149  line+= currentChar;
150  }
151  lastChar = currentChar;
152  }
153  if (statusHeader.empty() == false) {
154  StringTokenizer t;
155  t.tokenize(statusHeader, " ");
156  for (auto i = 0; i < 3 && t.hasMoreTokens(); i++) {
157  auto token = t.nextToken();
158  if (i == 1) {
159  statusCode = Integer::parse(token);
160  }
161  }
162  }
163 }
164 
166  url.clear();
167  method.clear();
168  headers.clear();
169  getParameters.clear();
170  postParameters.clear();
171  body.clear();
172  contentType.clear();
173 
174  rawResponse.clear();
175  statusCode = -1;
176  responseHeaders.clear();
177 }
178 
180  //
181  unique_ptr<TCPSocket> socket;
182  try {
183  // TODO: we might need a class to determine protocol, hostname and port, yaaar
184  auto protocolSeparatorIdx = StringTools::indexOf(url, string("://"));
185  if (protocolSeparatorIdx == -1) throw HTTPClientException("Invalid URL");
186  auto relativeUrl = StringTools::substring(url, protocolSeparatorIdx + 3);
187  if (relativeUrl.empty() == true) throw HTTPClientException("No URL given");
188  auto slashIdx = relativeUrl.find('/');
189  auto hostname = relativeUrl;
190  if (slashIdx != -1) hostname = StringTools::substring(relativeUrl, 0, slashIdx);
191  relativeUrl = StringTools::substring(relativeUrl, hostname.size());
192  //
193  if (StringTools::startsWith(url, "http://") == true) {
194  auto ip = Network::getIpByHostname(hostname);
195  if (ip.empty() == true) {
196  Console::println("HTTPClient::execute(): failed");
197  throw HTTPClientException("Could not resolve host IP by hostname");
198  }
199  //
200  socket = make_unique<TCPSocket>();
201  socket->connect(ip, 80);
202  } else
203  if (StringTools::startsWith(url, "https://") == true) {
204  socket = make_unique<SecureTCPSocket>();
205  socket->connect(hostname, 443);
206  } else {
207  throw HTTPClientException("Invalid protocol");
208  }
209  //
210  auto request = createHTTPRequestHeaders(hostname, relativeUrl, body);
211  socket->write((void*)request.data(), request.length());
212 
213  char rawResponseBuf[16384];
214  auto rawResponseBytesRead = 0;
215  try {
216  for (;true;) {
217  auto rawResponseBytesRead = socket->read(rawResponseBuf, sizeof(rawResponseBuf));
218  rawResponse.write(rawResponseBuf, rawResponseBytesRead);
219  };
220  } catch (NetworkSocketClosedException& sce) {
221  // end of stream
222  }
223 
224  //
226 
227  //
228  socket->shutdown();
229  } catch (Exception& exception) {
230  socket->shutdown();
231  //
232  Console::println(string("HTTPClient::execute(): performed HTTP request: FAILED: ") + exception.what());
233  // rethrow
234  throw;
235  }
236 
237 }
unordered_map< string, string > headers
Definition: HTTPClient.h:29
void execute()
Execute HTTP request.
Definition: HTTPClient.cpp:179
static STATIC_DLL_IMPEXT const string HTTP_METHOD_POST
Definition: HTTPClient.h:67
static STATIC_DLL_IMPEXT const string HTTP_METHOD_PUT
Definition: HTTPClient.h:68
unordered_map< string, string > getParameters
Definition: HTTPClient.h:30
unordered_map< string, string > responseHeaders
Definition: HTTPClient.h:39
unordered_map< string, string > postParameters
Definition: HTTPClient.h:31
static string urlEncode(const string &value)
Returns a URL encoded representation of value.
Definition: HTTPClient.cpp:58
void reset()
Reset this HTTP client.
Definition: HTTPClient.cpp:165
string createHTTPRequestHeaders(const string &hostname, const string &relativeUrl, const string &body)
Create HTTP request headers.
Definition: HTTPClient.cpp:83
void parseHTTPResponseHeaders(stringstream &rawResponse)
Parse HTTP response headers.
Definition: HTTPClient.cpp:125
Network class.
Definition: Network.h:14
Class representing a secure TCP socket.
Class representing a TCP socket.
Definition: TCPSocket.h:15
Base64 encoding/decoding class.
Definition: Base64.h:16
Character class.
Definition: Character.h:17
Console class.
Definition: Console.h:29
Integer class.
Definition: Integer.h:25
String tokenizer class.
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
String tools class.
Definition: StringTools.h:22
std::exception Exception
Exception base class.
Definition: Exception.h:18