blob: f9509fa2d9af599dca03422a46fc1239403403ec [file] [log] [blame]
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001// Licensed to the Apache Software Foundation(ASF) under one
2// or more contributor license agreements.See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18using System;
19using System.Collections.Generic;
20using System.IO;
21using System.Linq;
22using System.Net;
23using System.Net.Security;
24using System.Security.Cryptography.X509Certificates;
25using System.Threading;
26using System.Threading.Tasks;
27using Microsoft.Extensions.Logging;
28using Thrift;
29using Thrift.Protocol;
30using Thrift.Transport;
31using Thrift.Transport.Client;
32using tutorial;
33using shared;
Jens Geyerb11f63c2019-03-14 21:12:38 +010034using Microsoft.Extensions.DependencyInjection;
Kyle Smith7b94dd42019-03-23 17:26:56 +010035using System.Diagnostics;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010036
37namespace Client
38{
39 public class Program
40 {
Jens Geyerb11f63c2019-03-14 21:12:38 +010041 private static ServiceCollection ServiceCollection = new ServiceCollection();
42 private static ILogger Logger;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010043
44 private static void DisplayHelp()
45 {
46 Logger.LogInformation(@"
47Usage:
48 Client.exe -help
49 will diplay help information
50
Kyle Smith7b94dd42019-03-23 17:26:56 +010051 Client.exe -tr:<transport> -bf:<buffering> -pr:<protocol> -mc:<numClients>
Jens Geyeraa0c8b32019-01-28 23:27:45 +010052 will run client with specified arguments (tcp transport and binary protocol by default) and with 1 client
53
54Options:
55 -tr (transport):
56 tcp - (default) tcp transport will be used (host - ""localhost"", port - 9090)
Jens Geyeraa0c8b32019-01-28 23:27:45 +010057 namedpipe - namedpipe transport will be used (pipe address - "".test"")
58 http - http transport will be used (address - ""http://localhost:9090"")
59 tcptls - tcp tls transport will be used (host - ""localhost"", port - 9090)
Kyle Smith7b94dd42019-03-23 17:26:56 +010060
61 -bf (buffering):
62 none - (default) no buffering will be used
63 buffered - buffered transport will be used
64 framed - framed transport will be used
Jens Geyeraa0c8b32019-01-28 23:27:45 +010065
66 -pr (protocol):
67 binary - (default) binary protocol will be used
68 compact - compact protocol will be used
69 json - json protocol will be used
70 multiplexed - multiplexed protocol will be used
71
72 -mc (multiple clients):
73 <numClients> - number of multiple clients to connect to server (max 100, default 1)
74
75Sample:
76 Client.exe -tr:tcp -p:binary
77");
78 }
79
80 public static void Main(string[] args)
81 {
82 args = args ?? new string[0];
83
Jens Geyerb11f63c2019-03-14 21:12:38 +010084 ServiceCollection.AddLogging(logging => ConfigureLogging(logging));
85 Logger = ServiceCollection.BuildServiceProvider().GetService<ILoggerFactory>().CreateLogger(nameof(Client));
86
Jens Geyeraa0c8b32019-01-28 23:27:45 +010087 if (args.Any(x => x.StartsWith("-help", StringComparison.OrdinalIgnoreCase)))
88 {
89 DisplayHelp();
90 return;
91 }
92
93 Logger.LogInformation("Starting client...");
94
95 using (var source = new CancellationTokenSource())
96 {
97 RunAsync(args, source.Token).GetAwaiter().GetResult();
98 }
99 }
100
Jens Geyerb11f63c2019-03-14 21:12:38 +0100101 private static void ConfigureLogging(ILoggingBuilder logging)
102 {
103 logging.SetMinimumLevel(LogLevel.Trace);
104 logging.AddConsole();
105 logging.AddDebug();
106 }
107
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100108 private static async Task RunAsync(string[] args, CancellationToken cancellationToken)
109 {
110 var numClients = GetNumberOfClients(args);
111
112 Logger.LogInformation($"Selected # of clients: {numClients}");
113
114 var transports = new TTransport[numClients];
115 for (int i = 0; i < numClients; i++)
116 {
117 var t = GetTransport(args);
118 transports[i] = t;
119 }
120
121 Logger.LogInformation($"Selected client transport: {transports[0]}");
122
123 var protocols = new Tuple<Protocol, TProtocol>[numClients];
124 for (int i = 0; i < numClients; i++)
125 {
126 var p = GetProtocol(args, transports[i]);
127 protocols[i] = p;
128 }
129
130 Logger.LogInformation($"Selected client protocol: {protocols[0].Item1}");
131
132 var tasks = new Task[numClients];
133 for (int i = 0; i < numClients; i++)
134 {
135 var task = RunClientAsync(protocols[i], cancellationToken);
136 tasks[i] = task;
137 }
138
139 Task.WaitAll(tasks);
140
141 await Task.CompletedTask;
142 }
143
144 private static TTransport GetTransport(string[] args)
145 {
Kyle Smith7b94dd42019-03-23 17:26:56 +0100146 TTransport transport = new TSocketTransport(IPAddress.Loopback, 9090);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100147
Kyle Smith7b94dd42019-03-23 17:26:56 +0100148 // construct endpoint transport
149 var transportArg = args.FirstOrDefault(x => x.StartsWith("-tr"))?.Split(':')?[1];
150 if (Enum.TryParse(transportArg, true, out Transport selectedTransport))
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100151 {
152 switch (selectedTransport)
153 {
154 case Transport.Tcp:
Kyle Smith7b94dd42019-03-23 17:26:56 +0100155 transport = new TSocketTransport(IPAddress.Loopback, 9090);
156 break;
157
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100158 case Transport.NamedPipe:
Kyle Smith7b94dd42019-03-23 17:26:56 +0100159 transport = new TNamedPipeTransport(".test");
160 break;
161
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100162 case Transport.Http:
Kyle Smith7b94dd42019-03-23 17:26:56 +0100163 transport = new THttpTransport(new Uri("http://localhost:9090"), null);
164 break;
165
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100166 case Transport.TcpTls:
Kyle Smith7b94dd42019-03-23 17:26:56 +0100167 transport = new TTlsSocketTransport(IPAddress.Loopback, 9090, GetCertificate(), CertValidator, LocalCertificateSelectionCallback);
168 break;
169
170 default:
171 Debug.Assert(false, "unhandled case");
172 break;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100173 }
174 }
175
Kyle Smith7b94dd42019-03-23 17:26:56 +0100176 // optionally add layered transport(s)
177 var bufferingArg = args.FirstOrDefault(x => x.StartsWith("-bf"))?.Split(':')?[1];
178 if (Enum.TryParse<Buffering>(bufferingArg, out var selectedBuffering))
179 {
180 switch (selectedBuffering)
181 {
182 case Buffering.Buffered:
183 transport = new TBufferedTransport(transport);
184 break;
185
186 case Buffering.Framed:
187 transport = new TFramedTransport(transport);
188 break;
189
190 default: // layered transport(s) are optional
191 Debug.Assert(selectedBuffering == Buffering.None, "unhandled case");
192 break;
193 }
194 }
195
196 return transport;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100197 }
198
199 private static int GetNumberOfClients(string[] args)
200 {
201 var numClients = args.FirstOrDefault(x => x.StartsWith("-mc"))?.Split(':')?[1];
202
203 Logger.LogInformation($"Selected # of clients: {numClients}");
204
205 int c;
206 if( int.TryParse(numClients, out c) && (0 < c) && (c <= 100))
207 return c;
208 else
209 return 1;
210 }
211
212 private static X509Certificate2 GetCertificate()
213 {
214 // due to files location in net core better to take certs from top folder
215 var certFile = GetCertPath(Directory.GetParent(Directory.GetCurrentDirectory()));
216 return new X509Certificate2(certFile, "ThriftTest");
217 }
218
219 private static string GetCertPath(DirectoryInfo di, int maxCount = 6)
220 {
221 var topDir = di;
222 var certFile =
223 topDir.EnumerateFiles("ThriftTest.pfx", SearchOption.AllDirectories)
224 .FirstOrDefault();
225 if (certFile == null)
226 {
227 if (maxCount == 0)
228 throw new FileNotFoundException("Cannot find file in directories");
229 return GetCertPath(di.Parent, maxCount - 1);
230 }
231
232 return certFile.FullName;
233 }
234
235 private static X509Certificate LocalCertificateSelectionCallback(object sender,
236 string targetHost, X509CertificateCollection localCertificates,
237 X509Certificate remoteCertificate, string[] acceptableIssuers)
238 {
239 return GetCertificate();
240 }
241
242 private static bool CertValidator(object sender, X509Certificate certificate,
243 X509Chain chain, SslPolicyErrors sslPolicyErrors)
244 {
245 return true;
246 }
247
248 private static Tuple<Protocol, TProtocol> GetProtocol(string[] args, TTransport transport)
249 {
250 var protocol = args.FirstOrDefault(x => x.StartsWith("-pr"))?.Split(':')?[1];
251
252 Protocol selectedProtocol;
253 if (Enum.TryParse(protocol, true, out selectedProtocol))
254 {
255 switch (selectedProtocol)
256 {
257 case Protocol.Binary:
258 return new Tuple<Protocol, TProtocol>(selectedProtocol, new TBinaryProtocol(transport));
259 case Protocol.Compact:
260 return new Tuple<Protocol, TProtocol>(selectedProtocol, new TCompactProtocol(transport));
261 case Protocol.Json:
262 return new Tuple<Protocol, TProtocol>(selectedProtocol, new TJsonProtocol(transport));
263 case Protocol.Multiplexed:
264 // it returns BinaryProtocol to avoid making wrapped protocol as public in TProtocolDecorator (in RunClientAsync it will be wrapped into Multiplexed protocol)
265 return new Tuple<Protocol, TProtocol>(selectedProtocol, new TBinaryProtocol(transport));
Kyle Smith7b94dd42019-03-23 17:26:56 +0100266 default:
267 Debug.Assert(false, "unhandled case");
268 break;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100269 }
270 }
271
272 return new Tuple<Protocol, TProtocol>(selectedProtocol, new TBinaryProtocol(transport));
273 }
274
275 private static async Task RunClientAsync(Tuple<Protocol, TProtocol> protocolTuple, CancellationToken cancellationToken)
276 {
277 try
278 {
279 var protocol = protocolTuple.Item2;
280 var protocolType = protocolTuple.Item1;
281
282 TBaseClient client = null;
283
284 try
285 {
286 if (protocolType != Protocol.Multiplexed)
287 {
288
289 client = new Calculator.Client(protocol);
290 await ExecuteCalculatorClientOperations(cancellationToken, (Calculator.Client)client);
291 }
292 else
293 {
294 // it uses binary protocol there to create Multiplexed protocols
295 var multiplex = new TMultiplexedProtocol(protocol, nameof(Calculator));
296 client = new Calculator.Client(multiplex);
297 await ExecuteCalculatorClientOperations(cancellationToken, (Calculator.Client)client);
298
299 multiplex = new TMultiplexedProtocol(protocol, nameof(SharedService));
300 client = new SharedService.Client(multiplex);
301 await ExecuteSharedServiceClientOperations(cancellationToken, (SharedService.Client)client);
302 }
303 }
304 catch (Exception ex)
305 {
306 Logger.LogError($"{client?.ClientId} " + ex);
307 }
308 finally
309 {
310 protocol.Transport.Close();
311 }
312 }
313 catch (TApplicationException x)
314 {
315 Logger.LogError(x.ToString());
316 }
317 }
318
319 private static async Task ExecuteCalculatorClientOperations(CancellationToken cancellationToken, Calculator.Client client)
320 {
321 await client.OpenTransportAsync(cancellationToken);
322
323 // Async version
324
325 Logger.LogInformation($"{client.ClientId} PingAsync()");
326 await client.pingAsync(cancellationToken);
327
328 Logger.LogInformation($"{client.ClientId} AddAsync(1,1)");
329 var sum = await client.addAsync(1, 1, cancellationToken);
330 Logger.LogInformation($"{client.ClientId} AddAsync(1,1)={sum}");
331
332 var work = new Work
333 {
334 Op = Operation.DIVIDE,
335 Num1 = 1,
336 Num2 = 0
337 };
338
339 try
340 {
341 Logger.LogInformation($"{client.ClientId} CalculateAsync(1)");
342 await client.calculateAsync(1, work, cancellationToken);
343 Logger.LogInformation($"{client.ClientId} Whoa we can divide by 0");
344 }
345 catch (InvalidOperation io)
346 {
347 Logger.LogInformation($"{client.ClientId} Invalid operation: " + io);
348 }
349
350 work.Op = Operation.SUBTRACT;
351 work.Num1 = 15;
352 work.Num2 = 10;
353
354 try
355 {
356 Logger.LogInformation($"{client.ClientId} CalculateAsync(1)");
357 var diff = await client.calculateAsync(1, work, cancellationToken);
358 Logger.LogInformation($"{client.ClientId} 15-10={diff}");
359 }
360 catch (InvalidOperation io)
361 {
362 Logger.LogInformation($"{client.ClientId} Invalid operation: " + io);
363 }
364
365 Logger.LogInformation($"{client.ClientId} GetStructAsync(1)");
366 var log = await client.getStructAsync(1, cancellationToken);
367 Logger.LogInformation($"{client.ClientId} Check log: {log.Value}");
368
369 Logger.LogInformation($"{client.ClientId} ZipAsync() with delay 100mc on server side");
370 await client.zipAsync(cancellationToken);
371 }
372 private static async Task ExecuteSharedServiceClientOperations(CancellationToken cancellationToken, SharedService.Client client)
373 {
374 await client.OpenTransportAsync(cancellationToken);
375
376 // Async version
377
378 Logger.LogInformation($"{client.ClientId} SharedService GetStructAsync(1)");
379 var log = await client.getStructAsync(1, cancellationToken);
380 Logger.LogInformation($"{client.ClientId} SharedService Value: {log.Value}");
381 }
382
383
384 private enum Transport
385 {
386 Tcp,
387 NamedPipe,
388 Http,
389 TcpBuffered,
390 Framed,
391 TcpTls
392 }
393
394 private enum Protocol
395 {
396 Binary,
397 Compact,
398 Json,
399 Multiplexed
400 }
Kyle Smith7b94dd42019-03-23 17:26:56 +0100401
402 private enum Buffering
403 {
404 None,
405 Buffered,
406 Framed
407 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100408 }
409}