interop_server.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /*
  2. *
  3. * Copyright 2015, Google Inc.
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are
  8. * met:
  9. *
  10. * * Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above
  13. * copyright notice, this list of conditions and the following disclaimer
  14. * in the documentation and/or other materials provided with the
  15. * distribution.
  16. * * Neither the name of Google Inc. nor the names of its
  17. * contributors may be used to endorse or promote products derived from
  18. * this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. *
  32. */
  33. 'use strict';
  34. var fs = require('fs');
  35. var path = require('path');
  36. var _ = require('lodash');
  37. var AsyncDelayQueue = require('./async_delay_queue');
  38. var grpc = require('..');
  39. var testProto = grpc.load({
  40. root: __dirname + '/../../..',
  41. file: 'src/proto/grpc/testing/test.proto'}).grpc.testing;
  42. var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
  43. var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';
  44. var incompressible_data = fs.readFileSync(
  45. __dirname + '/../../../test/cpp/interop/rnd.dat');
  46. /**
  47. * Create a buffer filled with size zeroes
  48. * @param {number} size The length of the buffer
  49. * @return {Buffer} The new buffer
  50. */
  51. function zeroBuffer(size) {
  52. var zeros = new Buffer(size);
  53. zeros.fill(0);
  54. return zeros;
  55. }
  56. /**
  57. * Echos a header metadata item as specified in the interop spec.
  58. * @param {Call} call The call to echo metadata on
  59. */
  60. function echoHeader(call) {
  61. var echo_initial = call.metadata.get(ECHO_INITIAL_KEY);
  62. if (echo_initial.length > 0) {
  63. var response_metadata = new grpc.Metadata();
  64. response_metadata.set(ECHO_INITIAL_KEY, echo_initial[0]);
  65. call.sendMetadata(response_metadata);
  66. }
  67. }
  68. /**
  69. * Gets the trailer metadata that should be echoed when the call is done,
  70. * as specified in the interop spec.
  71. * @param {Call} call The call to get metadata from
  72. * @return {grpc.Metadata} The metadata to send as a trailer
  73. */
  74. function getEchoTrailer(call) {
  75. var echo_trailer = call.metadata.get(ECHO_TRAILING_KEY);
  76. var response_trailer = new grpc.Metadata();
  77. if (echo_trailer.length > 0) {
  78. response_trailer.set(ECHO_TRAILING_KEY, echo_trailer[0]);
  79. }
  80. return response_trailer;
  81. }
  82. function getPayload(payload_type, size) {
  83. if (payload_type === 'RANDOM') {
  84. payload_type = ['COMPRESSABLE',
  85. 'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
  86. }
  87. var body;
  88. switch (payload_type) {
  89. case 'COMPRESSABLE': body = zeroBuffer(size); break;
  90. case 'UNCOMPRESSABLE': incompressible_data.slice(size); break;
  91. }
  92. return {type: payload_type, body: body};
  93. }
  94. /**
  95. * Respond to an empty parameter with an empty response.
  96. * NOTE: this currently does not work due to issue #137
  97. * @param {Call} call Call to handle
  98. * @param {function(Error, Object)} callback Callback to call with result
  99. * or error
  100. */
  101. function handleEmpty(call, callback) {
  102. echoHeader(call);
  103. callback(null, {}, getEchoTrailer(call));
  104. }
  105. /**
  106. * Handle a unary request by sending the requested payload
  107. * @param {Call} call Call to handle
  108. * @param {function(Error, Object)} callback Callback to call with result or
  109. * error
  110. */
  111. function handleUnary(call, callback) {
  112. echoHeader(call);
  113. var req = call.request;
  114. if (req.response_status) {
  115. var status = req.response_status;
  116. status.metadata = getEchoTrailer(call);
  117. callback(status);
  118. return;
  119. }
  120. var payload = getPayload(req.response_type, req.response_size);
  121. callback(null, {payload: payload},
  122. getEchoTrailer(call));
  123. }
  124. /**
  125. * Respond to a streaming call with the total size of all payloads
  126. * @param {Call} call Call to handle
  127. * @param {function(Error, Object)} callback Callback to call with result or
  128. * error
  129. */
  130. function handleStreamingInput(call, callback) {
  131. echoHeader(call);
  132. var aggregate_size = 0;
  133. call.on('data', function(value) {
  134. aggregate_size += value.payload.body.length;
  135. });
  136. call.on('end', function() {
  137. callback(null, {aggregated_payload_size: aggregate_size},
  138. getEchoTrailer(call));
  139. });
  140. }
  141. /**
  142. * Respond to a payload request with a stream of the requested payloads
  143. * @param {Call} call Call to handle
  144. */
  145. function handleStreamingOutput(call) {
  146. echoHeader(call);
  147. var delay_queue = new AsyncDelayQueue();
  148. var req = call.request;
  149. if (req.response_status) {
  150. var status = req.response_status;
  151. status.metadata = getEchoTrailer(call);
  152. call.emit('error', status);
  153. return;
  154. }
  155. _.each(req.response_parameters, function(resp_param) {
  156. delay_queue.add(function(next) {
  157. call.write({payload: getPayload(req.response_type, resp_param.size)});
  158. next();
  159. }, resp_param.interval_us);
  160. });
  161. delay_queue.add(function(next) {
  162. call.end(getEchoTrailer(call));
  163. next();
  164. });
  165. }
  166. /**
  167. * Respond to a stream of payload requests with a stream of payload responses as
  168. * they arrive.
  169. * @param {Call} call Call to handle
  170. */
  171. function handleFullDuplex(call) {
  172. echoHeader(call);
  173. var delay_queue = new AsyncDelayQueue();
  174. call.on('data', function(value) {
  175. if (value.response_status) {
  176. var status = value.response_status;
  177. status.metadata = getEchoTrailer(call);
  178. call.emit('error', status);
  179. return;
  180. }
  181. _.each(value.response_parameters, function(resp_param) {
  182. delay_queue.add(function(next) {
  183. call.write({payload: getPayload(value.response_type, resp_param.size)});
  184. next();
  185. }, resp_param.interval_us);
  186. });
  187. });
  188. call.on('end', function() {
  189. delay_queue.add(function(next) {
  190. call.end(getEchoTrailer(call));
  191. next();
  192. });
  193. });
  194. }
  195. /**
  196. * Respond to a stream of payload requests with a stream of payload responses
  197. * after all requests have arrived
  198. * @param {Call} call Call to handle
  199. */
  200. function handleHalfDuplex(call) {
  201. call.emit('error', Error('HalfDuplexCall not yet implemented'));
  202. }
  203. /**
  204. * Get a server object bound to the given port
  205. * @param {string} port Port to which to bind
  206. * @param {boolean} tls Indicates that the bound port should use TLS
  207. * @return {{server: Server, port: number}} Server object bound to the support,
  208. * and port number that the server is bound to
  209. */
  210. function getServer(port, tls) {
  211. // TODO(mlumish): enable TLS functionality
  212. var options = {};
  213. var server_creds;
  214. if (tls) {
  215. var key_path = path.join(__dirname, '../test/data/server1.key');
  216. var pem_path = path.join(__dirname, '../test/data/server1.pem');
  217. var key_data = fs.readFileSync(key_path);
  218. var pem_data = fs.readFileSync(pem_path);
  219. server_creds = grpc.ServerCredentials.createSsl(null,
  220. [{private_key: key_data,
  221. cert_chain: pem_data}]);
  222. } else {
  223. server_creds = grpc.ServerCredentials.createInsecure();
  224. }
  225. var server = new grpc.Server(options);
  226. server.addProtoService(testProto.TestService.service, {
  227. emptyCall: handleEmpty,
  228. unaryCall: handleUnary,
  229. streamingOutputCall: handleStreamingOutput,
  230. streamingInputCall: handleStreamingInput,
  231. fullDuplexCall: handleFullDuplex,
  232. halfDuplexCall: handleHalfDuplex
  233. });
  234. var port_num = server.bind('0.0.0.0:' + port, server_creds);
  235. return {server: server, port: port_num};
  236. }
  237. if (require.main === module) {
  238. var parseArgs = require('minimist');
  239. var argv = parseArgs(process.argv, {
  240. string: ['port', 'use_tls']
  241. });
  242. var server_obj = getServer(argv.port, argv.use_tls === 'true');
  243. console.log('Server attaching to port ' + argv.port);
  244. server_obj.server.start();
  245. }
  246. /**
  247. * See docs for getServer
  248. */
  249. exports.getServer = getServer;