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.

744 lines
27 KiB

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