Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code for communicating with another host via HTTP or HTTPS #823

Merged
merged 8 commits into from
Nov 17, 2022

Conversation

hannahbast
Copy link
Member

Add a new class HttpClient which can open HTTP and HTTPS connections to a given host at a given port, and which can then send GET or POST requests, receive the response synchronously, and write it to a (potentially very large) string. Using Boost.Beast, just like for our HttpServer class.

Add a combined test for our HttpServer and HttpClient (for a meaningful test one needs a server and a client). So far, only tests the HTTP case because our HttpServer does not do HTTPS yet. But that is already much better than before when our HTTP communication is not tested at all.

Use the occasion to rename util/HttpServer (a directory with quite a few files in it) to util/http because httpServer was simply a misnomer. This became very apparent with the addition of the .h and .cpp file for HttpClient. This renaming of the directory required many small changes (in the includes and in the various CMakeLists.txt).

This will be needed in the PR for the support of the SERVICE clause, which comes when we are finished with this one.

@hannahbast hannahbast requested a review from joka921 November 12, 2022 21:59
@hannahbast
Copy link
Member Author

hannahbast commented Nov 12, 2022

@joka921 GitHub shows 47 changed files for this PR. However, it is really just six files where non-trivial stuff happens, the rest is due to renaming util/HttpServer to util/http. The six files, where something happens, are:

util/http/HttpClient.h      [completely new]
util/http/HttpClient.cpp    [completely new]
util/http/HttpUtils.cpp     [new function, rather simple] 
util/http/HttpServer.cpp    [small adaption, so that the infinite server loop can be broken if desired]
test/HttpTest.cpp           [new test, rather simple]
test/HttpUtilsTest.cpp      [new test, rather simple]

Add a new class HttpClient which can open HTTP and HTTPS connections to
a given host, and which can then send GET or POST requests, receive the
result synchronously, and write it to a (potentially very large) string.
Uses Boost.Beast, just like our HttpServer.

Add a combined test for our HttpServer and HttpClient (for a meaningful
test one needs a server and a client). So far only tests the HTTP case
because our HttpServer does not do HTTPS yet.

Used the occasion to rename util/HttpServer to util/http because
httpServer was simply a misnomer, which became even more apparent with
the addition of HttpClient. This change required many small changes (in
includes and in the various CMakeLists.txt).
Copy link
Member

@joka921 joka921 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already a great start.
I have some minor syntactical suggestions and very few questions that are worth discussion.

Currently some CI checks are failing:

  1. You have to add OpenSSL-dev to the Dockerfile(s), this is done in your Service-PR but is also required here.
  2. The failing of GCC12 seems odd to me. It is probably also related to our hacking of boost-asio. Can you try if it goes away if you include <utility> in our beast.h? That include has to appear before the include of the first Boost::Asio header.

@hannahbast
Copy link
Member Author

Note to self: rename header beast.h to AsioAndBeast.h or something like that.

Note: The seemingly large diff concerning
`src/util/http/ContentEncodingHelper.h` stems from the fact that Robin
stored that file (as well as several others) in DOS format. I changed it
to UNIX format. This only affects the whitespace in the file.
Copy link
Member

@joka921 joka921 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have two very minor suggestions,
And three not so minor (in relevance, not in size) requests concerning threadsafety.

httpServer.exitServerLoopAfterNextRequest();
HttpClient httpClient;
// Run the server in its own thread.
std::jthread httpServerThread([&]() { httpServer.run(); });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not neccesary neither correct to put the httpServer.run() call into a jthread

  • Necessary: The server internally creates the threads on which it actually runs (always at least two), it doesn't hijack the calling threads.
  • Incorrect: You assume, that the server.run() in the jthread completes before your first client sends a response. That are quite some assumptions on thread scheduling and the network stack that nobody guarantees to you.

TLDR:
A plain httpServer.run() inside the main thread should suffice and be correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so, too, initially, and it was the very first thing I did when I wrote this test, but the httpServer.run() blocks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, that behavior is even described in detail in the run() function...
To make it correct, let the Server also have an atomic<bool> that is called isReady() or something which is set to true in the listener after the _acceptor.listen() has succeeded. Your test code can then wait for this bool to become true, and everything is safe.

Copy link
Member

@joka921 joka921 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answers to Hannah's questions concerning my previous review.

httpServer.exitServerLoopAfterNextRequest();
HttpClient httpClient;
// Run the server in its own thread.
std::jthread httpServerThread([&]() { httpServer.run(); });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, that behavior is even described in detail in the run() function...
To make it correct, let the Server also have an atomic<bool> that is called isReady() or something which is set to true in the listener after the _acceptor.listen() has succeeded. Your test code can then wait for this bool to become true, and everything is safe.

Hannah Bast and others added 3 commits November 16, 2022 19:14
Except waiting for the server to be ready in the next. I would prefer to
do that together in a 1-1 session, since I am not sure what the best
design here is.
Also made the tests safe for the case that the running of the server fails.
Copy link
Member

@joka921 joka921 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small comments from my own changes.

net::strand<net::io_context::executor_type> _acceptorStrand =
net::make_strand(_ioContext);
std::atomic<bool> _serverIsReady = false;
std::atomic<bool> _shutDownAfterNextConnectionIsAccepted = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable is now unused.

// next connection right away).
// While the `_acceptor` is open, accept connections and handle each
// connection asynchronously (so that we are ready to accept the next
// connection right away). The `_acceptor` will be closed via the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// connection right away). The `_acceptor` will be closed via the
// connection right away). The `_acceptor` can be closed via the

// While the `_acceptor` is open, accept connections and handle each
// connection asynchronously (so that we are ready to accept the next
// connection right away). The `_acceptor` will be closed via the
// `shutdown` method.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// `shutdown` method.
// `shutdown` method. This currently only happens in `test/HttpTest.cpp`

Copy link
Member

@joka921 joka921 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much, I think we have converged to some version we both agree with.

@hannahbast hannahbast changed the title Code for communicating with a client via HTTP or HTTPS Code for communicating with another host via HTTP or HTTPS Nov 17, 2022
@hannahbast hannahbast changed the title Code for communicating with another host via HTTP or HTTPS Code for sending GET or POST requests to another host Nov 17, 2022
@hannahbast hannahbast changed the title Code for sending GET or POST requests to another host Code for communicating with another host via HTTP or HTTPS Nov 17, 2022
@hannahbast hannahbast merged commit 8d0026a into master Nov 17, 2022
@hannahbast hannahbast deleted the http-client branch November 17, 2022 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants