icerpc / icerpc-csharp

A C# RPC framework built for QUIC, with bidirectional streaming, first-class async/await, and Protobuf support.
https://docs.icerpc.dev
Apache License 2.0
107 stars 13 forks source link

Interoperability C# IceRPC server and python Ice client #3928

Closed b-antwan closed 8 months ago

b-antwan commented 8 months ago

Hello, I'm trying to get interoperability working with an IceRPC server and a python Ice client. I tried to get a simple greeter to work at first, but I can't seem to get it to work and I don't see anything wrong. My slice files are:

// Copyright (c) ZeroC, Inc.

module VisitorCenter
{
    interface Greeter 
    {
        string greet(string name);
    }
}

For the Ice client

and

// Copyright (c) ZeroC, Inc.
mode = Slice2

module VisitorCenter

/// Represents a simple greeter.
interface Greeter {
    /// Creates a personalized greeting.
    /// @param name: The name of the person to greet.
    /// @returns: The greeting.
    greet(name: string) -> string
}

For the IceRPC server.

I tried playing around with the Uri both on client and server, but here is what I have right now:

Server:

var chatbot = new Chatbot();
var router = new Router();

await using var server = new Server(
    router.Map("/greeter", chatbot),
    new Uri("ice://127.0.0.1:10000"));

Console.WriteLine($"Listening on {server.Listen()}"); 

await CancelKeyPressed;
await server.ShutdownAsync();

Client:

import sys, Ice
import VisitorCenter

with Ice.initialize(sys.argv) as communicator:
    base = communicator.stringToProxy("greeter:default -p 10000")
    print(base)
    greeter = VisitorCenter.GreeterPrx.checkedCast(base)
    if not greeter:
        raise RuntimeError("Invalid proxy")

    greeter.greet("python")

The errors I get on the client are always on the greeter = VisitorCenter.GreeterPrx.checkedCast(base) line and are either:

Ice.OperationNotExistException: exception ::Ice::OperationNotExistException
{
    id =
    {
        name = greeter
        category =
    }
    facet =
    operation = ice_isA
}

( what these snippets produce ) I also managed to get ObjectNotExistException errors (although I don't quite remember which combination of parameters gave me these errors).

Some information that might be useful:

The IceRPC server is running on net8.0 and IceRpc 0.3.0 The Ice client is running on python3 and Ice 3.7.9.1

Is there something I'm missing?

InsertCreativityHere commented 8 months ago

Hey @b-antwan! I haven't actually tried testing your example yet, but just glancing through it, in your Slice file, you should change mode = Slice2 to mode = Slice1

In addition to some other things, it ensures that the Slice definitions are encoded in a way that Ice can consume. Slice2 only works with IceRPC. You can check this page for more information if you're curious: https://docs.icerpc.dev/icerpc-for-ice-users/slice/new-slice#compilation-mode-and-encoding-version

InsertCreativityHere commented 8 months ago

I believe I found the real issue:

greeter = VisitorCenter.GreeterPrx.checkedCast(base)

You should change this to an unchecked cast:

greeter = VisitorCenter.GreeterPrx.uncheckedCast(base)

I'll write another comment with an explanation about why you need to do this, but if you don't want to wait on me, it's also outlined here: https://docs.icerpc.dev/icerpc-for-ice-users/slice/ice-object#checked-cast

TL;DR

The Slice2 encoding is only used by IceRPC, Ice can't handle it. To fix this, change your .slice file to use mode = Slice1. This tells IceRPC to use the 1.1 encoding, which Ice can understand.

Use unchecked casts instead of checked casts.

b-antwan commented 8 months ago

Thanks a lot for your help @InsertCreativityHere ! I was looking into changing the encoding in the python client as well thinking it defaulted to 1.1 instead of 1.0. But your second comment did solve the issue!

InsertCreativityHere commented 8 months ago

Yeah of course!

The Why

Calling checkedCast actually makes an RPC - it asks the other side "hey, is this actually the type I'm requesting?", specifically it calls the ice_isA operation.

In Ice, this operation is built in for all proxy types, but with IceRPC, you have to explicitly opt into it (by implementing Ice.Object). But IMO, checked casts are rarely (if ever) needed. In this case, you're already fully aware what type the proxy is, so unchecked cast is completely fine.