AWS SDK for C++

AWS SDK for C++ Version 1.11.844

Loading...
Searching...
No Matches
ChunkingInterceptor.h
1
5#pragma once
6#include <aws/core/http/HttpRequest.h>
7#include <aws/core/utils/Array.h>
8#include <aws/core/utils/StringUtils.h>
9#include <aws/core/utils/HashingUtils.h>
10#include <aws/core/utils/logging/LogMacros.h>
11#include <aws/core/utils/memory/stl/AWSStringStream.h>
12#include <aws/core/utils/memory/stl/AWSVector.h>
13#include <smithy/interceptor/Interceptor.h>
14#include <aws/core/client/ClientConfiguration.h>
15#include <aws/core/utils/Outcome.h>
16#include <aws/core/client/AWSError.h>
17#include <memory>
18
19namespace smithy {
20namespace client {
21namespace features {
22
23static const size_t AWS_DATA_BUFFER_SIZE = 65536;
24static const char* ALLOCATION_TAG = "ChunkingInterceptor";
25static const char* CHECKSUM_HEADER_PREFIX = "x-amz-checksum-";
26
27template <size_t DataBufferSize = AWS_DATA_BUFFER_SIZE>
28class AwsChunkedStreamBuf : public std::streambuf {
29public:
31 const std::shared_ptr<Aws::IOStream>& stream,
32 size_t bufferSize = DataBufferSize)
33 : m_request(request),
34 m_stream(stream),
35 m_data(bufferSize)
36 {
37 assert(m_stream != nullptr);
38 if (m_stream == nullptr) {
39 AWS_LOGSTREAM_ERROR("AwsChunkedStream", "stream is null");
40 }
41 assert(m_request != nullptr);
42 if (m_request == nullptr) {
43 AWS_LOGSTREAM_ERROR("AwsChunkedStream", "request is null");
44 }
45 }
46
47protected:
48 int_type underflow() override {
49 if (gptr() && gptr() < egptr()) {
50 return traits_type::to_int_type(*gptr());
51 }
52
53 // only read and write to chunked stream if the underlying stream
54 // is still in a valid state and we have buffer space
55 if (m_stream->good() && m_chunkingBufferPos >= m_chunkingBufferSize) {
56 // Reset buffer for new data only when buffer is consumed
57 m_chunkingBufferPos = 0;
58 m_chunkingBufferSize = 0;
59
60 // Check if we have enough space for worst-case chunk (data + header + footer)
61 size_t maxChunkSize = m_data.GetLength() + 20; // data + hex header + CRLF
62 if (m_chunkingBufferSize + maxChunkSize <= m_chunkingBuffer.GetLength()) {
63 // Try to read in a 64K chunk, if we cant we know the stream is over
64 m_stream->read(m_data.GetUnderlyingData(), m_data.GetLength());
65 size_t bytesRead = static_cast<size_t>(m_stream->gcount());
66 writeChunk(bytesRead);
67
68 // if we've read everything from the stream, we want to add the trailer
69 // to the underlying stream
70 if ((m_stream->peek() == EOF || m_stream->eof()) && !m_stream->bad()) {
71 writeTrailerToUnderlyingStream();
72 }
73 }
74 }
75
76 // if the chunking buffer is empty there is nothing to read
77 if (m_chunkingBufferPos >= m_chunkingBufferSize) {
78 return traits_type::eof();
79 }
80
81 // Set up buffer pointers to read from chunking buffer
82 size_t remainingBytes = m_chunkingBufferSize - m_chunkingBufferPos;
83 size_t bytesToRead = (std::min)(remainingBytes, DataBufferSize);
84
85 setg(m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferPos,
86 m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferPos,
87 m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferPos + bytesToRead);
88
89 m_chunkingBufferPos += bytesToRead;
90
91 return traits_type::to_int_type(*gptr());
92 }
93
94private:
95 void writeTrailerToUnderlyingStream() {
96 Aws::String trailer = "0\r\n";
97 if (m_request->GetRequestHash().second != nullptr) {
98 trailer += "x-amz-checksum-" + m_request->GetRequestHash().first + ":"
99 + Aws::Utils::HashingUtils::Base64Encode(m_request->GetRequestHash().second->GetHash().GetResult()) + "\r\n";
100 }
101 trailer += "\r\n";
102 if (m_chunkingBufferSize + trailer.length() <= m_chunkingBuffer.GetLength()) {
103 std::memcpy(m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferSize, trailer.c_str(), trailer.length());
104 m_chunkingBufferSize += trailer.length();
105 }
106 }
107
108 void writeChunk(size_t bytesRead) {
109 if (m_request->GetRequestHash().second != nullptr) {
110 m_request->GetRequestHash().second->Update(reinterpret_cast<unsigned char*>(m_data.GetUnderlyingData()), bytesRead);
111 }
112
113 if (bytesRead > 0) {
114 Aws::String chunkHeader = Aws::Utils::StringUtils::ToHexString(bytesRead) + "\r\n";
115 size_t totalSize = chunkHeader.length() + bytesRead + 2;
116 if (m_chunkingBufferSize + totalSize <= m_chunkingBuffer.GetLength()) {
117 std::memcpy(m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferSize, chunkHeader.c_str(), chunkHeader.length());
118 m_chunkingBufferSize += chunkHeader.length();
119 std::memcpy(m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferSize, m_data.GetUnderlyingData(), bytesRead);
120 m_chunkingBufferSize += bytesRead;
121 std::memcpy(m_chunkingBuffer.GetUnderlyingData() + m_chunkingBufferSize, "\r\n", 2);
122 m_chunkingBufferSize += 2;
123 }
124 }
125 }
126
127 // Buffer for chunked data plus overhead for HTTP chunked encoding headers, trailers, and safety margin
128 Aws::Utils::Array<char> m_chunkingBuffer{DataBufferSize + 128};
129 size_t m_chunkingBufferSize{0};
130 size_t m_chunkingBufferPos{0};
131 Aws::Http::HttpRequest* m_request{nullptr};
132 std::shared_ptr<Aws::IOStream> m_stream;
134};
135
137public:
139 const std::shared_ptr<Aws::IOStream>& originalBody,
140 size_t bufferSize = AWS_DATA_BUFFER_SIZE)
141 : Aws::IOStream(&m_buf),
142 m_buf(request, originalBody, bufferSize) {}
143
144private:
146};
147
153public:
155 : m_httpClientChunkedMode(httpClientChunkedMode), m_bufferSize(AWS_DATA_BUFFER_SIZE) {}
156 explicit ChunkingInterceptor(Aws::Client::HttpClientChunkedMode httpClientChunkedMode, size_t bufferSize)
157 : m_httpClientChunkedMode(httpClientChunkedMode), m_bufferSize(bufferSize) {}
158 ~ChunkingInterceptor() override = default;
159
161 if (m_bufferSize < 8 * 1024) {
163 "ValidationErrorException",
164 "aws-chunked buffer must be over 8KiB to content encode",
165 false};
166 }
167 auto request = context.GetTransmitRequest();
168
169 if (!ShouldApplyChunking(request, context)) {
170 return request;
171 }
172
173 const auto& hashPair = request->GetRequestHash();
174 if (hashPair.second != nullptr) {
175 Aws::String checksumHeaderValue = Aws::String(CHECKSUM_HEADER_PREFIX) + hashPair.first;
176 request->DeleteHeader(checksumHeaderValue.c_str());
177 request->SetHeaderValue(Aws::Http::AWS_TRAILER_HEADER, checksumHeaderValue);
178 request->SetTransferEncoding(Aws::Http::CHUNKED_VALUE);
179
180 if (!request->HasContentEncoding()) {
181 request->SetContentEncoding(Aws::Http::AWS_CHUNKED_VALUE);
182 } else {
183 Aws::String currentEncoding = request->GetContentEncoding();
184 if (currentEncoding.find(Aws::Http::AWS_CHUNKED_VALUE) == Aws::String::npos) {
185 request->SetContentEncoding(Aws::String{Aws::Http::AWS_CHUNKED_VALUE} + "," + currentEncoding);
186 }
187 }
188
189 if (request->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER)) {
190 request->SetHeaderValue(Aws::Http::DECODED_CONTENT_LENGTH_HEADER, request->GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER));
191 request->DeleteHeader(Aws::Http::CONTENT_LENGTH_HEADER);
192 }
193 }
194
195 return context.GetTransmitRequest();
196 }
197
199 if (m_bufferSize < 8 * 1024) {
201 "ValidationErrorException",
202 "aws-chunked buffer must be over 8KiB to content encode",
203 false};
204 }
205 auto request = context.GetTransmitRequest();
206
207 if (!ShouldApplyChunking(request, context)) {
208 return request;
209 }
210
211 auto originalBody = request->GetContentBody();
212 if (!originalBody) {
213 return request;
214 }
215
216 auto chunkedBody = Aws::MakeShared<AwsChunkedIOStream>(ALLOCATION_TAG, request.get(), originalBody, m_bufferSize);
217
218 request->AddContentBody(chunkedBody);
219 return request;
220 }
221
223 return context.GetTransmitResponse();
224 }
225
226private:
227 bool ShouldApplyChunking(const std::shared_ptr<Aws::Http::HttpRequest>& request,
228 const smithy::interceptor::InterceptorContext& context) const {
229 //TODO: remove this once we figure out why MRAP cannot trail chunk
230 const auto signerName = context.GetAttribute("signer_name");
231 if (signerName.has_value() && signerName.value() == "AsymmetricSignatureV4") {
232 return false;
233 }
234 // Use configuration setting to determine chunking behavior
235 if (m_httpClientChunkedMode != Aws::Client::HttpClientChunkedMode::DEFAULT) {
236 return false;
237 }
238
239 if (!request || !request->GetContentBody()) {
240 return false;
241 }
242
243 // Check if request has checksum requirements
244 const auto& hashPair = request->GetRequestHash();
245 return hashPair.second != nullptr;
246 }
247
248 Aws::Client::HttpClientChunkedMode m_httpClientChunkedMode;
249 size_t m_bufferSize;
250};
251
252} // namespace features
253} // namespace client
254} // namespace smithy
const std::pair< Aws::String, std::shared_ptr< Aws::Utils::Crypto::Hash > > & GetRequestHash() const
T * GetUnderlyingData() const
Definition Array.h:232
size_t GetLength() const
Definition Array.h:222
static Aws::String Base64Encode(const ByteBuffer &byteBuffer)
static Aws::String ToHexString(T value)
AwsChunkedIOStream(Aws::Http::HttpRequest *request, const std::shared_ptr< Aws::IOStream > &originalBody, size_t bufferSize=AWS_DATA_BUFFER_SIZE)
AwsChunkedStreamBuf(Aws::Http::HttpRequest *request, const std::shared_ptr< Aws::IOStream > &stream, size_t bufferSize=DataBufferSize)
ModifyRequestOutcome ModifyBeforeTransmit(smithy::interceptor::InterceptorContext &context) override
ModifyResponseOutcome ModifyBeforeDeserialization(smithy::interceptor::InterceptorContext &context) override
ChunkingInterceptor(Aws::Client::HttpClientChunkedMode httpClientChunkedMode, size_t bufferSize)
ModifyRequestOutcome ModifyBeforeSigning(smithy::interceptor::InterceptorContext &context) override
ChunkingInterceptor(Aws::Client::HttpClientChunkedMode httpClientChunkedMode)
Aws::Crt::Optional< Aws::String > GetAttribute(const Aws::String &key) const
std::shared_ptr< Aws::Http::HttpRequest > GetTransmitRequest() const
std::shared_ptr< Aws::Http::HttpResponse > GetTransmitResponse() const
AWS_CORE_API const char CHUNKED_VALUE[]
AWS_CORE_API const char DECODED_CONTENT_LENGTH_HEADER[]
AWS_CORE_API const char CONTENT_LENGTH_HEADER[]
AWS_CORE_API const char AWS_CHUNKED_VALUE[]
AWS_CORE_API const char AWS_TRAILER_HEADER[]
std::basic_iostream< char, std::char_traits< char > > IOStream
std::basic_string< char, std::char_traits< char >, Aws::Allocator< char > > String
Definition AWSString.h:97
static const char * ALLOCATION_TAG
static const char * CHECKSUM_HEADER_PREFIX
static const size_t AWS_DATA_BUFFER_SIZE