1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module thrift.codegen.processor; 20 21 import std.algorithm : find; 22 import std.array : empty, front; 23 import std.conv : to; 24 import std.traits : ParameterTypeTuple, ReturnType, Unqual; 25 import std.typetuple : allSatisfy, TypeTuple; 26 import std.variant : Variant; 27 import thrift.base; 28 import thrift.codegen.base; 29 import thrift.internal.codegen; 30 import thrift.internal.ctfe; 31 import thrift.protocol.base; 32 import thrift.protocol.processor; 33 34 /** 35 * Service processor for Interface, which implements TProcessor by 36 * synchronously forwarding requests for the service methods to a handler 37 * implementing Interface. 38 * 39 * The generated class implements TProcessor and additionally allows a 40 * TProcessorEventHandler to be specified via the public eventHandler property. 41 * The constructor takes a single argument of type Interface, which is the 42 * handler to forward the requests to: 43 * --- 44 * this(Interface iface); 45 * TProcessorEventHandler eventHandler; 46 * --- 47 * 48 * If Interface is derived from another service BaseInterface, this class is 49 * also derived from TServiceProcessor!BaseInterface. 50 * 51 * The optional Protocols template tuple parameter can be used to specify 52 * one or more TProtocol implementations to specifically generate code for. If 53 * the actual types of the protocols passed to process() at runtime match one 54 * of the items from the list, the optimized code paths are taken, otherwise, 55 * a generic TProtocol version is used as fallback. For cases where the input 56 * and output protocols differ, TProtocolPair!(InputProtocol, OutputProtocol) 57 * can be used in the Protocols list: 58 * --- 59 * interface FooService { void foo(); } 60 * class FooImpl { override void foo {} } 61 * 62 * // Provides fast path if TBinaryProtocol!TBufferedTransport is used for 63 * // both input and output: 64 * alias TServiceProcessor!(FooService, TBinaryProtocol!TBufferedTransport) 65 * BinaryProcessor; 66 * 67 * auto proc = new BinaryProcessor(new FooImpl()); 68 * 69 * // Low overhead. 70 * proc.process(tBinaryProtocol(tBufferTransport(someSocket))); 71 * 72 * // Not in the specialization list – higher overhead. 73 * proc.process(tBinaryProtocol(tFramedTransport(someSocket))); 74 * 75 * // Same as above, but optimized for the Compact protocol backed by a 76 * // TPipedTransport for input and a TBufferedTransport for output. 77 * alias TServiceProcessor!(FooService, TProtocolPair!( 78 * TCompactProtocol!TPipedTransport, TCompactProtocol!TBufferedTransport) 79 * ) MixedProcessor; 80 * --- 81 */ 82 template TServiceProcessor(Interface, Protocols...) if ( 83 isService!Interface && allSatisfy!(isTProtocolOrPair, Protocols) 84 ) { 85 mixin({ 86 static if (is(Interface BaseInterfaces == super) && BaseInterfaces.length > 0) { 87 static assert(BaseInterfaces.length == 1, 88 "Services cannot be derived from more than one parent."); 89 90 string code = "class TServiceProcessor : " ~ 91 "TServiceProcessor!(BaseService!Interface) {\n"; 92 code ~= "private Interface iface_;\n"; 93 94 string constructorCode = "this(Interface iface) {\n"; 95 constructorCode ~= "super(iface);\n"; 96 constructorCode ~= "iface_ = iface;\n"; 97 } else { 98 string code = "class TServiceProcessor : TProcessor {"; 99 code ~= q{ 100 override bool process(TProtocol iprot, TProtocol oprot, 101 Variant context = Variant() 102 ) { 103 auto msg = iprot.readMessageBegin(); 104 105 void writeException(TApplicationException e) { 106 oprot.writeMessageBegin(TMessage(msg.name, TMessageType.EXCEPTION, 107 msg.seqid)); 108 e.write(oprot); 109 oprot.writeMessageEnd(); 110 oprot.transport.writeEnd(); 111 oprot.transport.flush(); 112 } 113 114 if (msg.type != TMessageType.CALL && msg.type != TMessageType.ONEWAY) { 115 skip(iprot, TType.STRUCT); 116 iprot.readMessageEnd(); 117 iprot.transport.readEnd(); 118 119 writeException(new TApplicationException( 120 TApplicationException.Type.INVALID_MESSAGE_TYPE)); 121 return false; 122 } 123 124 auto dg = msg.name in processMap_; 125 if (!dg) { 126 skip(iprot, TType.STRUCT); 127 iprot.readMessageEnd(); 128 iprot.transport.readEnd(); 129 130 writeException(new TApplicationException("Invalid method name: '" ~ 131 msg.name ~ "'.", TApplicationException.Type.INVALID_MESSAGE_TYPE)); 132 133 return false; 134 } 135 136 (*dg)(msg.seqid, iprot, oprot, context); 137 return true; 138 } 139 140 TProcessorEventHandler eventHandler; 141 142 alias void delegate(int, TProtocol, TProtocol, Variant) ProcessFunc; 143 protected ProcessFunc[string] processMap_; 144 private Interface iface_; 145 }; 146 147 string constructorCode = "this(Interface iface) {\n"; 148 constructorCode ~= "iface_ = iface;\n"; 149 } 150 151 // Generate the handling code for each method, consisting of the dispatch 152 // function, registering it in the constructor, and the actual templated 153 // handler function. 154 foreach (methodName; 155 FilterMethodNames!(Interface, __traits(derivedMembers, Interface)) 156 ) { 157 // Register the processing function in the constructor. 158 immutable procFuncName = "process_" ~ methodName; 159 immutable dispatchFuncName = procFuncName ~ "_protocolDispatch"; 160 constructorCode ~= "processMap_[`" ~ methodName ~ "`] = &" ~ 161 dispatchFuncName ~ ";\n"; 162 163 bool methodMetaFound; 164 TMethodMeta methodMeta; 165 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { 166 enum meta = find!`a.name == b`(Interface.methodMeta, methodName); 167 if (!meta.empty) { 168 methodMetaFound = true; 169 methodMeta = meta.front; 170 } 171 } 172 173 // The dispatch function to call the specialized handler functions. We 174 // test the protocols if they can be converted to one of the passed 175 // protocol types, and if not, fall back to the generic TProtocol 176 // version of the processing function. 177 code ~= "void " ~ dispatchFuncName ~ 178 "(int seqid, TProtocol iprot, TProtocol oprot, Variant context) {\n"; 179 code ~= "foreach (Protocol; TypeTuple!(Protocols, TProtocol)) {\n"; 180 code ~= q{ 181 static if (is(Protocol _ : TProtocolPair!(I, O), I, O)) { 182 alias I IProt; 183 alias O OProt; 184 } else { 185 alias Protocol IProt; 186 alias Protocol OProt; 187 } 188 auto castedIProt = cast(IProt)iprot; 189 auto castedOProt = cast(OProt)oprot; 190 }; 191 code ~= "if (castedIProt && castedOProt) {\n"; 192 code ~= procFuncName ~ 193 "!(IProt, OProt)(seqid, castedIProt, castedOProt, context);\n"; 194 code ~= "return;\n"; 195 code ~= "}\n"; 196 code ~= "}\n"; 197 code ~= "throw new TException(`Internal error: Null iprot/oprot " ~ 198 "passed to processor protocol dispatch function.`);\n"; 199 code ~= "}\n"; 200 201 // The actual handler function, templated on the input and output 202 // protocol types. 203 code ~= "void " ~ procFuncName ~ "(IProt, OProt)(int seqid, IProt " ~ 204 "iprot, OProt oprot, Variant connectionContext) " ~ 205 "if (isTProtocol!IProt && isTProtocol!OProt) {\n"; 206 code ~= "TArgsStruct!(Interface, `" ~ methodName ~ "`) args;\n"; 207 208 // Store the (qualified) method name in a manifest constant to avoid 209 // having to litter the code below with lots of string manipulation. 210 code ~= "enum methodName = `" ~ methodName ~ "`;\n"; 211 212 code ~= q{ 213 enum qName = Interface.stringof ~ "." ~ methodName; 214 215 Variant callContext; 216 if (eventHandler) { 217 callContext = eventHandler.createContext(qName, connectionContext); 218 } 219 220 scope (exit) { 221 if (eventHandler) { 222 eventHandler.deleteContext(callContext, qName); 223 } 224 } 225 226 if (eventHandler) eventHandler.preRead(callContext, qName); 227 228 args.read(iprot); 229 iprot.readMessageEnd(); 230 iprot.transport.readEnd(); 231 232 if (eventHandler) eventHandler.postRead(callContext, qName); 233 }; 234 235 code ~= "TResultStruct!(Interface, `" ~ methodName ~ "`) result;\n"; 236 code ~= "try {\n"; 237 238 // Generate the parameter list to pass to the called iface function. 239 string[] paramList; 240 foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { 241 string paramName; 242 if (methodMetaFound && i < methodMeta.params.length) { 243 paramName = methodMeta.params[i].name; 244 } else { 245 paramName = "param" ~ to!string(i + 1); 246 } 247 paramList ~= "args." ~ paramName; 248 } 249 250 immutable call = "iface_." ~ methodName ~ "(" ~ ctfeJoin(paramList) ~ ")"; 251 if (is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { 252 code ~= call ~ ";\n"; 253 } else { 254 code ~= "result.set!`success`(" ~ call ~ ");\n"; 255 } 256 257 // If this is not a oneway method, generate the receiving code. 258 if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) { 259 if (methodMetaFound) { 260 foreach (e; methodMeta.exceptions) { 261 code ~= "} catch (Interface." ~ e.type ~ " " ~ e.name ~ ") {\n"; 262 code ~= "result.set!`" ~ e.name ~ "`(" ~ e.name ~ ");\n"; 263 } 264 } 265 code ~= "}\n"; 266 267 code ~= q{ 268 catch (Exception e) { 269 if (eventHandler) { 270 eventHandler.handlerError(callContext, qName, e); 271 } 272 273 auto x = new TApplicationException(to!string(e)); 274 oprot.writeMessageBegin( 275 TMessage(methodName, TMessageType.EXCEPTION, seqid)); 276 x.write(oprot); 277 oprot.writeMessageEnd(); 278 oprot.transport.writeEnd(); 279 oprot.transport.flush(); 280 return; 281 } 282 283 if (eventHandler) eventHandler.preWrite(callContext, qName); 284 285 oprot.writeMessageBegin(TMessage(methodName, 286 TMessageType.REPLY, seqid)); 287 result.write(oprot); 288 oprot.writeMessageEnd(); 289 oprot.transport.writeEnd(); 290 oprot.transport.flush(); 291 292 if (eventHandler) eventHandler.postWrite(callContext, qName); 293 }; 294 } else { 295 // For oneway methods, we obviously cannot notify the client of any 296 // exceptions, just call the event handler if one is set. 297 code ~= "}\n"; 298 code ~= q{ 299 catch (Exception e) { 300 if (eventHandler) { 301 eventHandler.handlerError(callContext, qName, e); 302 } 303 return; 304 } 305 306 if (eventHandler) eventHandler.onewayComplete(callContext, qName); 307 }; 308 } 309 code ~= "}\n"; 310 311 } 312 313 code ~= constructorCode ~ "}\n"; 314 code ~= "}\n"; 315 316 return code; 317 }()); 318 } 319 320 /** 321 * A struct representing the arguments of a Thrift method call. 322 * 323 * There should usually be no reason to use this directly without the help of 324 * TServiceProcessor, but it is documented publicly to help debugging in case 325 * of CTFE errors. 326 * 327 * Consider this example: 328 * --- 329 * interface Foo { 330 * int bar(string a, bool b); 331 * 332 * enum methodMeta = [ 333 * TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)]) 334 * ]; 335 * } 336 * 337 * alias TArgsStruct!(Foo, "bar") FooBarArgs; 338 * --- 339 * 340 * The definition of FooBarArgs is equivalent to: 341 * --- 342 * struct FooBarArgs { 343 * string a; 344 * bool b; 345 * 346 * mixin TStructHelpers!([TFieldMeta("a", 1, TReq.OPT_IN_REQ_OUT), 347 * TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT)]); 348 * } 349 * --- 350 * 351 * If the TVerboseCodegen version is defined, a warning message is issued at 352 * compilation if no TMethodMeta for Interface.methodName is found. 353 */ 354 template TArgsStruct(Interface, string methodName) { 355 static assert(is(typeof(mixin("Interface." ~ methodName))), 356 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); 357 mixin({ 358 bool methodMetaFound; 359 TMethodMeta methodMeta; 360 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { 361 auto meta = find!`a.name == b`(Interface.methodMeta, methodName); 362 if (!meta.empty) { 363 methodMetaFound = true; 364 methodMeta = meta.front; 365 } 366 } 367 368 string memberCode; 369 string[] fieldMetaCodes; 370 foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { 371 // If we have no meta information, just use param1, param2, etc. as 372 // field names, it shouldn't really matter anyway. 1-based »indexing« 373 // is used to match the common scheme in the Thrift world. 374 string memberId; 375 string memberName; 376 if (methodMetaFound && i < methodMeta.params.length) { 377 memberId = to!string(methodMeta.params[i].id); 378 memberName = methodMeta.params[i].name; 379 } else { 380 memberId = to!string(i + 1); 381 memberName = "param" ~ to!string(i + 1); 382 } 383 384 // Unqual!() is needed to generate mutable fields for ref const() 385 // struct parameters. 386 memberCode ~= "Unqual!(ParameterTypeTuple!(Interface." ~ methodName ~ 387 ")[" ~ to!string(i) ~ "])" ~ memberName ~ ";\n"; 388 389 fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~ 390 ", TReq.OPT_IN_REQ_OUT)"; 391 } 392 393 string code = "struct TArgsStruct {\n"; 394 code ~= memberCode; 395 version (TVerboseCodegen) { 396 if (!methodMetaFound && 397 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) 398 { 399 code ~= "pragma(msg, `[thrift.codegen.processor.TArgsStruct] Warning: No " ~ 400 "meta information for method '" ~ methodName ~ "' in service '" ~ 401 Interface.stringof ~ "' found.`);\n"; 402 } 403 } 404 immutable fieldMetaCode = 405 fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]"; 406 code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n"; 407 code ~= "}\n"; 408 return code; 409 }()); 410 } 411 412 /** 413 * A struct representing the result of a Thrift method call. 414 * 415 * It contains a field called "success" for the return value of the function 416 * (with id 0), and additional fields for the exceptions declared for the 417 * method, if any. 418 * 419 * There should usually be no reason to use this directly without the help of 420 * TServiceProcessor, but it is documented publicly to help debugging in case 421 * of CTFE errors. 422 * 423 * Consider the following example: 424 * --- 425 * interface Foo { 426 * int bar(string a); 427 * 428 * alias .FooException FooException; 429 * 430 * enum methodMeta = [ 431 * TMethodMeta("bar", 432 * [TParamMeta("a", 1)], 433 * [TExceptionMeta("fooe", 1, "FooException")] 434 * ) 435 * ]; 436 * } 437 * alias TResultStruct!(Foo, "bar") FooBarResult; 438 * --- 439 * 440 * The definition of FooBarResult is equivalent to: 441 * --- 442 * struct FooBarResult { 443 * int success; 444 * FooException fooe; 445 * 446 * mixin(TStructHelpers!([TFieldMeta("success", 0, TReq.OPTIONAL), 447 * TFieldMeta("fooe", 1, TReq.OPTIONAL)])); 448 * } 449 * --- 450 * 451 * If the TVerboseCodegen version is defined, a warning message is issued at 452 * compilation if no TMethodMeta for Interface.methodName is found. 453 */ 454 template TResultStruct(Interface, string methodName) { 455 static assert(is(typeof(mixin("Interface." ~ methodName))), 456 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); 457 458 mixin({ 459 string code = "struct TResultStruct {\n"; 460 461 string[] fieldMetaCodes; 462 463 static if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { 464 code ~= "ReturnType!(Interface." ~ methodName ~ ") success;\n"; 465 fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)"; 466 } 467 468 bool methodMetaFound; 469 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { 470 auto meta = find!`a.name == b`(Interface.methodMeta, methodName); 471 if (!meta.empty) { 472 foreach (e; meta.front.exceptions) { 473 code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n"; 474 fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~ 475 ", TReq.OPTIONAL)"; 476 } 477 methodMetaFound = true; 478 } 479 } 480 481 version (TVerboseCodegen) { 482 if (!methodMetaFound && 483 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) 484 { 485 code ~= "pragma(msg, `[thrift.codegen.processor.TResultStruct] Warning: No " ~ 486 "meta information for method '" ~ methodName ~ "' in service '" ~ 487 Interface.stringof ~ "' found.`);\n"; 488 } 489 } 490 491 immutable fieldMetaCode = 492 fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]"; 493 code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n"; 494 code ~= "}\n"; 495 return code; 496 }()); 497 }