29 int read = stream->Read(&c, 0, 1);
39 struct HttpClient::Impl :
public Object {
43 String ResolveHost(
const Uri& uri,
int& riPort) {
55 return arrIpAddresses[0];
59 String sHost = uri.GetHost();
60 String sPath = uri.GetAbsolutePath();
61 if (sPath.IsEmpty()) {
64 String sQuery = uri.GetQuery();
65 std::string sRequestPath = sPath.GetRawString();
66 if (!sQuery.IsEmpty()) {
68 sRequestPath += sQuery.GetRawString();
72 std::ostringstream ssHeadersStream;
73 ssHeadersStream << spRequest->GetMethod().GetMethod().GetRawString() <<
" " << sRequestPath <<
" HTTP/1.1\r\n";
76 ssHeadersStream <<
"Host: " << sHost.GetRawString();
77 if (!uri.IsDefaultPort() && uri.GetPort() > 0) {
78 ssHeadersStream <<
":" << uri.GetPort();
80 ssHeadersStream <<
"\r\n";
83 auto arrDefaultHeadersKeys = m_defaultRequestHeaders.GetKeys();
84 for (
int i = 0; i < arrDefaultHeadersKeys.GetLength(); ++i) {
85 String sKey = arrDefaultHeadersKeys[i];
86 ssHeadersStream << sKey.GetRawString() <<
": " << m_defaultRequestHeaders[sKey].GetRawString() <<
"\r\n";
90 auto arrRequestHeadersKeys = spRequest->GetHeaders().GetKeys();
91 for (
int i = 0; i < arrRequestHeadersKeys.GetLength(); ++i) {
92 String sKey = arrRequestHeadersKeys[i];
93 ssHeadersStream << sKey.GetRawString() <<
": " << spRequest->GetHeaders()[sKey].GetRawString() <<
"\r\n";
97 auto spContent = spRequest->GetContent();
98 if (!spContent.IsNull()) {
99 auto arrContentHeadersKeys = spContent->GetHeaders().GetKeys();
100 for (
int i = 0; i < arrContentHeadersKeys.GetLength(); ++i) {
101 String sKey = arrContentHeadersKeys[i];
102 ssHeadersStream << sKey.GetRawString() <<
": " << spContent->GetHeaders()[sKey].GetRawString() <<
"\r\n";
105 long lLen = spContent->GetLength();
107 ssHeadersStream <<
"Content-Length: " << lLen <<
"\r\n";
111 ssHeadersStream <<
"Connection: close\r\n\r\n";
113 return ssHeadersStream.str();
116 void SendRequest(
const SmartPointer<IO::Stream>& spStream,
const std::string& sHeaders,
const HttpContentPtr& spContent) {
117 spStream->Write(sHeaders.data(), 0,
static_cast<int>(sHeaders.size()));
120 if (!spContent.IsNull()) {
121 spContent->CopyTo(spStream);
126 std::string sStatusLine =
ReadLine(spStream);
127 if (sStatusLine.empty()) {
132 size_t iFirstSpace = sStatusLine.find(
' ');
133 if (iFirstSpace == std::string::npos) {
137 size_t iSecondSpace = sStatusLine.find(
' ', iFirstSpace + 1);
138 int iStatusCodeVal = 0;
139 std::string sReasonPhrase;
140 if (iSecondSpace == std::string::npos) {
141 iStatusCodeVal = std::atoi(sStatusLine.substr(iFirstSpace + 1).c_str());
143 iStatusCodeVal = std::atoi(sStatusLine.substr(iFirstSpace + 1, iSecondSpace - iFirstSpace - 1).c_str());
144 sReasonPhrase = sStatusLine.substr(iSecondSpace + 1);
148 spResponse->SetReasonPhrase(
String(sReasonPhrase.c_str()));
152 void ParseHeaders(
const SmartPointer<IO::Stream>& spStream,
const HttpResponseMessagePtr& spResponse,
bool& rbChunked,
long& rlContentLength, String& rsContentType) {
153 auto& dictRespHeaders = spResponse->GetHeaders();
155 std::string sHeaderLine =
ReadLine(spStream);
156 if (sHeaderLine.empty())
break;
158 size_t iColon = sHeaderLine.find(
':');
159 if (iColon != std::string::npos) {
160 std::string sKey = sHeaderLine.substr(0, iColon);
161 std::string sVal = sHeaderLine.substr(iColon + 1);
164 while (!sKey.empty() && std::isspace(
static_cast<unsigned char>(sKey.front()))) sKey.erase(sKey.begin());
165 while (!sKey.empty() && std::isspace(
static_cast<unsigned char>(sKey.back()))) sKey.pop_back();
166 while (!sVal.empty() && std::isspace(
static_cast<unsigned char>(sVal.front()))) sVal.erase(sVal.begin());
167 while (!sVal.empty() && std::isspace(
static_cast<unsigned char>(sVal.back()))) sVal.pop_back();
169 String sKeyObj(sKey.c_str());
170 String sValObj(sVal.c_str());
171 dictRespHeaders[sKeyObj] = sValObj;
173 if (sKeyObj.ToLower() ==
"transfer-encoding" && sValObj.ToLower() ==
"chunked") {
175 }
else if (sKeyObj.ToLower() ==
"content-length") {
176 rlContentLength = std::atol(sVal.c_str());
177 }
else if (sKeyObj.ToLower() ==
"content-type") {
178 rsContentType = sValObj;
184 Array<char> ReadResponseBody(
const SmartPointer<IO::Stream>& spStream,
bool bChunked,
long lContentLength) {
185 std::vector<char> vecBodyData;
188 std::string sSizeLine =
ReadLine(spStream);
189 if (sSizeLine.empty())
break;
191 long lChunkSize = std::strtol(sSizeLine.c_str(),
nullptr, 16);
192 if (lChunkSize <= 0) {
197 std::vector<char> vecChunk(lChunkSize);
199 while (iTotalRead < lChunkSize) {
200 int iRead = spStream->Read(vecChunk.data() + iTotalRead, 0,
static_cast<int>(lChunkSize - iTotalRead));
206 vecBodyData.insert(vecBodyData.end(), vecChunk.begin(), vecChunk.end());
210 }
else if (lContentLength >= 0) {
211 vecBodyData.resize(lContentLength);
213 while (iTotalRead < lContentLength) {
214 int iRead = spStream->Read(vecBodyData.data() + iTotalRead, 0,
static_cast<int>(lContentLength - iTotalRead));
222 char arrBuffer[4096];
224 while ((iBytesRead = spStream->Read(arrBuffer, 0,
sizeof(arrBuffer))) > 0) {
225 vecBodyData.insert(vecBodyData.end(), arrBuffer, arrBuffer + iBytesRead);
229 Array<char> arrData(
static_cast<int>(vecBodyData.size()));
230 if (!vecBodyData.empty()) {
231 std::memcpy(arrData.GetData(), vecBodyData.data(), vecBodyData.size());
236 SmartPointer<IO::Stream> ConnectStream(
const Uri& uri,
const String& scheme) {
237 int iPort = uri.GetPort() > 0 ? uri.GetPort() : ((scheme ==
"https") ? 443 : 80);
238 String sResolvedIp = ResolveHost(uri, iPort);
240 try { m_pLastTcpClient->Connect(sResolvedIp, iPort); }
242 SmartPointer<IO::Stream> spStream = m_pLastTcpClient->GetStream();
243 if (scheme ==
"https") {
245 try { spSsl->AuthenticateAsClient(uri.GetHost()); }
253 auto spResponse = ParseStatusLine(spStream);
254 bool bChunked =
false;
long lContentLength = -1;
String sContentType =
"text/plain";
255 ParseHeaders(spStream, spResponse, bChunked, lContentLength, sContentType);
257 spResponseContent->GetHeaders()[
"Content-Type"] = sContentType;
258 if (lContentLength >= 0) spResponseContent->GetHeaders()[
"Content-Length"] =
Convert::ToString(
static_cast<long long>(lContentLength));
259 spResponse->SetContent(spResponseContent);
264 auto spResponse = ParseStatusLine(spStream);
265 bool bChunked =
false;
266 long lContentLength = -1;
267 String sContentType =
"text/plain";
268 ParseHeaders(spStream, spResponse, bChunked, lContentLength, sContentType);
269 Array<char> arrData = ReadResponseBody(spStream, bChunked, lContentLength);
271 spResponseContent->GetHeaders()[
"Content-Type"] = sContentType;
272 spResponse->SetContent(spResponseContent);
285 return Get(
Uri(requestUri));
291 return Send(request);
296 return Post(
Uri(requestUri), content);
302 request->SetContent(content);
303 return Send(request);
308 return Put(
Uri(requestUri), content);
314 request->SetContent(content);
315 return Send(request);
326 return Send(request);
336 auto response =
Get(requestUri);
337 response->EnsureSuccessStatusCode();
338 auto content = response->GetContent();
339 if (content.IsNull())
return String(
"");
340 return content->ReadAsString();
350 auto response =
Get(requestUri);
351 response->EnsureSuccessStatusCode();
352 auto content = response->GetContent();
354 return content->ReadAsByteArray();
359 return m_pImpl->m_defaultRequestHeaders;
364 return m_pImpl->m_defaultRequestHeaders;
376 Uri uri = request->GetRequestUri();
378 if (scheme !=
"http" && scheme !=
"https")
throw ArgumentException(
"Only 'http' and 'https' schemes are supported.");
380 auto spStream = m_pImpl->ConnectStream(uri, scheme);
382 m_pImpl->SendRequest(spStream, m_pImpl->PrepareHeaders(request, uri), request->GetContent());
385 return m_pImpl->PrepareResponse(spStream);
Defines the exception thrown when an invalid argument is provided to a method.
Defines the exception thrown when a null reference is passed to a method that does not accept it.
Converts a base data type to another base data type, and encodes/decodes Base64 data.
Provides simple domain name resolution functionality.
Provides a base class for sending HTTP requests and receiving HTTP responses mirroring ....
Exception thrown when an HTTP request or connection failure occurs.
Provides the underlying stream of data for network access.
Exception thrown when a network socket error occurs per RFC 793 / RFC 768.
Provides a stream that uses the Transport Layer Security (TLS) protocol to secure network communicati...
Provides client connections for TCP network services.
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
ArgumentNullException(const String &sMessage)
Initializes a new instance of the ArgumentNullException class with a specified error message.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
int GetLength() const
Gets the total number of elements in all dimensions of the Array.
Represents a collection of keys and values.
static String ToString(bool value)
static Array< String > GetHostAddresses(const String &hostName)
Returns the Internet Protocol (IP) addresses for the specified host.
ByteArrayContent(const Array< char > &content)
Initializes a new instance of ByteArrayContent with full byte buffer.
HttpResponseMessagePtr Post(const String &requestUri, const HttpContentPtr &content)
Sends a POST request with content to the specified string Uri.
String GetString(const String &requestUri)
Sends a GET request to the specified URI and returns the response body as a string.
~HttpClient()
Releases unmanaged resources and destroys the HttpClient instance.
HttpClient()
Initializes a new instance of the HttpClient class.
HttpResponseMessagePtr Send(const HttpRequestMessagePtr &request)
Sends an HTTP request as an operation.
HttpResponseMessagePtr Delete(const String &requestUri)
Sends a DELETE request to the specified string Uri.
HttpResponseMessagePtr Get(const String &requestUri)
Sends a GET request to the specified string Uri.
Collections::Generic::Dictionary< String, String > & GetDefaultRequestHeaders()
Gets the headers which should be sent with each request.
Array< char > GetByteArray(const String &requestUri)
Sends a GET request to the specified URI and returns the response body as a byte array.
HttpResponseMessagePtr Put(const String &requestUri, const HttpContentPtr &content)
Sends a PUT request with content to the specified string Uri.
static const HttpMethod Get
Represents an HTTP GET protocol method.
static const HttpMethod Put
Represents an HTTP PUT protocol method.
static const HttpMethod Post
Represents an HTTP POST protocol method.
static const HttpMethod Delete
Represents an HTTP DELETE protocol method.
HttpRequestException(const String &sMessage)
Initializes a new instance of the HttpRequestException class with a specified error message.
StreamContent(const SmartPointer< IO::Stream > &stream)
Initializes a new instance of StreamContent wrapping an existing Stream.
Supports all classes in the DotNetDupe class hierarchy.
A unified smart pointer that supports both unique and shared ownership semantics.
static SmartPointer< HttpResponseMessage > NewShared()
bool IsNull() const noexcept
Checks if the SmartPointer is null.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
String()
Initializes a new instance of the String class to an empty string.
Represents an object representation of a Uniform Resource Identifier (URI) and provides easy access t...
String GetHost() const
Gets the host component of this instance.
int GetPort() const
Gets the port number of this URI.
String GetScheme() const
Gets the scheme name for this URI.
Uri(const String &uriString)
Initializes a new instance of the Uri class with the specified URI string.
static std::string ReadLine(const SmartPointer< IO::Stream > &stream)
SmartPointer< HttpResponseMessage > HttpResponseMessagePtr
Type alias for reference-counted SmartPointer to HttpResponseMessage.
HttpCompletionOption
Indicates if HttpClient operations should be considered completed as soon as headers are read,...
@ ResponseContentRead
Operation should complete after reading the entire response including content.
@ ResponseHeadersRead
Operation should complete as soon as a response is available and headers are read.
SmartPointer< HttpContent > HttpContentPtr
Type alias for reference-counted SmartPointer to HttpContent.
SmartPointer< HttpRequestMessage > HttpRequestMessagePtr
Type alias for reference-counted SmartPointer to HttpRequestMessage.
HttpStatusCode
Contains the values of status codes defined for HTTP in RFC 9110 and RFC 7231.