RpcHttpRouter.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using EdjCase.JsonRpc.Core;
  8. using EdjCase.JsonRpc.Router.Abstractions;
  9. using Microsoft.AspNetCore.Routing;
  10. using Microsoft.Extensions.Logging;
  11. using Newtonsoft.Json;
  12. using EdjCase.JsonRpc.Router.Utilities;
  13. using Microsoft.Extensions.Options;
  14. using EdjCase.JsonRpc.Router.Defaults;
  15. using Microsoft.AspNetCore.Http;
  16. using EdjCase.JsonRpc.Core.Tools;
  17. using Microsoft.Extensions.DependencyInjection;
  18. namespace EdjCase.JsonRpc.Router
  19. {
  20. /// <summary>
  21. /// Router for Asp.Net to direct Http Rpc requests to the correct method, invoke it and return the proper response
  22. /// </summary>
  23. public class RpcHttpRouter : IRouter
  24. {
  25. /// <summary>
  26. /// Provider that allows the retrieval of all configured routes
  27. /// </summary>
  28. private IRpcRouteProvider routeProvider { get; }
  29. /// <param name="routeProvider">Provider that allows the retrieval of all configured routes</param>
  30. public RpcHttpRouter(IRpcRouteProvider routeProvider)
  31. {
  32. this.routeProvider = routeProvider ?? throw new ArgumentNullException(nameof(routeProvider));
  33. }
  34. /// <summary>
  35. /// Generates the virtual path data for the router
  36. /// </summary>
  37. /// <param name="context">Virtual path context</param>
  38. /// <returns>Virtual path data for the router</returns>
  39. public VirtualPathData GetVirtualPath(VirtualPathContext context)
  40. {
  41. // We return null here because we're not responsible for generating the url, the route is.
  42. return null;
  43. }
  44. /// <summary>
  45. /// Takes a route/http contexts and attempts to parse, invoke, respond to an Rpc request
  46. /// </summary>
  47. /// <param name="context">Route context</param>
  48. /// <returns>Task for async routing</returns>
  49. public async Task RouteAsync(RouteContext context)
  50. {
  51. ILogger<RpcHttpRouter> logger = context.HttpContext.RequestServices.GetService<ILogger<RpcHttpRouter>>();
  52. try
  53. {
  54. RpcPath requestPath;
  55. if (!context.HttpContext.Request.Path.HasValue)
  56. {
  57. requestPath = RpcPath.Default;
  58. }
  59. else
  60. {
  61. if (!RpcPath.TryParse(context.HttpContext.Request.Path.Value, out requestPath))
  62. {
  63. logger?.LogInformation($"Could not parse the path '{context.HttpContext.Request.Path.Value}' for the " +
  64. $"request into an rpc path. Skipping rpc router middleware.");
  65. return;
  66. }
  67. }
  68. if (!requestPath.TryRemoveBasePath(this.routeProvider.BaseRequestPath, out requestPath))
  69. {
  70. logger?.LogTrace("Request did not match the base request path. Skipping rpc router.");
  71. return;
  72. }
  73. logger?.LogInformation($"Rpc request with route '{requestPath}' started.");
  74. string jsonString;
  75. if (context.HttpContext.Request.Body == null)
  76. {
  77. jsonString = null;
  78. }
  79. else
  80. {
  81. using (StreamReader streamReader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8,
  82. detectEncodingFromByteOrderMarks: true,
  83. bufferSize: 1024,
  84. leaveOpen: true))
  85. {
  86. try
  87. {
  88. jsonString = await streamReader.ReadToEndAsync();
  89. }
  90. catch (TaskCanceledException ex)
  91. {
  92. throw new RpcCanceledRequestException("Cancelled while reading the request.", ex);
  93. }
  94. jsonString = jsonString.Trim();
  95. }
  96. }
  97. IRpcRequestHandler requestHandler = context.HttpContext.RequestServices.GetRequiredService<IRpcRequestHandler>();
  98. var routeContext = DefaultRouteContext.FromHttpContext(context.HttpContext, this.routeProvider);
  99. string responseJson = await requestHandler.HandleRequestAsync(requestPath, jsonString, routeContext);
  100. if (responseJson == null)
  101. {
  102. //No response required, but status code must be 204
  103. context.HttpContext.Response.StatusCode = 204;
  104. context.MarkAsHandled();
  105. return;
  106. }
  107. context.HttpContext.Response.ContentType = "application/json";
  108. bool responseSet = false;
  109. string acceptEncoding = context.HttpContext.Request.Headers["Accept-Encoding"];
  110. if (!string.IsNullOrWhiteSpace(acceptEncoding))
  111. {
  112. IStreamCompressor compressor = context.HttpContext.RequestServices.GetService<IStreamCompressor>();
  113. if (compressor != null)
  114. {
  115. string[] encodings = acceptEncoding.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
  116. foreach (string encoding in encodings)
  117. {
  118. bool haveType = Enum.TryParse(encoding, true, out CompressionType compressionType);
  119. if (!haveType)
  120. {
  121. continue;
  122. }
  123. context.HttpContext.Response.Headers.Add("Content-Encoding", new[] { encoding });
  124. using (Stream responseStream = new MemoryStream(Encoding.UTF8.GetBytes(responseJson)))
  125. {
  126. compressor.Compress(responseStream, context.HttpContext.Response.Body, compressionType);
  127. }
  128. responseSet = true;
  129. break;
  130. }
  131. }
  132. }
  133. if (!responseSet)
  134. {
  135. await context.HttpContext.Response.WriteAsync(responseJson);
  136. }
  137. context.MarkAsHandled();
  138. logger?.LogInformation("Rpc request complete");
  139. }
  140. catch (Exception ex)
  141. {
  142. string errorMessage = "Unknown exception occurred when trying to process Rpc request. Marking route unhandled";
  143. logger?.LogException(ex, errorMessage);
  144. context.MarkAsHandled();
  145. }
  146. }
  147. }
  148. }