Skip to content

Commit

Permalink
Merge pull request #6 from kassane/cpp-serverClient
Browse files Browse the repository at this point in the history
Add C++ HTTP server and client
  • Loading branch information
orhun committed Aug 18, 2023
2 parents 2357b41 + c005787 commit ee65497
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 1 deletion.
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;
}

0 comments on commit ee65497

Please sign in to comment.