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.

412 lines
13 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Net;
  5. using System.Text;
  6. namespace Apewer.Web
  7. {
  8. /// <summary>Response 模型。</summary>
  9. public abstract class ApiModel : IToJson
  10. {
  11. #region
  12. private int _expires = 0;
  13. internal ApiRequest _request;
  14. internal ApiResponse _response;
  15. internal ApiOptions _options;
  16. internal ApiProvider _provider;
  17. static int SafeExpires(int seconds)
  18. {
  19. var s = seconds;
  20. if (s < 0) s = 0;
  21. if (s > 2592000) s = 2592000;
  22. return s;
  23. }
  24. #endregion
  25. #region 内部属性和方法。
  26. /// <summary>处理当前模型的 API 请求。</summary>
  27. protected ApiRequest Request { get => _request; }
  28. /// <summary>处理当前模型的 API 响应。</summary>
  29. protected ApiResponse Response { get => Response; }
  30. /// <summary>处理当前模型的 API 选项。</summary>
  31. protected ApiOptions Options { get => _options; }
  32. /// <summary>处理当前模型的服务程序实例。</summary>
  33. protected ApiProvider Provider { get => _provider; }
  34. /// <summary>在 Response 头中添加用于设置文件名的属性。</summary>
  35. protected void SetAttachment()
  36. {
  37. if (_provider == null) return;
  38. var name = Attachment;
  39. if (string.IsNullOrEmpty(name)) return;
  40. var encoded = TextUtility.EncodeUrl(name);
  41. _provider.SetHeader("Content-Disposition", $"attachment; filename={encoded}");
  42. }
  43. private List<string> WriteHeader()
  44. {
  45. if (_provider == null) return null;
  46. var status = Status > 0 ? Status : 200;
  47. if (status != 200) _provider.SetStatus(status);
  48. var headers = Headers;
  49. var added = new List<string>(32);
  50. if (headers != null)
  51. {
  52. foreach (var header in headers)
  53. {
  54. if (header.Key.IsEmpty()) continue;
  55. if (header.Value.IsEmpty()) continue;
  56. _provider.SetHeader(header.Key, header.Value);
  57. added.Add(header.Key.Lower());
  58. }
  59. }
  60. SetAttachment();
  61. _provider.SetCache(Expires);
  62. if (!added.Contains("content-type")) _provider.SetContentType(ContentType);
  63. return added;
  64. }
  65. /// <summary>以指定参数输出。</summary>
  66. protected void Output(byte[] bytes)
  67. {
  68. if (_provider == null) return;
  69. if (_provider.PreWrite().NotEmpty()) return;
  70. var added = WriteHeader();
  71. var length = bytes == null ? 0 : bytes.Length;
  72. if (!added.Contains("content-length")) _provider.SetContentLength(length);
  73. if (length > 0) _provider.ResponseBody().Write(bytes);
  74. _provider.Sent();
  75. }
  76. /// <summary>以指定参数输出。</summary>
  77. protected void Output(Stream stream, bool dispose)
  78. {
  79. if (_provider == null) return;
  80. if (_provider.PreWrite().NotEmpty()) return;
  81. var added = WriteHeader();
  82. if (!added.Contains("content-length"))
  83. {
  84. var length = stream.Length - stream.Position;
  85. _provider.SetContentLength(length);
  86. }
  87. _provider.ResponseBody().Write(stream);
  88. _provider.Sent();
  89. if (dispose) RuntimeUtility.Dispose(stream);
  90. }
  91. #endregion
  92. /// <summary>状态。</summary>
  93. /// <remarks>默认值:200。</remarks>
  94. public virtual int Status { get; set; }
  95. /// <summary>内容类型。当 Headers 中包含 Content-Type 时此属性将被忽略。</summary>
  96. public virtual string ContentType { get; set; }
  97. /// <summary>响应缓存的过期时间,以秒为单位。</summary>
  98. public virtual int Expires { get => _expires; set => _expires = SafeExpires(value); }
  99. /// <summary>设置文件名,告知客户端此附件处理此响应。</summary>
  100. public virtual string Attachment { get; set; }
  101. /// <summary>设置 Response 头。</summary>
  102. public virtual StringPairs Headers { get; set; }
  103. /// <summary>执行输出。</summary>
  104. /// <remarks>此方法由 API 调用器发起调用,用户程序不应主动调用。</remarks>
  105. /// <exception cref="InvalidOperationException"></exception>
  106. public abstract void Output();
  107. /// <summary>创建对象实例,并设置默认属性。</summary>
  108. public ApiModel()
  109. {
  110. Status = 200;
  111. ContentType = "application/octet-stream";
  112. Expires = 0;
  113. Attachment = null;
  114. Headers = new StringPairs();
  115. }
  116. /// <summary></summary>
  117. public Json ToJson()
  118. {
  119. var json = new Json();
  120. json.SetProperty("status", Status);
  121. json.SetProperty("content-type", ContentType);
  122. return json;
  123. }
  124. }
  125. /// <summary>输出二进制的 Response 模型。</summary>
  126. public class ApiBytesModel : ApiModel
  127. {
  128. /// <summary>向 Body 写入的字节数组。</summary>
  129. public byte[] Bytes { get; set; }
  130. /// <summary>输出字节数组。</summary>
  131. public override void Output() => Output(Bytes);
  132. /// <summary>创建对象实例,并设置默认属性。</summary>
  133. public ApiBytesModel(byte[] bytes = null, string contentType = "application/octet-stream")
  134. {
  135. Bytes = bytes;
  136. ContentType = contentType;
  137. }
  138. }
  139. /// <summary>输出二进制的 Response 模型。</summary>
  140. public class ApiStreamModel : ApiModel, IDisposable
  141. {
  142. /// <summary>将要读取的流,用于向 Body 写入。</summary>
  143. public Stream Stream { get; set; }
  144. /// <summary>执行输出后释放流。</summary>
  145. /// <remarks>默认值:TRUE。</remarks>
  146. public bool AutoDispose { get; set; }
  147. /// <summary>输出流。</summary>
  148. public override void Output() => Output(Stream, AutoDispose);
  149. /// <summary>当指定 AutoDispose 属性时释放流。</summary>
  150. public void Dispose()
  151. {
  152. if (AutoDispose) RuntimeUtility.Dispose(Stream);
  153. }
  154. /// <summary>创建对象实例,并设置默认属性。</summary>
  155. public ApiStreamModel(Stream stream = null, bool autoDispose = true)
  156. {
  157. Stream = stream;
  158. AutoDispose = autoDispose;
  159. }
  160. }
  161. /// <summary>输出二进制的 Response 模型。</summary>
  162. public class ApiFileModel : ApiModel
  163. {
  164. string _path;
  165. void SetPath(string path)
  166. {
  167. if (string.IsNullOrEmpty(path)) throw new FileNotFoundException("没有指定文件路径。");
  168. if (!File.Exists(path)) throw new FileNotFoundException($"文件 {path} 不存在。");
  169. _path = path;
  170. }
  171. /// <summary>将要读取的文件所在路径,用于向 Body 写入。</summary>
  172. /// <exception cref="FileNotFoundException"></exception>
  173. public string Path { get => _path; set => SetPath(value); }
  174. /// <summary>输出指定路径的文件。</summary>
  175. public override void Output()
  176. {
  177. try
  178. {
  179. if (!File.Exists(Path)) return;
  180. var info = new FileInfo(Path);
  181. if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name;
  182. using (var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read))
  183. {
  184. Output(stream, false);
  185. }
  186. }
  187. catch { }
  188. }
  189. /// <summary></summary>
  190. /// <exception cref="FileNotFoundException"></exception>
  191. public ApiFileModel(string path)
  192. {
  193. SetPath(path);
  194. }
  195. }
  196. /// <summary>输出文本的 Response 模型。</summary>
  197. public class ApiTextModel : ApiModel
  198. {
  199. /// <summary>自定义文本。</summary>
  200. public string Text { get; set; }
  201. /// <summary>输出文本。</summary>
  202. public override void Output() => Output(TextUtility.Bytes(Text));
  203. /// <summary>创建对象实例,并设置默认属性。</summary>
  204. public ApiTextModel(string text = null, string contentType = "text/plain")
  205. {
  206. ContentType = contentType;
  207. Text = text;
  208. }
  209. }
  210. /// <summary>输出文本的 Response 模型。</summary>
  211. public class ApiJsonModel : ApiModel
  212. {
  213. /// <summary>Json 对象。</summary>
  214. public Json Json { get; set; }
  215. /// <summary>缩进排版。</summary>
  216. /// <remarks>默认值:TRUE</remarks>
  217. public bool Indented { get; set; }
  218. /// <summary>转为属性名为驼峰形式。</summary>
  219. /// <remarks>默认值:FALSE</remarks>
  220. public bool Camel { get; set; }
  221. /// <summary>输出文本。</summary>
  222. public override void Output()
  223. {
  224. var json = (Json != null && Json.Available) ? Json : Json.NewObject();
  225. if (Camel) Json.Camel(json);
  226. Output(TextUtility.Bytes(json.ToString(Indented)));
  227. }
  228. /// <summary>创建对象实例,并设置默认属性。</summary>
  229. public ApiJsonModel(Json json = null, bool camel = false, bool indented = true)
  230. {
  231. ContentType = "application/json";
  232. Camel = camel;
  233. Indented = indented;
  234. Json = json;
  235. }
  236. }
  237. /// <summary>输出重定向的 Response 模型。</summary>
  238. public class ApiRedirectModel : ApiModel
  239. {
  240. /// <summary>将要重定向的位置。</summary>
  241. public string Location { get; set; }
  242. /// <summary>执行重定向。</summary>
  243. public override void Output()
  244. {
  245. var location = Location;
  246. if (string.IsNullOrEmpty(location)) return;
  247. if (Provider == null) return;
  248. Provider.SetRedirect(Location);
  249. }
  250. /// <summary>重定向到指定的 URL。</summary>
  251. public ApiRedirectModel(string location = null)
  252. {
  253. Location = location;
  254. }
  255. }
  256. /// <summary>输出说明 Exception 的 Response 模型。</summary>
  257. public class ApiExceptionModel : ApiModel
  258. {
  259. /// <summary>要输出的 Exception。</summary>
  260. public Exception Exception { get; set; }
  261. /// <summary>解析 Exception 的内容并输出。</summary>
  262. public override void Output()
  263. {
  264. Status = 500;
  265. ContentType = "text/plain";
  266. Output(ToString().Bytes());
  267. }
  268. /// <summary></summary>
  269. public ApiExceptionModel(Exception exception = null)
  270. {
  271. Exception = exception;
  272. }
  273. /// <summary></summary>
  274. public override string ToString()
  275. {
  276. var ex = Exception;
  277. var sb = new StringBuilder();
  278. if (ex == null)
  279. {
  280. sb.Append("Invalid Exception");
  281. }
  282. else
  283. {
  284. try
  285. {
  286. sb.Append(Exception.GetType().FullName);
  287. var props = ex.GetType().GetProperties();
  288. foreach (var prop in props)
  289. {
  290. var getter = prop.GetGetMethod();
  291. if (getter == null) continue;
  292. var value = getter.Invoke(ex, null);
  293. if (value == null) continue;
  294. sb.Append("\r\n\r\n");
  295. sb.Append(prop.Name);
  296. sb.Append(" : ");
  297. sb.Append(prop.PropertyType.FullName);
  298. sb.Append("\r\n");
  299. if (value is Json) sb.Append(((Json)value).ToString(true));
  300. else sb.Append(value.ToString() ?? "");
  301. }
  302. // sb.Append("\r\n\r\nToString\r\n");
  303. // sb.Append("\r\n");
  304. // sb.Append(ex.ToString());
  305. }
  306. catch { }
  307. }
  308. sb.Append("\r\n");
  309. var text = sb.ToString();
  310. return text;
  311. }
  312. }
  313. /// <summary>输出带有指定 Status 的 Response 模型。</summary>
  314. public class ApiStatusModel : ApiModel
  315. {
  316. /// <summary>向 Body 写入的字节数组。</summary>
  317. public byte[] Bytes { get; set; }
  318. /// <summary>执行重定向。</summary>
  319. public override void Output() => Output(Bytes);
  320. /// <summary></summary>
  321. public ApiStatusModel(int status = 200) => Status = status;
  322. /// <summary></summary>
  323. public ApiStatusModel(HttpStatusCode status) => Status = (int)status;
  324. }
  325. }