You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

759 lines
27 KiB

3 years ago
11 months ago
3 years ago
2 years ago
11 months ago
2 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
2 years ago
11 months ago
3 years ago
2 years ago
11 months ago
2 years ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
11 months ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
11 months ago
3 years ago
11 months ago
11 months ago
9 months ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
3 years ago
11 months ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
11 months ago
3 years ago
11 months ago
11 months ago
3 years ago
11 months ago
11 months ago
3 years ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
2 years ago
3 years ago
11 months ago
2 years ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
3 years ago
3 years ago
3 years ago
11 months ago
3 years ago
2 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
11 months ago
3 years ago
11 months ago
11 months ago
3 years ago
11 months ago
11 months ago
3 years ago
2 years ago
3 years ago
  1. using Apewer.Network;
  2. using Apewer.Source;
  3. using System;
  4. using System.Net;
  5. using System.Reflection;
  6. using static Apewer.Web.ApiUtility;
  7. namespace Apewer.Web
  8. {
  9. internal class ApiProcessor
  10. {
  11. private ApiContext _context = null;
  12. internal ApiProcessor(ApiContext context) => _context = context ?? throw new ArgumentNullException(nameof(context));
  13. #region prepare
  14. /// <summary>执行处理程序,返回错误信息。</summary>
  15. public void Run()
  16. {
  17. var url = null as Uri;
  18. var method = HttpMethod.NULL;
  19. var response = null as ApiResponse;
  20. try
  21. {
  22. // 检查执行的前提条件,获取 Method 和 URL。
  23. var check = Check(ref method, ref url);
  24. if (!string.IsNullOrEmpty(check))
  25. {
  26. Logger.Internals.Error(typeof(ApiInvoker), check);
  27. return;
  28. }
  29. // 准备请求模型。
  30. var request = GetRequest(_context.Provider, _context.Options, method, url);
  31. _context.Request = request;
  32. // 准备响应模型。
  33. response = new ApiResponse();
  34. response.Random = request.Random;
  35. response.Application = request.Application;
  36. response.Function = request.Function;
  37. _context.Response = response;
  38. // 调用 API。
  39. Invoke();
  40. }
  41. catch (Exception ex)
  42. {
  43. var message = ex.Message();
  44. Logger.Internals.Error(typeof(ApiInvoker), message);
  45. }
  46. finally
  47. {
  48. // 输出。
  49. if (response != null)
  50. {
  51. try
  52. {
  53. response.Duration = Duration(_context.Beginning);
  54. Output(_context.Provider, _context.Options, response, null, method);
  55. }
  56. catch { }
  57. finally
  58. {
  59. RuntimeUtility.Dispose(response.Model);
  60. }
  61. }
  62. }
  63. }
  64. static string Duration(DateTime beginning)
  65. {
  66. var span = DateTime.Now - beginning;
  67. var ms = span.TotalMilliseconds;
  68. if (ms < 1000) return Math.Round(ms, 0).ToString() + "ms";
  69. if (ms < 10000) return Math.Round(ms / 1000, 2).ToString() + "s";
  70. if (ms < 60000) return Math.Round(ms / 1000, 1).ToString() + "s";
  71. return Math.Round(ms / 1000, 0).ToString() + "s";
  72. }
  73. string Check(ref HttpMethod method, ref Uri url)
  74. {
  75. // 服务程序检查。
  76. var check = _context.Provider.PreInvoke();
  77. if (!string.IsNullOrEmpty(check)) return check;
  78. // URL
  79. url = _context.Provider.GetUrl();
  80. if (url == null) return "URL 无效。";
  81. // Method
  82. method = _context.Provider.GetMethod();
  83. switch (method)
  84. {
  85. case HttpMethod.NULL:
  86. return "HTTP 方法无效。";
  87. case HttpMethod.OPTIONS:
  88. if (!_context.Options.AllowOptions) return null;
  89. break;
  90. }
  91. // favicon.ico
  92. var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(url.AbsolutePath), "/");
  93. if (!_context.Options.AllowFavIcon)
  94. {
  95. if (lowerPath.StartsWith("/favicon.ico"))
  96. {
  97. Output(_context.Provider, _context.Options, null, null, null);
  98. return "已取消对 favicon.ico 的请求。";
  99. }
  100. }
  101. // robots.txt
  102. if (!_context.Options.AllowRobots)
  103. {
  104. if (lowerPath.StartsWith("/robots.txt"))
  105. {
  106. const string text = "User-agent: *\nDisallow: / \n";
  107. Output(_context.Provider, _context.Options, null, "text/plain", TextUtility.Bytes(text));
  108. return "已取消对 robots.txt 的请求。";
  109. }
  110. }
  111. return null;
  112. }
  113. // 寻找入口。
  114. void Invoke()
  115. {
  116. // OPTIONS
  117. if (_context.Request.Method == HttpMethod.OPTIONS)
  118. {
  119. if (!_context.Options.AllowOptions)
  120. {
  121. _context.Response.Model = new ApiTextModel("");
  122. return;
  123. }
  124. }
  125. // 路由
  126. if (_context.Options.UseRoute)
  127. {
  128. var path = _context?.Request?.Url?.AbsolutePath;
  129. path = path.TrimEnd('/');
  130. var action = _context.Entries.GetAction(path);
  131. if (action != null)
  132. {
  133. _context.ApiAction = action;
  134. Invoke(action);
  135. return;
  136. }
  137. }
  138. // 反射
  139. if (_context.Options.UseReflection)
  140. {
  141. var appName = _context.Request.Application;
  142. var application = _context.Entries.GetApplication(appName);
  143. Invoke(application);
  144. return;
  145. }
  146. // 未匹配到
  147. _context.Response.Duration = Duration(_context.Beginning);
  148. _context.Response.Model = new ApiStatusModel(404);
  149. }
  150. #endregion
  151. #region common
  152. static Type Void = typeof(void);
  153. // 创建控制器实例
  154. static ApiController CreateController(Type type, ApiContext context)
  155. {
  156. var controller = (ApiController)Activator.CreateInstance(type);
  157. ApiUtility.SetContext(controller, context);
  158. return controller;
  159. }
  160. static void Invoke(ApiContext context, MethodInfo method, ApiParameter[] parameters)
  161. {
  162. context.MethodInfo = method;
  163. // 调用。
  164. var parametersValue = ReadParameters(context.Request, parameters);
  165. var controller = context.Controller;
  166. var returnValue = method.Invoke(controller, parametersValue);
  167. // 程序要求停止输出。
  168. var response = context.Response;
  169. if (response.StopReturn) return;
  170. // 已经有了返回模型。
  171. if (response.Model != null) return;
  172. // 没有返回类型。
  173. var returnType = method.ReturnType;
  174. if (returnType == null || returnType.Equals(Void)) return;
  175. // 已明确字符串类型。
  176. if (returnType.Equals(typeof(string)))
  177. {
  178. var textValue = returnValue as string;
  179. var textRenderer = context.Options.TextRenderer;
  180. if (textRenderer != null)
  181. {
  182. textRenderer.Invoke(context, textValue);
  183. return;
  184. }
  185. // 默认视为提示错误
  186. if (!string.IsNullOrEmpty(textValue)) response.Error(textValue);
  187. return;
  188. }
  189. // 已明确 Exception 类型,视为提示错误。
  190. if (returnValue is Exception)
  191. {
  192. ApiUtility.Exception(response, returnValue as Exception);
  193. return;
  194. }
  195. // 已明确 Json 类型。
  196. if (returnValue is Json json)
  197. {
  198. var renderer = context.Options.JsonRenderer;
  199. if (renderer != null)
  200. {
  201. renderer.Invoke(context, json);
  202. return;
  203. }
  204. // 默认设置到 data 属性。
  205. response.Data = json;
  206. return;
  207. }
  208. // 已明确 Model 类型。
  209. if (returnValue is IApiModel model)
  210. {
  211. response.Model = model;
  212. return;
  213. }
  214. // 已明确 Result 类型。
  215. if (returnValue is IActionResult result)
  216. {
  217. response.Model = result;
  218. return;
  219. }
  220. // 类型未知,尝试 ToJson 方法。
  221. if (returnValue is IToJson toJson)
  222. {
  223. var tojson = toJson.ToJson();
  224. var renderer = context.Options.JsonRenderer;
  225. if (renderer != null)
  226. {
  227. renderer.Invoke(context, tojson);
  228. return;
  229. }
  230. response.Data = tojson;
  231. return;
  232. }
  233. // 未知返回类型,尝试使用默认渲染器。
  234. var defaultRenderer = context.Options.DefaultRenderer;
  235. if (defaultRenderer != null) defaultRenderer.Invoke(context, returnValue);
  236. }
  237. #endregion
  238. #region route
  239. // 执行 Action。
  240. void Invoke(ApiAction action)
  241. {
  242. var controller = null as ApiController;
  243. try
  244. {
  245. // 准备控制器。
  246. controller = CreateController(action.Type, _context);
  247. // 准备参数。
  248. var parameters = action.Parameters;
  249. var values = ReadParameters(_context.Request, parameters);
  250. // 调用。
  251. _context.Controller = controller;
  252. Invoke(_context, action.MethodInfo, action.Parameters);
  253. }
  254. catch (Exception ex)
  255. {
  256. if (ex.InnerException != null) ex = ex.InnerException;
  257. ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);
  258. var catcher = _context.Invoker.Catcher;
  259. if (catcher != null)
  260. {
  261. try
  262. {
  263. var apiCatch = new ApiCatch(_context, ex);
  264. catcher.Invoke(apiCatch);
  265. }
  266. catch { }
  267. }
  268. }
  269. finally
  270. {
  271. RuntimeUtility.Dispose(controller);
  272. }
  273. }
  274. #endregion
  275. #region reflection
  276. // 创建控制器。
  277. void Invoke(ApiApplication application)
  278. {
  279. var options = _context.Options;
  280. var entries = _context.Entries;
  281. var request = _context.Request;
  282. var response = _context.Response;
  283. // Application 无效,尝试默认控制器和枚举。
  284. if (application == null)
  285. {
  286. var @default = options.Default;
  287. if (@default == null)
  288. {
  289. // 没有指定默认控制器,尝试枚举。
  290. response.Status = "notfound";
  291. response.Message = "Not Found";
  292. if (options.AllowEnumerate) response.Data = Enumerate(entries.Applications, options);
  293. return;
  294. }
  295. else
  296. {
  297. // 创建默认控制器。
  298. var controller = null as ApiController;
  299. try
  300. {
  301. controller = CreateController(@default, _context);
  302. Invoke(controller, application, null, options, request, response);
  303. }
  304. catch (Exception ex)
  305. {
  306. ApiUtility.Exception(response, ex.InnerException ?? ex);
  307. }
  308. finally
  309. {
  310. RuntimeUtility.Dispose(controller);
  311. }
  312. }
  313. }
  314. else
  315. {
  316. // 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。
  317. var function = application.GetFunction(request.Function);
  318. var controller = null as ApiController;
  319. try
  320. {
  321. controller = CreateController(application.Type, _context);
  322. Invoke(controller, application, function, options, request, response);
  323. }
  324. catch (Exception ex)
  325. {
  326. ApiUtility.Exception(response, ex.InnerException ?? ex);
  327. }
  328. finally
  329. {
  330. RuntimeUtility.Dispose(controller);
  331. }
  332. }
  333. }
  334. // 调用 Function。
  335. void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response)
  336. {
  337. try
  338. {
  339. // 控制器初始化。
  340. var initializer = ApiUtility.GetInitialier(controller);
  341. var match = initializer == null ? true : initializer.Invoke(controller);
  342. if (!match) return;
  343. if (application.Independent) return;
  344. if (function != null)
  345. {
  346. // 调用 API,获取返回值。
  347. _context.Controller = controller;
  348. Invoke(_context, function.Method, function.Parameters);
  349. }
  350. else
  351. {
  352. // 未匹配到 Function,尝试 Default。
  353. var @default = ApiUtility.GetDefault(controller);
  354. if (@default != null)
  355. {
  356. @default.Invoke(controller);
  357. return;
  358. }
  359. // 没有执行任何 Function,尝试枚举。
  360. response.Status = "notfound";
  361. if (application.Hidden)
  362. {
  363. response.Message = "Not Found";
  364. }
  365. else
  366. {
  367. response.Message = "Not Found";
  368. if (options.AllowEnumerate) response.Data = Enumerate(application.Functions, options);
  369. }
  370. }
  371. }
  372. catch (Exception ex)
  373. {
  374. if (ex.InnerException != null) ex = ex.InnerException;
  375. ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);
  376. var catcher = _context.Invoker.Catcher;
  377. if (catcher != null)
  378. {
  379. try
  380. {
  381. var apiCatch = new ApiCatch(_context, ex);
  382. catcher.Invoke(apiCatch);
  383. }
  384. catch { }
  385. }
  386. }
  387. }
  388. #endregion
  389. #region static
  390. internal static ApiRequest GetRequest(ApiProvider provider, ApiOptions options, HttpMethod method, Uri url)
  391. {
  392. // 创建数据对象。
  393. var request = new ApiRequest();
  394. // Http Method。
  395. request.Method = method;
  396. // 基本信息。
  397. var ip = provider.GetClientIP();
  398. var headers = provider.GetHeaders() ?? new HttpHeaders();
  399. request.Headers = headers;
  400. request.IP = ip;
  401. request.Url = url;
  402. request.Referrer = provider.GetReferrer();
  403. request.Parameters = ApiUtility.Parameters(url.Query);
  404. // Headers。
  405. request.UserAgent = ApiUtility.UserAgent(headers);
  406. request.Cookies = ParseCookies(headers) ?? new CookieCollection();
  407. // 匹配 API。
  408. var application = null as string;
  409. var function = null as string;
  410. var random = null as string;
  411. var ticket = null as string;
  412. var session = null as string;
  413. var page = null as string;
  414. // 解析 POST 请求。
  415. switch (request.Method)
  416. {
  417. case HttpMethod.PATCH:
  418. case HttpMethod.POST:
  419. case HttpMethod.PUT:
  420. var preRead = provider.PreRead();
  421. if (string.IsNullOrEmpty(preRead))
  422. {
  423. var post = null as byte[];
  424. var length = 0L;
  425. var max = options.MaxRequestBody;
  426. if (max == 0) post = new byte[0];
  427. else if (max < 0) post = provider.RequestBody().Read();
  428. else
  429. {
  430. length = provider.GetContentLength();
  431. if (length <= max) post = provider.RequestBody().Read();
  432. }
  433. length = post == null ? 0 : post.Length;
  434. if (length > 1)
  435. {
  436. request.PostData = post;
  437. if (length < 104857600)
  438. {
  439. var text = TextUtility.FromBytes(post);
  440. request.PostText = text;
  441. // 尝试解析 Json,首尾必须是“{}”或“[]”。
  442. var first = post[0];
  443. var last = post[length - 1];
  444. if ((first == 123 && last == 125) || (first == 91 && last == 93))
  445. {
  446. var json = Json.From(text);
  447. if (json != null && json.IsObject)
  448. {
  449. application = json["application"];
  450. function = json["function"];
  451. random = json["random"];
  452. ticket = json["ticket"];
  453. session = json["session"];
  454. page = json["page"];
  455. var data = json.GetProperty("data");
  456. request.PostJson = json;
  457. request.Data = data ?? Json.NewObject();
  458. }
  459. }
  460. // 尝试解析 Form,需要 application/x-www-form-urlencoded
  461. var contentType = headers.GetValue("Content-Type") ?? "";
  462. if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text);
  463. }
  464. }
  465. }
  466. break;
  467. }
  468. // 解析 URL 参数。
  469. // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
  470. var urlParameters = ApiUtility.Parameters(request.Url.Query);
  471. if (string.IsNullOrEmpty(application)) application = urlParameters.GetValue("application");
  472. if (string.IsNullOrEmpty(function)) function = urlParameters.GetValue("function");
  473. if (string.IsNullOrEmpty(random)) random = urlParameters.GetValue("random");
  474. if (string.IsNullOrEmpty(ticket)) ticket = urlParameters.GetValue("ticket");
  475. if (string.IsNullOrEmpty(session)) session = urlParameters.GetValue("session");
  476. if (string.IsNullOrEmpty(page)) page = urlParameters.GetValue("page");
  477. // 从 Cookie 中获取 Ticket。
  478. var cookies = request.Cookies;
  479. if (string.IsNullOrEmpty(ticket)) ticket = cookies.GetValue("ticket");
  480. // 最后检查 URL 路径。
  481. var paths = (request.Url.AbsolutePath ?? "").Split('/');
  482. if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
  483. if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
  484. // 修正内容。
  485. application = TextUtility.Trim(application);
  486. function = TextUtility.Trim(function);
  487. random = TextUtility.Trim(random);
  488. ticket = TextUtility.Trim(ticket);
  489. session = TextUtility.Trim(session);
  490. page = TextUtility.Trim(page);
  491. // 设置请求:回传。
  492. request.Application = application;
  493. request.Function = function;
  494. request.Random = random;
  495. // 设置请求:不回传。
  496. request.Ticket = ticket;
  497. request.Session = session;
  498. request.Page = page;
  499. return request;
  500. }
  501. static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response, ApiRequest request = null)
  502. {
  503. var merged = new StringPairs();
  504. if (options != null)
  505. {
  506. // 跨域访问。
  507. if (options.WithAccessControl)
  508. {
  509. merged.Add("Access-Control-Allow-Headers", "Content-Type");
  510. merged.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  511. merged.Add("Access-Control-Allow-Origin", "*");
  512. var maxage = options.AccessControlMaxAge;
  513. if (maxage > 0) merged.Add("Access-Control-Max-Age", maxage.ToString());
  514. if (request != null && request.Headers != null)
  515. {
  516. var @private = request.Headers.GetValue("Access-Control-Request-Private-Network");
  517. if (NumberUtility.Boolean(@private)) merged.Add("Access-Control-Allow-Private-Network", "true");
  518. }
  519. }
  520. // Content-Type 检查。
  521. if (options.WithContentTypeOptions)
  522. {
  523. merged.Add("X-Content-Type-Options", "nosniff");
  524. }
  525. // 用于客户端,当前页面使用 HTTPS 时,将资源升级为 HTTPS。
  526. if (options.UpgradeHttps)
  527. {
  528. merged.Add("Content-Security-Policy", "upgrade-insecure-requests");
  529. }
  530. // 包含 API 的处理时间。
  531. if (options.WithDuration && response != null)
  532. {
  533. if (response.Duration.NotEmpty()) merged.Add("Duration", response.Duration);
  534. }
  535. }
  536. if (response != null)
  537. {
  538. // Cookies。
  539. var setCookies = SetCookie(response.Cookies);
  540. if (setCookies != null)
  541. {
  542. foreach (var value in setCookies) merged.Add("Set-Cookie", value);
  543. }
  544. // 自定义头。
  545. var headers = response.Headers;
  546. if (headers != null)
  547. {
  548. foreach (var header in headers)
  549. {
  550. var key = TextUtility.Trim(header.Name);
  551. if (string.IsNullOrEmpty(key)) continue;
  552. var value = header.Value;
  553. if (string.IsNullOrEmpty(value)) continue;
  554. merged.Add(key, value);
  555. }
  556. }
  557. }
  558. return merged;
  559. }
  560. internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes)
  561. {
  562. var preWrite = provider.PreWrite();
  563. if (!string.IsNullOrEmpty(preWrite)) return;
  564. if (response != null)
  565. {
  566. var responsePreOutput = response.PreOutput;
  567. if (responsePreOutput != null)
  568. {
  569. var @continue = responsePreOutput.Invoke(_context);
  570. if (!@continue) return;
  571. }
  572. }
  573. var invokerPreOutput = _context.Invoker.PreOutput;
  574. if (invokerPreOutput != null)
  575. {
  576. var @continue = invokerPreOutput.Invoke(_context);
  577. if (!@continue) return;
  578. }
  579. var optionsPreOutput = _context.Options.PreOutput;
  580. if (optionsPreOutput != null)
  581. {
  582. var @continue = optionsPreOutput.Invoke(_context);
  583. if (!@continue) return;
  584. }
  585. var headers = PrepareHeaders(options, response);
  586. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  587. provider.SetCache(0);
  588. provider.SetContentType(string.IsNullOrEmpty(type) ? "application/octet-stream" : type);
  589. var length = bytes == null ? 0 : bytes.Length;
  590. provider.SetContentLength(length);
  591. if (length > 0) provider.ResponseBody().Write(bytes, 0, bytes.Length);
  592. provider.Sent();
  593. }
  594. internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
  595. {
  596. var preWrite = provider.PreWrite();
  597. if (!string.IsNullOrEmpty(preWrite)) return;
  598. if (response != null)
  599. {
  600. var responsePreOutput = response.PreOutput;
  601. if (responsePreOutput != null)
  602. {
  603. var @continue = responsePreOutput.Invoke(_context);
  604. if (!@continue) return;
  605. }
  606. }
  607. var invokerPreOutput = _context.Invoker.PreOutput;
  608. if (invokerPreOutput != null)
  609. {
  610. var @continue = invokerPreOutput.Invoke(_context);
  611. if (!@continue) return;
  612. }
  613. var optionsPreOutput = _context.Options.PreOutput;
  614. if (optionsPreOutput != null)
  615. {
  616. var @continue = optionsPreOutput.Invoke(_context);
  617. if (!@continue) return;
  618. }
  619. // 设置头。
  620. var headers = PrepareHeaders(options, response, request);
  621. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  622. // 自定义模型
  623. var model = response.Model as IApiModel;
  624. var result = response.Model as IActionResult;
  625. if (model != null)
  626. {
  627. try
  628. {
  629. model.Output(_context);
  630. }
  631. catch (Exception ex)
  632. {
  633. Logger.Internals.Exception(ex, model);
  634. }
  635. RuntimeUtility.Dispose(model);
  636. return;
  637. }
  638. else if (result != null)
  639. {
  640. try
  641. {
  642. result.ExecuteResult(_context);
  643. }
  644. catch (Exception ex)
  645. {
  646. Logger.Internals.Exception(ex, result);
  647. }
  648. RuntimeUtility.Dispose(result);
  649. return;
  650. }
  651. var text = ApiUtility.ToJson(response, options);
  652. var bytes = TextUtility.Bytes(text);
  653. provider.SetCache(0);
  654. provider.SetContentType("text/json; charset=utf-8");
  655. provider.SetContentLength(bytes.Length);
  656. var stream = provider.ResponseBody();
  657. if (stream != null && stream.CanWrite) stream.Write(bytes, 0, bytes.Length);
  658. provider.Sent();
  659. }
  660. #endregion
  661. }
  662. }