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
10 * http://www.apache.org/licenses/LICENSE-2.0
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
19 module thrift.codegen.processor;
21 import std.algorithm : find;
22 import std.array : empty, front;
24 import std.traits : ParameterTypeTuple, ReturnType, Unqual;
25 import std.typetuple : allSatisfy, TypeTuple;
26 import std.variant : Variant;
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;
35 * Service processor for Interface, which implements TProcessor by
36 * synchronously forwarding requests for the service methods to a handler
37 * implementing Interface.
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:
44 * this(Interface iface);
45 * TProcessorEventHandler eventHandler;
48 * If Interface is derived from another service BaseInterface, this class is
49 * also derived from TServiceProcessor!BaseInterface.
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:
59 * interface FooService { void foo(); }
60 * class FooImpl { override void foo {} }
62 * // Provides fast path if TBinaryProtocol!TBufferedTransport is used for
63 * // both input and output:
64 * alias TServiceProcessor!(FooService, TBinaryProtocol!TBufferedTransport)
67 * auto proc = new BinaryProcessor(new FooImpl());
70 * proc.process(tBinaryProtocol(tBufferTransport(someSocket)));
72 * // Not in the specialization list – higher overhead.
73 * proc.process(tBinaryProtocol(tFramedTransport(someSocket)));
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)
82 template TServiceProcessor(Interface, Protocols...) if (
83 isService!Interface && allSatisfy!(isTProtocolOrPair, Protocols)
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.");
90 string code = "class TServiceProcessor : " ~
91 "TServiceProcessor!(BaseService!Interface) {\n";
92 code ~= "private Interface iface_;\n";
94 string constructorCode = "this(Interface iface) {\n";
95 constructorCode ~= "super(iface);\n";
96 constructorCode ~= "iface_ = iface;\n";
98 string code = "class TServiceProcessor : TProcessor {";
100 override bool process(TProtocol iprot, TProtocol oprot,
101 Variant context = Variant()
103 auto msg = iprot.readMessageBegin();
105 void writeException(TApplicationException e) {
106 oprot.writeMessageBegin(TMessage(msg.name, TMessageType.EXCEPTION,
109 oprot.writeMessageEnd();
110 oprot.transport.writeEnd();
111 oprot.transport.flush();
114 if (msg.type != TMessageType.CALL && msg.type != TMessageType.ONEWAY) {
115 skip(iprot, TType.STRUCT);
116 iprot.readMessageEnd();
117 iprot.transport.readEnd();
119 writeException(new TApplicationException(
120 TApplicationException.Type.INVALID_MESSAGE_TYPE));
124 auto dg = msg.name in processMap_;
126 skip(iprot, TType.STRUCT);
127 iprot.readMessageEnd();
128 iprot.transport.readEnd();
130 writeException(new TApplicationException("Invalid method name: '" ~
131 msg.name ~ "'.", TApplicationException.Type.INVALID_MESSAGE_TYPE));
136 (*dg)(msg.seqid, iprot, oprot, context);
140 TProcessorEventHandler eventHandler;
142 alias void delegate(int, TProtocol, TProtocol, Variant) ProcessFunc;
143 protected ProcessFunc[string] processMap_;
144 private Interface iface_;
147 string constructorCode = "this(Interface iface) {\n";
148 constructorCode ~= "iface_ = iface;\n";
151 // Generate the handling code for each method, consisting of the dispatch
152 // function, registering it in the constructor, and the actual templated
155 FilterMethodNames!(Interface, __traits(derivedMembers, Interface))
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";
163 bool methodMetaFound;
164 TMethodMeta methodMeta;
165 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
166 enum meta = find!`a.name == b`(Interface.methodMeta, methodName);
168 methodMetaFound = true;
169 methodMeta = meta.front;
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";
181 static if (is(Protocol _ : TProtocolPair!(I, O), I, O)) {
185 alias Protocol IProt;
186 alias Protocol OProt;
188 auto castedIProt = cast(IProt)iprot;
189 auto castedOProt = cast(OProt)oprot;
191 code ~= "if (castedIProt && castedOProt) {\n";
192 code ~= procFuncName ~
193 "!(IProt, OProt)(seqid, castedIProt, castedOProt, context);\n";
197 code ~= "throw new TException(`Internal error: Null iprot/oprot " ~
198 "passed to processor protocol dispatch function.`);\n";
201 // The actual handler function, templated on the input and output
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";
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";
213 enum qName = Interface.stringof ~ "." ~ methodName;
217 callContext = eventHandler.createContext(qName, connectionContext);
222 eventHandler.deleteContext(callContext, qName);
226 if (eventHandler) eventHandler.preRead(callContext, qName);
229 iprot.readMessageEnd();
230 iprot.transport.readEnd();
232 if (eventHandler) eventHandler.postRead(callContext, qName);
235 code ~= "TResultStruct!(Interface, `" ~ methodName ~ "`) result;\n";
238 // Generate the parameter list to pass to the called iface function.
240 foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
242 if (methodMetaFound && i < methodMeta.params.length) {
243 paramName = methodMeta.params[i].name;
245 paramName = "param" ~ to!string(i + 1);
247 paramList ~= "args." ~ paramName;
250 immutable call = "iface_." ~ methodName ~ "(" ~ ctfeJoin(paramList) ~ ")";
251 if (is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
252 code ~= call ~ ";\n";
254 code ~= "result.set!`success`(" ~ call ~ ");\n";
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";
268 catch (Exception e) {
270 eventHandler.handlerError(callContext, qName, e);
273 auto x = new TApplicationException(to!string(e));
274 oprot.writeMessageBegin(
275 TMessage(methodName, TMessageType.EXCEPTION, seqid));
277 oprot.writeMessageEnd();
278 oprot.transport.writeEnd();
279 oprot.transport.flush();
283 if (eventHandler) eventHandler.preWrite(callContext, qName);
285 oprot.writeMessageBegin(TMessage(methodName,
286 TMessageType.REPLY, seqid));
288 oprot.writeMessageEnd();
289 oprot.transport.writeEnd();
290 oprot.transport.flush();
292 if (eventHandler) eventHandler.postWrite(callContext, qName);
295 // For oneway methods, we obviously cannot notify the client of any
296 // exceptions, just call the event handler if one is set.
299 catch (Exception e) {
301 eventHandler.handlerError(callContext, qName, e);
306 if (eventHandler) eventHandler.onewayComplete(callContext, qName);
313 code ~= constructorCode ~ "}\n";
321 * A struct representing the arguments of a Thrift method call.
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
327 * Consider this example:
330 * int bar(string a, bool b);
332 * enum methodMeta = [
333 * TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)])
337 * alias TArgsStruct!(Foo, "bar") FooBarArgs;
340 * The definition of FooBarArgs is equivalent to:
342 * struct FooBarArgs {
346 * mixin TStructHelpers!([TFieldMeta("a", 1, TReq.OPT_IN_REQ_OUT),
347 * TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT)]);
351 * If the TVerboseCodegen version is defined, a warning message is issued at
352 * compilation if no TMethodMeta for Interface.methodName is found.
354 template TArgsStruct(Interface, string methodName) {
355 static assert(is(typeof(mixin("Interface." ~ methodName))),
356 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
358 bool methodMetaFound;
359 TMethodMeta methodMeta;
360 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
361 auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
363 methodMetaFound = true;
364 methodMeta = meta.front;
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.
376 if (methodMetaFound && i < methodMeta.params.length) {
377 memberId = to!string(methodMeta.params[i].id);
378 memberName = methodMeta.params[i].name;
380 memberId = to!string(i + 1);
381 memberName = "param" ~ to!string(i + 1);
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";
389 fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~
390 ", TReq.OPT_IN_REQ_OUT)";
393 string code = "struct TArgsStruct {\n";
395 version (TVerboseCodegen) {
396 if (!methodMetaFound &&
397 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
399 code ~= "pragma(msg, `[thrift.codegen.processor.TArgsStruct] Warning: No " ~
400 "meta information for method '" ~ methodName ~ "' in service '" ~
401 Interface.stringof ~ "' found.`);\n";
404 immutable fieldMetaCode =
405 fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
406 code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n";
413 * A struct representing the result of a Thrift method call.
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
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
423 * Consider the following example:
428 * alias .FooException FooException;
430 * enum methodMeta = [
432 * [TParamMeta("a", 1)],
433 * [TExceptionMeta("fooe", 1, "FooException")]
437 * alias TResultStruct!(Foo, "bar") FooBarResult;
440 * The definition of FooBarResult is equivalent to:
442 * struct FooBarResult {
446 * mixin(TStructHelpers!([TFieldMeta("success", 0, TReq.OPTIONAL),
447 * TFieldMeta("fooe", 1, TReq.OPTIONAL)]));
451 * If the TVerboseCodegen version is defined, a warning message is issued at
452 * compilation if no TMethodMeta for Interface.methodName is found.
454 template TResultStruct(Interface, string methodName) {
455 static assert(is(typeof(mixin("Interface." ~ methodName))),
456 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
459 string code = "struct TResultStruct {\n";
461 string[] fieldMetaCodes;
463 static if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
464 code ~= "ReturnType!(Interface." ~ methodName ~ ") success;\n";
465 fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)";
468 bool methodMetaFound;
469 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
470 auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
472 foreach (e; meta.front.exceptions) {
473 code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n";
474 fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~
477 methodMetaFound = true;
481 version (TVerboseCodegen) {
482 if (!methodMetaFound &&
483 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
485 code ~= "pragma(msg, `[thrift.codegen.processor.TResultStruct] Warning: No " ~
486 "meta information for method '" ~ methodName ~ "' in service '" ~
487 Interface.stringof ~ "' found.`);\n";
491 immutable fieldMetaCode =
492 fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
493 code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n";