Oat++ 1.3.0
Previous release – 1.2.5
Feel free to ask questions – Chat on Gitter!
Contents:
- The New oatpp::String
- ConnectionPool::get() Timeout
- JSON Serializer Escape Flags
- Headers Stored In unordered_multimap
- QueryParameters Stored In unordered_multimap
- Polymorphic DTO_FIELD
- ConnectionMonitor
- Request Data Bundle
- ConnectionProviderSwitch
- Proper Server Stoppage
- TemporaryFile
- Better Multipart
- Response::getBody()
- data::stream::FIFOStream
- data::stream::BufferedInputStream
- oatpp::parser::json::mapping::Serializer::Config::alwaysIncludeRequired
The New oatpp::String
Now it’s much easier to use oatpp::String
since oatpp::String
is now wrapper over std::string
解释
{ std::string s1 = Hello; oatpp::String s2 = s1; } { oatpp::String s1 = "Hello"; std::string s2 = *s1; // *s1 returns a reference to the internal std::string object } { oatpp::String s1 = "Hello"; std::string s2 = s1; // implicit cast } { oatpp::String s1 = nullptr; std::string s2 = s1; // implicit cast from null-value throws runtime_error } { oatpp::String s1 = "Hello"; bool b = s1 == "Hello"; // compare s1 with const char* assert(b); } { oatpp::String s1 = "Hello"; std::stringg s2 = "Hello"; bool b = s1 == s2; // compare s1 with std::string assert(b); } { oatpp::String s1 = "Hello"; std::string s2 = "World"; oatpp::String s3 = s1 + " " + s2; // concat oatpp::String with const char* and std::string directly OATPP_LOGD("TEST", "str='%s'", s3->c_str()) // prints 'Hello World' } { oatpp::String s1 = nullptr; oatpp::String s2 = "hello"; OATPP_ASSERT(s1.getValue("default") == "default") OATPP_ASSERT(s2.getValue("default") == "hello") }
ConnectionPool::get() Timeout
解释
{ auto connectionProvider = oatpp::network::tcp::client::ConnectionProvider::createShared({"httpbin.org", 80}); auto pool = oatpp::network::ClientConnectionPool::createShared(connectionProvider, 1, std::chrono::seconds(10), std::chrono::seconds(5)); OATPP_LOGD("TEST", "start") auto c1 = pool->get(); //<--- this one will succeed OATPP_LOGD("TEST", "c1=%llu", c1.get()) auto c2 = pool->get(); //<--- this one will fail in 5 sec. Since Max-Resources is 1, Pool timeout is 5 sec. And c1 is not freed. OATPP_LOGD("TEST", "c2=%llu", c2.get()) }
Output:
D |2021-08-04 01:32:56 1628029976986744| TEST:start
D |2021-08-04 01:32:57 1628029977126940| TEST:c1=140716915331208
D |2021-08-04 01:33:02 1628029982128324| TEST:c2=0
JSON Serializer Escape Flags
Now you can control if solidus is escaped or not.
Default Behavior
解释
oatpp::parser::json::mapping::ObjectMapper mapper; // mapper.getSerializer()->getConfig()->escapeFlags = 0; // by default FLAG_ESCAPE_SOLIDUS is ON auto res = mapper.writeToString(oatpp::String("https://oatpp.io/")); OATPP_LOGD("TEST", "res='%s'", res->c_str())
Output:
res='"https:\/\/oatpp.io\/"' # by default, solidus is escaped
Clear Escape Flags
解释
oatpp::parser::json::mapping::ObjectMapper mapper; mapper.getSerializer()->getConfig()->escapeFlags = 0; auto res = mapper.writeToString(oatpp::String("https://oatpp.io/")); OATPP_LOGD("TEST", "res='%s'", res->c_str())
Output:
res='"https://oatpp.io/"' # solidus isn't escaped
Headers Stored In unordered_multimap
Headers are now stored using std::unordered_multimap.
Put multiple headers:
解释
auto response = createResponse(Status::CODE_200, ""); response->putHeader("Set-Cookie", "..."); response->putHeader("Set-Cookie", "..."); return response;
Log all “Set-Cookie” headers:
解释
const auto& map = headers.getAll(); auto bucket = map.bucket("Set-Cookie"); auto bucketBegin = map.begin(bucket); auto bucketEnd = map.end(bucket); for(auto it = bucketBegin; it != bucketEnd; it ++) { oatpp::String value = it->second.toString(); OATPP_LOGD("Header", "Set-Cookie: %s", value->c_str()) }
QueryParameters Stored In unordered_multimap
QueryParameters are now stored using std::unordered_multimap.
Log all entries of “userId” query parameter:
解释
const auto& map = request->getQueryParameters().getAll(); auto bucket = map.bucket("userId"); auto bucketBegin = map.begin(bucket); auto bucketEnd = map.end(bucket); for(auto it = bucketBegin; it != bucketEnd; it ++) { oatpp::String value = it->second.toString(); OATPP_LOGD("QueryParameter", "userId: %s", value->c_str()) }
Polymorphic DTO_FIELD
Now, when used inside of a DTO, we can specify exact types that oatpp::Any
can store by specifying DTO_FIELD_TYPE_SELECTOR
:
解释
/* Possible type of a DTO_FIELD */ class ClassA : public oatpp::DTO { DTO_INIT(ClassA, DTO) DTO_FIELD(String, value); }; /* Possible type of a DTO_FIELD */ class ClassB : public oatpp::DTO { DTO_INIT(ClassB, DTO) DTO_FIELD(Vector<String>, values); }; /* enum of possible DTO_FIELD types */ ENUM(ClassType, v_int32, VALUE(CLASS_TYPE_A, 0), VALUE(CLASS_TYPE_B, 1) ) /* our DTO */ class ResponseDto : public oatpp::DTO { DTO_INIT(ResponseDto, DTO) /* type control field */ DTO_FIELD(Enum<ClassType>::AsString, payloadType); /* polymorphic field */ DTO_FIELD(Any, payload); /* type selector */ DTO_FIELD_TYPE_SELECTOR(payload) { if(!payloadType) return Void::Class::getType(); switch (*payloadType) { case ClassType::CLASS_TYPE_A: return Object<ClassA>::Class::getType(); case ClassType::CLASS_TYPE_B: return Object<ClassB>::Class::getType(); } } }; ... /* send polymorphic payload to client */ ENDPOINT("GET", "payload", getPayload) { auto payload = ClassB::createShared(); payload->values = {"value1", "value2", "value3"}; auto r = ResponseDto::createShared(); r->payloadType = ClassType::CLASS_TYPE_B; r->payload = payload; return createDtoResponse(Status::CODE_200, r); } /* receive polymorphic payload from client */ ENDPOINT("POST", "payload", postPayload, BODY_DTO(oatpp::Object<ResponseDto>, r)) { /* check type-control field and retrieve value of the corresponding type */ if(r->payloadType == ClassType::CLASS_TYPE_B) { auto payload = r->payload.retrieve<oatpp::Object<ClassB>>(); for(auto& value : *payload->values) { OATPP_LOGD("VALUE", "%s", value->c_str()) } } return createResponse(Status::CODE_200, "OK"); }
ConnectionMonitor
oatpp::network::monitor::ConnectionMonitor
is a middleman who’s able to monitor provided connections and close those ones that not satisfy selected rules.
解释
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] { auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({"0.0.0.0", 8000, oatpp::network::Address::IP_4}); auto monitor = std::make_shared<oatpp::network::monitor::ConnectionMonitor>(connectionProvider); /* close all connections that stay opened for more than 120 seconds */ monitor->addMetricsChecker( std::make_shared<oatpp::network::monitor::ConnectionMaxAgeChecker>( std::chrono::seconds(120) ) ); /* close all connections that have had no successful reads and writes for longer than 5 seconds */ monitor->addMetricsChecker( std::make_shared<oatpp::network::monitor::ConnectionInactivityChecker>( std::chrono::seconds(5), std::chrono::seconds(5), ) ); return monitor; }());
Note: ConnectionMonitor
also works with ClientConnectionProvider
as well.
Request Data Bundle
Now there is a data bundle associated with the Request and the Response which makes it easy to pass data through middleware interceptors and endpoints.
Example:
解释
class MyAuthInterceptor : public oatpp::web::server::interceptor::RequestInterceptor { public: std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request) override { /* authorize request and get auth data */ oatpp::Object<AuthDto> authData = authorize(request); if(!authData) { return OutgoingResponse::createShared(Status::CODE_401, nullptr); } /* put auth data to bundle for later use at an endpoint */ request->putBundleData("auth", authData); return nullptr; // continue processing } }; ... ENDPOINT("GET", "videos/{videoId}", getVideoById, PATH(String, videoId), BUNDLE(oatpp::Object<AuthDto>, authData, "auth")) { ... }
ConnectionProviderSwitch
oatpp::network::ConnectionProviderSwitch
can be used to change connection providers on the go, ex.: when you want to reload an SSL certificate without stopping the server.
解释
/* create server connection provider component */ /* use ConnectionProviderSwitch instead of a regular ServerConnectionProvider */ OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionProviderSwitch>, serverConnectionProvider)([this] { /* create SSL provider */ auto sslProvider = oatpp::libressl::server::ConnectionProvider::createShared(...); /* create oatpp::network::ConnectionProviderSwitch*/ return std::make_shared<oatpp::network::ConnectionProviderSwitch>(sslProvider /* current provider */); }()); ... void reloadCert() { /* get server connection provider component */ OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionProviderSwitch>, providerSwitch); /* create new SSL provider with new cert */ auto sslProvider = oatpp::libressl::server::ConnectionProvider::createShared(...); /* set new provider */ providerSwitch->resetProvider(sslProvider); }
Additionally, resource invalidation is no longer supported by ConnectionProvider. Please use either ResourceHandleTemplate::invalidate() or Invalidator::invalidate(resource) directly.
Proper Server Stoppage
Now call to HttpConnectionHandler::stop()
, AsyncHttpConnectionHandler::stop()
will shutdown all opened connections and will wait until all request handlers exit.
TemporaryFile
Introduce oatpp::data::resource::TemporaryFile
.
Use-case:
Temporary file resolves concurrency issues during file uploads. Also, a temporary file ensures that partially uploaded (due to errors/exceptions) resources will be automatically deleted at the end of the block.
解释
#include "oatpp/core/data/resource/TemporaryFile.hpp" ... ENDPOINT("POST", "/upload", upload, REQUEST(std::shared_ptr<IncomingRequest>, request)) { /* create random file in '/tmp' folder */ oatpp::data::resource::TemporaryFile tmp("/tmp"); /* transfer body to temporary file */ request->transferBody(tmp.openOutputStream()); /* move file to permanent storage */ OATPP_ASSERT_HTTP(tmp.moveFile("/path/to/permanent/storage/avatar.png"), Status::CODE_500, "Failed to save file") /* return 200 */ return createResponse(Status::CODE_200, "OK"); }
Better Multipart
Multipart API has been changed and improved.
Now it’s possible to upload multiple files using TemporaryFile
and keep track of all parts and their corresponding data resources.
解释
#include "oatpp/web/mime/multipart/TemporaryFileProvider.hpp" #include "oatpp/web/mime/multipart/Reader.hpp" #include "oatpp/web/mime/multipart/PartList.hpp" ... namespace multipart = oatpp::web::mime::multipart; ... ENDPOINT("POST", "upload", upload, REQUEST(std::shared_ptr<IncomingRequest>, request)) { /* create multipart object */ multipart::PartList multipart(request->getHeaders()); /* create multipart reader */ multipart::Reader multipartReader(&multipart); /* setup reader to stream parts to a temporary files by default */ multipartReader.setDefaultPartReader(multipart::createTemporaryFilePartReader("/tmp" /* /tmp directory */)); /* upload multipart data */ request->transferBody(&multipartReader); /* list all parts and locations to corresponding temporary files */ auto parts = multipart.getAllParts(); for(auto& p : parts) { OATPP_LOGD("part", "name=%s, location=%s", p->getName()->c_str(), p->getPayload()->getLocation()->c_str()) } /* return 200 */ return createResponse(Status::CODE_200, "OK"); }
Response::getBody()
oatpp::web::protocol::http::outgoing::Response
has a new method getBody()
to retrieve the body of the response. This is handy for response interceptors.
data::stream::FIFOStream
The new FIFOStream
stream is a buffered InputStream
with an WriteCallback
. Check the corresponding documentation on how to use these interfaces.
Instead of using a static buffer like BufferInputStream
it is build upon data::buffer::FIFOBuffer
and is able to dynamically grow when data is written to it that would surpass its capacity. It is especially useful if you need to buffer data from a stream upfront or have multiple data sources that should be buffered in a single stream. However, it is not synchronized, so be careful when using FIFOStream
in a multithreaded manner. You need to implement your own locking.
data::stream::BufferedInputStream
FIFOStream
also introduced a new interface BufferedInputStream
which unifies the buffered-stream-interface all existing buffered streams (InputStreamBufferedProxy
, BufferInputStream
, FIFOStream
) to allow for generalisation.
oatpp::parser::json::mapping::Serializer::Config::alwaysIncludeRequired
If oatpp::parser::json::mapping::Serializer::Config::includeNullFields == false
there might still be the requirement to include some fields even if they are nullptr
, because they are required by the deserializing end.
Consider the following DTO and endpoint-snippet.
解释
class StatusDto : public oatpp::DTO { DTO_INIT(StatusDto, DTO) DTO_FIELD_INFO(status) { info->required = true; } DTO_FIELD(String, status); DTO_FIELD(Int32, code); DTO_FIELD(String, message); }; // endpoint code: ENDPOINT("GET", "/status", status) { auto dto = StatusDto::createShared(); dto->code = 200; return createDtoResponse(Status::CODE_200, dto); }
With a serializer with its config set to Serializer::Config::includeNullFields = false
, the snippet would just yield {"code":200}
.
However, status
is a required field. Now, one can set Serializer::Config::alwaysIncludeRequired = true
. With alwaysIncludeRequired == true
, the same snippet would yield {"status":null,"code":200}
, even with includeNullFields == false
.