Skip to content

Commit

Permalink
New forwarder demo in C++ (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardnormier authored Feb 11, 2025
1 parent c552627 commit 9d0cb9e
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 6 deletions.
16 changes: 16 additions & 0 deletions cpp/Ice/forwarder/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.16)

project(greeter CXX)

include(../../cmake/common.cmake)

add_executable(client Client.cpp Greeter.ice)
slice2cpp_generate(client)
target_link_libraries(client Ice::Ice)

add_executable(server Server.cpp Chatbot.cpp Chatbot.h Greeter.ice)
slice2cpp_generate(server)
target_link_libraries(server Ice::Ice)

add_executable(forwardingserver Forwarder.cpp Forwarder.h ForwardingServer.cpp)
target_link_libraries(forwardingserver Ice::Ice)
18 changes: 18 additions & 0 deletions cpp/Ice/forwarder/Chatbot.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) ZeroC, Inc.

#include "Chatbot.h"

#include <iostream>
#include <sstream>

using namespace std;

string
Server::Chatbot::greet(string name, const Ice::Current&)
{
cout << "Dispatching greet request { name = '" << name << "' }" << endl;

ostringstream os;
os << "Hello, " << name << "!";
return os.str();
}
20 changes: 20 additions & 0 deletions cpp/Ice/forwarder/Chatbot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) ZeroC, Inc.

#ifndef CHATBOT_H
#define CHATBOT_H

#include "Greeter.h"

namespace Server
{
/// Chatbot is an Ice servant that implements Slice interface Greeter.
class Chatbot : public VisitorCenter::Greeter
{
public:
// Implements the pure virtual function in the base class (VisitorCenter::Greeter) generated by the Slice
// compiler.
std::string greet(std::string name, const Ice::Current&) override;
};
}

#endif
36 changes: 36 additions & 0 deletions cpp/Ice/forwarder/Client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) ZeroC, Inc.

#include "Greeter.h"

#include <cstdlib>
#include <iostream>

using namespace std;

int
main(int argc, char* argv[])
{
// Figure out my name.
const char* name = getenv("USER");
if (name == nullptr)
{
name = getenv("USERNAME");
}
if (name == nullptr)
{
name = "masked user";
}

// Create an Ice communicator to initialize the Ice runtime.
const Ice::CommunicatorHolder communicatorHolder{argc, argv};
const Ice::CommunicatorPtr& communicator = communicatorHolder.communicator();

// We create a Greeter proxy for a Greeter object in the Forwarder server (port 10000).
VisitorCenter::GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 10000"};

// Send a request to the remote object and wait for the response.
string greeting = greeter->greet(name);
cout << greeting << endl;

return 0;
}
51 changes: 51 additions & 0 deletions cpp/Ice/forwarder/Forwarder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) ZeroC, Inc.

#include "Forwarder.h"

using namespace std;

ForwardingServer::Forwarder::Forwarder(Ice::ObjectPrx targetTemplate) : _targetTemplate{std::move(targetTemplate)} {}

void
ForwardingServer::Forwarder::dispatch(Ice::IncomingRequest& request, function<void(Ice::OutgoingResponse)> sendResponse)
{
// Make a copy of the current object carried by the request.
const Ice::Current current{request.current()};

// Create a proxy with the the desired identity and facet.
Ice::ObjectPrx target = _targetTemplate.ice_identity(current.id).ice_facet(current.facet);

if (current.requestId == 0)
{
// The incoming request is one-way, so we reconfigure target to be one-way.
target = target.ice_oneway();
}

// Read the encapsulation containing the in-parameters.
const std::byte* inEncapsulationStart = nullptr;
int32_t inEncapsulationSize = 0;
request.inputStream().readEncapsulation(inEncapsulationStart, inEncapsulationSize);

// Make the invocation asynchronously. This call reports most exceptions through its exception callback.
target.ice_invokeAsync(
current.operation,
current.mode,
make_pair(inEncapsulationStart, inEncapsulationStart + inEncapsulationSize),
[sendResponse, current](bool ok, pair<const std::byte*, const std::byte*> outEncapsulation)
{
// The response callback is executed by a thread from the Ice client thread pool when the invocation
// completes successfully (ok is true) or with a user exception (ok is false).
// We create an OutgoingResponse object and send it back to the client with sendResponse.
sendResponse(Ice::makeOutgoingResponse(ok, outEncapsulation, current));
},
[sendResponse, current](std::exception_ptr exceptionPtr)
{
// The exception callback.
// We create an OutgoingResponse object with the exception and send it back to the client with
// sendResponse. If the exception is an Ice local exception that cannot be marshaled, such as
// Ice::ConnectionRefusedException, makeOutgoingResponse marshals an Ice::UnknownLocalException.
sendResponse(Ice::makeOutgoingResponse(exceptionPtr, current));
},
nullptr, // no sent callback
current.ctx);
}
27 changes: 27 additions & 0 deletions cpp/Ice/forwarder/Forwarder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) ZeroC, Inc.

#ifndef FORWARDER_H
#define FORWARDER_H

#include <Ice/Ice.h>

namespace ForwardingServer
{
/// Forwarder is an Ice servant that implements Ice::Object by forwarding all requests it receives to a remote
/// Ice object.
class Forwarder final : public Ice::Object
{
public:
/// Constructs a Forwarder servant.
/// @param targetTemplate A template for the target proxy.
explicit Forwarder(Ice::ObjectPrx targetTemplate);

// Implements the pure virtual function dispatch declared on Ice::Object.
void dispatch(Ice::IncomingRequest& request, std::function<void(Ice::OutgoingResponse)> sendResponse) final;

private:
Ice::ObjectPrx _targetTemplate;
};
}

#endif
40 changes: 40 additions & 0 deletions cpp/Ice/forwarder/ForwardingServer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) ZeroC, Inc.

#include "Forwarder.h"

#include <iostream>

using namespace std;

int
main(int argc, char* argv[])
{
// CtrlCHandler is a helper class that handles Ctrl+C and similar signals. It must be constructed at the beginning
// of the program, before creating an Ice communicator or starting any thread.
Ice::CtrlCHandler ctrlCHandler;

// Create an Ice communicator to initialize the Ice runtime. The CommunicatorHolder is a RAII helper that creates
// the communicator in its constructor and destroys it when it goes out of scope.
const Ice::CommunicatorHolder communicatorHolder{argc, argv};
const Ice::CommunicatorPtr& communicator = communicatorHolder.communicator();

// Create an object adapter that listens for incoming requests and dispatches them to servants.
auto adapter = communicator->createObjectAdapterWithEndpoints("ForwarderAdapter", "tcp -p 10000");

// Create a target proxy template, with a dummy identity.
Ice::ObjectPrx targetTemplate{communicator, "dummy:tcp -h localhost -p 4061"};

// Register the Forwarder servant as default servant with the object adapter. The empty category means this default
// servant receives requests to all Ice objects.
adapter->addDefaultServant(make_shared<ForwardingServer::Forwarder>(targetTemplate), "");

// Start dispatching requests.
adapter->activate();
cout << "Listening on port 10000..." << endl;

// Wait until the user presses Ctrl+C.
int signal = ctrlCHandler.wait();
cout << "Caught signal " << signal << ", exiting..." << endl;

return 0;
}
15 changes: 15 additions & 0 deletions cpp/Ice/forwarder/Greeter.ice
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) ZeroC, Inc.

#pragma once

module VisitorCenter
{
/// Represents a simple greeter.
interface Greeter
{
/// Creates a personalized greeting.
/// @param name The name of the person to greet.
/// @return The greeting.
string greet(string name);
}
}
68 changes: 68 additions & 0 deletions cpp/Ice/forwarder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Forwarder

The Forwarder demo shows how to write a servant that forwards all requests it receives to another remote Ice object,
as-is.

The core of this demo is the generic Forwarding server. This server listens on tcp port 10000 and forwards all
requests to tcp port 4061 on the same host.

The demo also includes a Greeter client and server; these are the usual Greeter client and server, except the client
is configured to use port 10000:

```mermaid
flowchart LR
c[Greeter Client] --> f[Forwarding Server:10000] --> s[Greeter Server:4061]
```

The Forwarding server is generic can be inserted between any client and server. In particular, the Forwarding server
does not use any Slice generated code.

To build the demo, run:

```shell
cmake -B build
cmake --build build --config Release
```

The build produces 3 executables: client, server, and forwardingserver.

To run the demo, first start the server and the forwarding server in separate terminals:

**Linux/macOS:**

```shell
./build/server --Ice.Trace.Dispatch
```

```shell
./build/forwardingserver --Ice.Trace.Dispatch
```

**Windows:**

```shell
build\Release\server --Ice.Trace.Dispatch
```

```shell
build\Release\forwardingserver --Ice.Trace.Dispatch
```

In a third terminal, start the client:

**Linux/macOS:**

```shell
./build/client --Ice.Trace.Network
```

**Windows:**

```shell
build\Release\client --Ice.Trace.Network
```

> [!NOTE]
> The `--Ice.Trace` command-line options are optional: they turn-on tracing (logging) for request dispatches
> (`--Ice.Trace.Dispatch`) and connection establishment/closure (`--Ice.Trace.Network`) and help you follow the call
> flow.
36 changes: 36 additions & 0 deletions cpp/Ice/forwarder/Server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) ZeroC, Inc.

#include "Chatbot.h"

#include <iostream>

using namespace std;

int
main(int argc, char* argv[])
{
// CtrlCHandler is a helper class that handles Ctrl+C and similar signals. It must be constructed at the beginning
// of the program, before creating an Ice communicator or starting any thread.
Ice::CtrlCHandler ctrlCHandler;

// Create an Ice communicator to initialize the Ice runtime. The CommunicatorHolder is a RAII helper that creates
// the communicator in its constructor and destroys it when it goes out of scope.
const Ice::CommunicatorHolder communicatorHolder{argc, argv};
const Ice::CommunicatorPtr& communicator = communicatorHolder.communicator();

// Create an object adapter that listens for incoming requests and dispatches them to servants.
auto adapter = communicator->createObjectAdapterWithEndpoints("GreeterAdapter", "tcp -p 4061");

// Register the Chatbot servant with the adapter.
adapter->add(make_shared<Server::Chatbot>(), Ice::stringToIdentity("greeter"));

// Start dispatching requests.
adapter->activate();
cout << "Listening on port 4061..." << endl;

// Wait until the user presses Ctrl+C.
int signal = ctrlCHandler.wait();
cout << "Caught signal " << signal << ", exiting..." << endl;

return 0;
}
6 changes: 3 additions & 3 deletions csharp/Ice/Forwarder/ForwardingServer/Forwarder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class Forwarder : Ice.Object
// Implements abstract method dispatchAsync defined on Ice.Object.
public async ValueTask<OutgoingResponse> dispatchAsync(IncomingRequest request)
{
// Change the identity and facet of _target to match the identity and facet in the incoming request.
// Create a proxy with the desired identity and facet.
Ice.ObjectPrx target = _targetTemplate.ice_identity(request.current.id).ice_facet(request.current.facet);

if (request.current.requestId == 0)
Expand All @@ -23,8 +23,8 @@ public async ValueTask<OutgoingResponse> dispatchAsync(IncomingRequest request)
target = target.ice_oneway();
}

// Make the invocation asynchronously. If `ice_invokeAsync` throws an Ice.LocalException that cannot be
// marshaled (such as Ice.ConnectionRefusedException), the object adapter that calls `dispatchAsync` converts it
// Make the invocation asynchronously. If ice_invokeAsync throws an Ice.LocalException that cannot be
// marshaled (such as Ice.ConnectionRefusedException), the object adapter that calls dispatchAsync converts it
// into an Ice.UnknownLocalException.
(bool ok, byte[] encapsulation) = await target.ice_invokeAsync(
request.current.operation,
Expand Down
4 changes: 1 addition & 3 deletions csharp/Ice/Forwarder/ForwardingServer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Copyright (c) ZeroC, Inc.

using ForwardingServer;

// Create an Ice communicator to initialize the Ice runtime. The communicator is disposed before the program exits.
using Ice.Communicator communicator = Ice.Util.initialize(ref args);

Expand All @@ -13,7 +11,7 @@

// Register the Forwarder servant as default servant with the object adapter. The empty category means this default
// servant receives requests to all Ice objects.
adapter.addDefaultServant(new Forwarder(targetTemplate), category: "");
adapter.addDefaultServant(new ForwardingServer.Forwarder(targetTemplate), category: "");

// Start dispatching requests.
adapter.activate();
Expand Down

0 comments on commit 9d0cb9e

Please sign in to comment.