JsonRpcClient.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. using JsonRpcLite.Services;
  2. using JsonRpcLite.Utilities;
  3. using System;
  4. using System.Text.Json;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. namespace JsonRpcLite.Rpc
  8. {
  9. public class JsonRpcClient : IDisposable, IAsyncDisposable
  10. {
  11. private int _requestId;
  12. private IJsonRpcClientEngine _engine;
  13. private bool _disposed;
  14. ~JsonRpcClient()
  15. {
  16. Dispose();
  17. }
  18. /// <summary>
  19. /// Use given engine to handle the request.
  20. /// </summary>
  21. /// <param name="engine">The engine for server.</param>
  22. public void UseEngine(IJsonRpcClientEngine engine)
  23. {
  24. _engine = engine;
  25. }
  26. /// <summary>
  27. /// Create a proxy for given interface.
  28. /// </summary>
  29. /// <typeparam name="T">The interface type.</typeparam>
  30. /// <param name="timeout">The timeout of the proxy.</param>
  31. /// <param name="serviceName">The name of the service.</param>
  32. /// <returns>The proxy which implement the given interface.</returns>
  33. public T CreateProxy<T>(int timeout = Timeout.Infinite, string serviceName = null)
  34. {
  35. var interfaceType = typeof(T);
  36. if (!interfaceType.IsInterface)
  37. {
  38. throw new InvalidOperationException($"{nameof(T)} is not an interface");
  39. }
  40. var name = serviceName;
  41. if (string.IsNullOrEmpty(serviceName))
  42. {
  43. name = interfaceType.Name;
  44. if (!string.IsNullOrEmpty(serviceName))
  45. {
  46. name = serviceName;
  47. }
  48. var serviceAttributes = interfaceType.GetCustomAttributes(typeof(RpcServiceAttribute), false);
  49. if (serviceAttributes.Length > 1)
  50. {
  51. throw new InvalidOperationException($"Service {interfaceType.Name} defined more than one rpc service attributes.");
  52. }
  53. if (serviceAttributes.Length > 0)
  54. {
  55. var serviceAttribute = (RpcServiceAttribute)serviceAttributes[0];
  56. if (!string.IsNullOrEmpty(serviceAttribute.Name))
  57. {
  58. name = serviceAttribute.Name;
  59. }
  60. }
  61. }
  62. return JsonRpcClientProxy.CreateProxy<T>(name, this, timeout);
  63. }
  64. /// <summary>
  65. /// Invoke remote method and get the result.
  66. /// </summary>
  67. /// <typeparam name="T">The return type.</typeparam>
  68. /// <param name="serviceName">The name of the service</param>
  69. /// <param name="methodName">The method name to call.</param>
  70. /// <param name="args">The parameters of the method.</param>
  71. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  72. /// <returns>The result value.</returns>
  73. public async Task<T> InvokeAsync<T>(string serviceName, string methodName, object[] args, CancellationToken cancellationToken = default)
  74. {
  75. var id = Interlocked.Increment(ref _requestId);
  76. var request = new JsonRpcRequest(id, methodName, new JsonRpcRequestParameter(RequestParameterType.Object, args));
  77. var requestData = await JsonRpcCodec.EncodeRequestsAsync(new[] { request }, cancellationToken).ConfigureAwait(false);
  78. var responseData = await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false);
  79. var responses = await JsonRpcCodec.DecodeResponsesAsync(responseData, cancellationToken).ConfigureAwait(false);
  80. if (responses.Length > 0)
  81. {
  82. var response = responses[0];
  83. var responseId = Convert.ToInt32(response.Id);
  84. if (responseId != id)
  85. {
  86. throw new InvalidOperationException("Response id is not matched.");
  87. }
  88. if (response.Result is RpcException exception)
  89. {
  90. throw exception;
  91. }
  92. var resultString = (string)response.Result;
  93. using var utf8StringData = Utf8StringData.Get(resultString);
  94. return await JsonSerializer.DeserializeAsync<T>(utf8StringData.Stream, JsonRpcConvertSettings.SerializerOptions, cancellationToken).ConfigureAwait(false);
  95. }
  96. throw new InvalidOperationException("Fail to get invoke result from server.");
  97. }
  98. /// <summary>
  99. /// Invoke remote method without result.
  100. /// </summary>
  101. /// <param name="serviceName">The name of the service</param>
  102. /// <param name="methodName">The method name to call.</param>
  103. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  104. /// <param name="args">The parameters of the method.</param>
  105. /// <returns>Void</returns>
  106. public async Task VoidInvokeAsync(string serviceName, string methodName, CancellationToken cancellationToken, params object[] args)
  107. {
  108. var id = Interlocked.Increment(ref _requestId);
  109. var request = new JsonRpcRequest(id, methodName, new JsonRpcRequestParameter(RequestParameterType.Object, args));
  110. var requestData = await JsonRpcCodec.EncodeRequestsAsync(new[] { request }, cancellationToken).ConfigureAwait(false);
  111. var responseData = await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false);
  112. var responses = await JsonRpcCodec.DecodeResponsesAsync(responseData, cancellationToken).ConfigureAwait(false);
  113. if (responses.Length > 0)
  114. {
  115. var response = responses[0];
  116. var responseId = Convert.ToInt32(response.Id);
  117. if (responseId != id)
  118. {
  119. throw new InvalidOperationException("Response id is not matched.");
  120. }
  121. if (response.Result is RpcException exception)
  122. {
  123. throw exception;
  124. }
  125. var resultString = (string)response.Result;
  126. if (resultString != "null")
  127. {
  128. throw new InvalidOperationException("The result from server is not [null]");
  129. }
  130. }
  131. else
  132. {
  133. throw new InvalidOperationException("Fail to get invoke result from server.");
  134. }
  135. }
  136. /// <summary>
  137. /// Process a string request which contains the json data.
  138. /// </summary>
  139. /// <param name="serviceName">The name of the service.</param>
  140. /// <param name="requestString">The request string</param>
  141. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  142. /// <returns>The response string.</returns>
  143. public async Task<string> ProcessAsync(string serviceName, string requestString, CancellationToken cancellationToken = default)
  144. {
  145. if (_engine == null) throw new NullReferenceException("The engine is null.");
  146. return await _engine.ProcessAsync(serviceName, requestString, cancellationToken).ConfigureAwait(false);
  147. }
  148. /// <summary>
  149. /// Process a byte[] request which contains the json data.
  150. /// </summary>
  151. /// <param name="serviceName">The name of the service.</param>
  152. /// <param name="requestData">The request data</param>
  153. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  154. /// <returns>The response data.</returns>
  155. public async Task<byte[]> ProcessAsync(string serviceName, byte[] requestData, CancellationToken cancellationToken = default)
  156. {
  157. if (_engine == null) throw new NullReferenceException("The engine is null.");
  158. return await _engine.ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false);
  159. }
  160. /// <summary>
  161. /// Process a string request which contains the json data, return nothing for benchmark..
  162. /// </summary>
  163. /// <param name="serviceName">The name of the service.</param>
  164. /// <param name="requestString">The request string</param>
  165. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  166. /// <returns>Void</returns>
  167. public async Task BenchmarkAsync(string serviceName, string requestString, CancellationToken cancellationToken = default)
  168. {
  169. await ProcessAsync(serviceName, requestString, cancellationToken).ConfigureAwait(false);
  170. }
  171. /// <summary>
  172. /// Process a string request which contains the json data, return nothing for benchmark..
  173. /// </summary>
  174. /// <param name="serviceName">The name of the service.</param>
  175. /// <param name="requestData">The request data</param>
  176. /// <param name="cancellationToken">The cancellation token which can cancel this method.</param>
  177. /// <returns>Void</returns>
  178. public async Task BenchmarkAsync(string serviceName, byte[] requestData, CancellationToken cancellationToken = default)
  179. {
  180. await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false);
  181. }
  182. /// <summary>
  183. /// Close and release resource of the client.
  184. /// </summary>
  185. public void Dispose()
  186. {
  187. DisposeAsync().AsTask().Wait();
  188. }
  189. /// <summary>
  190. /// Close and release resource of the client.
  191. /// </summary>
  192. /// <returns>Void</returns>
  193. public async ValueTask DisposeAsync()
  194. {
  195. if (!_disposed)
  196. {
  197. if (_engine != null)
  198. {
  199. await _engine.CloseAsync().ConfigureAwait(false);
  200. }
  201. _disposed = true;
  202. GC.SuppressFinalize(this);
  203. }
  204. }
  205. }
  206. }