Add semi-functional rpc example
This commit is contained in:
77
examples/calculator-client.py
Executable file
77
examples/calculator-client.py
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import capnp
|
||||||
|
|
||||||
|
import calculator_capnp
|
||||||
|
import rpc_capnp
|
||||||
|
|
||||||
|
class PowerFunction(calculator_capnp.Calculator.Function.Server):
|
||||||
|
'''An implementation of the Function interface wrapping pow(). Note that
|
||||||
|
we're implementing this on the client side and will pass a reference to
|
||||||
|
the server. The server will then be able to make calls back to the client.'''
|
||||||
|
|
||||||
|
def call(self, params):
|
||||||
|
return pow(params[0], params[1])
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser('Connects to the Calculator server at the given address and does some RPCs')
|
||||||
|
parser.add_argument("host", help="HOST:PORT")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
host, port = parse_args().host.split(':')
|
||||||
|
|
||||||
|
sock = socket.create_connection((host, port))
|
||||||
|
client = capnp.RpcClient(sock)
|
||||||
|
|
||||||
|
ref = rpc_capnp.SturdyRef.new_message()
|
||||||
|
ref.objectId.set_as_text('calculator')
|
||||||
|
calculator = client.restore(ref.objectId).cast_as(calculator_capnp.Calculator)
|
||||||
|
|
||||||
|
'''Make a request that just evaluates the literal value 123.
|
||||||
|
|
||||||
|
What's interesting here is that evaluate() returns a "Value", which is
|
||||||
|
another interface and therefore points back to an object living on the
|
||||||
|
server. We then have to call read() on that object to read it.
|
||||||
|
However, even though we are making two RPC's, this block executes in
|
||||||
|
*one* network round trip because of promise pipelining: we do not wait
|
||||||
|
for the first call to complete before we send the second call to the
|
||||||
|
server.'''
|
||||||
|
|
||||||
|
print('Evaluating a literal... ', end="")
|
||||||
|
|
||||||
|
request = calculator.evaluate_request()
|
||||||
|
request.expression.literal = 123
|
||||||
|
|
||||||
|
eval_promise = request.send()
|
||||||
|
read_promise = eval_promise.value.read()
|
||||||
|
response = read_promise.wait()
|
||||||
|
assert response.value == 123
|
||||||
|
|
||||||
|
print("PASS")
|
||||||
|
|
||||||
|
'''Make a request to evaluate 123 + 45 - 67.
|
||||||
|
# //
|
||||||
|
The Calculator interface requires that we first call getOperator() to
|
||||||
|
get the addition and subtraction functions, then call evaluate() to use
|
||||||
|
them. But, once again, we can get both functions, call evaluate(), and
|
||||||
|
then read() the result -- four RPCs -- in the time of *one* network
|
||||||
|
round trip, because of promise pipelining.'''
|
||||||
|
|
||||||
|
print("Using add and subtract... ", end='')
|
||||||
|
add = calculator.getOperator(op='add').func
|
||||||
|
subtract = calculator.getOperator(op='subtract').func
|
||||||
|
|
||||||
|
request = calculator.evaluate_request()
|
||||||
|
subtract_call = request.expression.init('call')
|
||||||
|
subtract_call.function = subtract
|
||||||
|
params = subtract_call.init('params', 2)
|
||||||
|
params[1] = 67.0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
97
examples/calculator.capnp
Normal file
97
examples/calculator.capnp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
@0x85150b117366d14b;
|
||||||
|
|
||||||
|
interface Calculator {
|
||||||
|
# A "simple" mathematical calculator, callable via RPC.
|
||||||
|
#
|
||||||
|
# But, to show off Cap'n Proto, we add some twists:
|
||||||
|
#
|
||||||
|
# - You can use the result from one call as the input to the next
|
||||||
|
# without a network round trip. To accomplish this, evaluate()
|
||||||
|
# returns a `Value` object wrapping the actual numeric value.
|
||||||
|
# This object may be used in a subsequent expression. With
|
||||||
|
# promise pipelining, the Value can actually be used before
|
||||||
|
# the evaluate() call that creates it returns!
|
||||||
|
#
|
||||||
|
# - You can define new functions, and then call them. This again
|
||||||
|
# shows off pipelining, but it also gives the client the
|
||||||
|
# opportunity to define a function on the client side and have
|
||||||
|
# the server call back to it.
|
||||||
|
#
|
||||||
|
# - The basic arithmetic operators are exposed as Functions, and
|
||||||
|
# you have to call getOperator() to obtain them from the server.
|
||||||
|
# This again demonstrates pipelining -- using getOperator() to
|
||||||
|
# get each operator and then using them in evaluate() still
|
||||||
|
# only takes one network round trip.
|
||||||
|
|
||||||
|
evaluate @0 (expression: Expression) -> (value: Value);
|
||||||
|
# Evaluate the given expression and return the result. The
|
||||||
|
# result is returned wrapped in a Value interface so that you
|
||||||
|
# may pass it back to the server in a pipelined request. To
|
||||||
|
# actually get the numeric value, you must call read() on the
|
||||||
|
# Value -- but again, this can be pipelined so that it incurs
|
||||||
|
# no additional latency.
|
||||||
|
|
||||||
|
struct Expression {
|
||||||
|
# A numeric expression.
|
||||||
|
|
||||||
|
union {
|
||||||
|
literal @0 :Float64;
|
||||||
|
# A literal numeric value.
|
||||||
|
|
||||||
|
previousResult @1 :Value;
|
||||||
|
# A value that was (or, will be) returned by a previous
|
||||||
|
# evaluate().
|
||||||
|
|
||||||
|
parameter @2 :UInt32;
|
||||||
|
# A parameter to the function (only valid in function bodies;
|
||||||
|
# see defFunction).
|
||||||
|
|
||||||
|
call :group {
|
||||||
|
# Call a function on a list of parameters.
|
||||||
|
function @3 :Function;
|
||||||
|
params @4 :List(Expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Value {
|
||||||
|
# Wraps a numeric value in an RPC object. This allows the value
|
||||||
|
# to be used in subsequent evaluate() requests without the client
|
||||||
|
# waiting for the evaluate() that returns the Value to finish.
|
||||||
|
|
||||||
|
read @0 () -> (value :Float64);
|
||||||
|
# Read back the raw numeric value.
|
||||||
|
}
|
||||||
|
|
||||||
|
defFunction @1 (paramCount :Int32, body :Expression)
|
||||||
|
-> (func :Function);
|
||||||
|
# Define a function that takes `paramCount` parameters and returns the
|
||||||
|
# evaluation of `body` after substituting these parameters.
|
||||||
|
|
||||||
|
interface Function {
|
||||||
|
# An algebraic function. Can be called directly, or can be used inside
|
||||||
|
# an Expression.
|
||||||
|
#
|
||||||
|
# A client can create a Function that runs on the server side using
|
||||||
|
# `defFunction()` or `getOperator()`. Alternatively, a client can
|
||||||
|
# implement a Function on the client side and the server will call back
|
||||||
|
# to it. However, a function defined on the client side will require a
|
||||||
|
# network round trip whenever the server needs to call it, whereas
|
||||||
|
# functions defined on the server and then passed back to it are called
|
||||||
|
# locally.
|
||||||
|
|
||||||
|
call @0 (params :List(Float64)) -> (value: Float64);
|
||||||
|
# Call the function on the given parameters.
|
||||||
|
}
|
||||||
|
|
||||||
|
getOperator @2 (op: Operator) -> (func: Function);
|
||||||
|
# Get a Function representing an arithmetic operator, which can then be
|
||||||
|
# used in Expressions.
|
||||||
|
|
||||||
|
enum Operator {
|
||||||
|
add @0;
|
||||||
|
subtract @1;
|
||||||
|
multiply @2;
|
||||||
|
divide @3;
|
||||||
|
}
|
||||||
|
}
|
||||||
1251
examples/rpc.capnp
Normal file
1251
examples/rpc.capnp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user