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.
856 lines
31 KiB
856 lines
31 KiB
using Apewer;
|
|
using Apewer.Models;
|
|
using Apewer.Network;
|
|
using Apewer.Source;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
#if NETFX
|
|
using System.Web;
|
|
#endif
|
|
|
|
#if NETCORE
|
|
using Microsoft.AspNetCore.Http;
|
|
#endif
|
|
|
|
namespace Apewer.Web
|
|
{
|
|
|
|
internal abstract class ApiProcessor
|
|
{
|
|
|
|
// 请求 Body 大于 100MB 时,不尝试解析 API。
|
|
const long MaxRequestBody = 100L * 1024L * 1024L;
|
|
|
|
#region fields & properties
|
|
|
|
internal ApiInvoker Invoker = null;
|
|
|
|
private DateTime Beginning = DateTime.Now;
|
|
private DateTime Ending;
|
|
|
|
private ApiRequest ApiRequest;
|
|
private ApiResponse ApiResponse;
|
|
|
|
public Dictionary<string, ApiApplication> Entries;
|
|
|
|
public bool HaveContext { get; protected set; }
|
|
|
|
// 抽象方法的返回值。
|
|
private Uri Url;
|
|
private HttpMethod Method;
|
|
|
|
protected abstract object GetContext();
|
|
|
|
protected abstract string GetClientIP();
|
|
protected abstract HttpMethod GetMethod();
|
|
protected abstract Uri GetURL();
|
|
protected abstract Uri GetReferrer();
|
|
protected abstract StringPairs GetHeaders();
|
|
protected abstract StringPairs GetCookies();
|
|
protected abstract long GetRequestLength();
|
|
protected abstract byte[] ReadRequest();
|
|
|
|
protected abstract void SetCacheControl(int seconds);
|
|
protected abstract void SetContentType(string value);
|
|
protected abstract void SetContentLength(long value);
|
|
protected abstract void Redirect(string url);
|
|
protected abstract string AddHeader(string name, string value);
|
|
|
|
protected abstract void WriteResponse(byte[] bytes);
|
|
protected abstract void WriteResponse(Stream stream);
|
|
|
|
#endregion
|
|
|
|
/// <summary>执行处理程序,返回错误信息。</summary>
|
|
public string Run()
|
|
{
|
|
try
|
|
{
|
|
// 检查依赖的属性,若不通过,则无法执行。
|
|
var preCheckError = PreCheck();
|
|
if (!string.IsNullOrEmpty(preCheckError)) return preCheckError;
|
|
|
|
// 准备变量。
|
|
ApiRequest = GetRequest();
|
|
ApiResponse = new ApiResponse();
|
|
|
|
// 准备向 Controller 传递的属性。
|
|
var an = ApiRequest.Application;
|
|
var a = null as ApiApplication;
|
|
var fn = ApiRequest.Function;
|
|
var f = null as ApiFunction;
|
|
var r = ApiRequest.Random;
|
|
var t = ApiRequest.Ticket;
|
|
|
|
// 调用。
|
|
if (Entries.ContainsKey(an))
|
|
{
|
|
a = Entries[an];
|
|
if (a.Functions.ContainsKey(fn))
|
|
{
|
|
f = a.Functions[fn];
|
|
Invoke(a, f, ApiRequest, ApiResponse);
|
|
}
|
|
else
|
|
{
|
|
Invoke(a, null, ApiRequest, ApiResponse);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var @default = ApiOptions.Default;
|
|
if (@default != null)
|
|
{
|
|
Invoke(ApiInvoker.GetApplication(@default, false), null, ApiRequest, ApiResponse);
|
|
}
|
|
else
|
|
{
|
|
ApiResponse.Error("指定的 Application 无效。");
|
|
if (an.IsEmpty() && ApiOptions.AllowEnumerate) Enumerate(Entries);
|
|
}
|
|
}
|
|
|
|
// 记录结束时间。
|
|
Ending = DateTime.Now;
|
|
|
|
// 调整响应。
|
|
ApiResponse.Beginning = Beginning;
|
|
ApiResponse.Ending = Ending;
|
|
ApiResponse.Application = an;
|
|
ApiResponse.Function = fn;
|
|
ApiResponse.Random = r;
|
|
|
|
// 向客户端输出。
|
|
return Output(ApiResponse);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Web.Exception(this, ex);
|
|
return ex.Message;
|
|
}
|
|
}
|
|
|
|
// 检查执行的前提条件。
|
|
string PreCheck()
|
|
{
|
|
// Context
|
|
if (!HaveContext) return "Context 无效。";
|
|
|
|
// Entries
|
|
if (Entries == null) return "Entries 无效。";
|
|
|
|
// Method
|
|
Method = GetMethod();
|
|
if (Method == HttpMethod.NULL) return "Methods 无效。";
|
|
|
|
// AccessControl
|
|
// 在此之后可以输出
|
|
if (ApiOptions.WithAccessControl)
|
|
{
|
|
AddHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
AddHeader("Access-Control-Allow-Origin", "*");
|
|
|
|
var maxage = ApiOptions.AccessControlMaxAge;
|
|
if (maxage > 0) AddHeader("Access-Control-Max-Age", maxage.ToString());
|
|
}
|
|
|
|
// URL
|
|
Url = GetURL();
|
|
var lowerPath = TextUtility.ToLower(Url.AbsolutePath);
|
|
|
|
// favicon.ico
|
|
if (!ApiOptions.AllowFavIcon)
|
|
{
|
|
if (lowerPath.StartsWith("/favicon.ico"))
|
|
{
|
|
SetCacheControl(0);
|
|
Output(null as byte[]);
|
|
return "已取消对 favicon.ico 的请求。";
|
|
}
|
|
}
|
|
|
|
// robots.txt
|
|
if (!ApiOptions.AllowRobots)
|
|
{
|
|
if (lowerPath.StartsWith("/robots.txt"))
|
|
{
|
|
const string text = "User-agent: *\nDisallow: / \n";
|
|
SetCacheControl(0);
|
|
Output(text);
|
|
return "已取消对 robots.txt 的请求。";
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void Enumerate(Dictionary<string, ApiApplication> applications)
|
|
{
|
|
var count = 0;
|
|
var list = Json.NewArray();
|
|
if (applications != null)
|
|
{
|
|
foreach (var i in applications)
|
|
{
|
|
if (!i.Value.Visible) continue;
|
|
var item = Json.NewObject();
|
|
item["name"] = i.Value.Name;
|
|
item["caption"] = i.Value.Caption;
|
|
if (ApiOptions.WithModuleName) item["module"] = i.Value.Module;
|
|
if (ApiOptions.WithTypeName) item["type"] = i.Value.Type.FullName;
|
|
list.AddItem(item);
|
|
count = count + 1;
|
|
}
|
|
}
|
|
ApiResponse.Data.Reset(Json.NewObject());
|
|
ApiResponse.Data.SetProperty("count", count);
|
|
ApiResponse.Data.SetProperty("list", list);
|
|
}
|
|
|
|
void Enumerate(Dictionary<string, ApiFunction> functions)
|
|
{
|
|
var count = 0;
|
|
var list = Json.NewArray();
|
|
if (functions != null)
|
|
{
|
|
foreach (var i in functions)
|
|
{
|
|
if (!i.Value.Visible) continue;
|
|
var item = Json.NewObject();
|
|
item["name"] = i.Value.Name;
|
|
item["caption"] = i.Value.Caption;
|
|
item["description"] = i.Value.Description;
|
|
list.AddItem(item);
|
|
count = count + 1;
|
|
}
|
|
}
|
|
ApiResponse.Data.Reset(Json.NewObject());
|
|
ApiResponse.Data.SetProperty("count", count);
|
|
ApiResponse.Data.SetProperty("list", list);
|
|
}
|
|
|
|
internal static ApiController CreateController(Type type)
|
|
{
|
|
try
|
|
{
|
|
return (ApiController)Activator.CreateInstance(type);
|
|
}
|
|
catch { }
|
|
return null;
|
|
}
|
|
|
|
void Invoke(ApiApplication application, ApiFunction function, ApiRequest request, ApiResponse response)
|
|
{
|
|
if (request == null || response == null) return;
|
|
|
|
// 创建控制器实例。
|
|
if (application == null)
|
|
{
|
|
response.Error("指定的 Application 无效,无法创建实例。");
|
|
return;
|
|
}
|
|
var controller = CreateController(application.Type);
|
|
if (controller == null)
|
|
{
|
|
response.Error("创建控制器实例失败。");
|
|
return;
|
|
}
|
|
|
|
controller.Application = application;
|
|
controller.Function = function;
|
|
controller.Request = request;
|
|
controller.Response = response;
|
|
Invoke(controller);
|
|
}
|
|
|
|
void Invoke(ApiController controller)
|
|
{
|
|
if (controller == null) return;
|
|
var application = controller.Application;
|
|
var function = controller.Function;
|
|
var request = controller.Request;
|
|
var response = controller.Response;
|
|
if (application == null) return;
|
|
if (request == null || response == null) return;
|
|
|
|
// 调用功能。
|
|
try
|
|
{
|
|
if (controller.AfterInitialized != null) controller.AfterInitialized.Invoke();
|
|
|
|
// 控制器要求单独处理,此时框架不再处理 Function。
|
|
var allowFunction = controller.AllowFunction;
|
|
if (application.Independent) allowFunction = false;
|
|
|
|
// 由框架调用 Function。
|
|
if (allowFunction)
|
|
{
|
|
if (function == null)
|
|
{
|
|
var @default = controller.DefaultFunction;
|
|
if (@default != null)
|
|
{
|
|
@default.Invoke();
|
|
}
|
|
else
|
|
{
|
|
controller.Response.Error("指定的 Function 无效。");
|
|
if (ApiOptions.AllowEnumerate) Enumerate(application.Functions);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var result = function.Method.Invoke(controller, null);
|
|
if (response.Status.IsBlank()) response.Status = "ok";
|
|
if (function.Returnable && result != null)
|
|
{
|
|
if (result is string)
|
|
{
|
|
var error = (string)result;
|
|
if (!string.IsNullOrEmpty(error)) response.Error(error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
response.Error(exception.InnerException);
|
|
}
|
|
|
|
// 释放控制器。
|
|
RuntimeUtility.Dispose(controller);
|
|
}
|
|
|
|
void Output(string text, string type = "text/plain; charset=utf-8")
|
|
{
|
|
var bytes = TextUtility.ToBinary(text);
|
|
Output(bytes, type);
|
|
}
|
|
|
|
void Output(byte[] bytes, string type = "application/octet-stream")
|
|
{
|
|
var length = bytes == null ? 0 : bytes.LongLength;
|
|
SetContentType(type);
|
|
SetContentLength(length);
|
|
if (bytes != null && bytes.LongLength > 0L)
|
|
{
|
|
WriteResponse(bytes);
|
|
}
|
|
}
|
|
|
|
void Output(Stream stream, string type = "application/octet-stream")
|
|
{
|
|
SetContentType(type);
|
|
if (stream != null)
|
|
{
|
|
var length = stream.Length - stream.Position;
|
|
SetContentLength(length);
|
|
if (length > 0 && stream.CanRead) WriteResponse(stream);
|
|
RuntimeUtility.Dispose(stream);
|
|
}
|
|
}
|
|
|
|
// 输出 ApiResponse,返回错误信息。
|
|
string Output(ApiResponse apiResponse)
|
|
{
|
|
// Ticket。
|
|
if (ApiRequest.Method == HttpMethod.GET)
|
|
{
|
|
if (apiResponse.Ticket != null && !apiResponse.Cookies.HasKey("ticket", true))
|
|
{
|
|
apiResponse.Cookies.Add("ticket", apiResponse.Ticket);
|
|
}
|
|
}
|
|
|
|
// 设置自定义头。
|
|
AddHeaders(apiResponse.Headers);
|
|
var setCookies = SetCookies(apiResponse.Cookies);
|
|
if (setCookies.NotEmpty())
|
|
{
|
|
apiResponse.Error("系统错误。");
|
|
apiResponse.Data.Reset(Json.NewObject());
|
|
apiResponse.Data["cookies"] = setCookies;
|
|
}
|
|
|
|
//
|
|
if (apiResponse.Type == ApiFormat.Redirect)
|
|
{
|
|
SetCacheControl(0);
|
|
Redirect(apiResponse.RedirectUrl);
|
|
return null;
|
|
}
|
|
|
|
//
|
|
if (apiResponse.Type == ApiFormat.Json)
|
|
{
|
|
SetCacheControl(apiResponse.Expires);
|
|
Output(ExportJson(apiResponse));
|
|
return null;
|
|
}
|
|
|
|
//
|
|
if (apiResponse.Type == ApiFormat.Text)
|
|
{
|
|
SetCacheControl(apiResponse.Expires);
|
|
Output(apiResponse.TextString, apiResponse.TextType);
|
|
return null;
|
|
}
|
|
|
|
//
|
|
if (apiResponse.Type == ApiFormat.Binary)
|
|
{
|
|
var type = apiResponse.BinaryType ?? "application/octet-stream";
|
|
if (apiResponse.BinaryBytes != null)
|
|
{
|
|
SetCacheControl(apiResponse.Expires);
|
|
Output(apiResponse.BinaryBytes, type);
|
|
}
|
|
else if (apiResponse.BinaryStream != null)
|
|
{
|
|
SetCacheControl(apiResponse.Expires);
|
|
Output(apiResponse.BinaryStream, type);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
//
|
|
if (apiResponse.Type == ApiFormat.File)
|
|
{
|
|
if (apiResponse.FileStream != null)
|
|
{
|
|
SetCacheControl(apiResponse.Expires);
|
|
AddHeader("Content-Disposition", $"attachment; filename={TextUtility.EncodeUrl(apiResponse.FileName)}");
|
|
Output(apiResponse.FileStream, apiResponse.FileType ?? "application/octet-stream");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Context.Response.Flush();
|
|
// Context.Response.End();
|
|
|
|
return null;
|
|
}
|
|
|
|
void AddHeaders(StringPairs headers)
|
|
{
|
|
foreach (var header in headers) AddHeader(header.Key, header.Value);
|
|
}
|
|
|
|
string SetCookies(IEnumerable<KeyValuePair<string, string>> cookies)
|
|
{
|
|
if (cookies == null) return null;
|
|
|
|
var ps = new List<string>();
|
|
foreach (var kvp in cookies)
|
|
{
|
|
var k = kvp.Key ?? "";
|
|
var v = kvp.Value ?? "";
|
|
if (k.IsEmpty()) continue;
|
|
ps.Add(TextUtility.Merge(k, "=", v));
|
|
}
|
|
|
|
if (ps.Count < 1) return null;
|
|
|
|
var value = TextUtility.Join("; ", ps);
|
|
return AddHeader("Set-Cookie", value);
|
|
}
|
|
|
|
// 从 Context 获取 ApiRequest 模型。
|
|
ApiRequest GetRequest()
|
|
{
|
|
// 检查 URL 的 Path。
|
|
// if (string.IsNullOrEmpty(request.Path)) return null;
|
|
|
|
// 创建数据对象。
|
|
var apiRequest = new ApiRequest();
|
|
|
|
// Http Method。
|
|
apiRequest.Method = Method;
|
|
|
|
// 基本信息。
|
|
apiRequest.Headers = GetHeaders();
|
|
apiRequest.IP = GetClientIP();
|
|
apiRequest.Url = Url;
|
|
apiRequest.Referrer = GetReferrer();
|
|
apiRequest.Parameters = WebUtility.ParseParameters(Url.Query);
|
|
|
|
// Headers。
|
|
apiRequest.UserAgent = WebUtility.GetUserAgent(apiRequest.Headers);
|
|
apiRequest.Cookies = GetCookies();
|
|
|
|
// 匹配 API。
|
|
var application = null as string;
|
|
var function = null as string;
|
|
var random = null as string;
|
|
var ticket = null as string;
|
|
var session = null as string;
|
|
var page = null as string;
|
|
|
|
// 解析 POST 请求。
|
|
if (apiRequest.Method == Apewer.Network.HttpMethod.POST)
|
|
{
|
|
var post = null as byte[];
|
|
var length = GetRequestLength();
|
|
if (length < 0 || (length > 0 && length <= MaxRequestBody))
|
|
{
|
|
post = ReadRequest();
|
|
}
|
|
|
|
// 解析 POST。
|
|
if (post != null && post.Length > 1L)
|
|
{
|
|
// 检查 JSON 的格式,第一位必须是“{”或“[”。
|
|
if (post[0] == 123 || post[0] == 91)
|
|
{
|
|
var text = TextUtility.FromBinary(post);
|
|
var json = Json.Parse(text);
|
|
if (json != null && json.IsObject)
|
|
{
|
|
application = json["application"];
|
|
function = json["function"];
|
|
random = json["random"];
|
|
ticket = json["ticket"];
|
|
session = json["session"];
|
|
page = json["page"];
|
|
|
|
var data = json.GetProperty("data");
|
|
apiRequest.PostData = post;
|
|
apiRequest.PostText = text;
|
|
apiRequest.PostJson = json;
|
|
apiRequest.Data = data ?? Json.NewObject();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 解析 URL 参数。
|
|
// URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
|
|
if (string.IsNullOrEmpty(application)) application = WebUtility.GetParameter(apiRequest.Parameters, "application");
|
|
if (string.IsNullOrEmpty(function)) function = WebUtility.GetParameter(apiRequest.Parameters, "function");
|
|
if (string.IsNullOrEmpty(random)) random = WebUtility.GetParameter(apiRequest.Parameters, "random");
|
|
if (string.IsNullOrEmpty(ticket)) ticket = WebUtility.GetParameter(apiRequest.Parameters, "ticket");
|
|
if (string.IsNullOrEmpty(session)) session = WebUtility.GetParameter(apiRequest.Parameters, "session");
|
|
if (string.IsNullOrEmpty(page)) page = WebUtility.GetParameter(apiRequest.Parameters, "page");
|
|
|
|
// 从 Cookie 中获取 Ticket。
|
|
if (string.IsNullOrEmpty(ticket)) ticket = apiRequest.Cookies.GetValue("ticket");
|
|
|
|
// 最后检查 URL 路径。
|
|
var paths = (apiRequest.Url.AbsolutePath ?? "").Split('/');
|
|
if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
|
|
if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
|
|
|
|
// 修正内容。
|
|
application = application.SafeLower().SafeTrim();
|
|
function = function.SafeLower().SafeTrim();
|
|
random = random.SafeLower().SafeTrim();
|
|
ticket = ticket.SafeLower().SafeTrim();
|
|
session = session.SafeLower().SafeTrim();
|
|
page = page.SafeTrim();
|
|
|
|
// 设置请求:回传。
|
|
apiRequest.Application = application;
|
|
apiRequest.Function = function;
|
|
apiRequest.Random = random;
|
|
|
|
// 设置请求:不回传。
|
|
apiRequest.Ticket = ticket;
|
|
apiRequest.Session = session;
|
|
apiRequest.Page = page;
|
|
|
|
// 返回结果。
|
|
return apiRequest;
|
|
}
|
|
|
|
private static string ExportJson(ApiResponse response)
|
|
{
|
|
if (response == null) return "{}";
|
|
|
|
var json = Json.NewObject();
|
|
|
|
// 执行时间。
|
|
if (ApiOptions.WithClock)
|
|
{
|
|
json.SetProperty("clock", response.Ending.ToLucid());
|
|
}
|
|
|
|
// 持续时间。
|
|
if (ApiOptions.WithDuration)
|
|
{
|
|
var seconds = Math.Floor((response.Ending - response.Beginning).TotalMilliseconds) / 1000D;
|
|
json.SetProperty("duration", seconds);
|
|
}
|
|
|
|
// 随机值。
|
|
if (response.Random.NotEmpty()) json.SetProperty("random", response.Random);
|
|
|
|
// 调用。
|
|
if (ApiOptions.WithTarget)
|
|
{
|
|
json.SetProperty("application", response.Application);
|
|
json.SetProperty("function", response.Function);
|
|
}
|
|
|
|
// 状态。
|
|
json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower()));
|
|
json.SetProperty("message", response.Message);
|
|
|
|
// Ticket。
|
|
if (response.Ticket != null) json.SetProperty("ticket", response.Ticket);
|
|
|
|
// 异常。
|
|
if (ApiOptions.AllowException && response.Exception != null)
|
|
{
|
|
try
|
|
{
|
|
var exMessage = null as string;
|
|
var exStackTrace = null as string;
|
|
var exSource = null as string;
|
|
var exHelpLink = null as string;
|
|
|
|
exMessage = response.Exception.Message;
|
|
exStackTrace = response.Exception.StackTrace;
|
|
exSource = response.Exception.Source;
|
|
exHelpLink = response.Exception.HelpLink;
|
|
|
|
// Exception 对象的主要属性。
|
|
var exJson = Json.NewObject();
|
|
exJson.SetProperty("type", response.Exception.GetType().FullName);
|
|
exJson.SetProperty("message", exMessage);
|
|
exJson.SetProperty("stack", Json.Parse((exStackTrace ?? "").Replace("\r", "").Split('\n'), false));
|
|
exJson.SetProperty("source", exSource);
|
|
exJson.SetProperty("helplink", exHelpLink);
|
|
|
|
// WebException 附加数据。
|
|
var webex = response.Exception as System.Net.WebException;
|
|
if (webex != null) exJson.SetProperty("data", Json.Parse(webex.Data));
|
|
|
|
json.SetProperty("exception", exJson);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var exJson = Json.NewObject();
|
|
exJson.SetProperty("message", TextUtility.Merge("设置 Exception 时再次发生异常:", ex.Message));
|
|
json.SetProperty("exception", exJson);
|
|
}
|
|
}
|
|
|
|
// 用户数据。
|
|
json.SetProperty("data", response.Data);
|
|
|
|
var indented = ApiOptions.JsonIndent || response.Indented;
|
|
var text = json.ToString(indented);
|
|
return text;
|
|
}
|
|
|
|
}
|
|
|
|
internal sealed class ApiProcessorListener : ApiProcessor
|
|
{
|
|
|
|
private HttpListenerContext Context;
|
|
private HttpListenerRequest Request;
|
|
private HttpListenerResponse Response;
|
|
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public ApiProcessorListener(HttpListenerContext context)
|
|
{
|
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
Context = context;
|
|
Request = context.Request;
|
|
Response = context.Response;
|
|
HaveContext = true;
|
|
}
|
|
|
|
protected override object GetContext() => Context;
|
|
|
|
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
|
|
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
|
|
protected override Uri GetURL() => WebUtility.GetUrl(Request);
|
|
protected override Uri GetReferrer() => Request.UrlReferrer;
|
|
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
|
|
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
|
|
protected override long GetRequestLength() => Request.ContentLength64;
|
|
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.InputStream);
|
|
|
|
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
|
|
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
|
|
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
|
|
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
|
|
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
|
|
protected override void WriteResponse(byte[] bytes) => BinaryUtility.Write(Response.OutputStream, bytes);
|
|
protected override void WriteResponse(Stream stream) => BinaryUtility.Read(stream, Response.OutputStream);
|
|
|
|
}
|
|
|
|
#if NETFX
|
|
|
|
internal sealed class ApiProcessorIIS : ApiProcessor
|
|
{
|
|
|
|
private System.Web.HttpContext Context;
|
|
private System.Web.HttpRequest Request;
|
|
private System.Web.HttpResponse Response;
|
|
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public ApiProcessorIIS(HttpContext context)
|
|
{
|
|
if (context == null) context = HttpContext.Current;
|
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
Context = context;
|
|
Request = context.Request;
|
|
Response = context.Response;
|
|
HaveContext = true;
|
|
}
|
|
|
|
protected override object GetContext() => Context;
|
|
|
|
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
|
|
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
|
|
protected override Uri GetURL() => WebUtility.GetUrl(Request);
|
|
protected override Uri GetReferrer() => Request.UrlReferrer;
|
|
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
|
|
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
|
|
protected override long GetRequestLength() => Request.ContentLength;
|
|
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.InputStream);
|
|
|
|
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
|
|
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
|
|
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
|
|
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
|
|
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
|
|
|
|
protected override void WriteResponse(byte[] bytes)
|
|
{
|
|
BinaryUtility.Write(Context.Response.OutputStream, bytes);
|
|
}
|
|
|
|
protected override void WriteResponse(Stream stream)
|
|
{
|
|
BinaryUtility.Read(stream, Context.Response.OutputStream);
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if NETCORE
|
|
|
|
internal sealed class ApiProcessorCore : ApiProcessor
|
|
{
|
|
|
|
private bool AllowSynchronousIO = true;
|
|
|
|
private Microsoft.AspNetCore.Http.HttpContext Context;
|
|
private Microsoft.AspNetCore.Http.HttpRequest Request;
|
|
private Microsoft.AspNetCore.Http.HttpResponse Response;
|
|
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public ApiProcessorCore(HttpContext context)
|
|
{
|
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
Context = context;
|
|
Request = context.Request;
|
|
Response = context.Response;
|
|
HaveContext = true;
|
|
}
|
|
|
|
protected override object GetContext() => Context;
|
|
|
|
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
|
|
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
|
|
protected override Uri GetURL() => WebUtility.GetUrl(Request);
|
|
protected override Uri GetReferrer() => null;
|
|
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
|
|
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
|
|
protected override long GetRequestLength() => Request.ContentLength ?? -1;
|
|
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.Body);
|
|
|
|
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
|
|
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
|
|
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
|
|
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
|
|
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
|
|
|
|
protected override void WriteResponse(byte[] bytes)
|
|
{
|
|
var body = Context.Response.Body;
|
|
|
|
// sync
|
|
BinaryUtility.Write(body, bytes);
|
|
|
|
// async
|
|
// body.BeginWrite(bytes, 0, bytes.Length, (ar) => body.EndWrite(ar), null);
|
|
}
|
|
|
|
protected override void WriteResponse(Stream stream)
|
|
{
|
|
var body = Context.Response.Body;
|
|
|
|
// sync
|
|
BinaryUtility.Read(stream, body);
|
|
|
|
// async
|
|
}
|
|
|
|
private static void ReadAsync(Stream source, Stream destination, Func<long, bool> progress = null, int buffer = 4096, Action after = null)
|
|
{
|
|
if (source == null || !source.CanRead || destination == null || !destination.CanWrite)
|
|
{
|
|
after?.Invoke();
|
|
return;
|
|
}
|
|
|
|
ReadAsync(source, destination, progress, buffer, after, 0L);
|
|
}
|
|
|
|
private static void ReadAsync(Stream source, Stream destination, Func<long, bool> progress, int buffer, Action after, long total)
|
|
{
|
|
// 缓冲区。
|
|
var limit = buffer < 1 ? 1 : buffer;
|
|
var temp = new byte[limit];
|
|
|
|
// 读取一次。
|
|
try
|
|
{
|
|
source.BeginRead(temp, 0, limit, (ar1) =>
|
|
{
|
|
var count = source.EndRead(ar1);
|
|
if (count > 0)
|
|
{
|
|
destination.BeginWrite(temp, 0, count, (ar2) =>
|
|
{
|
|
destination.EndWrite(ar2);
|
|
total += count;
|
|
if (progress != null)
|
|
{
|
|
var @continue = progress.Invoke(total);
|
|
if (!@continue)
|
|
{
|
|
after?.Invoke();
|
|
return;
|
|
}
|
|
}
|
|
ReadAsync(source, destination, progress, buffer, after, total);
|
|
}, null);
|
|
}
|
|
else after?.Invoke();
|
|
}, null);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|