
Show HN: Oat++ – Zero-dependency C++ framework for high-performance web services - lganzzzo
https://github.com/oatpp/oatpp
======
FooBarWidget
Hi there, great work writing oat++. I am the author of Passenger
([https://www.phusionpassenger.com](https://www.phusionpassenger.com)), an
application server that powers 650.000 companies and sites world-wide such as
Apple, Pixar, Juniper, etc. Passenger is mostly written in C++ and like you,
performance is a priority for us.

I've done a fast code review, and I thought I'd share some of my findings with
you. These are based on my experience with writing high-performance servers.
You can read about some of my experience in these blog posts:
[https://www.rubyraptor.org/how-we-made-raptor-up-
to-4x-faste...](https://www.rubyraptor.org/how-we-made-raptor-up-to-4x-faster-
than-unicorn-and-up-to-2x-faster-than-puma-torquebox/)

Hopefully you can use my feedback to make oat++ even faster and better. Code
review here:
[https://github.com/oatpp/oatpp/issues/15](https://github.com/oatpp/oatpp/issues/15)

I've covered:

\- Pools and contention

\- Shared_ptr and atomics everywhere

\- List vs vector

\- Zero-copy architecture

\- SpinLock everywhere

\- New does not return nullptr?

\- More documentation

\- More tests

~~~
M_Bakhtiari
Good point on the shared_pointers. What's your opinion on using Ravenbrook MPS
pools and not deal with refcounts at all?

~~~
lganzzzo
Memory management, and pools.

Memory management is definitely super important and has big impact on the
performance. Also there is a memory fragmentation issue that you should keep
in mind building server apps. Solving this problems is time consuming and you
need to do a lot of experiments in order to tune all this system. So if there
is a robust time-proved solution for solving this problems it is definitely
worth of considering.

shared_ptr and refcounts - actually it is a compromise (like any other
solution). On the one hand you could avoid refcounts, atomics etc. On the
other hand you have a framework for the end user. And user may be not so
familiar in depth of your framework's thread management and concurrent access
to some shared objects.

Also end-user may decide to bring in some other library which for example uses
shared_ptr. And what user should do if he has to share some object among
std::shared_ptr and your own custom shared-ptr:)?

This is the reason I decided to go with std::shared_ptr (with it's atomic
refcounter) as I mentioned in the code-review in the response
[https://github.com/oatpp/oatpp/issues/15](https://github.com/oatpp/oatpp/issues/15)

Now about Ravenbrook MPS pools in particular: It seems to be a robust time-
proved solution which may help you to avoid a lot of pain. As for oatpp -
oatpp claims to be zero-dependency at the moment. So no external dependencies
are considered at the moment. This may be changed at some moment in the future
but not for now.

------
lganzzzo
The idea of the project is to give user something light, something that could
cover a set of basic needs when you develop a web-service (Like rest-
framework, basic DI-framework, web-client, connection management, object-
mapping etc...) and make it highly customizable at the same time.

So when you need to kickoff something from scratch you don't need to do
anything more than a git clone starter-project and start coding. And when it
comes time for additional requirements you cant easily substitute ex. any SSL
backend, any http-client, add http compression etc... And oat++ provides
interfaces to configure there things.

See more: Example how to create web-service with swagger-uI and auto-
documented endpoints [https://medium.com/oatpp/c-oatpp-web-service-with-
swagger-ui...](https://medium.com/oatpp/c-oatpp-web-service-with-swagger-ui-
and-auto-documented-endpoints-1d4bb7b82c21)

------
Chabs
What is the rationale behind going for from-full-scratch instead of packaging
a copy of ASIO with the library? Beating properly-used ASIO performance is
going to be a massive uphill battle, especially in multithreaded scenarios.

On that note, benchmarks against a simple ASIO http server are a must if
performance is your #1 stated goal.

~~~
lganzzzo
I agree. I should schedule to add ASIO vs oatpp benchmark.

However performance is not the only thing oatpp has. Among with other features
oatpp provides ObjectMapping layer. ObjectMapping layer enables you to do cool
stuff like: \- autodocument endpoints see [https://medium.com/oatpp/c-oatpp-
web-service-with-swagger-ui...](https://medium.com/oatpp/c-oatpp-web-service-
with-swagger-ui-and-auto-documented-endpoints-1d4bb7b82c21)

\- easily implement custom protocols (I will write article about this later)

~~~
Chabs
I think you are misunderstanding my question. I am not suggesting that ASIO
fills the role your library does. All it is, really, is just fancied-up cross-
patform select().

What I'm wondering is: Is there's a specific reason you chose to reinvent that
specific wheel?

~~~
lganzzzo
Oh ok, thank you for clarification.

During the initial investigation boost::asio appeared to work not very good on
MacOS. Problems where on the load higher than 10K concurrent connections.

There was multiple requests to compare networking performance of oatpp to ASIO
lately. So I want to make full-scale testing before making statements.

------
lganzzzo
Have you noticed oatpp ApiClient (retrofit-like client wrapper)? This is
another trick oatpp's ObjectMapper layer can do.

The oatpp-consul client is built using oatpp ApiClient. see ApiClient in the
oatpp-consul [https://github.com/oatpp/oatpp-
consul/blob/master/rest/Clien...](https://github.com/oatpp/oatpp-
consul/blob/master/rest/Client.hpp)

example how to do client requests [https://github.com/oatpp/oatpp-
examples/issues/3](https://github.com/oatpp/oatpp-examples/issues/3)

It is also possible to build your own RequestExecutor based on ex. Curl

------
Matthias247
Looks pretty cool and easy to use, at least for the synchronous methods. The
async methods could use a little bit more documentation, e.g. what is really
async about them (what is Action? Some kind of deferred response? Can you
store the request objects there for longer than the act() methods scope? Etc).

What I would also like to see there are additional examples which show how
streaming the request/response body is done, e.g. for file uploads and
downloads or SSE.

~~~
lganzzzo
Hey, thank you for the feedback! It is really very important to me what seems
unclear to user, and what info to add.

------
clishem
I wonder how this stacks against Cutelyst in terms of performance.

~~~
lganzzzo
Hello and thank you for the question!

The short answer: \- I don't know. \- Let Techempower to settle this.

I decided to take on Go(net/http) in the initial benchmarks as it shows good
performance and even overperformed c++ solutions that claimed to be more
performant (in their tests).

But! There multiple things that can be tuned especially with c++ servers. And
there are multiple tests which show different aspects of server's performance.
The problem is that currently I have no resources to do all these tests and
what is more important - I think such tests should be made by an independent
third party.

So I think oat++ should be submitted to Techempower to answer all those
questions.

~~~
clishem
You are expected to deliver tests to Techempower yourself. Optimizing for
those tests is part of the game. ;)

net/http is known for being quite slow

~~~
lganzzzo
Yeah, I already looked through requirements. It will take me some time to
prepare all that stuff. But I think it's gonna be fun

------
lganzzzo
Did you notice that [https://oatpp.io](https://oatpp.io) is served by oatpp
:)?

~~~
billconan
I have a question. I want to adopt oatpp.io, but I'm using a synchronous
database driver mongo-cxx-driver. Can I still benefit from oat++'s coroutine?
Also, when using coroutine, will the server be a single-thread process? or it
will be a multiple thread process with each thread using coroutines (to fully
utilize the cpu)?

If my server wants to visit third party rest APIs in an asynchronized way,
what's the best asynchronized library to choose to make use of coroutine?

~~~
lganzzzo
Thank you for the question.

\-----------------------------------------------------------------------------

// I want to adopt oatpp.io, but I'm using a synchronous database driver
mongo-cxx-driver.

\- Unfortunately no - if your driver has only synchronous API you can use it
with multithreaded oatpp only.

\-----------------------------------------------------------------------------

// Also, when using coroutine, will the server be a single-thread process?

// or it will be a multiple thread process with each thread using coroutines
(to fully utilize the cpu)

\- Yes, it will be a multiple thread process with each thread using
coroutines. You can configure how many threads should run the
AsyncHttpConnectionHandler. Number of threads is passed to the
AsyncHttpConnectionHandler constructor. Also you may tweak it in compile time
via option:

"-D OATPP_ASYNC_HTTP_CONNECTION_HANDLER_THREAD_NUM_DEFAULT=<num of threads>"

You have to play with this parameter in order to achieve best performance. In
most cases on : \- Linux it's value should be (<num of CPUs> \- 1). \- On
MacOS it's value should be 2.

\-----------------------------------------------------------------------------

// If my server wants to visit third party rest APIs in an asynchronous way,

// what's the best asynchronized library to choose to make use of coroutine?

\- Native way to do it is to use oatpp ApiClient
[https://oatpp.io/docs/component/api-
client](https://oatpp.io/docs/component/api-client)

it provides two declarations:

\- API_CALL("GET", "/resource", getResource) ///< Example of API call

\- API_CALL_ASYNC("GET", "/resource", getResourceAsync) ///< Example of Async
API call

Also there is an example project with the full example how to make
asynchronous calls to rest APIs [https://github.com/oatpp/oatpp-
examples/tree/master/tls-libr...](https://github.com/oatpp/oatpp-
examples/tree/master/tls-libressl)

There is no README currently in this example, but I gonna add asap.

------
ddorian43
Have you tried using (or building on top of) seastar-framework ?

~~~
lganzzzo
Thank you for the question,

No, I have not tried seastar. But thank you for the tip, I think there might
be things for me to point out.

------
petters
> return createResponse(Status::CODE_200, "Hello World!");

Is "Status::CODE_200" a useful abstraction over just "200"?

~~~
gpderetta
Assuming that's an enum, the compiler will tell you if you miss one case in a
switch statement. Also if you mistype 200 for example as 20 or 2000 the
compiler will also tell you. None are huge reasons, but it is nice and cheap.

~~~
lganzzzo
good point

------
sytelus
Is this cross platform? It looks like there is direct dependency on Linux
socket APIs.

~~~
lganzzzo
Hello, Thank you for the question!

oatpp has been recently tested on: \- Linux (Ubuntu 16, 18. CentOS 7.5) \-
MacOS

Windows support is not currently available. But I would love to add it. If
there someone with windows machine and willing to help, I will provide all
needed assistance. Windows support issue:
[https://github.com/oatpp/oatpp/issues/2](https://github.com/oatpp/oatpp/issues/2)

------
IloveHN84
What about TLS 1.3?

~~~
lganzzzo
Thank you for the question,

Oat++ provides interfaces to substitute SSL backend. One can easily substitute
any SSL backend by implementing two class: \- Connection \- ConnectionProvider

Currently there is an adapter for LibreSSL [https://github.com/oatpp/oatpp-
libressl/tree/master/server](https://github.com/oatpp/oatpp-
libressl/tree/master/server)

But if you need TLS 1.3 one may add GnuTLS adapter for example

