calculator-client.c++ (13000B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #include "calculator.capnp.h" 23 #include <capnp/ez-rpc.h> 24 #include <kj/debug.h> 25 #include <math.h> 26 #include <iostream> 27 28 class PowerFunction final: public Calculator::Function::Server { 29 // An implementation of the Function interface wrapping pow(). Note that 30 // we're implementing this on the client side and will pass a reference to 31 // the server. The server will then be able to make calls back to the client. 32 33 public: 34 kj::Promise<void> call(CallContext context) { 35 auto params = context.getParams().getParams(); 36 KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); 37 context.getResults().setValue(pow(params[0], params[1])); 38 return kj::READY_NOW; 39 } 40 }; 41 42 int main(int argc, const char* argv[]) { 43 if (argc != 2) { 44 std::cerr << "usage: " << argv[0] << " HOST:PORT\n" 45 "Connects to the Calculator server at the given address and " 46 "does some RPCs." << std::endl; 47 return 1; 48 } 49 50 capnp::EzRpcClient client(argv[1]); 51 Calculator::Client calculator = client.getMain<Calculator>(); 52 53 // Keep an eye on `waitScope`. Whenever you see it used is a place where we 54 // stop and wait for the server to respond. If a line of code does not use 55 // `waitScope`, then it does not block! 56 auto& waitScope = client.getWaitScope(); 57 58 { 59 // Make a request that just evaluates the literal value 123. 60 // 61 // What's interesting here is that evaluate() returns a "Value", which is 62 // another interface and therefore points back to an object living on the 63 // server. We then have to call read() on that object to read it. 64 // However, even though we are making two RPC's, this block executes in 65 // *one* network round trip because of promise pipelining: we do not wait 66 // for the first call to complete before we send the second call to the 67 // server. 68 69 std::cout << "Evaluating a literal... "; 70 std::cout.flush(); 71 72 // Set up the request. 73 auto request = calculator.evaluateRequest(); 74 request.getExpression().setLiteral(123); 75 76 // Send it, which returns a promise for the result (without blocking). 77 auto evalPromise = request.send(); 78 79 // Using the promise, create a pipelined request to call read() on the 80 // returned object, and then send that. 81 auto readPromise = evalPromise.getValue().readRequest().send(); 82 83 // Now that we've sent all the requests, wait for the response. Until this 84 // point, we haven't waited at all! 85 auto response = readPromise.wait(waitScope); 86 KJ_ASSERT(response.getValue() == 123); 87 88 std::cout << "PASS" << std::endl; 89 } 90 91 { 92 // Make a request to evaluate 123 + 45 - 67. 93 // 94 // The Calculator interface requires that we first call getOperator() to 95 // get the addition and subtraction functions, then call evaluate() to use 96 // them. But, once again, we can get both functions, call evaluate(), and 97 // then read() the result -- four RPCs -- in the time of *one* network 98 // round trip, because of promise pipelining. 99 100 std::cout << "Using add and subtract... "; 101 std::cout.flush(); 102 103 Calculator::Function::Client add = nullptr; 104 Calculator::Function::Client subtract = nullptr; 105 106 { 107 // Get the "add" function from the server. 108 auto request = calculator.getOperatorRequest(); 109 request.setOp(Calculator::Operator::ADD); 110 add = request.send().getFunc(); 111 } 112 113 { 114 // Get the "subtract" function from the server. 115 auto request = calculator.getOperatorRequest(); 116 request.setOp(Calculator::Operator::SUBTRACT); 117 subtract = request.send().getFunc(); 118 } 119 120 // Build the request to evaluate 123 + 45 - 67. 121 auto request = calculator.evaluateRequest(); 122 123 auto subtractCall = request.getExpression().initCall(); 124 subtractCall.setFunction(subtract); 125 auto subtractParams = subtractCall.initParams(2); 126 subtractParams[1].setLiteral(67); 127 128 auto addCall = subtractParams[0].initCall(); 129 addCall.setFunction(add); 130 auto addParams = addCall.initParams(2); 131 addParams[0].setLiteral(123); 132 addParams[1].setLiteral(45); 133 134 // Send the evaluate() request, read() the result, and wait for read() to 135 // finish. 136 auto evalPromise = request.send(); 137 auto readPromise = evalPromise.getValue().readRequest().send(); 138 139 auto response = readPromise.wait(waitScope); 140 KJ_ASSERT(response.getValue() == 101); 141 142 std::cout << "PASS" << std::endl; 143 } 144 145 { 146 // Make a request to evaluate 4 * 6, then use the result in two more 147 // requests that add 3 and 5. 148 // 149 // Since evaluate() returns its result wrapped in a `Value`, we can pass 150 // that `Value` back to the server in subsequent requests before the first 151 // `evaluate()` has actually returned. Thus, this example again does only 152 // one network round trip. 153 154 std::cout << "Pipelining eval() calls... "; 155 std::cout.flush(); 156 157 Calculator::Function::Client add = nullptr; 158 Calculator::Function::Client multiply = nullptr; 159 160 { 161 // Get the "add" function from the server. 162 auto request = calculator.getOperatorRequest(); 163 request.setOp(Calculator::Operator::ADD); 164 add = request.send().getFunc(); 165 } 166 167 { 168 // Get the "multiply" function from the server. 169 auto request = calculator.getOperatorRequest(); 170 request.setOp(Calculator::Operator::MULTIPLY); 171 multiply = request.send().getFunc(); 172 } 173 174 // Build the request to evaluate 4 * 6 175 auto request = calculator.evaluateRequest(); 176 177 auto multiplyCall = request.getExpression().initCall(); 178 multiplyCall.setFunction(multiply); 179 auto multiplyParams = multiplyCall.initParams(2); 180 multiplyParams[0].setLiteral(4); 181 multiplyParams[1].setLiteral(6); 182 183 auto multiplyResult = request.send().getValue(); 184 185 // Use the result in two calls that add 3 and add 5. 186 187 auto add3Request = calculator.evaluateRequest(); 188 auto add3Call = add3Request.getExpression().initCall(); 189 add3Call.setFunction(add); 190 auto add3Params = add3Call.initParams(2); 191 add3Params[0].setPreviousResult(multiplyResult); 192 add3Params[1].setLiteral(3); 193 auto add3Promise = add3Request.send().getValue().readRequest().send(); 194 195 auto add5Request = calculator.evaluateRequest(); 196 auto add5Call = add5Request.getExpression().initCall(); 197 add5Call.setFunction(add); 198 auto add5Params = add5Call.initParams(2); 199 add5Params[0].setPreviousResult(multiplyResult); 200 add5Params[1].setLiteral(5); 201 auto add5Promise = add5Request.send().getValue().readRequest().send(); 202 203 // Now wait for the results. 204 KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27); 205 KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29); 206 207 std::cout << "PASS" << std::endl; 208 } 209 210 { 211 // Our calculator interface supports defining functions. Here we use it 212 // to define two functions and then make calls to them as follows: 213 // 214 // f(x, y) = x * 100 + y 215 // g(x) = f(x, x + 1) * 2; 216 // f(12, 34) 217 // g(21) 218 // 219 // Once again, the whole thing takes only one network round trip. 220 221 std::cout << "Defining functions... "; 222 std::cout.flush(); 223 224 Calculator::Function::Client add = nullptr; 225 Calculator::Function::Client multiply = nullptr; 226 Calculator::Function::Client f = nullptr; 227 Calculator::Function::Client g = nullptr; 228 229 { 230 // Get the "add" function from the server. 231 auto request = calculator.getOperatorRequest(); 232 request.setOp(Calculator::Operator::ADD); 233 add = request.send().getFunc(); 234 } 235 236 { 237 // Get the "multiply" function from the server. 238 auto request = calculator.getOperatorRequest(); 239 request.setOp(Calculator::Operator::MULTIPLY); 240 multiply = request.send().getFunc(); 241 } 242 243 { 244 // Define f. 245 auto request = calculator.defFunctionRequest(); 246 request.setParamCount(2); 247 248 { 249 // Build the function body. 250 auto addCall = request.getBody().initCall(); 251 addCall.setFunction(add); 252 auto addParams = addCall.initParams(2); 253 addParams[1].setParameter(1); // y 254 255 auto multiplyCall = addParams[0].initCall(); 256 multiplyCall.setFunction(multiply); 257 auto multiplyParams = multiplyCall.initParams(2); 258 multiplyParams[0].setParameter(0); // x 259 multiplyParams[1].setLiteral(100); 260 } 261 262 f = request.send().getFunc(); 263 } 264 265 { 266 // Define g. 267 auto request = calculator.defFunctionRequest(); 268 request.setParamCount(1); 269 270 { 271 // Build the function body. 272 auto multiplyCall = request.getBody().initCall(); 273 multiplyCall.setFunction(multiply); 274 auto multiplyParams = multiplyCall.initParams(2); 275 multiplyParams[1].setLiteral(2); 276 277 auto fCall = multiplyParams[0].initCall(); 278 fCall.setFunction(f); 279 auto fParams = fCall.initParams(2); 280 fParams[0].setParameter(0); 281 282 auto addCall = fParams[1].initCall(); 283 addCall.setFunction(add); 284 auto addParams = addCall.initParams(2); 285 addParams[0].setParameter(0); 286 addParams[1].setLiteral(1); 287 } 288 289 g = request.send().getFunc(); 290 } 291 292 // OK, we've defined all our functions. Now create our eval requests. 293 294 // f(12, 34) 295 auto fEvalRequest = calculator.evaluateRequest(); 296 auto fCall = fEvalRequest.initExpression().initCall(); 297 fCall.setFunction(f); 298 auto fParams = fCall.initParams(2); 299 fParams[0].setLiteral(12); 300 fParams[1].setLiteral(34); 301 auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send(); 302 303 // g(21) 304 auto gEvalRequest = calculator.evaluateRequest(); 305 auto gCall = gEvalRequest.initExpression().initCall(); 306 gCall.setFunction(g); 307 gCall.initParams(1)[0].setLiteral(21); 308 auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send(); 309 310 // Wait for the results. 311 KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234); 312 KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244); 313 314 std::cout << "PASS" << std::endl; 315 } 316 317 { 318 // Make a request that will call back to a function defined locally. 319 // 320 // Specifically, we will compute 2^(4 + 5). However, exponent is not 321 // defined by the Calculator server. So, we'll implement the Function 322 // interface locally and pass it to the server for it to use when 323 // evaluating the expression. 324 // 325 // This example requires two network round trips to complete, because the 326 // server calls back to the client once before finishing. In this 327 // particular case, this could potentially be optimized by using a tail 328 // call on the server side -- see CallContext::tailCall(). However, to 329 // keep the example simpler, we haven't implemented this optimization in 330 // the sample server. 331 332 std::cout << "Using a callback... "; 333 std::cout.flush(); 334 335 Calculator::Function::Client add = nullptr; 336 337 { 338 // Get the "add" function from the server. 339 auto request = calculator.getOperatorRequest(); 340 request.setOp(Calculator::Operator::ADD); 341 add = request.send().getFunc(); 342 } 343 344 // Build the eval request for 2^(4+5). 345 auto request = calculator.evaluateRequest(); 346 347 auto powCall = request.getExpression().initCall(); 348 powCall.setFunction(kj::heap<PowerFunction>()); 349 auto powParams = powCall.initParams(2); 350 powParams[0].setLiteral(2); 351 352 auto addCall = powParams[1].initCall(); 353 addCall.setFunction(add); 354 auto addParams = addCall.initParams(2); 355 addParams[0].setLiteral(4); 356 addParams[1].setLiteral(5); 357 358 // Send the request and wait. 359 auto response = request.send().getValue().readRequest() 360 .send().wait(waitScope); 361 KJ_ASSERT(response.getValue() == 512); 362 363 std::cout << "PASS" << std::endl; 364 } 365 366 return 0; 367 }