Introduce Multipart InMemoryReader

This commit is contained in:
lganzzzo 2019-07-17 04:40:42 +03:00
parent a032358614
commit cc4f27d41e
16 changed files with 570 additions and 156 deletions

View File

@ -141,6 +141,10 @@ add_library(oatpp
oatpp/web/client/HttpRequestExecutor.hpp
oatpp/web/client/RequestExecutor.cpp
oatpp/web/client/RequestExecutor.hpp
oatpp/web/mime/multipart/InMemoryReader.cpp
oatpp/web/mime/multipart/InMemoryReader.hpp
oatpp/web/mime/multipart/Multipart.cpp
oatpp/web/mime/multipart/Multipart.hpp
oatpp/web/mime/multipart/Part.cpp
oatpp/web/mime/multipart/Part.hpp
oatpp/web/mime/multipart/StatefulParser.cpp

View File

@ -267,7 +267,7 @@ info->body.type = oatpp::data::mapping::type::__class::String::getType();
#define OATPP_MACRO_API_CONTROLLER_BODY_DTO(TYPE, NAME, PARAM_LIST) \
TYPE NAME; \
__request->readBodyToDto(NAME, getDefaultObjectMapper()); \
__request->readBodyToDto(NAME, getDefaultObjectMapper().get()); \
if(!NAME) { \
return ApiController::handleError(Status::CODE_400, "Missing valid body parameter '" #NAME "'"); \
}

View File

@ -90,6 +90,19 @@ data::v_io_size ConsistentOutputStream::writeAsString(bool value) {
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AsyncWriteCallbackWithCoroutineStarter
oatpp::async::Action AsyncWriteCallbackWithCoroutineStarter::writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine,
const void*& currBufferPtr,
data::v_io_size& bytesLeft,
oatpp::async::Action&& nextAction)
{
auto coroutineStarter = writeAsync(currBufferPtr, bytesLeft);
currBufferPtr = &((p_char8) currBufferPtr)[bytesLeft];
bytesLeft = 0;
return coroutineStarter.next(std::forward<async::Action>(nextAction));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DefaultWriteCallback

View File

@ -335,6 +335,40 @@ public:
oatpp::async::Action&& nextAction) = 0;
};
/**
* Convenience callback to use coroutine starter instead of async inline method.
*/
class AsyncWriteCallbackWithCoroutineStarter : public AsyncWriteCallback {
public:
/**
* Async-Inline write callback. <br>
* Calls &l:AsyncWriteCallbackWithCoroutineStarter::writeAsync (); internally.
* @param coroutine - caller coroutine.
* @param currBufferPtr - pointer to current data position.
* @param bytesLeft - how much bytes left to write.
* @param nextAction - next action when write finished.
* @return - &id:oatpp::async::Action;.
*/
oatpp::async::Action writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine,
const void*& currBufferPtr,
data::v_io_size& bytesLeft,
oatpp::async::Action&& nextAction) override;
public:
/**
* Implement this method! <br>
* Write Callback. Data should be fully utilized on call to this method - no partial writes for this method. <br>
* Return Coroutine starter to start data-processing coroutine or, `nullptr` to do nothing.
* @param data - pointer to data.
* @param count - data size.
* @return - data processing Coroutine-Starter. &id:oatpp::async::CoroutineStarter;.
*/
virtual async::CoroutineStarter writeAsync(const void *data, data::v_io_size count) = 0;
};
/**
* Default callback for stream write operation. <br>
* Uses &l:writeExactSizeData (); method underhood.

View File

@ -0,0 +1,85 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#include "InMemoryReader.hpp"
#include "oatpp/core/data/stream/BufferInputStream.hpp"
namespace oatpp { namespace web { namespace mime { namespace multipart {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InMemoryParser
InMemoryParser::InMemoryParser(Multipart* multipart)
: m_multipart(multipart)
{}
void InMemoryParser::onPartHeaders(const Headers& partHeaders) {
m_currPart = std::make_shared<Part>(partHeaders);
}
void InMemoryParser::onPartData(p_char8 data, oatpp::data::v_io_size size) {
if(size > 0) {
m_buffer.write(data, size);
} else {
auto fullData = m_buffer.toString();
m_buffer.clear();
auto stream = std::make_shared<data::stream::BufferInputStream>(fullData.getPtr(), fullData->getData(), fullData->getSize());
m_currPart->setDataInfo(stream, fullData, fullData->getSize());
m_multipart->addPart(m_currPart);
m_currPart = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InMemoryReader
InMemoryReader::InMemoryReader(Multipart* multipart)
: m_parser(multipart->getBoundary(), std::make_shared<InMemoryParser>(multipart))
{}
data::v_io_size InMemoryReader::write(const void *data, data::v_io_size count) {
m_parser.parseNext((p_char8) data, count);
return count;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AsyncInMemoryReader
AsyncInMemoryReader::AsyncInMemoryReader(Multipart* multipart)
: m_parser(multipart->getBoundary(), std::make_shared<InMemoryParser>(multipart))
{}
oatpp::async::Action AsyncInMemoryReader::writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine,
const void*& currBufferPtr,
data::v_io_size& bytesLeft,
oatpp::async::Action&& nextAction)
{
m_parser.parseNext((p_char8) currBufferPtr, bytesLeft);
currBufferPtr = &((p_char8) currBufferPtr)[bytesLeft];
bytesLeft = 0;
return std::forward<async::Action>(nextAction);
}
}}}}

View File

@ -0,0 +1,98 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#ifndef oatpp_web_mime_multipart_InMemoryReader_hpp
#define oatpp_web_mime_multipart_InMemoryReader_hpp
#include "Multipart.hpp"
#include "StatefulParser.hpp"
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
namespace oatpp { namespace web { namespace mime { namespace multipart {
/**
* In memory multipart parser. <br>
* Extends - &id:oatpp::web::mime::multipart::StatefulParser::Listener;.
*/
class InMemoryParser : public StatefulParser::Listener {
private:
Multipart* m_multipart;
std::shared_ptr<Part> m_currPart;
data::stream::ChunkedBuffer m_buffer;
public:
/**
* Constructor.
* @param multipart - pointer to &id:oatpp::web::mime::multipart::Multipart;.
*/
InMemoryParser(Multipart* multipart);
void onPartHeaders(const Headers& partHeaders) override;
void onPartData(p_char8 data, oatpp::data::v_io_size size) override;
};
/**
* In memory Multipart reader.
* Extends - &id:oatpp::data::stream::WriteCallback;.
*/
class InMemoryReader : public oatpp::data::stream::WriteCallback {
private:
StatefulParser m_parser;
public:
/**
* Constructor.
* @param multipart - Multipart object to save read data to.
*/
InMemoryReader(Multipart* multipart);
data::v_io_size write(const void *data, data::v_io_size count) override;
};
/**
* In memory Multipart reader.
* Extends - &id:oatpp::data::stream::AsyncWriteCallback;.
*/
class AsyncInMemoryReader : public oatpp::data::stream::AsyncWriteCallback {
private:
StatefulParser m_parser;
public:
/**
* Constructor.
* @param multipart - Multipart object to save read data to.
*/
AsyncInMemoryReader(Multipart* multipart);
oatpp::async::Action writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine,
const void*& currBufferPtr,
data::v_io_size& bytesLeft,
oatpp::async::Action&& nextAction) override;
};
}}}}
#endif //oatpp_web_mime_multipart_InMemoryReader_hpp

View File

@ -0,0 +1,94 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#include "Multipart.hpp"
#include "oatpp/web/protocol/http/Http.hpp"
namespace oatpp { namespace web { namespace mime { namespace multipart {
Multipart::Multipart(const oatpp::String& boundary)
: m_boundary(boundary)
{}
Multipart::Multipart(const Headers& requestHeaders){
auto it = requestHeaders.find("Content-Type");
if(it != requestHeaders.end()) {
oatpp::web::protocol::http::HeaderValueData valueData;
oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, it->second, ';');
m_boundary = valueData.getTitleParamValue("boundary");
if(!m_boundary) {
throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::Multipart()]: Error. Boundary not defined.");
}
} else {
throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::Multipart()]: Error. 'Content-Type' header is missing.");
}
}
oatpp::String Multipart::getBoundary() {
return m_boundary;
}
void Multipart::addPart(const std::shared_ptr<Part>& part) {
if(part->getName()) {
auto it = m_namedParts.find(part->getName());
if(it != m_namedParts.end()) {
throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::addPart()]: Error. Part with such name already exists.");
}
m_namedParts.insert({part->getName(), part});
}
m_parts.push_back(part);
}
std::shared_ptr<Part> Multipart::getNamedPart(const oatpp::String& name) {
auto it = m_namedParts.find(name);
if(it != m_namedParts.end()) {
return it->second;
}
return nullptr;
}
const std::list<std::shared_ptr<Part>>& Multipart::getAllParts() {
return m_parts;
}
v_int32 Multipart::count() {
return m_parts.size();
}
}}}}

View File

@ -0,0 +1,104 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#ifndef oatpp_web_mime_multipart_Multipart_hpp
#define oatpp_web_mime_multipart_Multipart_hpp
#include "Part.hpp"
#include <unordered_map>
#include <list>
namespace oatpp { namespace web { namespace mime { namespace multipart {
/**
* Typedef for headers map. Headers map key is case-insensitive.
* `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;.
*/
typedef std::unordered_map<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
/**
* Structure that holds parts of Multipart.
*/
class Multipart {
private:
oatpp::String m_boundary;
std::unordered_map<oatpp::String, std::shared_ptr<Part>> m_namedParts;
std::list<std::shared_ptr<Part>> m_parts;
public:
/**
* Constructor.
* @param boundary - multipart boundary value.
*/
Multipart(const oatpp::String& boundary);
/**
* Constructor.
* @param requestHeaders - request headers. Headers must contain "Content-Type" header.
*/
Multipart(const Headers& requestHeaders);
/**
* Default virtual Destructor.
*/
virtual ~Multipart() = default;
/**
* Get multipart boundary value.
* @return - multipart boundary value.
*/
oatpp::String getBoundary();
/**
* Add part to Multipart.
* @param part - &id:oatpp::web::mime::multipart::Part;.
*/
void addPart(const std::shared_ptr<Part>& part);
/**
* Get part by name <br>
* Applicable to named parts only.
* @param name - &id:oatpp::String;.
* @return - &id:oatpp::web::mime::multipart::Part;.
*/
std::shared_ptr<Part> getNamedPart(const oatpp::String& name);
/**
* Get list of all parts.
* @return - `std::list` of `std::shared_ptr` to &id:oatpp::web::mime::multipart::Part;.
*/
const std::list<std::shared_ptr<Part>>& getAllParts();
/**
* Get parts count.
* @return - parts count.
*/
v_int32 count();
};
}}}}
#endif // oatpp_web_mime_multipart_Multipart_hpp

View File

@ -24,46 +24,13 @@
#include "Part.hpp"
#include "oatpp/web/protocol/http/Http.hpp"
#include "oatpp/core/parser/Caret.hpp"
#include <cstring>
namespace oatpp { namespace web { namespace mime { namespace multipart {
oatpp::String Part::parseContentDispositionValue(const char* key, p_char8 data, v_int32 size) {
parser::Caret caret(data, size);
if(caret.findText(key)) {
caret.inc(std::strlen(key));
parser::Caret::Label label(nullptr);
if(caret.isAtChar('"')) {
label = caret.parseStringEnclosed('"', '"', '\\');
} else if(caret.isAtChar('\'')) {
label = caret.parseStringEnclosed('\'', '\'', '\\');
} else {
label = caret.putLabel();
caret.findCharFromSet(" \t\n\r\f");
label.end();
}
if(label) {
return label.toString();
} else {
throw std::runtime_error("[oatpp::web::mime::multipart::Part::parseContentDispositionValue()]: Error. Can't parse value.");
}
}
return nullptr;
}
Part::Part(const Headers &headers,
const std::shared_ptr<data::stream::InputStream> &inputStream,
const oatpp::String inMemoryData,
@ -77,16 +44,12 @@ Part::Part(const Headers &headers,
auto it = m_headers.find("Content-Disposition");
if(it != m_headers.end()) {
m_name = parseContentDispositionValue("name=", it->second.getData(), it->second.getSize());
oatpp::web::protocol::http::HeaderValueData valueData;
oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, it->second, ';');
if(!m_name) {
throw std::runtime_error("[oatpp::web::mime::multipart::Part::Part()]: Error. Part name is missing in 'Content-Disposition' header.");
}
m_name = valueData.getTitleParamValue("name");
m_filename = valueData.getTitleParamValue("filename");
m_filename = parseContentDispositionValue("filename=", it->second.getData(), it->second.getSize());
} else {
throw std::runtime_error("[oatpp::web::mime::multipart::Part::Part()]: Error. Missing 'Content-Disposition' header.");
}
}

View File

@ -42,11 +42,6 @@ public:
* `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;.
*/
typedef std::unordered_map<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
private:
/**
* Parse value from the `Content-Disposition` header.
*/
static oatpp::String parseContentDispositionValue(const char* key, p_char8 data, v_int32 size);
private:
oatpp::String m_name;
oatpp::String m_filename;
@ -97,7 +92,7 @@ public:
oatpp::String getFilename() const;
/**
* Get request's headers map
* Get request's headers map.
* @return Headers map
*/
const Headers& getHeaders() const;

View File

@ -27,42 +27,12 @@
#include "oatpp/web/protocol/http/Http.hpp"
#include "oatpp/core/parser/Caret.hpp"
#include "oatpp/core/parser/ParsingError.hpp"
namespace oatpp { namespace web { namespace mime { namespace multipart {
oatpp::String StatefulParser::parsePartName(p_char8 data, v_int32 size) {
parser::Caret caret(data, size);
if(caret.findText((p_char8)"name=", 5)) {
caret.inc(5);
parser::Caret::Label nameLabel(nullptr);
if(caret.isAtChar('"')) {
nameLabel = caret.parseStringEnclosed('"', '"', '\\');
} else if(caret.isAtChar('\'')) {
nameLabel = caret.parseStringEnclosed('\'', '\'', '\\');
} else {
nameLabel = caret.putLabel();
caret.findCharFromSet(" \t\n\r\f");
nameLabel.end();
}
if(nameLabel) {
return nameLabel.toString();
} else {
throw std::runtime_error("[oatpp::web::mime::multipart::StatefulParser::parsePartName()]: Error. Can't parse part name.");
}
} else {
throw std::runtime_error("[oatpp::web::mime::multipart::StatefulParser::parsePartName()]: Error. Part name is missing.");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StatefulParser
StatefulParser::StatefulParser(const oatpp::String& boundary, const std::shared_ptr<Listener>& listener)
: m_state(STATE_BOUNDARY)
@ -85,10 +55,8 @@ void StatefulParser::onPartHeaders(const Headers& partHeaders) {
auto it = partHeaders.find("Content-Disposition");
if(it != partHeaders.end()) {
m_currPartName = parsePartName(it->second.getData(), it->second.getSize());
if(m_listener) {
m_listener->onPartHeaders(m_currPartName, partHeaders);
m_listener->onPartHeaders(partHeaders);
}
} else {
@ -100,7 +68,7 @@ void StatefulParser::onPartHeaders(const Headers& partHeaders) {
void StatefulParser::onPartData(p_char8 data, v_int32 size) {
if(m_listener) {
m_listener->onPartData(m_currPartName, data, size);
m_listener->onPartData(data, size);
}
}

View File

@ -52,11 +52,6 @@ private:
* `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;.
*/
typedef std::unordered_map<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
private:
/**
* Parse name of the part from `Content-Disposition` header.
*/
static oatpp::String parsePartName(p_char8 data, v_int32 size);
public:
/**
@ -71,23 +66,26 @@ public:
typedef std::unordered_map<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
public:
/**
* Default virtual Destructor.
*/
virtual ~Listener() = default;
/**
* Called on new part found in the stream.
* Always called before `onPartData` events.
* @param name - name of the part.
* @param partHeaders - complete set of part headers.
*/
virtual void onPartHeaders(const oatpp::String& name, const Headers& partHeaders) = 0;
virtual void onPartHeaders(const Headers& partHeaders) = 0;
/**
* Called on each new chunk of bytes parsed from the part body.
* When all data of message is read, readMessage is called again with size == 0 to
* indicate end of the message.
* @param name - name of the part.
* indicate end of the part.
* @param data - pointer to data.
* @param size - size of the data in bytes.
*/
virtual void onPartData(const oatpp::String& name, p_char8 data, oatpp::data::v_io_size size) = 0;
virtual void onPartData(p_char8 data, oatpp::data::v_io_size size) = 0;
};
@ -104,7 +102,6 @@ private:
oatpp::String m_firstBoundarySample;
oatpp::String m_nextBoundarySample;
oatpp::String m_currPartName;
/*
* Headers of the part are stored in the buffer and are parsed as one chunk.

View File

@ -235,6 +235,15 @@ ContentRange ContentRange::parse(const oatpp::String& str) {
oatpp::parser::Caret caret(str);
return parse(caret);
}
oatpp::String HeaderValueData::getTitleParamValue(const data::share::StringKeyLabelCI& key) const {
auto it = titleParams.find(key);
if(it != titleParams.end()) {
return it->second.toString();
}
return nullptr;
}
oatpp::data::share::StringKeyLabelCI_FAST Parser::parseHeaderNameLabel(const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
oatpp::parser::Caret& caret) {
@ -351,20 +360,47 @@ void Parser::parseHeaders(Headers& headers,
}
std::unordered_set<oatpp::data::share::StringKeyLabelCI> Parser::parseHeaderValueSet(const oatpp::data::share::StringKeyLabel& headerValue, char separator) {
void Parser::parseHeaderValueData(HeaderValueData& data, const oatpp::data::share::StringKeyLabel& headerValue, v_char8 separator) {
std::unordered_set<oatpp::data::share::StringKeyLabelCI> result;
oatpp::parser::Caret caret(headerValue.getData(), headerValue.getSize());
while (caret.canContinue()) {
caret.skipChar(' '); // skip leading space
auto label = caret.putLabel();
caret.findChar(separator); // find separator char, or end of the header value
result.insert(oatpp::data::share::StringKeyLabelCI(headerValue.getMemoryHandle(), label.getData(), label.getSize()));
caret.inc();
}
v_char8 charSet[5] = {' ', '=', separator, '\r', '\n'};
v_char8 charSet2[4] = {' ', separator, '\r', '\n'};
return result;
while(caret.canContinue()) {
caret.skipChar(' ');
auto label = caret.putLabel();
auto res = caret.findCharFromSet(charSet, 5);
if(res == '=') {
data::share::StringKeyLabelCI key(headerValue.getMemoryHandle(), label.getData(), label.getSize());
caret.inc();
if(caret.isAtChar('"')) {
label = caret.parseStringEnclosed('"', '"', '\\');
} else if(caret.isAtChar('\'')) {
label = caret.parseStringEnclosed('\'', '\'', '\\');
} else {
label = caret.putLabel();
auto r = caret.findCharFromSet(charSet2, 4);
}
data.titleParams[key] = data::share::StringKeyLabel(headerValue.getMemoryHandle(), label.getData(), label.getSize());
} else {
data.tokens.insert(data::share::StringKeyLabelCI(headerValue.getMemoryHandle(), label.getData(), label.getSize()));
}
if(caret.isAtCharFromSet((p_char8)"\r\n", 2)) {
break;
} else if(caret.isAtChar(separator)) {
caret.inc();
}
}
}

View File

@ -578,6 +578,30 @@ struct ResponseStartingLine {
oatpp::data::share::StringKeyLabel description;
};
/**
* Data contained in the value of one header.
*/
struct HeaderValueData {
/**
* value tokens.
*/
std::unordered_set<data::share::StringKeyLabelCI> tokens;
/**
* Title params.
*/
std::unordered_map<data::share::StringKeyLabelCI, data::share::StringKeyLabel> titleParams;
/**
* Get title parm value by key.
* @param key
* @return
*/
oatpp::String getTitleParamValue(const data::share::StringKeyLabelCI& key) const;
};
/**
* Typedef for headers map. Headers map key is case-insensitive.
* `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;.
@ -653,13 +677,13 @@ public:
Status& error);
/**
* Parse header value separated by `char separator`.
* @param headerValue - value of the header.
* @param separator - separator char.
* @return - `std::unordered_set` of &id:oatpp::data::share::StringKeyLabelCI;.
* Parse data that is contained in a one header.
* @param data - out. parsed data.
* @param headerValue - header value string.
* @param separator - subvalues separator.
*/
static std::unordered_set<oatpp::data::share::StringKeyLabelCI> parseHeaderValueSet(const oatpp::data::share::StringKeyLabel& headerValue, char separator);
static void parseHeaderValueData(HeaderValueData& data, const oatpp::data::share::StringKeyLabel& headerValue, v_char8 separator);
};
}}}}

View File

@ -26,6 +26,9 @@
#define oatpp_test_web_app_Controller_hpp
#include "./DTOs.hpp"
#include "oatpp/web/mime/multipart/InMemoryReader.hpp"
#include "oatpp/web/server/api/ApiController.hpp"
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
#include "oatpp/core/utils/ConversionUtils.hpp"
@ -97,6 +100,14 @@ public:
return createDtoResponse(Status::CODE_200, dto);
}
ENDPOINT("POST", "body-dto", postBodyDto,
BODY_DTO(TestDto::ObjectWrapper, body)) {
//OATPP_LOGV(TAG, "POST body %s", body->c_str());
auto dto = TestDto::createShared();
dto->testValue = body->testValue;
return createDtoResponse(Status::CODE_200, dto);
}
ENDPOINT("POST", "echo", echo,
BODY_STRING(String, body)) {
//OATPP_LOGV(TAG, "POST body(echo) size=%d", body->getSize());
@ -105,13 +116,30 @@ public:
ENDPOINT("GET", "header-value-set", headerValueSet,
HEADER(String, valueSet, "X-VALUE-SET")) {
auto set = oatpp::web::protocol::http::Parser::parseHeaderValueSet(valueSet, ',');
OATPP_ASSERT_HTTP(set.find("VALUE_1") != set.end(), Status::CODE_400, "No header 'VALUE_1' in value set");
OATPP_ASSERT_HTTP(set.find("VALUE_2") != set.end(), Status::CODE_400, "No header 'VALUE_2' in value set");
OATPP_ASSERT_HTTP(set.find("VALUE_3") != set.end(), Status::CODE_400, "No header 'VALUE_3' in value set");
oatpp::web::protocol::http::HeaderValueData valueData;
oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, valueSet, ',');
OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_1") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_1' in value set");
OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_2") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_2' in value set");
OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_3") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_3' in value set");
return createResponse(Status::CODE_200, "");
}
ENDPOINT("POST", "test/multipart", multipartTest, REQUEST(std::shared_ptr<IncomingRequest>, request)) {
oatpp::web::mime::multipart::Multipart multipart(request->getHeaders());
oatpp::web::mime::multipart::InMemoryReader multipartReader(&multipart);
request->transferBody(&multipartReader);
for(auto& part : multipart.getAllParts()) {
OATPP_LOGD("multipart", "name='%s', value='%s'", part->getName()->getData(), part->getInMemoryData()->getData());
}
return createResponse(Status::CODE_200, "");
}
#include OATPP_CODEGEN_END(ApiController)
};

View File

@ -24,8 +24,7 @@
#include "StatefulParserTest.hpp"
#include "oatpp/web/mime/multipart/Part.hpp"
#include "oatpp/web/mime/multipart/StatefulParser.hpp"
#include "oatpp/web/mime/multipart/InMemoryReader.hpp"
#include "oatpp/core/data/stream/BufferInputStream.hpp"
@ -54,38 +53,10 @@ namespace {
"--12345--\r\n"
;
class Listener : public oatpp::web::mime::multipart::StatefulParser::Listener {
private:
oatpp::data::stream::ChunkedBuffer m_buffer;
public:
std::unordered_map<oatpp::String, std::shared_ptr<Part>> parts;
void onPartHeaders(const oatpp::String& name, const Headers& partHeaders) override {
auto part = std::make_shared<Part>(partHeaders);
parts.insert({name, part});
}
void onPartData(const oatpp::String& name, p_char8 data, oatpp::data::v_io_size size) override {
if(size > 0) {
m_buffer.write(data, size);
} else {
auto data = m_buffer.toString();
m_buffer.clear();
auto& part = parts[name];
OATPP_ASSERT(part);
auto stream = std::make_shared<oatpp::data::stream::BufferInputStream>(data.getPtr(), data->getData(), data->getSize());
part->setDataInfo(stream, data, data->getSize());
}
}
};
void parseStepByStep(const oatpp::String& text,
const oatpp::String& boundary,
const std::shared_ptr<Listener>& listener,
const std::shared_ptr<oatpp::web::mime::multipart::StatefulParser::Listener>& listener,
v_int32 step)
{
@ -128,20 +99,20 @@ void StatefulParserTest::onRun() {
for(v_int32 i = 1; i < text->getSize(); i++) {
auto listener = std::make_shared<Listener>();
oatpp::web::mime::multipart::Multipart multipart("12345");
auto listener = std::make_shared<oatpp::web::mime::multipart::InMemoryParser>(&multipart);
parseStepByStep(text, "12345", listener, i);
auto& parts = listener->parts;
if(parts.size() != 3) {
if(multipart.count() != 3) {
OATPP_LOGD(TAG, "TEST_DATA_1 itearation %d", i);
}
OATPP_ASSERT(parts.size() == 3);
OATPP_ASSERT(multipart.count() == 3);
auto part1 = parts["part1"];
auto part2 = parts["part2"];
auto part3 = parts["part3"];
auto part1 = multipart.getNamedPart("part1");
auto part2 = multipart.getNamedPart("part2");
auto part3 = multipart.getNamedPart("part3");
OATPP_ASSERT(part1);
OATPP_ASSERT(part2);