diff --git a/cpp/Ice/forwarder/CMakeLists.txt b/cpp/Ice/forwarder/CMakeLists.txt new file mode 100644 index 000000000..1c322e55f --- /dev/null +++ b/cpp/Ice/forwarder/CMakeLists.txt @@ -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) diff --git a/cpp/Ice/forwarder/Chatbot.cpp b/cpp/Ice/forwarder/Chatbot.cpp new file mode 100644 index 000000000..0f5816949 --- /dev/null +++ b/cpp/Ice/forwarder/Chatbot.cpp @@ -0,0 +1,18 @@ +// Copyright (c) ZeroC, Inc. + +#include "Chatbot.h" + +#include +#include + +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(); +} diff --git a/cpp/Ice/forwarder/Chatbot.h b/cpp/Ice/forwarder/Chatbot.h new file mode 100644 index 000000000..8b1830679 --- /dev/null +++ b/cpp/Ice/forwarder/Chatbot.h @@ -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 diff --git a/cpp/Ice/forwarder/Client.cpp b/cpp/Ice/forwarder/Client.cpp new file mode 100644 index 000000000..31edd51e4 --- /dev/null +++ b/cpp/Ice/forwarder/Client.cpp @@ -0,0 +1,36 @@ +// Copyright (c) ZeroC, Inc. + +#include "Greeter.h" + +#include +#include + +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; +} diff --git a/cpp/Ice/forwarder/Forwarder.cpp b/cpp/Ice/forwarder/Forwarder.cpp new file mode 100644 index 000000000..e9804c45b --- /dev/null +++ b/cpp/Ice/forwarder/Forwarder.cpp @@ -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 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 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); +} diff --git a/cpp/Ice/forwarder/Forwarder.h b/cpp/Ice/forwarder/Forwarder.h new file mode 100644 index 000000000..9c9256ec9 --- /dev/null +++ b/cpp/Ice/forwarder/Forwarder.h @@ -0,0 +1,27 @@ +// Copyright (c) ZeroC, Inc. + +#ifndef FORWARDER_H +#define FORWARDER_H + +#include + +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 sendResponse) final; + + private: + Ice::ObjectPrx _targetTemplate; + }; +} + +#endif diff --git a/cpp/Ice/forwarder/ForwardingServer.cpp b/cpp/Ice/forwarder/ForwardingServer.cpp new file mode 100644 index 000000000..8b06d061b --- /dev/null +++ b/cpp/Ice/forwarder/ForwardingServer.cpp @@ -0,0 +1,40 @@ +// Copyright (c) ZeroC, Inc. + +#include "Forwarder.h" + +#include + +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(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; +} diff --git a/cpp/Ice/forwarder/Greeter.ice b/cpp/Ice/forwarder/Greeter.ice new file mode 100644 index 000000000..0835f945f --- /dev/null +++ b/cpp/Ice/forwarder/Greeter.ice @@ -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); + } +} diff --git a/cpp/Ice/forwarder/README.md b/cpp/Ice/forwarder/README.md new file mode 100644 index 000000000..bb11f7d15 --- /dev/null +++ b/cpp/Ice/forwarder/README.md @@ -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. diff --git a/cpp/Ice/forwarder/Server.cpp b/cpp/Ice/forwarder/Server.cpp new file mode 100644 index 000000000..2b14af909 --- /dev/null +++ b/cpp/Ice/forwarder/Server.cpp @@ -0,0 +1,36 @@ +// Copyright (c) ZeroC, Inc. + +#include "Chatbot.h" + +#include + +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(), 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; +} diff --git a/csharp/Ice/Forwarder/ForwardingServer/Forwarder.cs b/csharp/Ice/Forwarder/ForwardingServer/Forwarder.cs index 3d72bea30..82886fcd7 100644 --- a/csharp/Ice/Forwarder/ForwardingServer/Forwarder.cs +++ b/csharp/Ice/Forwarder/ForwardingServer/Forwarder.cs @@ -14,7 +14,7 @@ internal class Forwarder : Ice.Object // Implements abstract method dispatchAsync defined on Ice.Object. public async ValueTask 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) @@ -23,8 +23,8 @@ public async ValueTask 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, diff --git a/csharp/Ice/Forwarder/ForwardingServer/Program.cs b/csharp/Ice/Forwarder/ForwardingServer/Program.cs index 219252b85..6d3f3cce5 100644 --- a/csharp/Ice/Forwarder/ForwardingServer/Program.cs +++ b/csharp/Ice/Forwarder/ForwardingServer/Program.cs @@ -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); @@ -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();