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

C++: HTTP server and client #6

Merged
merged 4 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ cpwd="$(pwd)"
required_bins=('zig' 'cargo' 'go' 'python' 'hyperfine')
zig_bins=('zig-http-server' 'zig-http-client')
rust_bins=('rust-http-server' 'rust-attohttpc' 'rust-hyper' 'rust-reqwest' 'rust-ureq')
cpp_bins=('cpp-asio-httpserver' 'cpp-asio-httpclient')
go_bins=('go-http-client')
python_bins=('python-http-client')

Expand All @@ -25,6 +26,12 @@ for rust_bin in "${rust_bins[@]}"; do
cargo build --release --manifest-path "${cpwd}/${rust_bin}/Cargo.toml"
done

for cpp_bin in "${cpp_bins[@]}"; do
echo "Building ${cpp_bin}..."
cd "${cpwd}/${cpp_bin}" || exit
zig build -Doptimize=ReleaseFast
done

for go_bin in "${go_bins[@]}"; do
echo "Building ${go_bin}..."
cd "${cpwd}/${go_bin}" || exit
Expand All @@ -35,6 +42,7 @@ cd "${cpwd}" || exit
server_bins=(
"${zig_bins[0]}/zig-out/bin/${zig_bins[0]}"
"${rust_bins[0]}/target/release/${rust_bins[0]}"
"${cpp_bins[0]}/zig-out/bin/${cpp_bins[0]}"
)
echo "Running the server..."
"${cpwd}/${server_bins[0]}" &
Expand All @@ -53,6 +61,7 @@ args=(
"--command-name" "rust-ureq"
"--command-name" "go-http-client"
"--command-name" "python-http-client"
"--command-name" "cpp-asio-httpclient"
)

commands=("curl http://127.0.0.1:8000/get?[1-1000]")
Expand All @@ -73,7 +82,11 @@ for python_bin in "${python_bins[@]}"; do
commands+=("python ${cpwd}/${python_bin}/${python_bin}.py")
done

hyperfine "${args[@]}" "${commands[@]}" --export-json benchmarks.json --export-markdown benchmarks.md
for cpp_bin in "${cpp_bins[@]:1}"; do
commands+=("${cpwd}/${cpp_bin}/zig-out/bin/${cpp_bin}")
done

hyperfine "${args[@]}" "${commands[@]}" -i --export-json benchmarks.json --export-markdown benchmarks.md
sed -i "s|$cpwd\/||g" benchmarks.*

kill -9 "$SERVER_PID"
46 changes: 46 additions & 0 deletions cpp-asio-httpclient/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const libasio_dep = b.dependency("asio", .{
.target = target,
.optimize = optimize,
});
const libasio = libasio_dep.artifact("asio");

const exe = b.addExecutable(.{
.name = "cpp-asio-httpclient",
.target = target,
.optimize = optimize,
});
// get include to zig-cache/i/{hash-pkg}/include
for (libasio.include_dirs.items) |include| {
exe.include_dirs.append(include) catch {};
}
exe.addCSourceFile(.{
.file = .{
.path = "src/main.cpp",
},
.flags = &.{
"-Wall",
"-Wextra",
"-Wshadow",
},
});
exe.linkLibrary(libasio);
exe.linkLibCpp();

b.installArtifact(exe);

const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());

if (b.args) |args| {
run_cmd.addArgs(args);
}

const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
11 changes: 11 additions & 0 deletions cpp-asio-httpclient/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.{
.name = "cpp-asio-httpclient",
.version = "0.1.0",
.license = "MIT",
.dependencies = .{
.asio = .{
.url = "https://github.com/kassane/asio/archive/2e97b6a4d37be85529d191380eeda67240fd61fe.tar.gz",
.hash = "12208b60f54e758b964ad3038973b7c4198d40ed6d6ea2955b6e44cee971e6edeb5e",
},
},
}
104 changes: 104 additions & 0 deletions cpp-asio-httpclient/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include <asio.hpp>
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>

using asio::ip::tcp;

class HttpClient {
public:
HttpClient(asio::io_context &ioContext)
: ioContext_(ioContext), socket_(ioContext) {}

void connect(const std::string &host, const std::string &port) {
tcp::resolver resolver(ioContext_);
endpoints_ = resolver.resolve(host, port);
asio::connect(socket_, endpoints_);
}

void sendRequest(const std::string &request,
std::function<void(const std::string &)> onResponse) {
onResponse_ = std::move(onResponse);
asio::async_write(socket_, asio::buffer(request),
[this](std::error_code ec, std::size_t /*length*/) {
if (!ec) {
readResponse();
} else {
std::cerr << "Write error: " << ec.message()
<< std::endl;
}
});
}

void readResponse() {
asio::async_read_until(socket_, responseBuffer_, "\r\n\r\n",
[this](std::error_code ec, std::size_t /*length*/) {
if (!ec) {
processResponse();
} else {
std::cerr << "Read error: " << ec.message()
<< std::endl;
}
});
}

void processResponse() {
std::istream responseStream(&responseBuffer_);

std::string header;
while (std::getline(responseStream, header) && header != "\r") {
std::cout << header << std::endl;
}

std::ostringstream contentStream;
if (responseBuffer_.size() > 0) {
contentStream << &responseBuffer_;
onResponse_(contentStream.str());
}

socket_.close();
}

private:
asio::io_context &ioContext_;
tcp::socket socket_;
tcp::resolver::results_type endpoints_;
asio::streambuf responseBuffer_;
std::function<void(const std::string &)> onResponse_;
};

int main() {
try {
asio::io_context ioContext;

const std::string host = "localhost";
const std::string port = "8000";

std::vector<std::unique_ptr<HttpClient>> clients;
std::vector<std::string> responses;

for (int i = 0; i < 1000; ++i) {
clients.emplace_back(std::make_unique<HttpClient>(ioContext));
clients.back()->connect(host, port);
clients.back()->sendRequest(
"GET /get HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: close\r\n"
"\r\n",
[&](const std::string &response) { responses.push_back(response); });
}

ioContext.run();

// Process responses as needed
for (const auto &response : responses) {
std::cout << "Response Content: " << response << std::endl;
}
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
}

return 0;
}
49 changes: 49 additions & 0 deletions cpp-asio-httpserver/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// Standalone-Server uses asio (standalone/non-boost)
const libasio_dep = b.dependency("standaloneServer", .{
.target = target,
.optimize = optimize,
});
const libasio = libasio_dep.artifact("Standalone-server");

const exe = b.addExecutable(.{
.name = "cpp-asio-httpserver",
.target = target,
.optimize = optimize,
});
// get include to zig-cache/i/{hash-pkg}/include
for (libasio.include_dirs.items) |include| {
exe.include_dirs.append(include) catch {};
}
exe.addCSourceFile(.{
.file = .{
.path = "src/main.cpp",
},
.flags = &.{
"-Wall",
"-Wextra",
"-Wshadow",
},
});
// use standalone asio - non-boost
exe.defineCMacro("ASIO_STANDALONE", null);
exe.linkLibrary(libasio);
exe.linkLibCpp();

b.installArtifact(exe);

const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());

if (b.args) |args| {
run_cmd.addArgs(args);
}

const run_step = b.step("run", b.fmt("Run the {s} app", .{exe.name}));
run_step.dependOn(&run_cmd.step);
}
11 changes: 11 additions & 0 deletions cpp-asio-httpserver/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.{
.name = "cpp-asio-httpserver",
.version = "0.1.0",
.license = "MIT",
.dependencies = .{
.standaloneServer = .{
.url = "https://github.com/kassane/Standalone-Server/archive/12af18dfce121f5c6dc59702a0bbf99ce69f1677.tar.gz",
.hash = "1220b0320efd82e58de88d8a7ea86777efd39cb6196b2a4abdcdf3e264707121770e",
},
},
}
23 changes: 23 additions & 0 deletions cpp-asio-httpserver/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <iostream>
#include <string>
#include <server_http.hpp>

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;

int main() {
// Create the HTTP server
HttpServer server;
server.config.port = 8000;

// Define the request handler
server.resource["^/get$"]["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
std::string content = "C++ Bits!\n";
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
};

// Start the server
std::cout << "Server started on port 8000." << std::endl;
server.start();

return 0;
}