]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
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 | } |