├── .DS_Store ├── .gitattributes ├── .gitignore ├── Bridge ├── HttpBridgeActivity.cs ├── HttpBridgeClient.cs ├── HttpBridgeMime.cs └── HttpBridgeRequest.cs ├── ImageCast.md ├── LICENSE ├── Mime ├── HttpMime.cs ├── HttpMimeContext.cs ├── HttpMimeRequest.cs ├── HttpMimeResponse.cs ├── HttpMimeServier.cs ├── HttpMimeSocket.cs ├── HttpMimeStream.cs └── HttpWebSocket.cs ├── Program.cs ├── Proxy ├── AvifConverter.cs ├── DataFactory.cs ├── Entities │ ├── Codes │ │ ├── Cookie.cs │ │ ├── Site.cs │ │ └── SiteHost.cs │ ├── Cookie.cs │ ├── Initializer.cs │ └── Site.cs ├── HttpProxy.cs ├── LogSetting.cs ├── SiteActivity.cs ├── SiteAppActivity.cs ├── SiteAuthActivity.cs ├── SiteConfActivity.cs ├── SiteConfImageActivity.cs ├── SiteConfig.cs ├── SiteImage.cs ├── SiteLogActivity.cs ├── SiteLogConfActivity.cs ├── SiteMimeActivity.cs ├── SiteServerActivity.cs ├── SiteUserActivity.cs ├── Utility.cs ├── WebFactory.cs └── WebServlet.cs ├── README.md ├── Resources ├── Auth │ ├── dingtalk.html │ ├── nosupport.html │ └── wxwork.html ├── app.png ├── check.html ├── close.html ├── desktop.doc.html ├── desktop.html ├── desktop.page.html ├── desktop.site.html ├── desktop.ui.html ├── desktop.umc.html ├── dingtalk.html ├── dir.html ├── error.html ├── favicon.ico ├── login-html.html ├── login.html ├── pc.html ├── pwd-Compel.html ├── pwd-Select.html ├── pwd-Selected.html ├── umc.html ├── user.html ├── weixin.html └── wxwork.html ├── UMC.Host.csproj └── WebVPN.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apiumc/Gateway/e7b6594c5f9fd4015170701b3e14c5a65b929b27/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | *.DS_Store -------------------------------------------------------------------------------- /Bridge/HttpBridgeMime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Security.Authentication; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Threading.Tasks; 10 | using UMC.Net; 11 | 12 | namespace UMC.Host 13 | { 14 | 15 | class HttpBridgeMime : HttpMime 16 | { 17 | HttpBridgeClient client; 18 | public HttpBridgeMime(int pid, HttpBridgeClient client) 19 | { 20 | this.client = client; 21 | this.pid = pid; 22 | } 23 | int pid; 24 | public override string Host => "127.0.0.1"; 25 | 26 | public override string RemoteIpAddress => "127.0.0.1"; 27 | 28 | public override void Dispose() 29 | { 30 | if (_webSocket != null) 31 | { 32 | _webSocket.Close(); 33 | _webSocket.Dispose(); 34 | } 35 | client.Remove(this.pid); 36 | } 37 | public void WebSocket(byte[] buffer, int offset, int count) 38 | { 39 | try 40 | { 41 | mimeStream?.Write(buffer, offset, count); 42 | _webSocket?.Write(buffer, offset, count); 43 | } 44 | catch 45 | { 46 | client.Write(this.pid, new byte[0], 0, 0); 47 | this.Dispose(); 48 | } 49 | } 50 | public override void Subscribe(HttpMimeRequest webRequest) 51 | { 52 | this.OutText(500, "穿透不支持UMC数据订阅协议"); 53 | } 54 | 55 | protected override void WebSocket(NetContext context) 56 | { 57 | if (context.Tag is HttpWebRequest) 58 | { 59 | var webr = context.Tag as HttpWebRequest; 60 | this.WebSocket(webr); 61 | } 62 | else 63 | { 64 | mimeStream = new HttpMimeStream(this); 65 | HttpWebSocket.AcceptWebSocketAsyncCore(context, mimeStream);// new HttpMimeStream(this)); 66 | } 67 | } 68 | HttpMimeStream mimeStream; 69 | async void WebSocket(HttpWebRequest webRequest) 70 | { 71 | try 72 | { 73 | var url = webRequest.RequestUri; 74 | if (webRequest.CookieContainer != null) 75 | { 76 | String cookie; 77 | if (webRequest.CookieContainer is Net.NetCookieContainer) 78 | { 79 | cookie = ((Net.NetCookieContainer)webRequest.CookieContainer).GetCookieHeader(url); 80 | } 81 | else 82 | { 83 | cookie = webRequest.CookieContainer.GetCookieHeader(url); 84 | } 85 | if (String.IsNullOrEmpty(cookie) == false) 86 | { 87 | webRequest.Headers[HttpRequestHeader.Cookie] = cookie; 88 | } 89 | } 90 | if (String.IsNullOrEmpty(webRequest.Headers[HttpRequestHeader.Host])) 91 | { 92 | webRequest.Headers[HttpRequestHeader.Host] = webRequest.Host; 93 | } 94 | webRequest.Headers["Connection"] = "Upgrade"; 95 | 96 | 97 | 98 | var client = new Socket(SocketType.Stream, ProtocolType.Tcp); 99 | 100 | await client.ConnectAsync(url.Host, url.Port); 101 | 102 | 103 | if (url.Scheme == "https") 104 | { 105 | SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true); 106 | 107 | await ssl.AuthenticateAsClientAsync(url.Host, new X509CertificateCollection(), SslProtocols.None, false); 108 | _webSocket = ssl; 109 | } 110 | else 111 | { 112 | _webSocket = new NetworkStream(client, true); 113 | 114 | } 115 | var buffer = new byte[0x600]; 116 | await _webSocket.WriteAsync(buffer, 0, NetHttpResponse.Header(webRequest, buffer)); 117 | WebSocketRead(buffer); 118 | 119 | } 120 | catch (Exception ex) 121 | { 122 | this.OutText(500, ex.ToString()); 123 | this.Dispose(); 124 | } 125 | } 126 | Stream _webSocket; 127 | async void WebSocketRead(byte[] buffer) 128 | { 129 | int size = 0; 130 | try 131 | { 132 | size = await _webSocket.ReadAsync(buffer, 0, buffer.Length); 133 | client.Write(this.pid, buffer, 0, size); 134 | WebSocketRead(buffer); 135 | } 136 | catch 137 | { 138 | client.Write(this.pid, Array.Empty(), 0, 0); 139 | this.Dispose(); 140 | } 141 | } 142 | public override void OutputFinish() 143 | { 144 | 145 | } 146 | public override void Write(byte[] buffer, int offset, int count) 147 | { 148 | if (count > 0) 149 | { 150 | 151 | client.Write(this.pid, buffer, offset, count); 152 | } 153 | } 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /Bridge/HttpBridgeRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UMC.Host 4 | { 5 | 6 | class HttpBridgeRequest : HttpMimeRequest 7 | { 8 | 9 | HttpBridgeMime httpBridge; 10 | public HttpBridgeRequest(HttpBridgeMime mime) : base(mime) 11 | { 12 | this.httpBridge = mime; 13 | } 14 | public override void Receive(byte[] buffer, int offset, int size) 15 | { 16 | if (this.IsWebSocket) 17 | { 18 | this.httpBridge.WebSocket(buffer, offset, size); 19 | } 20 | else 21 | { 22 | base.Receive(buffer, offset, size); 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ImageCast.md: -------------------------------------------------------------------------------- 1 | 2 |

Apiumc Gateway

3 |

图片处理组件

4 | 5 |

6 | star 7 | 8 | 9 |

10 | 11 | ### 图片处理。 12 | 图片是Web应用打开快慢关键因子,因此合适尺寸和格式能带来极大的速度提升,Apiumc网关的图片处理,在不改变原应用的情况下,来调整图片尺寸、增加水印、格式转码等等功能,同时也支持智能适配按浏览器兼容性从avif格式、webp格式、png格式转码,从而帮助应用把图片大小减少60%-90%,让应用快如闪电,大幅改善原应用的交互质量。 13 | 14 | Apiumc配置图片处理方式有分为两种方式快捷参数和图片模板,同时查看图片参数处理后的效果有以下方式 15 | 16 | 1. 可以通过在图片的url的QueryString上追加`umc-image=xxx`,其中xxx可以是图片处理的快捷参数或图片模板。 17 | 2. 可以通过在图片的url路径上的头部加上`/UMC.Image/xxx/`等同上面效果。 18 | 19 | **注意**:图片处理后会持久化保存并缓存,可手动在图片url后面追加`&umc=src`或?`umc=src`来获取新版本 20 | 21 | ### 快捷参数 22 | 快捷参数由三部分组成 23 | >[`方位`][`尺寸`][`格式`] 24 | #### 方位参数 25 | 26 | |参数|样例|说明| 27 | |--|--|--| 28 | |w |w100 |表示按宽度缩小 | 29 | |h |w100 |表示按高度缩小 | 30 | |c |c100 |全图居中缩放指定尺寸 | 31 | |t |t100 |向上或向左裁剪指定尺寸 | 32 | |m |m100 |正中裁剪指定尺寸 | 33 | |b |b100 |向下和向右裁剪指定尺寸 | 34 | 35 | 36 | #### 尺寸参数 37 | 分为单值、-对值和x对值 38 | 39 | |参数|样例|说明| 40 | |--|--|--| 41 | |单值 |100 |表示按固定正方式缩小 ,样例表示宽高各100 | 42 | |-对值 |100-200 |表示限定尺寸,样例表示宽限定100高限定200 单边超过,则单边缩放,等同w、h方位参数,双边比例超过,则按固定大小进行裁剪 | 43 | |x对值 |100x200 |表示按固定尺寸缩小,样例:宽100、高200 | 44 | 45 | 46 | #### 图片转码 47 | 支持对gif、jpeg、webp、png、avif格式的相互转换,默认值为原图格式 48 | 49 | |参数|样例|说明| 50 | |--|--|--| 51 | |g |w200g |转码为gif图片,样例:表示 宽度缩放到200并转化为gif格式图片 | 52 | |j |w200j |转码为jpeg图片,样例:表示 宽度缩放到200并转化为jpeg格式图片 | 53 | |w |w200w |转码为webp图片,样例:表示 宽度缩放到200并转化为webp格式图片 | 54 | |p |w200p |转码为png图片,样例:表示 宽度缩放到200并转化为png格式图片 | 55 | |a |w200a |转码为avif图片,样例:表示 宽度缩放到200并转化为avif格式图片 | 56 | |o |w200o |适配图片格式,样例:表示 宽度缩放到200并根据浏览器从avif--webp--png来适配格式 | 57 | 58 | 注意:avif图片浏览器支持有限,webp图片主流浏览器都支持,但要考虑国产各不一样的浏览器,建议图片优化采用了适配格式。 59 | ### 图片模板 60 | 在云桌面-应用设置-托管应用,点击应用,打开应用配置中再点击图片处理如下图: 61 | 62 | ![图片](https://www.apiumc.com/UserResources/1flmgtt/1666787012/image.png!m400) 63 | 64 | 点击下出现如下图: 65 | 66 | ![图片](https://www.apiumc.com/UserResources/1flmgtt/1666785404/image.png!m400) 67 | 68 | 再此配置路径格式,支持用一个`*`进行前后对比,也支持图片Content-Type类型配置,当图片的请求路径或Conent-tye能配对图片模板,则采用模板参数来处理图片。 69 | 70 | **注意**:在此配置的路径也可以通过在url的QueryString上加`umc-image=[模板名]`来使用,快速查看效果 71 | 72 | 再点击配置的路径,则会出现图片模板配置如下图: 73 | 74 | ![图片](https://www.apiumc.com/UserResources/1flmgtt/1666792125/image.png!m400) 75 | #### 图片宽度 76 | 设置图片的宽度,正值为固定宽度,负值为限定宽度 77 | #### 图片高度 78 | 设置图片的高度,正值为固定高度,负值为限定高度 79 | #### 裁剪方式 80 | 对图片进行裁剪的方式 81 | 82 | 居中缩放:是把图片放入新尺寸中间,短边居中的效果,是缩小效果 83 | 84 | 向上裁剪:对图片长图进行向上裁剪,对图片的宽图进行向左裁剪。 85 | 86 | 居中裁剪:对图片正中进行裁剪。 87 | 88 | 向下裁剪:对图片长图进行向下裁剪,对图片的宽图进行向右裁剪。 89 | 90 | #### 图片格式 91 | 可设置为原图格式、gif、png、jpeg、webp和适配格式 92 | 93 | **注意**:只设置的单边值,裁剪方式设置无效。 94 | #### 水印方式 95 | 方式水印可分可设置图片水印和文本水印 96 | 97 | 图片水印设置如下图: 98 | 99 | ![图片](https://www.apiumc.com/UserResources/1flmgtt/1666836877/image.png!m400) 100 | 101 | **提示**:水印图片,会自动设置50%透明度复加到了图片上 102 | 103 | 文本水印设置如下图: 104 | 105 | ![图片](https://www.apiumc.com/UserResources/1flmgtt/1666837029/image.png!m400) 106 | 107 | **提示**:文本颜色的支持8位和4位颜色的透明度设置 -------------------------------------------------------------------------------- /Mime/HttpMime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using UMC.Net; 5 | 6 | namespace UMC.Host 7 | { 8 | 9 | public abstract class HttpMime : IDisposable 10 | { 11 | public virtual string Scheme => "http"; 12 | public abstract void OutputFinish(); 13 | public abstract void Write(byte[] buffer, int offset, int count); 14 | 15 | 16 | public abstract void Dispose(); 17 | public abstract String Host { get; } 18 | public abstract String RemoteIpAddress { get; } 19 | 20 | 21 | protected abstract void WebSocket(UMC.Net.NetContext context); 22 | 23 | public abstract void Subscribe(HttpMimeRequest webRequest); 24 | public virtual void PrepareRespone(HttpMimeRequest httpMimeRequest) 25 | { 26 | 27 | Task.Factory.StartNew(() => 28 | { 29 | if (httpMimeRequest.IsWebSocket) 30 | { 31 | if (httpMimeRequest.IsSubscribe) 32 | { 33 | Subscribe(httpMimeRequest); 34 | } 35 | else 36 | { 37 | var context = new HttpMimeContext(httpMimeRequest, new HttpMimeResponse(this, httpMimeRequest)); 38 | try 39 | { 40 | context.ProcessRequest(); 41 | context.ProcessAfter(); 42 | 43 | this.WebSocket(context); 44 | 45 | } 46 | catch (Exception ex) 47 | { 48 | context.Error(ex); 49 | 50 | } 51 | 52 | } 53 | } 54 | else 55 | { 56 | var context = new HttpMimeContext(httpMimeRequest, new HttpMimeResponse(this, httpMimeRequest)); 57 | try 58 | { 59 | context.ProcessRequest(); 60 | context.ProcessAfter(); 61 | } 62 | catch (Exception ex) 63 | { 64 | context.Error(ex); 65 | 66 | } 67 | } 68 | }); 69 | 70 | } 71 | 72 | public void OutText(int status, string contentType, String text) 73 | { 74 | var writer = new TextWriter(this.Write); 75 | writer.Write($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n"); 76 | writer.Write($"Content-Type: {contentType}; charset=utf-8\r\n"); 77 | writer.Write($"Content-Length: {System.Text.Encoding.UTF8.GetByteCount(text)}\r\n"); 78 | writer.Write("Connection: close\r\n"); 79 | writer.Write("Server: UMC.Proxy\r\n\r\n"); 80 | writer.Write(text); 81 | writer.Flush(); 82 | writer.Close(); 83 | this.Dispose(); 84 | } 85 | public void OutText(int status, String text) 86 | { 87 | this.OutText(status, "text/plain", text); 88 | 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Mime/HttpMimeContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using UMC.Data; 9 | using UMC.Net; 10 | 11 | namespace UMC.Host 12 | { 13 | public class HttpMimeContext : UMC.Net.NetContext 14 | { 15 | public override string Server => Environment.MachineName; 16 | HttpMimeRequest _request; 17 | HttpMimeResponse _response; 18 | public override long? ContentLength { get => _request.ContentLength; set => _response.ContentLength = value; } 19 | public override void AppendCookie(string name, string value) 20 | { 21 | _response.AppendCookie(name, value); 22 | 23 | } 24 | public override void AppendCookie(string name, string value, string path) 25 | { 26 | 27 | _response.AppendCookie(name, value, path); 28 | } 29 | public override bool IsWebSocket => _request.IsWebSocket; 30 | Net.TextWriter writer; 31 | public override void RewriteUrl(string pathAndQuery) 32 | { 33 | _request.RewriteUrl(pathAndQuery); 34 | } 35 | public HttpMimeContext(HttpMimeRequest request, HttpMimeResponse response) 36 | { 37 | this._request = request; 38 | this._response = response; 39 | writer = new Net.TextWriter(response.OutputStream.Write); 40 | } 41 | public override void ReadAsData(NetReadData readData) 42 | { 43 | if (_request.ContentLength > 0 && _request.IsMimeFinish == false) 44 | { 45 | if (aseSynchronousIOEnd == null) 46 | { 47 | aseSynchronousIOEnd = () => { }; 48 | } 49 | } 50 | this._request.ReadAsData(readData); 51 | } 52 | Action aseSynchronousIOEnd; 53 | public override bool AllowSynchronousIO => aseSynchronousIOEnd != null; 54 | public override void UseSynchronousIO(Action action) 55 | { 56 | aseSynchronousIOEnd = action; 57 | } 58 | void UseSynchronousIOEnd() 59 | { 60 | try 61 | { 62 | aseSynchronousIOEnd(); 63 | } 64 | catch (Exception ex) 65 | { 66 | 67 | UMC.Data.Utility.Error("SynchronousIO", DateTime.Now, ex.ToString()); 68 | } 69 | } 70 | public override void OutputFinish() 71 | { 72 | 73 | if (aseSynchronousIOEnd != null) 74 | { 75 | this.Output.Flush(); 76 | if (_response.OutputFinish()) 77 | { 78 | UseSynchronousIOEnd(); 79 | _request._context.OutputFinish(); 80 | } 81 | else 82 | { 83 | _request._context.Dispose(); 84 | } 85 | 86 | _request.Dispose(); 87 | } 88 | 89 | } 90 | public override void Error(Exception ex) 91 | { 92 | this.Output.Flush(); 93 | _response.OutputError(ex); 94 | if (aseSynchronousIOEnd != null) 95 | { 96 | UseSynchronousIOEnd(); 97 | 98 | } 99 | _request._context.Dispose(); 100 | _request.Dispose(); 101 | 102 | } 103 | public override void ReadAsForm(Action action) 104 | { 105 | if (_request.ContentLength > 0 && _request.IsMimeFinish == false) 106 | { 107 | if (aseSynchronousIOEnd == null) 108 | { 109 | aseSynchronousIOEnd = () => { }; 110 | } 111 | } 112 | _request.ReadAsForm(action); 113 | } 114 | public virtual void ProcessRequest() 115 | { 116 | new UMC.Proxy.WebServlet().ProcessRequest(this); 117 | //this.ProcessAfter(); 118 | } 119 | internal protected virtual void ProcessAfter() 120 | { 121 | if (this.IsWebSocket == false && aseSynchronousIOEnd == null) 122 | { 123 | this.Output.Flush(); 124 | _response.OutputFinish(); 125 | _request._context.OutputFinish(); 126 | 127 | _request.Dispose(); 128 | } 129 | } 130 | public override int StatusCode 131 | { 132 | get 133 | { 134 | return this._response.StatusCode; 135 | } 136 | set 137 | { 138 | this._response.StatusCode = value; 139 | } 140 | } 141 | public override void AddHeader(string name, string value) 142 | { 143 | this._response.AddHeader(name, value); 144 | } 145 | public override NameValueCollection Headers 146 | { 147 | get 148 | { 149 | return _request.Headers; 150 | } 151 | } 152 | public override NameValueCollection Cookies 153 | { 154 | get 155 | { 156 | 157 | return _request.Cookies; ; 158 | } 159 | } 160 | NameValueCollection _QueryString; 161 | public override NameValueCollection QueryString 162 | { 163 | get 164 | { 165 | if (_QueryString == null) 166 | { 167 | var Query = this.Url.Query; 168 | if (String.IsNullOrEmpty(Query) == false) 169 | { 170 | _QueryString = System.Web.HttpUtility.ParseQueryString(Query); 171 | } 172 | else 173 | { 174 | _QueryString = new NameValueCollection(); 175 | } 176 | } 177 | return _QueryString; 178 | } 179 | } 180 | 181 | public override System.IO.TextWriter Output 182 | { 183 | get 184 | { 185 | return this.writer; 186 | } 187 | } 188 | public override System.IO.Stream OutputStream 189 | { 190 | get 191 | { 192 | return this._response.OutputStream; 193 | } 194 | } 195 | 196 | public override string ContentType 197 | { 198 | get 199 | { 200 | return this._request.ContentType; 201 | } 202 | set 203 | { 204 | this._response.ContentType = value; 205 | } 206 | } 207 | 208 | public override string UserHostAddress 209 | { 210 | get { return this._request.UserHostAddress; } 211 | } 212 | 213 | public override string RawUrl 214 | { 215 | get { return _request.RawUrl; } 216 | } 217 | 218 | public override string UserAgent 219 | { 220 | get { return this._request.Headers["User-Agent"]; } 221 | } 222 | Uri _Referer; 223 | public override Uri UrlReferrer 224 | { 225 | get 226 | { 227 | if (_Referer == null) 228 | { 229 | String referer = _request.Headers["Referer"]; 230 | if (String.IsNullOrEmpty(referer) == false) 231 | { 232 | try 233 | { 234 | _Referer = new Uri(referer); 235 | } 236 | catch 237 | { 238 | _Referer = new Uri(_request.Url, "/"); 239 | } 240 | 241 | } 242 | } 243 | return _Referer; 244 | } 245 | } 246 | 247 | public override Uri Url 248 | { 249 | get { return _request.Url; } 250 | } 251 | 252 | public override void Redirect(string url) 253 | { 254 | this._response.Redirect(url); 255 | } 256 | 257 | public override string HttpMethod 258 | { 259 | get { return this._request.HttpMethod; } 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /Mime/HttpMimeResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlTypes; 4 | using System.IO; 5 | using System.Net; 6 | using System.Text; 7 | using UMC.Net; 8 | 9 | namespace UMC.Host 10 | { 11 | 12 | public class HttpMimeResponse 13 | { 14 | protected static readonly byte[] ChunkedEnd = Encoding.ASCII.GetBytes($"0\r\n\r\n"); 15 | public int StatusCode 16 | { 17 | get; 18 | set; 19 | } 20 | public string ContentType 21 | { 22 | get; 23 | set; 24 | } 25 | public long? ContentLength { set; get; } 26 | HttpMime _context; 27 | HttpMimeRequest _req; 28 | public HttpMimeResponse(HttpMime context, HttpMimeRequest request) 29 | { 30 | this._req = request; 31 | this.StatusCode = 200; 32 | this._context = context; 33 | this.bodyStream = new BodyStream(this); 34 | } 35 | class Header 36 | { 37 | public String Name; 38 | public String Value; 39 | } 40 | List
headers = new List
(); 41 | public void AddHeader(string name, string value) 42 | { 43 | if (this.lengthWrite == 0) 44 | { 45 | headers.Add(new Header { Name = name, Value = value }); 46 | } 47 | else 48 | { 49 | UMC.Data.Utility.Debug("Http", DateTime.Now, "内容已经输出不可再追加Header", _req.Url.AbsoluteUri); 50 | } 51 | } 52 | 53 | public void Redirect(string url) 54 | { 55 | if (this.lengthWrite == 0) 56 | { 57 | this.StatusCode = 302; 58 | this.AddHeader("Location", url); 59 | this.ContentLength = null; 60 | this.isChunked = false; 61 | this.WriteHeader(); 62 | this.lengthWrite = -1; 63 | } 64 | else 65 | { 66 | UMC.Data.Utility.Debug("Http", DateTime.Now, "内容已经输出不可再重定向", _req.Url.AbsoluteUri); 67 | } 68 | 69 | } 70 | public void AppendCookie(string name, string value) 71 | { 72 | if (String.Equals(name, UMC.Web.WebServlet.SessionCookieName)) 73 | { 74 | this.AddHeader("Set-Cookie", $"{name}={value}; Expires={DateTime.Now.AddYears(10).ToString("r")}; Path=/"); 75 | 76 | } 77 | else 78 | { 79 | this.AddHeader("Set-Cookie", $"{name}={value}; Path=/"); 80 | } 81 | } 82 | public void AppendCookie(string name, string value, string path) 83 | { 84 | this.AddHeader("Set-Cookie", $"{name}={value}; Path={path}"); 85 | } 86 | BodyStream bodyStream; 87 | public System.IO.Stream OutputStream 88 | { 89 | get 90 | { 91 | return bodyStream; 92 | } 93 | } 94 | bool isChunked; 95 | 96 | void WriteHeader() 97 | { 98 | var header = new UMC.Net.TextWriter(_context.Write); 99 | try 100 | { 101 | header.Write($"HTTP/1.1 {this.StatusCode} {HttpStatusDescription.Get(this.StatusCode)}\r\n"); 102 | foreach (var h in this.headers) 103 | { 104 | header.Write($"{h.Name}: {h.Value}\r\n"); 105 | } 106 | if (String.IsNullOrEmpty(this.ContentType) == false) 107 | { 108 | header.Write($"Content-Type: {this.ContentType}\r\n"); 109 | } 110 | 111 | if (this.ContentLength.HasValue && this.ContentLength > 0) 112 | { 113 | header.Write($"Content-Length: {this.ContentLength}\r\n"); 114 | } 115 | else if (isChunked) 116 | { 117 | header.Write("Transfer-Encoding: chunked\r\n"); 118 | } 119 | else 120 | { 121 | if (this.headers.Exists(r => 122 | { 123 | switch (r.Name.ToLower()) 124 | { 125 | case "content-length": 126 | case "transfer-encoding": 127 | return true; 128 | } 129 | return false; 130 | 131 | }) == false) 132 | { 133 | header.Write("Content-Length: 0\r\n"); 134 | } 135 | } 136 | 137 | if (_IsClose || _req.IsClose) 138 | { 139 | header.Write("Connection: close\r\n"); 140 | } 141 | else 142 | { 143 | header.Write("Keep-Alive: timeout=20\r\n"); 144 | header.Write("Connection: keep-alive\r\n"); 145 | } 146 | header.Write("Server: UMC.Proxy\r\n\r\n"); 147 | } 148 | finally 149 | { 150 | header.Flush(); 151 | header.Dispose(); 152 | } 153 | } 154 | bool _IsClose; 155 | public void OutputError(Exception ex) 156 | { 157 | if (lengthWrite == 0) 158 | { 159 | //_context.er 160 | var errStr = ex.ToString(); 161 | var sbytes = System.Buffers.ArrayPool.Shared.Rent(errStr.Length * 2); 162 | try 163 | { 164 | this.StatusCode = 500; 165 | _IsClose = true; 166 | 167 | this.ContentType = "text/plain; charset=utf-8"; 168 | var blength = Encoding.UTF8.GetBytes(errStr, sbytes); 169 | this.ContentLength = blength;//.Length; 170 | this.isChunked = false; 171 | 172 | bodyStream.Write(sbytes, 0, blength); 173 | } 174 | finally 175 | { 176 | System.Buffers.ArrayPool.Shared.Return(sbytes); 177 | } 178 | 179 | } 180 | else if (this.isChunked) 181 | { 182 | _context.Write(ChunkedEnd, 0, ChunkedEnd.Length); 183 | } 184 | } 185 | public bool OutputFinish() 186 | { 187 | try 188 | { 189 | if (lengthWrite == -1) 190 | { 191 | return true; 192 | } 193 | else if (lengthWrite == 0) 194 | { 195 | if (this.ContentLength > 0) 196 | { 197 | _IsClose = true; 198 | 199 | UMC.Data.Utility.Debug("Http", DateTime.Now, $"输入内容长度只能是{this.ContentLength},但只输出了{lengthWrite}", _req.Url.AbsoluteUri); 200 | this.ContentLength = 0; 201 | this.isChunked = false; 202 | WriteHeader(); 203 | lengthWrite = -1; 204 | return false; 205 | } 206 | else 207 | { 208 | this.ContentLength = 0; 209 | this.isChunked = false; 210 | WriteHeader(); 211 | lengthWrite = -1; 212 | return true; 213 | } 214 | 215 | } 216 | else if (this.isChunked) 217 | { 218 | this.bodyStream.Flush(); 219 | 220 | _context.Write(ChunkedEnd, 0, ChunkedEnd.Length); 221 | 222 | return true; 223 | } 224 | else 225 | { 226 | return lengthWrite == this.ContentLength; 227 | } 228 | } 229 | finally 230 | { 231 | headers.Clear(); 232 | this.bodyStream.Dispose(); 233 | } 234 | 235 | } 236 | long lengthWrite = 0; 237 | class BodyStream : System.IO.Stream 238 | { 239 | HttpMimeResponse response; 240 | public BodyStream(HttpMimeResponse response) 241 | { 242 | this.response = response; 243 | } 244 | public override bool CanRead => false; 245 | 246 | public override bool CanSeek => false; 247 | 248 | 249 | public override bool CanWrite => true; 250 | 251 | public override long Length => throw new NotSupportedException(); 252 | 253 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 254 | 255 | public override void Flush() 256 | { 257 | 258 | } 259 | 260 | public override int Read(byte[] buffer, int offset, int count) 261 | { 262 | throw new NotSupportedException(); 263 | } 264 | 265 | public override long Seek(long offset, SeekOrigin origin) 266 | { 267 | throw new NotSupportedException(); 268 | } 269 | public override void SetLength(long value) 270 | { 271 | throw new NotSupportedException(); 272 | } 273 | 274 | public override void Write(byte[] buffer, int offset, int count) 275 | { 276 | switch (response.lengthWrite) 277 | { 278 | case -1: 279 | UMC.Data.Utility.Debug("Http", DateTime.Now, "内容输出已经关闭,不可再写入内容", response._req.Url.AbsoluteUri); 280 | return; 281 | case 0: 282 | response.isChunked = response.ContentLength.HasValue == false || response.ContentLength <= 0; 283 | response.WriteHeader(); 284 | 285 | break; 286 | } 287 | response.lengthWrite += count; 288 | if (response.ContentLength > 0) 289 | { 290 | if (response.ContentLength < response.lengthWrite) 291 | { 292 | UMC.Data.Utility.Debug("Http", DateTime.Now, $"输入内容长度只能是{response.ContentLength}", response._req.Url.AbsoluteUri); 293 | var size = response.ContentLength.Value - (response.lengthWrite - count); 294 | if (size > 0) 295 | { 296 | response._context.Write(buffer, offset, (int)size); 297 | } 298 | response._IsClose = true; 299 | } 300 | else 301 | { 302 | response._context.Write(buffer, offset, count); 303 | } 304 | } 305 | else if (count > 0) 306 | { 307 | var str = $"{(count).ToString("x")}\r\n"; 308 | var bytes = System.Buffers.ArrayPool.Shared.Rent(str.Length); 309 | try 310 | { 311 | response._context.Write(bytes, 0, Encoding.ASCII.GetBytes(str, bytes)); 312 | response._context.Write(buffer, offset, count); 313 | response._context.Write(HttpMimeBody.HeaderEnd, 0, 2); 314 | } 315 | finally 316 | { 317 | System.Buffers.ArrayPool.Shared.Return(bytes); 318 | } 319 | } 320 | } 321 | } 322 | } 323 | 324 | 325 | } -------------------------------------------------------------------------------- /Mime/HttpMimeSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Security; 7 | using System.Net.Sockets; 8 | using System.Security.Authentication; 9 | using System.Security.Cryptography.X509Certificates; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using UMC.Net; 13 | 14 | namespace UMC.Host 15 | { 16 | 17 | public class HttpMimeSocket : HttpMime 18 | { 19 | 20 | String _scheme = "http"; 21 | public override string Scheme => _scheme; 22 | int _timeOut = 20; 23 | public int TimeOut => _timeOut; 24 | 25 | public HttpMimeSocket(String scheme, System.IO.Stream stream, String host, String ip) 26 | { 27 | _scheme = scheme; 28 | this._stream = stream; 29 | this.ActiveTime = UMC.Data.Utility.TimeSpan(); 30 | 31 | this.pid = stream.GetHashCode(); 32 | 33 | this._Host = host; 34 | 35 | this._remoteIpAddress = ip; 36 | 37 | HttpMimeServier.httpMimes.TryAdd(pid, this); 38 | Read(new HttpMimeRequest(this)); 39 | 40 | } 41 | Stream _stream; 42 | int pid = 0; 43 | public int Id => pid; 44 | String _remoteIpAddress, _Host; 45 | public override String Host => _Host; 46 | public override String RemoteIpAddress => _remoteIpAddress; 47 | 48 | public override void Write(byte[] buffer, int offset, int count) 49 | { 50 | if (isDispose == false) 51 | { 52 | try 53 | { 54 | 55 | _stream.Write(buffer, offset, count); 56 | 57 | } 58 | catch 59 | { 60 | this.Dispose(); 61 | } 62 | } 63 | } 64 | 65 | 66 | 67 | public override void OutputFinish() 68 | { 69 | try 70 | { 71 | Read(new HttpMimeRequest(this)); 72 | 73 | this.ActiveTime = UMC.Data.Utility.TimeSpan(); 74 | _timeOut = 20; 75 | } 76 | catch 77 | { 78 | this.Dispose(); 79 | } 80 | } 81 | public override void PrepareRespone(HttpMimeRequest httpMimeRequest) 82 | { 83 | _timeOut = 300; 84 | base.PrepareRespone(httpMimeRequest); 85 | } 86 | public override void Subscribe(HttpMimeRequest webRequest) 87 | { 88 | var _subscribe = UMC.Net.NetSubscribe.Subscribe(webRequest.Headers, webRequest.UserHostAddress, HttpMimeServier.Server, _stream, UMC.Data.WebResource.Instance().Provider["appSecret"]); 89 | if (_subscribe != null) 90 | { 91 | HttpMimeSocket link; 92 | HttpMimeServier.httpMimes.TryRemove(pid, out link); 93 | 94 | var writer = new Net.TextWriter(this.Write, _data); 95 | writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n"); 96 | writer.Write("Connection: upgrade\r\n"); 97 | writer.Write("Upgrade: websocket\r\n"); 98 | writer.Write($"UMC-Publisher-Key: {HttpMimeServier.Server}\r\n"); 99 | writer.Write("Server: UMC.Proxy\r\n\r\n"); 100 | 101 | writer.Flush(); 102 | writer.Dispose(); 103 | _subscribe.Publish(); 104 | } 105 | else 106 | { 107 | OutText(401, "连接验证不通过"); 108 | } 109 | } 110 | protected override void WebSocket(NetContext context) 111 | { 112 | if (context.Tag is HttpWebRequest) 113 | { 114 | var webr = context.Tag as HttpWebRequest; 115 | this.WebSocket(webr); 116 | } 117 | else 118 | { 119 | HttpWebSocket.AcceptWebSocketAsyncCore(context, _stream); 120 | } 121 | } 122 | public int ActiveTime 123 | { 124 | 125 | get; set; 126 | } 127 | bool isDispose = false; 128 | public override void Dispose() 129 | { 130 | if (this._data != null) 131 | { 132 | System.Buffers.ArrayPool.Shared.Return(this._data); 133 | } 134 | if (isDispose == false) 135 | { 136 | isDispose = true; 137 | try 138 | { 139 | _stream.Close(); 140 | _stream.Dispose(); 141 | _data = null; 142 | if (_webSocket != null) 143 | { 144 | _webSocket.Dispose(); 145 | } 146 | } 147 | catch 148 | { 149 | 150 | } 151 | } 152 | HttpMimeServier.httpMimes.TryRemove(pid, out var _); 153 | 154 | } 155 | async void WebSocket(HttpWebRequest webRequest) 156 | { 157 | try 158 | { 159 | var url = webRequest.RequestUri; 160 | if (webRequest.CookieContainer != null) 161 | { 162 | String cookie; 163 | if (webRequest.CookieContainer is Net.NetCookieContainer) 164 | { 165 | cookie = ((Net.NetCookieContainer)webRequest.CookieContainer).GetCookieHeader(url); 166 | } 167 | else 168 | { 169 | cookie = webRequest.CookieContainer.GetCookieHeader(url); 170 | } 171 | if (String.IsNullOrEmpty(cookie) == false) 172 | { 173 | webRequest.Headers[HttpRequestHeader.Cookie] = cookie; 174 | } 175 | } 176 | if (String.IsNullOrEmpty(webRequest.Headers[HttpRequestHeader.Host])) 177 | { 178 | webRequest.Headers[HttpRequestHeader.Host] = webRequest.Host; 179 | } 180 | webRequest.Headers["Connection"] = "Upgrade"; 181 | 182 | 183 | var client = new Socket(SocketType.Stream, ProtocolType.Tcp); 184 | 185 | await client.ConnectAsync(url.Host, url.Port); 186 | 187 | _webSocket = new WebSocketer(); 188 | 189 | if (url.Scheme == "https") 190 | { 191 | SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true); 192 | await ssl.AuthenticateAsClientAsync(url.Host, new X509CertificateCollection(), SslProtocols.None, false); 193 | _webSocket.stream = ssl; 194 | } 195 | else 196 | { 197 | _webSocket.stream = new NetworkStream(client, true); 198 | 199 | } 200 | WebSocketWrite(); 201 | await _webSocket.stream.WriteAsync(_webSocket.buffer, 0, UMC.Net.NetHttpResponse.Header(webRequest, _webSocket.buffer)); 202 | WebSocketRead(); 203 | 204 | HttpMimeServier.httpMimes.TryRemove(this.pid, out var _); 205 | } 206 | catch (Exception ex) 207 | { 208 | OutText(500, ex.ToString()); 209 | } 210 | } 211 | 212 | class WebSocketer : IDisposable 213 | { 214 | public byte[] buffer = System.Buffers.ArrayPool.Shared.Rent(0x600);//new byte[0x600]; 215 | public System.IO.Stream stream; 216 | 217 | public void Dispose() 218 | { 219 | stream.Close(); 220 | stream.Dispose(); 221 | buffer = null; 222 | System.Buffers.ArrayPool.Shared.Return(buffer); 223 | } 224 | } 225 | WebSocketer _webSocket; 226 | byte[] _data = System.Buffers.ArrayPool.Shared.Rent(0x600);// new byte[0x600]; 227 | 228 | async void WebSocketRead() 229 | { 230 | int size = 0; 231 | try 232 | { 233 | size = await _webSocket.stream.ReadAsync(_webSocket.buffer, 0, _webSocket.buffer.Length); 234 | 235 | if (size > 0) 236 | { 237 | await _stream.WriteAsync(_webSocket.buffer, 0, size); 238 | WebSocketRead(); 239 | } 240 | else 241 | { 242 | this.Dispose(); 243 | } 244 | } 245 | catch 246 | { 247 | this.Dispose(); 248 | } 249 | } 250 | async void WebSocketWrite() 251 | { 252 | this.ActiveTime = UMC.Data.Utility.TimeSpan(); 253 | int size = 0; 254 | try 255 | { 256 | size = await _stream.ReadAsync(this._data, 0, this._data.Length); 257 | 258 | await _webSocket.stream.WriteAsync(_data, 0, size); 259 | 260 | 261 | WebSocketWrite(); 262 | 263 | } 264 | catch 265 | { 266 | this.Dispose(); 267 | } 268 | } 269 | async void Read(HttpMimeRequest req) 270 | { 271 | int size = 0; 272 | try 273 | { 274 | size = await _stream.ReadAsync(this._data, 0, this._data.Length); 275 | } 276 | catch 277 | { 278 | this.Dispose(); 279 | return; 280 | } 281 | if (size > 0) 282 | { 283 | this.ActiveTime = UMC.Data.Utility.TimeSpan(); 284 | try 285 | { 286 | req.Receive(this._data, 0, size); 287 | } 288 | catch (Exception ex) 289 | { 290 | this.OutText(500, ex.ToString()); 291 | 292 | return; 293 | } 294 | } 295 | else 296 | { 297 | this.Dispose(); 298 | return; 299 | } 300 | 301 | if (req.IsHttpFormatError) 302 | { 303 | req.Dispose(); 304 | this.Dispose(); 305 | return; 306 | } 307 | 308 | if (req.IsWebSocket == false && req.IsMimeFinish == false) 309 | { 310 | Read(req); 311 | } 312 | 313 | } 314 | 315 | } 316 | } 317 | 318 | -------------------------------------------------------------------------------- /Mime/HttpMimeStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Security.Authentication; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Threading.Tasks.Sources; 12 | using UMC.Net; 13 | 14 | namespace UMC.Host 15 | { 16 | 17 | class HttpMimeStream : System.IO.Stream, IValueTaskSource 18 | { 19 | private ManualResetValueTaskSourceCore _source = new ManualResetValueTaskSourceCore(); 20 | 21 | #region 实现接口,告诉调用者,任务是否已经完成,以及是否有结果,是否有异常等 22 | // 获取结果 23 | public int GetResult(short token) 24 | { 25 | return _source.GetResult(token); 26 | } 27 | 28 | public ValueTaskSourceStatus GetStatus(short token) 29 | { 30 | return _source.GetStatus(token); ; 31 | } 32 | 33 | // 实现延续 34 | public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) 35 | { 36 | _source.OnCompleted(continuation, state, token, flags); 37 | } 38 | 39 | #endregion 40 | 41 | 42 | // 以及完成任务,并给出结果 43 | public void SetResult(int result) 44 | { 45 | _source.SetResult(result); 46 | } 47 | short _token = 0; 48 | // 要执行的任务出现异常 49 | public void SetException(Exception exception) 50 | { 51 | _source.SetException(exception); 52 | } 53 | 54 | 55 | private int _disposed; 56 | 57 | 58 | public override bool CanRead => true; 59 | 60 | public override bool CanSeek => false; 61 | 62 | public override bool CanWrite => true; 63 | 64 | public override long Length 65 | { 66 | get 67 | { 68 | throw new NotSupportedException(); 69 | } 70 | } 71 | 72 | public override long Position 73 | { 74 | get 75 | { 76 | throw new NotSupportedException(); 77 | } 78 | set 79 | { 80 | throw new NotSupportedException(); 81 | } 82 | } 83 | HttpMime _mime; 84 | public HttpMimeStream(HttpMime mime) 85 | { 86 | this._mime = mime; 87 | } 88 | 89 | 90 | public override long Seek(long offset, SeekOrigin origin) 91 | { 92 | throw new NotSupportedException(); 93 | } 94 | 95 | public override int Read(byte[] buffer, int offset, int count) 96 | { 97 | return this.ReadAsync(buffer, offset, count).Result; 98 | } 99 | public void AppendData(byte[] buffer, int offset, int size) 100 | { 101 | if (_buffers.IsEmpty == false) 102 | { 103 | if (_buffers.Length >= size) 104 | { 105 | buffer.AsMemory(offset, size).CopyTo(_buffer); 106 | _source.SetResult(size); 107 | } 108 | else 109 | { 110 | buffer.AsMemory(offset, _buffers.Length).CopyTo(_buffer); 111 | int len = size - _buffers.Length; 112 | if (len + _bufferSize > _buffer.Length) 113 | { 114 | var _bs = new byte[len + _bufferSize + 200]; 115 | Array.Copy(_buffer, 0, _bs, 0, _bufferSize); 116 | _buffer = _bs; 117 | } 118 | Array.Copy(buffer, offset + _buffer.Length, _buffer, _bufferSize, len); 119 | 120 | 121 | _source.SetResult(_buffers.Length); 122 | 123 | } 124 | } 125 | } 126 | 127 | 128 | public override void Write(byte[] buffer, int offset, int count) 129 | { 130 | _mime.Write(buffer, offset, count); 131 | } 132 | 133 | 134 | 135 | protected override void Dispose(bool disposing) 136 | { 137 | if (Interlocked.Exchange(ref _disposed, 1) != 0) 138 | { 139 | return; 140 | } 141 | base.Dispose(disposing); 142 | } 143 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 144 | { 145 | Stream.ValidateBufferArguments(buffer, offset, count); 146 | return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); 147 | } 148 | byte[] _buffer = Array.Empty(); 149 | int _bufferSize = 0; 150 | Memory _buffers; 151 | //int _start = 0; 152 | public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) 153 | { 154 | if (_bufferSize == 0) 155 | { 156 | _token++; 157 | _buffers = buffer; 158 | _source.Reset(); 159 | return new ValueTask(this, _token); 160 | } 161 | if (buffer.Length >= _bufferSize) 162 | { 163 | int len = _bufferSize; 164 | _buffer.AsMemory(0, _bufferSize).CopyTo(buffer); 165 | _bufferSize = 0; 166 | 167 | return new ValueTask(len); 168 | } 169 | else 170 | { 171 | _buffer.AsMemory(0, buffer.Length).CopyTo(buffer); 172 | _bufferSize -= buffer.Length; 173 | Array.Copy(_buffer, buffer.Length, _buffer, 0, _bufferSize); 174 | 175 | return new ValueTask(buffer.Length); 176 | 177 | 178 | } 179 | } 180 | 181 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 182 | { 183 | Stream.ValidateBufferArguments(buffer, offset, count); 184 | _mime.Write(buffer, offset, count); 185 | return Task.CompletedTask; 186 | //NetworkStream 187 | } 188 | 189 | public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) 190 | { 191 | byte[] array = ArrayPool.Shared.Rent(buffer.Length); 192 | try 193 | { 194 | buffer.CopyTo(array); 195 | _mime.Write(array, 0, buffer.Length); 196 | return ValueTask.CompletedTask; 197 | } 198 | finally 199 | { 200 | ArrayPool.Shared.Return(array); 201 | } 202 | 203 | } 204 | 205 | public override void Flush() 206 | { 207 | } 208 | 209 | public override Task FlushAsync(CancellationToken cancellationToken) 210 | { 211 | return Task.CompletedTask; 212 | } 213 | 214 | public override void SetLength(long value) 215 | { 216 | throw new NotSupportedException(); 217 | } 218 | 219 | } 220 | } 221 | 222 | -------------------------------------------------------------------------------- /Mime/HttpWebSocket.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using UMC.Net; 6 | using System.Net.WebSockets; 7 | 8 | namespace UMC.Host 9 | { 10 | class HttpWebSocket 11 | { 12 | 13 | internal static string GetSecWebSocketAcceptString(string secWebSocketKey) 14 | { 15 | string s = secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 16 | byte[] bytes = Encoding.UTF8.GetBytes(s); 17 | byte[] inArray = System.Security.Cryptography.SHA1.HashData(bytes); 18 | return Convert.ToBase64String(inArray); 19 | } 20 | 21 | internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, out string acceptProtocol) 22 | { 23 | acceptProtocol = string.Empty; 24 | if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) 25 | { 26 | return false; 27 | } 28 | string[] array = clientSecWebSocketProtocol.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); 29 | if (array.Length > 0) 30 | { 31 | acceptProtocol = array[0]; 32 | } 33 | return true; 34 | } 35 | 36 | internal static bool ValidateWebSocketHeaders(NetContext context) 37 | { 38 | string text = context.Headers["Sec-WebSocket-Version"]; 39 | if (string.IsNullOrEmpty(text)) 40 | { 41 | return false; 42 | } 43 | if (!string.Equals(text, "13", StringComparison.OrdinalIgnoreCase)) 44 | { 45 | return false; 46 | } 47 | string text2 = context.Headers["Sec-WebSocket-Key"]; 48 | if (!string.IsNullOrWhiteSpace(text2)) 49 | { 50 | try 51 | { 52 | return Convert.FromBase64String(text2).Length == 16; 53 | } 54 | catch 55 | { 56 | return false; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | internal static void AcceptWebSocketAsyncCore(NetContext context, Stream stream) 63 | { 64 | if (context.Url.AbsolutePath == "/UMC.WS") 65 | { 66 | var Device = context.Cookies["device"] ?? context.QueryString.Get("device"); 67 | if (String.IsNullOrEmpty(Device) == false) 68 | { 69 | if (ValidateWebSocketHeaders(context) && false) 70 | { 71 | string secWebSocketKey = context.Headers["Sec-WebSocket-Key"]; 72 | string secWebSocketAcceptString = GetSecWebSocketAcceptString(secWebSocketKey); 73 | var writer = new Net.TextWriter(stream.Write); 74 | writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n"); 75 | writer.Write("Connection: Upgrade\r\n"); 76 | writer.Write("Upgrade: websocket\r\n"); 77 | 78 | 79 | writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n"); 80 | writer.Write($"Sec-WebSocket-Protocol: mqtt\r\n"); 81 | 82 | writer.Write("Server: UMC.Proxy\r\n\r\n"); 83 | 84 | writer.Flush(); 85 | writer.Dispose(); 86 | var DeviceId = UMC.Data.Utility.Guid(Device, true).Value; 87 | //16384, 88 | context.Tag = WebSocket.CreateFromStream(stream, isServer: true, "mqtt", WebSocket.DefaultKeepAliveInterval); 89 | return; 90 | } 91 | } 92 | } 93 | context.Error(new ArgumentException("WebSocket")); 94 | 95 | 96 | } 97 | 98 | } 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /Proxy/AvifConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace UMC.Proxy 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Runtime.InteropServices; 9 | using System.Threading.Tasks; 10 | 11 | /// 12 | /// A executer for `cavif` which is a Encoder/converter for AVIF images. 13 | /// Please have the native `cavif` libraries next to your application. 14 | /// Grab prebuilt `cavif` from here: https://github.com/kornelski/cavif-rs/releases 15 | /// 16 | public static class AvifConverter 17 | { 18 | 19 | /// 20 | /// Converts PNG/JPEG to AVIF format. 21 | /// 22 | public static Task EncodeImage(string imageFile, string outputImageFile = null, AvifConverterOptions options = null) 23 | { 24 | string converterPath = GetConverterPath(); 25 | string arguments = GenerateArgument(imageFile, outputImageFile, options); 26 | return RunEncodeProcessAsync(converterPath, arguments); 27 | } 28 | 29 | 30 | private static string GenerateArgument(string imageFile, string outputImage = null, AvifConverterOptions options = null) 31 | { 32 | List list = new List(8); 33 | if (options != null) 34 | { 35 | if (!options!.EmitMesage) 36 | { 37 | list.Add("--quiet"); 38 | } 39 | if (options!.Overwrite) 40 | { 41 | list.Add("--overwrite"); 42 | } 43 | if (options!.Speed.HasValue) 44 | { 45 | list.Add("--speed " + options!.Speed.Value); 46 | } 47 | if (options!.Quality.HasValue) 48 | { 49 | list.Add("--quality " + options!.Quality.Value); 50 | } 51 | if (options!.ColorRgb == true) 52 | { 53 | list.Add("--color rgb"); 54 | } 55 | if (options!.DirtyAlpha == true) 56 | { 57 | list.Add("--dirty-alpha"); 58 | } 59 | } 60 | else 61 | { 62 | list.Add("--quiet"); 63 | list.Add("--overwrite"); 64 | } 65 | if (outputImage != null) 66 | { 67 | list.Add("-o \"" + outputImage + "\""); 68 | } 69 | list.Add("\"" + imageFile + "\""); 70 | return string.Join(" ", list); 71 | } 72 | 73 | private static string GetConverterPath() 74 | { 75 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 76 | { 77 | return ".\\native\\cavif.exe"; 78 | } 79 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 80 | { 81 | return "./native/cavif"; 82 | } 83 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 84 | { 85 | return "./native/cavif"; 86 | } 87 | else 88 | { 89 | throw new PlatformNotSupportedException(); 90 | } 91 | } 92 | 93 | private static Task RunEncodeProcessAsync(string aviflibExe, string arguments) 94 | { 95 | TaskCompletionSource tcs = new TaskCompletionSource(); 96 | try 97 | { 98 | Process process = new Process 99 | { 100 | StartInfo = { 101 | FileName = aviflibExe, 102 | Arguments = arguments, 103 | WindowStyle = ProcessWindowStyle.Hidden, 104 | UseShellExecute = false, 105 | RedirectStandardOutput = true, 106 | CreateNoWindow = true 107 | }, 108 | EnableRaisingEvents = true 109 | }; 110 | process.Exited += delegate 111 | { 112 | string text = ""; 113 | while (!process.StandardOutput.EndOfStream) 114 | { 115 | text = text + process.StandardOutput.ReadLine() + Environment.NewLine; 116 | } 117 | bool success = process.ExitCode == 0; 118 | tcs.SetResult(new AvifConvertResult(success, text)); 119 | process.Dispose(); 120 | }; 121 | process.Start(); 122 | } 123 | catch (Exception ex) 124 | { 125 | return Task.FromResult(new AvifConvertResult(success: false, ex.Message)); 126 | } 127 | return tcs.Task; 128 | } 129 | } 130 | public class AvifConvertResult 131 | { 132 | public bool Success { get; } 133 | 134 | public string Message { get; } = string.Empty; 135 | 136 | 137 | public AvifConvertResult() 138 | { 139 | } 140 | 141 | public AvifConvertResult(bool success) 142 | { 143 | Success = success; 144 | } 145 | 146 | public AvifConvertResult(bool success, string message) 147 | { 148 | Success = success; 149 | Message = message; 150 | } 151 | } 152 | /// 153 | /// Read more about options here: 154 | /// https://github.com/kornelski/cavif-rs 155 | /// 156 | public class AvifConverterOptions 157 | { 158 | /// 159 | /// Quality from 1 (worst) to 100 (best), the default value is 80. The numbers have different meaning than JPEG's quality scale. Beware when comparing codecs. There is no lossless compression support. 160 | /// 161 | public int? Quality { get; set; } 162 | 163 | /// 164 | /// Encoding speed between 1 (best, but slowest) and 10 (fastest, but a blurry mess), the default value is 4. Speeds 1 and 2 are unbelievably slow, but make files ~3-5% smaller. Speeds 7 and above degrade compression significantly, and are not recommended. 165 | /// 166 | public int? Speed { get; set; } 167 | 168 | /// 169 | /// Replace files if there's .avif already. By default the existing files are overwritten. 170 | /// 171 | public bool Overwrite { get; set; } = true; 172 | 173 | 174 | /// 175 | /// Preserve RGB values of fully transparent pixels (not recommended). By default irrelevant color of transparent pixels is cleared to avoid wasting space. 176 | /// 177 | public bool? DirtyAlpha { get; set; } 178 | 179 | /// 180 | /// Encode using RGB instead of YCbCr color space. Makes colors closer to lossless, but makes files larger. Use only if you need to avoid even smallest color shifts. 181 | /// 182 | public bool? ColorRgb { get; set; } 183 | 184 | /// 185 | /// Generate output message 186 | /// 187 | public bool EmitMesage { get; set; } 188 | } 189 | 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /Proxy/DataFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Linq; 5 | using System.IO.Compression; 6 | using UMC.Data; 7 | using UMC.Net; 8 | using UMC.Proxy.Entities; 9 | using System.Collections.Generic; 10 | using System.Text.RegularExpressions; 11 | using System.Text; 12 | using UMC.Data.Caches; 13 | using System.Security.Cryptography.X509Certificates; 14 | 15 | namespace UMC.Proxy 16 | { 17 | public class DataFactory : IStringSubscribe 18 | { 19 | static DataFactory() 20 | { 21 | HotCache.Register("Root").Register("SiteKey"); 22 | HotCache.Register("user_id", "Domain", "IndexValue"); 23 | HotCache.Register("Host").Register("Root", "Host"); 24 | } 25 | public static DataFactory Instance() 26 | { 27 | if (_Instance == null) 28 | { 29 | _Instance = new DataFactory(); 30 | NetSubscribe.Subscribe("SiteConfig", _Instance); 31 | } 32 | return _Instance; 33 | } 34 | static DataFactory _Instance;// = new DataFactory(); 35 | public static void Instance(DataFactory dataFactory) 36 | { 37 | _Instance = dataFactory; 38 | NetSubscribe.Subscribe("SiteConfig", _Instance); 39 | } 40 | 41 | 42 | public virtual Site[] Site() 43 | { 44 | int index; 45 | return HotCache.Cache().Find(new Entities.Site(), 0, out index); 46 | 47 | } 48 | 49 | 50 | public virtual Site Site(String root) 51 | { 52 | return HotCache.Cache().Get(new Proxy.Entities.Site { Root = root }); 53 | 54 | } 55 | public virtual Site Site(int siteKey) 56 | { 57 | return HotCache.Cache().Get(new Proxy.Entities.Site { SiteKey = siteKey }); 58 | 59 | } 60 | 61 | public virtual void Put(SiteHost host) 62 | { 63 | HotCache.Cache().Put(host); 64 | } 65 | public virtual SiteHost HostSite(string host) 66 | { 67 | return HotCache.Cache().Get(new Entities.SiteHost { Host = host }); 68 | } 69 | public virtual SiteHost[] Host(string root) 70 | { 71 | int index; 72 | return HotCache.Cache().Find(new Entities.SiteHost { Root = root }, 0, out index); 73 | } 74 | public virtual void Delete(SiteHost host) 75 | { 76 | HotCache.Cache().Delete(host); 77 | } 78 | public virtual Cookie Cookie(String domain, Guid user_id, int index) 79 | { 80 | return HotCache.Cache().Get(new Proxy.Entities.Cookie { Domain = domain, user_id = user_id, IndexValue = index }); 81 | 82 | 83 | } 84 | public virtual Cookie[] Cookies(String domain, Guid user_id) 85 | { 86 | int index; 87 | return HotCache.Cache().Find(new Entities.Cookie { user_id = user_id, Domain = domain }, 0, out index); 88 | } 89 | public virtual void Put(Site site) 90 | { 91 | site.Root = site.Root.ToLower(); 92 | HotCache.Cache().Put(site); 93 | } 94 | public virtual bool IsRegister() 95 | { 96 | var appId = WebResource.Instance().Provider["appId"]; 97 | var secret = Data.WebResource.Instance().Provider["appSecret"]; 98 | if (String.IsNullOrEmpty(secret) == false && String.IsNullOrEmpty(appId) == false) 99 | { 100 | var webr4 = new Uri(APIProxy.Uri, "Transfer").WebRequest();// Utility.Parse36Encode(Utility.Guid(appId).Value))).WebRequest(); 101 | var nvs = new System.Collections.Specialized.NameValueCollection(); 102 | Utility.Sign(webr4, nvs, secret); 103 | return webr4.Get().StatusCode == System.Net.HttpStatusCode.OK; 104 | } 105 | return false; 106 | } 107 | public virtual void Delete(Site site) 108 | { 109 | HotCache.Cache().Delete(site); 110 | } 111 | public virtual void Delete(Cookie cookie) 112 | { 113 | if (String.IsNullOrEmpty(cookie.Domain) == false && cookie.user_id.HasValue) 114 | { 115 | if (cookie.IndexValue.HasValue == false) 116 | { 117 | cookie.IndexValue = 0; 118 | } 119 | HotCache.Cache().Delete(cookie); 120 | } 121 | } 122 | public virtual void Put(Cookie cookie) 123 | { 124 | if (cookie.IndexValue.HasValue == false) 125 | { 126 | cookie.IndexValue = 0; 127 | } 128 | if (String.IsNullOrEmpty(cookie.Domain) == false && cookie.user_id.HasValue) 129 | { 130 | HotCache.Cache().Put(cookie); 131 | } 132 | 133 | } 134 | public virtual String Evaluate(String js, params string[] args) 135 | { 136 | return ""; 137 | } 138 | public virtual Stream Decompress(Stream response, string encoding) 139 | { 140 | switch (encoding) 141 | { 142 | case "gzip": 143 | return new GZipStream(response, CompressionMode.Decompress); 144 | case "deflate": 145 | return new DeflateStream(response, CompressionMode.Decompress); 146 | case "br": 147 | return new BrotliStream(response, System.IO.Compression.CompressionMode.Decompress); 148 | default: 149 | return response; 150 | } 151 | } 152 | public virtual Stream Compress(Stream response, string encoding) 153 | { 154 | switch (encoding) 155 | { 156 | case "gzip": 157 | return new GZipStream(response, CompressionMode.Compress); 158 | case "deflate": 159 | return new DeflateStream(response, CompressionMode.Compress); 160 | case "br": 161 | return new BrotliStream(response, System.IO.Compression.CompressionLevel.Fastest); 162 | default: 163 | return response; 164 | } 165 | } 166 | 167 | Dictionary siteConfigs = new Dictionary(); 168 | public virtual SiteConfig SiteConfig(String root) 169 | { 170 | SiteConfig config; 171 | if (siteConfigs.TryGetValue(root, out config)) 172 | { 173 | return config; 174 | } 175 | else 176 | { 177 | 178 | var site = this.Site(root); 179 | if (site != null) 180 | { 181 | config = new SiteConfig(site); 182 | siteConfigs[root] = config; 183 | return config; 184 | } 185 | return null; 186 | 187 | } 188 | } 189 | 190 | public virtual void Delete(SiteConfig siteConfig) 191 | { 192 | siteConfigs.Remove(siteConfig.Root); 193 | WebFactory.Auths.Remove(siteConfig.Root); 194 | NetSubscribe.Publish("SiteConfig", siteConfig.Root); 195 | 196 | } 197 | 198 | void IStringSubscribe.Subscribe(string message) 199 | { 200 | WebFactory.Auths.Remove(message); 201 | siteConfigs.Remove(message); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Proxy/Entities/Codes/Cookie.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UMC.Data; 4 | namespace UMC.Proxy.Entities 5 | { 6 | public partial class Cookie 7 | { 8 | readonly static Action[] _SetValues = new Action[] { (r, t) => r.Account = Reflection.ParseObject(t, r.Account), (r, t) => r.Badge = Reflection.ParseObject(t, r.Badge), (r, t) => r.ChangedTime = Reflection.ParseObject(t, r.ChangedTime), (r, t) => r.Config = Reflection.ParseObject(t, r.Config), (r, t) => r.Cookies = Reflection.ParseObject(t, r.Cookies), (r, t) => r.Domain = Reflection.ParseObject(t, r.Domain), (r, t) => r.IndexValue = Reflection.ParseObject(t, r.IndexValue), (r, t) => r.LoginTime = Reflection.ParseObject(t, r.LoginTime), (r, t) => r.Model = Reflection.ParseObject(t, r.Model), (r, t) => r.Time = Reflection.ParseObject(t, r.Time), (r, t) => r.user_id = Reflection.ParseObject(t, r.user_id) }; 9 | readonly static string[] _Columns = new string[] { "Account", "Badge", "ChangedTime", "Config", "Cookies", "Domain", "IndexValue", "LoginTime", "Model", "Time", "user_id" }; 10 | protected override void SetValue(string name, object obv) 11 | { 12 | var index = Utility.Search(_Columns, name, StringComparer.CurrentCultureIgnoreCase); 13 | if (index > -1) _SetValues[index](this, obv); 14 | } 15 | protected override void GetValues(Action action) 16 | { 17 | AppendValue(action, "Account", this.Account); 18 | AppendValue(action, "Badge", this.Badge); 19 | AppendValue(action, "ChangedTime", this.ChangedTime); 20 | AppendValue(action, "Config", this.Config); 21 | AppendValue(action, "Cookies", this.Cookies); 22 | AppendValue(action, "Domain", this.Domain); 23 | AppendValue(action, "IndexValue", this.IndexValue); 24 | AppendValue(action, "LoginTime", this.LoginTime); 25 | AppendValue(action, "Model", this.Model); 26 | AppendValue(action, "Time", this.Time); 27 | AppendValue(action, "user_id", this.user_id); 28 | } 29 | 30 | protected override RecordColumn[] GetColumns() 31 | { 32 | var cols = new RecordColumn[11]; 33 | cols[0] = RecordColumn.Column("Account", this.Account); 34 | cols[1] = RecordColumn.Column("Badge", this.Badge); 35 | cols[2] = RecordColumn.Column("ChangedTime", this.ChangedTime); 36 | cols[3] = RecordColumn.Column("Config", this.Config); 37 | cols[4] = RecordColumn.Column("Cookies", this.Cookies); 38 | cols[5] = RecordColumn.Column("Domain", this.Domain); 39 | cols[6] = RecordColumn.Column("IndexValue", this.IndexValue); 40 | cols[7] = RecordColumn.Column("LoginTime", this.LoginTime); 41 | cols[8] = RecordColumn.Column("Model", this.Model); 42 | cols[9] = RecordColumn.Column("Time", this.Time); 43 | cols[10] = RecordColumn.Column("user_id", this.user_id); 44 | return cols; 45 | } 46 | 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Proxy/Entities/Codes/Site.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UMC.Data; 4 | namespace UMC.Proxy.Entities 5 | { 6 | public partial class Site 7 | { 8 | readonly static Action[] _SetValues = new Action[] { (r, t) => r.Account = Reflection.ParseObject(t, r.Account), (r, t) => r.AdminConf = Reflection.ParseObject(t, r.AdminConf), (r, t) => r.AppendJSConf = Reflection.ParseObject(t, r.AppendJSConf), (r, t) => r.AppSecret = Reflection.ParseObject(t, r.AppSecret), (r, t) => r.AuthConf = Reflection.ParseObject(t, r.AuthConf), (r, t) => r.AuthExpire = Reflection.ParseObject(t, r.AuthExpire), (r, t) => r.AuthType = Reflection.ParseObject(t, r.AuthType), (r, t) => r.Caption = Reflection.ParseObject(t, r.Caption), (r, t) => r.Conf = Reflection.ParseObject(t, r.Conf), (r, t) => r.Domain = Reflection.ParseObject(t, r.Domain), (r, t) => r.Flag = Reflection.ParseObject(t, r.Flag), (r, t) => r.HeaderConf = Reflection.ParseObject(t, r.HeaderConf), (r, t) => r.HelpKey = Reflection.ParseObject(t, r.HelpKey), (r, t) => r.Home = Reflection.ParseObject(t, r.Home), (r, t) => r.Host = Reflection.ParseObject(t, r.Host), (r, t) => r.HostModel = Reflection.ParseObject(t, r.HostModel), (r, t) => r.HostReConf = Reflection.ParseObject(t, r.HostReConf), (r, t) => r.ImagesConf = Reflection.ParseObject(t, r.ImagesConf), (r, t) => r.IsAuth = Reflection.ParseObject(t, r.IsAuth), (r, t) => r.IsDebug = Reflection.ParseObject(t, r.IsDebug), (r, t) => r.IsDesktop = Reflection.ParseObject(t, r.IsDesktop), (r, t) => r.IsModule = Reflection.ParseObject(t, r.IsModule), (r, t) => r.LogConf = Reflection.ParseObject(t, r.LogConf), (r, t) => r.LogoutPath = Reflection.ParseObject(t, r.LogoutPath), (r, t) => r.MobileHome = Reflection.ParseObject(t, r.MobileHome), (r, t) => r.ModifyTime = Reflection.ParseObject(t, r.ModifyTime), (r, t) => r.OpenModel = Reflection.ParseObject(t, r.OpenModel), (r, t) => r.OutputCookies = Reflection.ParseObject(t, r.OutputCookies), (r, t) => r.RedirectPath = Reflection.ParseObject(t, r.RedirectPath), (r, t) => r.Root = Reflection.ParseObject(t, r.Root), (r, t) => r.SiteKey = Reflection.ParseObject(t, r.SiteKey), (r, t) => r.SLB = Reflection.ParseObject(t, r.SLB), (r, t) => r.StaticConf = Reflection.ParseObject(t, r.StaticConf), (r, t) => r.Time = Reflection.ParseObject(t, r.Time), (r, t) => r.Timeout = Reflection.ParseObject(t, r.Timeout), (r, t) => r.Type = Reflection.ParseObject(t, r.Type), (r, t) => r.UserBrowser = Reflection.ParseObject(t, r.UserBrowser), (r, t) => r.UserModel = Reflection.ParseObject(t, r.UserModel), (r, t) => r.Version = Reflection.ParseObject(t, r.Version) }; 9 | readonly static string[] _Columns = new string[] { "Account", "AdminConf", "AppendJSConf", "AppSecret", "AuthConf", "AuthExpire", "AuthType", "Caption", "Conf", "Domain", "Flag", "HeaderConf", "HelpKey", "Home", "Host", "HostModel", "HostReConf", "ImagesConf", "IsAuth", "IsDebug", "IsDesktop", "IsModule", "LogConf", "LogoutPath", "MobileHome", "ModifyTime", "OpenModel", "OutputCookies", "RedirectPath", "Root", "SiteKey", "SLB", "StaticConf", "Time", "Timeout", "Type", "UserBrowser", "UserModel", "Version" }; 10 | protected override void SetValue(string name, object obv) 11 | { 12 | var index = Utility.Search(_Columns, name, StringComparer.CurrentCultureIgnoreCase); 13 | if (index > -1) _SetValues[index](this, obv); 14 | } 15 | protected override void GetValues(Action action) 16 | { 17 | AppendValue(action, "Account", this.Account); 18 | AppendValue(action, "AdminConf", this.AdminConf); 19 | AppendValue(action, "AppendJSConf", this.AppendJSConf); 20 | AppendValue(action, "AppSecret", this.AppSecret); 21 | AppendValue(action, "AuthConf", this.AuthConf); 22 | AppendValue(action, "AuthExpire", this.AuthExpire); 23 | AppendValue(action, "AuthType", this.AuthType); 24 | AppendValue(action, "Caption", this.Caption); 25 | AppendValue(action, "Conf", this.Conf); 26 | AppendValue(action, "Domain", this.Domain); 27 | AppendValue(action, "Flag", this.Flag); 28 | AppendValue(action, "HeaderConf", this.HeaderConf); 29 | AppendValue(action, "HelpKey", this.HelpKey); 30 | AppendValue(action, "Home", this.Home); 31 | AppendValue(action, "Host", this.Host); 32 | AppendValue(action, "HostModel", this.HostModel); 33 | AppendValue(action, "HostReConf", this.HostReConf); 34 | AppendValue(action, "ImagesConf", this.ImagesConf); 35 | AppendValue(action, "IsAuth", this.IsAuth); 36 | AppendValue(action, "IsDebug", this.IsDebug); 37 | AppendValue(action, "IsDesktop", this.IsDesktop); 38 | AppendValue(action, "IsModule", this.IsModule); 39 | AppendValue(action, "LogConf", this.LogConf); 40 | AppendValue(action, "LogoutPath", this.LogoutPath); 41 | AppendValue(action, "MobileHome", this.MobileHome); 42 | AppendValue(action, "ModifyTime", this.ModifyTime); 43 | AppendValue(action, "OpenModel", this.OpenModel); 44 | AppendValue(action, "OutputCookies", this.OutputCookies); 45 | AppendValue(action, "RedirectPath", this.RedirectPath); 46 | AppendValue(action, "Root", this.Root); 47 | AppendValue(action, "SiteKey", this.SiteKey); 48 | AppendValue(action, "SLB", this.SLB); 49 | AppendValue(action, "StaticConf", this.StaticConf); 50 | AppendValue(action, "Time", this.Time); 51 | AppendValue(action, "Timeout", this.Timeout); 52 | AppendValue(action, "Type", this.Type); 53 | AppendValue(action, "UserBrowser", this.UserBrowser); 54 | AppendValue(action, "UserModel", this.UserModel); 55 | AppendValue(action, "Version", this.Version); 56 | } 57 | 58 | protected override RecordColumn[] GetColumns() 59 | { 60 | var cols = new RecordColumn[39]; 61 | cols[0] = RecordColumn.Column("Account", this.Account); 62 | cols[1] = RecordColumn.Column("AdminConf", this.AdminConf); 63 | cols[2] = RecordColumn.Column("AppendJSConf", this.AppendJSConf); 64 | cols[3] = RecordColumn.Column("AppSecret", this.AppSecret); 65 | cols[4] = RecordColumn.Column("AuthConf", this.AuthConf); 66 | cols[5] = RecordColumn.Column("AuthExpire", this.AuthExpire); 67 | cols[6] = RecordColumn.Column("AuthType", this.AuthType); 68 | cols[7] = RecordColumn.Column("Caption", this.Caption); 69 | cols[8] = RecordColumn.Column("Conf", this.Conf); 70 | cols[9] = RecordColumn.Column("Domain", this.Domain); 71 | cols[10] = RecordColumn.Column("Flag", this.Flag); 72 | cols[11] = RecordColumn.Column("HeaderConf", this.HeaderConf); 73 | cols[12] = RecordColumn.Column("HelpKey", this.HelpKey); 74 | cols[13] = RecordColumn.Column("Home", this.Home); 75 | cols[14] = RecordColumn.Column("Host", this.Host); 76 | cols[15] = RecordColumn.Column("HostModel", this.HostModel); 77 | cols[16] = RecordColumn.Column("HostReConf", this.HostReConf); 78 | cols[17] = RecordColumn.Column("ImagesConf", this.ImagesConf); 79 | cols[18] = RecordColumn.Column("IsAuth", this.IsAuth); 80 | cols[19] = RecordColumn.Column("IsDebug", this.IsDebug); 81 | cols[20] = RecordColumn.Column("IsDesktop", this.IsDesktop); 82 | cols[21] = RecordColumn.Column("IsModule", this.IsModule); 83 | cols[22] = RecordColumn.Column("LogConf", this.LogConf); 84 | cols[23] = RecordColumn.Column("LogoutPath", this.LogoutPath); 85 | cols[24] = RecordColumn.Column("MobileHome", this.MobileHome); 86 | cols[25] = RecordColumn.Column("ModifyTime", this.ModifyTime); 87 | cols[26] = RecordColumn.Column("OpenModel", this.OpenModel); 88 | cols[27] = RecordColumn.Column("OutputCookies", this.OutputCookies); 89 | cols[28] = RecordColumn.Column("RedirectPath", this.RedirectPath); 90 | cols[29] = RecordColumn.Column("Root", this.Root); 91 | cols[30] = RecordColumn.Column("SiteKey", this.SiteKey); 92 | cols[31] = RecordColumn.Column("SLB", this.SLB); 93 | cols[32] = RecordColumn.Column("StaticConf", this.StaticConf); 94 | cols[33] = RecordColumn.Column("Time", this.Time); 95 | cols[34] = RecordColumn.Column("Timeout", this.Timeout); 96 | cols[35] = RecordColumn.Column("Type", this.Type); 97 | cols[36] = RecordColumn.Column("UserBrowser", this.UserBrowser); 98 | cols[37] = RecordColumn.Column("UserModel", this.UserModel); 99 | cols[38] = RecordColumn.Column("Version", this.Version); 100 | return cols; 101 | } 102 | 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /Proxy/Entities/Codes/SiteHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UMC.Data; 4 | namespace UMC.Proxy.Entities 5 | { 6 | public partial class SiteHost 7 | { 8 | readonly static Action[] _SetValues = new Action[] { (r, t) => r.Host = Reflection.ParseObject(t, r.Host), (r, t) => r.Root = Reflection.ParseObject(t, r.Root), (r, t) => r.Scheme = Reflection.ParseObject(t, r.Scheme) }; 9 | readonly static string[] _Columns = new string[] { "Host", "Root", "Scheme" }; 10 | protected override void SetValue(string name, object obv) 11 | { 12 | var index = Utility.Search(_Columns, name, StringComparer.CurrentCultureIgnoreCase); 13 | if (index > -1) _SetValues[index](this, obv); 14 | } 15 | protected override void GetValues(Action action) 16 | { 17 | AppendValue(action, "Host", this.Host); 18 | AppendValue(action, "Root", this.Root); 19 | AppendValue(action, "Scheme", this.Scheme); 20 | } 21 | 22 | protected override RecordColumn[] GetColumns() 23 | { 24 | var cols = new RecordColumn[3]; 25 | cols[0] = RecordColumn.Column("Host", this.Host); 26 | cols[1] = RecordColumn.Column("Root", this.Root); 27 | cols[2] = RecordColumn.Column("Scheme", this.Scheme); 28 | return cols; 29 | } 30 | 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Proxy/Entities/Cookie.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using UMC.Data; 5 | 6 | namespace UMC.Proxy.Entities 7 | { 8 | public enum AccountModel 9 | { 10 | /// 11 | /// 标准账户 12 | /// 13 | Standard = 0, 14 | /// 15 | /// 需要托管密码 16 | /// 17 | Changed = 1, 18 | /// 19 | /// 由Check账户而来 20 | /// 21 | Check = 2 22 | } 23 | 24 | public partial class Cookie : Record 25 | { 26 | /// 27 | /// 站点 28 | /// 29 | public string Domain 30 | { 31 | get; set; 32 | } 33 | /// 34 | /// 服务密码 35 | /// 36 | public Guid? user_id 37 | { 38 | get; set; 39 | } 40 | public string Cookies 41 | { 42 | get; set; 43 | } 44 | /// 45 | /// 个数 46 | /// 47 | public int? IndexValue { get; set; } 48 | /// 49 | /// 账户 50 | /// 51 | public string Account 52 | { 53 | get; set; 54 | } 55 | 56 | public DateTime? Time 57 | { 58 | get; set; 59 | } 60 | /// 61 | /// 更新密码时间 62 | /// 63 | public int? ChangedTime 64 | { 65 | get; set; 66 | } 67 | /// 68 | /// 最近登录时间 69 | /// 70 | public int? LoginTime 71 | { 72 | get; set; 73 | } 74 | public int? Badge { get; set; } 75 | 76 | public String Config { get; set; } 77 | 78 | 79 | public AccountModel? Model { get; set; } 80 | 81 | 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Proxy/Entities/Initializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using UMC.Data; 6 | using UMC.Data.Entities; 7 | using UMC.Data.Sql; 8 | using UMC.Net; 9 | 10 | namespace UMC.Proxy.Entities 11 | { 12 | [Web.Mapping] 13 | public class Initializer : UMC.Data.Sql.Initializer 14 | { 15 | 16 | public override string Name => "Proxy"; 17 | 18 | public override string Caption => "应用网关"; 19 | 20 | public override void Setup(CSV.Log log) 21 | { 22 | Data.DataFactory.Instance().Put(new Menu() 23 | { 24 | Icon = "\uf085", 25 | Caption = "应用管理", 26 | IsHidden = false, 27 | ParentId = 0, 28 | Seq = 10, 29 | Id = 200, 30 | Url = "#proxy" 31 | 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Proxy/Entities/Site.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using UMC.Data; 5 | 6 | namespace UMC.Proxy.Entities 7 | { 8 | public enum UserModel 9 | { 10 | Standard = 0, 11 | /// 12 | /// 共享 13 | /// 14 | Share = 1, 15 | /// 16 | /// 引用 17 | /// 18 | Quote = 3, 19 | /// 20 | /// 桥接 21 | /// 22 | Bridge = 4, 23 | /// 24 | /// 管理员检测密码 25 | /// 26 | Check = 5, 27 | /// 28 | /// 管理员检测密码 29 | /// 30 | Checked = 6 31 | 32 | 33 | } 34 | public enum HostModel 35 | { 36 | /// 37 | /// 不处理 38 | /// 39 | None = 0, 40 | /// 41 | /// 在登录入口跳转 42 | /// 43 | Login = 1, 44 | /// 45 | /// 游览器跳转 46 | /// 47 | Check = 3, 48 | /// 49 | /// 所有请求跳转 50 | /// 51 | Disable = 4 52 | 53 | } 54 | public enum UserBrowser 55 | { 56 | All = 0, 57 | Chrome = 1, 58 | Firefox = 2, 59 | IE = 4, 60 | WebKit = 8, 61 | //Opera = 16, 62 | Dingtalk = 32, 63 | WeiXin = 64 64 | 65 | } 66 | 67 | public partial class Site : Record 68 | { 69 | public string Root 70 | { 71 | get; 72 | set; 73 | } 74 | public string Host 75 | { 76 | get; 77 | set; 78 | } 79 | 80 | public int? SiteKey 81 | { 82 | get; set; 83 | } 84 | public int? Timeout { get; set; } 85 | public string Caption { get; set; } 86 | 87 | /// 88 | /// 应用子目录 89 | /// 90 | public String Conf { get; set; } 91 | 92 | public String AuthConf { get; set; } 93 | public String StaticConf { get; set; } 94 | public String AppendJSConf { get; set; } 95 | public string Domain { get; set; } 96 | public int? Type { get; set; } 97 | /// 98 | /// 服务账户 99 | /// 100 | public string Account 101 | { 102 | get; set; 103 | } 104 | /// 105 | /// 客户端版本 106 | /// 107 | public string Version 108 | { 109 | get; set; 110 | } 111 | 112 | public string HelpKey 113 | { 114 | get; set; 115 | } 116 | public string OutputCookies 117 | { 118 | get; set; 119 | } 120 | public DateTime? Time 121 | { 122 | get; set; 123 | } 124 | /// 125 | /// 登录后的主页 126 | /// 127 | public string Home { get; set; } 128 | /// 129 | /// 移动主页 130 | /// 131 | public string MobileHome 132 | { 133 | get; set; 134 | } 135 | public int? OpenModel { get; set; } 136 | 137 | public UserModel? UserModel { get; set; } 138 | 139 | 140 | public Web.WebAuthType? AuthType { get; set; } 141 | 142 | public int? AuthExpire { get; set; } 143 | 144 | /// 145 | /// 标签,-1逻辑删除 146 | /// 147 | public int? Flag { get; set; } 148 | 149 | 150 | public int? SLB { get; set; } 151 | 152 | /// 153 | /// 请求头配置 154 | /// 155 | public string HeaderConf 156 | { 157 | get; set; 158 | } 159 | /// 160 | /// 日志地址 161 | /// 162 | public string LogConf 163 | { 164 | get; set; 165 | } 166 | /// 167 | /// 退出地址 168 | /// 169 | public string LogoutPath 170 | { 171 | get; set; 172 | } 173 | /// 174 | /// 替换域名的路径 175 | /// 176 | public string HostReConf 177 | { 178 | get; set; 179 | } 180 | 181 | /// 182 | /// 配置管理人 183 | /// 184 | public string AdminConf 185 | { 186 | get; set; 187 | } 188 | /// 189 | /// 是否是模块 190 | /// 191 | public bool? IsModule 192 | { 193 | get; set; 194 | } 195 | /// 196 | /// 是否是显示在桌面 197 | /// 198 | public bool? IsDesktop 199 | { 200 | get; set; 201 | } 202 | /// 203 | /// 是否是调试模式 204 | /// 205 | public bool? IsDebug 206 | { 207 | get; set; 208 | } 209 | 210 | ///// 211 | ///// 支持的浏览器 212 | ///// 213 | public UserBrowser? UserBrowser 214 | { 215 | get; set; 216 | } 217 | 218 | 219 | public HostModel? HostModel 220 | { 221 | get; set; 222 | } 223 | 224 | 225 | public string AppSecret { get; set; } 226 | 227 | 228 | public string RedirectPath 229 | { 230 | get; set; 231 | } 232 | public bool? IsAuth 233 | { 234 | get; set; 235 | } 236 | 237 | public String ImagesConf { get; set; } 238 | public int? ModifyTime { get; set; } 239 | // public String EventsConf { get; set; } 240 | } 241 | public partial class SiteHost : Record 242 | { 243 | public string Host 244 | { 245 | get; set; 246 | } 247 | public string Root 248 | { 249 | get; set; 250 | } 251 | public int? Scheme 252 | { 253 | 254 | get; set; 255 | } 256 | } 257 | 258 | 259 | } 260 | -------------------------------------------------------------------------------- /Proxy/SiteAuthActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Collections; 6 | using System.Reflection; 7 | using UMC.Web; 8 | using UMC.Data.Entities; 9 | using UMC.Web.UI; 10 | using UMC.Proxy.Entities; 11 | 12 | namespace UMC.Proxy.Activities 13 | { 14 | /// 15 | /// 邮箱账户 16 | /// 17 | [UMC.Web.Mapping("Proxy", "Auth", Auth = WebAuthType.Guest)] 18 | public class SiteAuthActivity : WebActivity 19 | { 20 | 21 | public override void ProcessActivity(WebRequest request, WebResponse response) 22 | { 23 | var seesionKey = Utility.MD5(this.Context.Token.Device.Value); 24 | 25 | var sesion = UMC.Data.DataFactory.Instance().Session(this.Context.Token.Device.ToString()); 26 | 27 | if (sesion != null) 28 | { 29 | sesion.SessionKey = seesionKey; 30 | UMC.Data.DataFactory.Instance().Put(sesion); 31 | response.Redirect(new WebMeta().Put("AuthKey", seesionKey)); 32 | 33 | } 34 | 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Proxy/SiteConfActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UMC.Web; 3 | using UMC.Data.Entities; 4 | 5 | namespace UMC.Proxy.Activities 6 | { 7 | /// 8 | /// 邮箱账户 9 | /// 10 | [UMC.Web.Mapping("Proxy", "Conf", Auth = WebAuthType.User)] 11 | public class SiteConfActivity : WebActivity 12 | { 13 | public override void ProcessActivity(WebRequest request, WebResponse response) 14 | { 15 | var mainKey = this.AsyncDialog("Key", g => 16 | { 17 | this.Prompt("请传入KEY"); 18 | return this.DialogValue("none"); 19 | }); 20 | var config = UMC.Data.DataFactory.Instance().Config(mainKey); 21 | var Conf = this.AsyncDialog("Conf", g => 22 | { 23 | var title = "内容配置"; 24 | if (mainKey.StartsWith("SITE_JS_CONFIG_")) 25 | { 26 | title = "脚本配置"; 27 | } 28 | var from5 = new UIFormDialog() { Title = title }; 29 | from5.AddTextarea(title, "ConfValue", config?.ConfValue).Put("Rows", 20).NotRequired(); 30 | 31 | from5.Submit("确认", "Mime.Config"); 32 | return from5; 33 | 34 | }); 35 | if (mainKey.StartsWith("SITE_") == false) 36 | { 37 | this.Prompt("只能配置站点相关内容"); 38 | } 39 | var ConfValue = Conf["ConfValue"]; 40 | 41 | Config platformConfig = new Config(); 42 | platformConfig.ConfKey = mainKey; 43 | if (String.IsNullOrEmpty(ConfValue)) 44 | { 45 | 46 | UMC.Data.DataFactory.Instance().Delete(platformConfig); 47 | } 48 | else 49 | { 50 | platformConfig.ConfValue = ConfValue; 51 | UMC.Data.DataFactory.Instance().Put(platformConfig); 52 | } 53 | this.Context.Send("Mime.Config", true); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Proxy/SiteLogActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UMC.Web; 5 | 6 | namespace UMC.Proxy.Activities 7 | { 8 | /// 9 | /// 应用管理 10 | /// 11 | [UMC.Web.Mapping("Proxy", "Log", Auth = WebAuthType.User)] 12 | public class SiteLogActivity : WebActivity 13 | { 14 | 15 | public override void ProcessActivity(WebRequest request, WebResponse response) 16 | { 17 | 18 | var Key = this.AsyncDialog("Key", g => 19 | { 20 | 21 | var sts = new System.Data.DataTable(); 22 | sts.Columns.Add("id"); 23 | sts.Columns.Add("name"); 24 | sts.Columns.Add("root"); 25 | sts.Columns.Add("domain"); 26 | sts.Columns.Add("module"); 27 | sts.Columns.Add("auth"); 28 | var keys = new List(); 29 | var ds = DataFactory.Instance().Site(); 30 | 31 | var Keyword = (request.SendValues ?? request.Arguments)["Keyword"]; 32 | if (String.IsNullOrEmpty(Keyword) == false) 33 | { 34 | 35 | ds = ds.Where(r => r.Caption.Contains(Keyword) || r.Root.Contains(Keyword) || r.Domain.Contains(Keyword)).OrderBy(r => r.Caption).ToArray(); 36 | } 37 | else 38 | { 39 | ds = ds.OrderBy(r => r.Caption).ToArray(); 40 | } 41 | 42 | foreach (var d in ds) 43 | { 44 | sts.Rows.Add(d.SiteKey ?? UMC.Data.Utility.IntParse(UMC.Data.Utility.Guid(d.Root, true).Value), d.Caption, d.Root, (d.Domain.IndexOf(',') > 0 || d.Domain.IndexOf('\n') > 0) ? "多例均衡" : d.Domain, d.IsModule == true ? "模块" : "应用", d.AuthType ?? Web.WebAuthType.All); 45 | } 46 | 47 | var rdata = new WebMeta().Put("data", sts); 48 | response.Redirect(request.IsMaster ? rdata.Put("IsMaster", true) : rdata); 49 | return this.DialogValue("none"); 50 | }); 51 | 52 | var site = DataFactory.Instance().Site(Key); 53 | 54 | var data = new WebMeta(); 55 | var caption = site.Caption; 56 | var vindex = caption.IndexOf("v.", StringComparison.CurrentCultureIgnoreCase); 57 | if (vindex > -1) 58 | { 59 | caption = caption.Substring(0, vindex); 60 | } 61 | data.Put("caption", $"{caption}的使用情况"); 62 | var Users = this.AsyncDialog("User", g => this.DialogValue(this.Context.Token.Username)).Split(','); 63 | 64 | var webDate = new WebMeta(); 65 | data.Put("data", webDate); 66 | var userManager = UMC.Security.Membership.Instance(); 67 | var cookies = new List(); 68 | foreach (var u in Users) 69 | { 70 | var iden = userManager.Identity(u); 71 | if (iden == null) 72 | { 73 | var cookie = DataFactory.Instance().Cookie(site.Root, UMC.Data.Utility.Guid(u, true).Value, 0); 74 | if (cookie != null && String.IsNullOrEmpty(cookie.Account) == false) 75 | { 76 | webDate.Put(u, UMC.Data.Utility.GetDate(cookie.Time)); 77 | } 78 | else 79 | { 80 | webDate.Put(u, "未使用"); 81 | } 82 | } 83 | else 84 | { 85 | var cookie = DataFactory.Instance().Cookie(site.Root, iden.Id.Value, 0); 86 | if (cookie != null) 87 | { 88 | webDate.Put(u, UMC.Data.Utility.GetDate(cookie.Time)); 89 | } 90 | else 91 | { 92 | webDate.Put(u, "未使用"); 93 | } 94 | } 95 | } 96 | response.Redirect(data); 97 | 98 | 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /Proxy/SiteLogConfActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UMC.Data; 4 | using UMC.Web; 5 | using UMC.Web.UI; 6 | 7 | namespace UMC.Proxy 8 | { 9 | [Mapping("Proxy", "LogConf", Auth = WebAuthType.Admin, Desc = "服务器日志")] 10 | public class SiteLogConfActivity : WebActivity 11 | { 12 | 13 | public override void ProcessActivity(WebRequest request, WebResponse response) 14 | { 15 | 16 | var assConf = Reflection.Configuration("assembly"); 17 | 18 | var provider = assConf["Log"] ?? Data.Provider.Create("Log", "none"); 19 | 20 | var model = this.AsyncDialog("Id", akey => 21 | { 22 | var form = request.SendValues ?? new WebMeta(); 23 | if (form.ContainsKey("limit") == false) 24 | { 25 | this.Context.Send(new UISectionBuilder(request.Model, request.Command) 26 | .RefreshEvent($"{request.Model}.{request.Command}") 27 | .Builder(), true); 28 | 29 | } 30 | 31 | var ui = UISection.Create(new UITitle("日志组件")); 32 | 33 | 34 | 35 | switch (provider.Type) 36 | { 37 | case "csv": 38 | ui.AddCell("日志类型", "文本格式", new UIClick("CHANGE").Send(request.Model, request.Command)); 39 | var fui = ui.NewSection(); 40 | var fields = provider["field"]; 41 | 42 | var fs = new List(new string[] { "Address", "Site", "Path", "Username", "Duration", "Time", "Status", "UserAgent", "Account", "Referrer", "Attachment", "Server" }); 43 | 44 | if (String.IsNullOrEmpty(fields) == false) 45 | { 46 | 47 | foreach (var c in fields.Split(',')) 48 | { 49 | var k = c.Trim(); 50 | if (String.IsNullOrEmpty(k) == false) 51 | { 52 | if (fs.Exists(r => String.Equals(r, k)) == false) 53 | { 54 | fs.Add(k); 55 | } 56 | } 57 | } 58 | 59 | } 60 | foreach (var k in fs) 61 | { 62 | switch (k) 63 | { 64 | case "Address": 65 | fui.AddCell(k, "客户端IP"); 66 | break; 67 | case "Site": 68 | fui.AddCell(k, "应用名称"); 69 | break; 70 | case "Path": 71 | fui.AddCell(k, "请求路径"); 72 | break; 73 | case "Username": 74 | fui.AddCell(k, "所属账户"); 75 | break; 76 | case "Duration": 77 | fui.AddCell(k, "请求耗时"); 78 | break; 79 | case "Time": 80 | fui.AddCell(k, "发生时间"); 81 | break; 82 | case "Status": 83 | fui.AddCell(k, "响应状态"); 84 | break; 85 | case "UserAgent": 86 | fui.AddCell(k, "终端设备"); 87 | break; 88 | case "Account": 89 | fui.AddCell(k, "应用账户"); 90 | break; 91 | case "Referrer": 92 | fui.AddCell(k, "所在页面"); 93 | break; 94 | case "Attachment": 95 | fui.AddCell(k, "下载文件"); 96 | break; 97 | case "Server": 98 | fui.AddCell(k, "服务器名"); 99 | break; 100 | default: 101 | 102 | fui.AddCell(k, new UIClick(k).Send(request.Model, request.Command)); 103 | break; 104 | } 105 | } 106 | 107 | break; 108 | 109 | case "json": 110 | ui.AddCell("日志类型", "Json格式", new UIClick("CHANGE").Send(request.Model, request.Command)); 111 | ui.NewSection().AddCell("发送网址", provider["url"]).AddCell("发送方式", provider["method"]); 112 | break; 113 | default: 114 | ui.AddCell("日志类型", "未启用", new UIClick("CHANGE").Send(request.Model, request.Command)); 115 | 116 | UIDesc desc = new UIDesc("日志记录未启用"); 117 | desc.Desc("{icon}\n{desc}").Put("icon", "\uf24a"); 118 | desc.Style.Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60)); 119 | ui.NewSection().Add(desc); 120 | break; 121 | } 122 | 123 | 124 | 125 | 126 | ui.UIFootBar = new UIFootBar() { IsFixed = true }; 127 | ui.UIFootBar.AddText(new UIEventText("新增字段").Click(new UIClick("ADD").Send(request.Model, request.Command)), 128 | new UIEventText("重新加载").Click(new UIClick("LoadConf").Send(request.Model, request.Command)).Style(new UIStyle().BgColor())); 129 | response.Redirect(ui); 130 | 131 | return this.DialogValue("none"); 132 | }); 133 | 134 | switch (model) 135 | { 136 | case "JSON": 137 | var Domains = this.AsyncDialog("JSON", r => 138 | { 139 | var fm = new UIFormDialog() { Title = "JSON日志格式" }; 140 | 141 | fm.AddText("发送网址", "url", provider["url"]); 142 | fm.AddRadio("发送方式", "method").Put("POST", "POST", provider["method"] == "POST").Put("PUT", "PUT", provider["method"] == "PUT"); 143 | fm.Submit("确认", $"{request.Model}.{request.Command}"); 144 | return fm; 145 | }); 146 | var provider2 = Data.Provider.Create("Log", "json"); 147 | provider2.Attributes.Add(provider.Attributes); 148 | provider2.Attributes["method"] = Domains["method"]; 149 | 150 | provider2.Attributes["url"] = new Uri(Domains["url"]).AbsoluteUri; 151 | 152 | assConf.Add(provider2); 153 | UMC.Data.Reflection.Configuration("assembly", assConf); 154 | this.Context.Send($"{request.Model}.{request.Command}", true); 155 | break; 156 | 157 | case "ADD": 158 | if (String.Equals(provider.Type, "csv") == false) 159 | { 160 | this.Prompt("不是文本格式不支持添加字段"); 161 | } 162 | var aField = this.AsyncDialog("Field", r => 163 | { 164 | var fm = new UIFormDialog() { Title = "新增文本日志字段" }; 165 | 166 | fm.AddText("字段", "field", provider["url"]); 167 | fm.Submit("确认", $"{request.Model}.{request.Command}"); 168 | return fm; 169 | })["field"]; 170 | { 171 | var fields = provider["field"]; 172 | 173 | var fs = new List(); 174 | 175 | if (String.IsNullOrEmpty(fields) == false) 176 | { 177 | 178 | foreach (var c in fields.Split(',')) 179 | { 180 | var k = c.Trim(); 181 | if (String.IsNullOrEmpty(k) == false) 182 | { 183 | if (fs.Exists(r => String.Equals(r, k)) == false) 184 | { 185 | fs.Add(k); 186 | } 187 | } 188 | } 189 | 190 | } 191 | fs.Add(aField); 192 | provider.Attributes["field"] = String.Join(",", fs.ToArray()); 193 | 194 | assConf.Add(provider); 195 | UMC.Data.Reflection.Configuration("assembly", assConf); 196 | this.Context.Send($"{request.Model}.{request.Command}", true); 197 | } 198 | break; 199 | case "CHANGE": 200 | var changeType = this.AsyncDialog("Change", r => 201 | { 202 | var uis = new UISheetDialog(); 203 | switch (provider.Type) 204 | { 205 | case "json": 206 | uis.Put("文本格式", "csv").Put("关闭日志", "none"); 207 | break; 208 | case "csv": 209 | uis.Put(new UIClick("JSON") { Text = "JSON格式" }.Send(request.Model, request.Command)).Put("关闭日志", "none"); 210 | break; 211 | default: 212 | case "none": 213 | uis.Put(new UIClick("JSON") { Text = "JSON格式" }.Send(request.Model, request.Command)).Put("文本格式", "csv"); 214 | break; 215 | } 216 | return uis; 217 | }); 218 | 219 | var provider3 = Data.Provider.Create("Log", changeType); 220 | provider3.Attributes.Add(provider.Attributes); 221 | 222 | assConf.Add(provider3); 223 | UMC.Data.Reflection.Configuration("assembly", assConf); 224 | this.Context.Send($"{request.Model}.{request.Command}", true); 225 | break; 226 | default: 227 | 228 | { 229 | var fields = provider["field"]; 230 | 231 | var fs = new List(); 232 | 233 | if (String.IsNullOrEmpty(fields) == false) 234 | { 235 | 236 | foreach (var c in fields.Split(',')) 237 | { 238 | var k = c.Trim(); 239 | if (String.IsNullOrEmpty(k) == false) 240 | { 241 | if (fs.Exists(r => String.Equals(r, k)) == false) 242 | { 243 | fs.Add(k); 244 | } 245 | } 246 | } 247 | 248 | } 249 | fs.Remove(model); 250 | provider.Attributes["field"] = String.Join(",", fs.ToArray()); 251 | 252 | assConf.Add(provider);//["Log"] = provider; 253 | UMC.Data.Reflection.Configuration("assembly", assConf); 254 | this.Context.Send($"{request.Model}.{request.Command}", true); 255 | } 256 | break; 257 | case "LoadConf": 258 | UMC.Proxy.LogSetting.Instance().LoadConf(); 259 | this.Prompt("已经从新加载日志组件参数"); 260 | break; 261 | } 262 | 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /Proxy/SiteUserActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Collections; 6 | using UMC.Web; 7 | using UMC.Data.Entities; 8 | 9 | namespace UMC.Proxy.Activities 10 | { 11 | 12 | [UMC.Web.Mapping("Proxy", "User", Auth = WebAuthType.User)] 13 | public class SiteUserActivity : WebActivity 14 | { 15 | 16 | 17 | public override void ProcessActivity(WebRequest request, WebResponse response) 18 | { 19 | var strUser = this.AsyncDialog("Id", d => 20 | { 21 | response.Redirect("Settings", "User"); 22 | return this.DialogValue("none"); 23 | }); 24 | //System.Net.Q 25 | var site = UMC.Data.Utility.IntParse(this.AsyncDialog("Site", "0"), 0); 26 | 27 | var userId = UMC.Data.Utility.Guid(strUser) ?? Guid.Empty; 28 | 29 | var setting = Web.UIDialog.AsyncDialog(this.Context, "Setting", d => 30 | { 31 | var form = request.SendValues ?? new WebMeta(); 32 | if (form.ContainsKey("limit") == false) 33 | { 34 | this.Context.Send(new UISectionBuilder(request.Model, request.Command, request.Arguments) 35 | .RefreshEvent($"{request.Model}.{request.Command}") 36 | .Builder(), true); 37 | 38 | } 39 | var user = Data.DataFactory.Instance().User(userId); 40 | 41 | 42 | var ui = UISection.Create(new UITitle("应用账户")); 43 | 44 | ui.AddCell("别名", user.Alias); 45 | 46 | 47 | ui.AddCell("账户", user.Username); 48 | 49 | if (String.IsNullOrEmpty(Data.DataFactory.Instance().Password(user.Id.Value))) 50 | { 51 | ui.AddCell("密码登录", "未开启"); 52 | } 53 | else 54 | { 55 | ui.AddCell("密码登录", "已开启"); 56 | } 57 | 58 | if (user.ActiveTime.HasValue) 59 | ui.AddCell("最后登录", String.Format("{0:yy-MM-dd HH:mm}", user.ActiveTime)); 60 | if (user.RegistrTime.HasValue) 61 | ui.AddCell("注册时间", String.Format("{0:yy-MM-dd HH:mm}", user.RegistrTime)); 62 | 63 | 64 | var status = "正常"; 65 | var flags = user.Flags ?? UMC.Security.UserFlags.Normal; 66 | var opts = new Web.ListItemCollection(); 67 | if (user.IsDisabled == true) 68 | { 69 | status = "禁用"; 70 | } 71 | else if ((int)(flags & UMC.Security.UserFlags.Lock) > 0) 72 | { 73 | status = "锁定"; 74 | } 75 | ui.NewSection().AddCell("状态", status) 76 | .AddCell("口令", String.IsNullOrEmpty(Data.DataFactory.Instance().Password(user.Id.Value)) ? "未开通" : "已开启"); 77 | 78 | 79 | 80 | 81 | var roes = Data.DataFactory.Instance().Roles(user.Id.Value, site); 82 | 83 | var ui2 = ui.NewSection(); 84 | ui2.AddCell("应用角色", "设置", new UIClick(new WebMeta(request.Arguments).Put(d, "Role")).Send(request.Model, request.Command)); 85 | foreach (var dr in roes) 86 | { 87 | switch (dr) 88 | { 89 | case UMC.Security.Membership.GuestRole: 90 | break; 91 | case UMC.Security.Membership.AdminRole: 92 | ui2.Delete(UICell.UI('\uf0c0', "超级管理员", dr), new UIEventText() 93 | .Click(new UIClick(new WebMeta(request.Arguments).Put(d, "Rolename").Put("Rolename", dr).Put("Site", site)).Send(request.Model, request.Command))); 94 | 95 | break; 96 | case UMC.Security.Membership.UserRole: 97 | ui2.Delete(UICell.UI('\uf0c0', "内部员工", dr), new UIEventText() 98 | .Click(new UIClick(new WebMeta(request.Arguments).Put(d, "Rolename").Put("Rolename", dr).Put("Site", site)).Send(request.Model, request.Command))); 99 | 100 | break; 101 | default: 102 | ui2.Delete(UICell.UI('\uf0c0', dr, ""), new UIEventText() 103 | .Click(new UIClick(new WebMeta(request.Arguments).Put(d, "Rolename").Put("Rolename", dr).Put("Site", site)).Send(request.Model, request.Command))); 104 | 105 | break; 106 | } 107 | } 108 | if (ui2.Length == 1) 109 | { 110 | ui2.Add("Desc", new UMC.Web.WebMeta().Put("desc", "只拥有来宾角色").Put("icon", "\uF016"), new UMC.Web.WebMeta().Put("desc", "{icon}\n{desc}"), 111 | 112 | new UIStyle().Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60))); 113 | 114 | 115 | } 116 | 117 | var Organize = UMC.Data.DataFactory.Instance().Organizes(new User { Id = user.Id.Value }); 118 | 119 | var ui4 = ui.NewSection(); 120 | 121 | 122 | ui4.AddCell("所属组织", ""); 123 | 124 | 125 | if (Organize.Length > 0) 126 | { 127 | foreach (var s in Organize) 128 | { 129 | ui4.Add(UICell.Create("UI", new WebMeta().Put("text", s.Caption).Put("Icon", "\uf0e8"))); 130 | 131 | } 132 | } 133 | else 134 | { 135 | ui4.Add("Desc", new UMC.Web.WebMeta().Put("desc", "未加入组织").Put("icon", "\uf0e8"), new UMC.Web.WebMeta().Put("desc", "{icon}\n{desc}"), 136 | 137 | new UIStyle().Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60))); 138 | 139 | 140 | } 141 | var dSite = DataFactory.Instance().Site(site); 142 | var scookies = DataFactory.Instance().Cookies(dSite.Root, user.Id.Value).Where(r => String.IsNullOrEmpty(r.Account) == false).OrderBy(r => r.IndexValue).ToArray(); 143 | 144 | if (scookies.Length > 0) 145 | { 146 | var um = ui.NewSection(); 147 | um.Header.Put("text", "对接账户"); 148 | 149 | foreach (var ac in scookies) 150 | { 151 | 152 | if (ac.LoginTime.HasValue) 153 | { 154 | um.AddCell('\uf1bb', ac.Account, UMC.Data.Utility.GetDate(UMC.Data.Utility.TimeSpan(ac.LoginTime.Value))); 155 | 156 | } 157 | else 158 | { 159 | 160 | um.AddCell('\uf1bb', ac.Account, String.Empty); 161 | } 162 | break; 163 | 164 | } 165 | } 166 | var sess = UMC.Data.DataFactory.Instance().Session(user.Id.Value) 167 | .Where(r => String.Equals("Settings", r.ContentType) == false).ToArray(); 168 | 169 | if (sess.Length > 0) 170 | { 171 | var ui5 = ui.NewSection(); 172 | ui5.Header.Put("text", "登录会话"); 173 | foreach (var s in sess) 174 | { 175 | ui5.Add(UICell.Create("UI", new WebMeta().Put("value", UMC.Data.Utility.GetDate(s.UpdateTime), "text", s.ContentType) 176 | .Put("Icon", "\uf286"))); 177 | } 178 | } 179 | ui.NewSection().AddCell('\uEA05', "功能授权", String.Empty, new UIClick(new WebMeta(request.Arguments).Put(d, "Auth")).Send(request.Model, request.Command)); 180 | 181 | 182 | response.Redirect(ui); 183 | 184 | return this.DialogValue("none"); 185 | }); 186 | if (request.IsMaster == false) 187 | { 188 | var rols = UMC.Data.DataFactory.Instance().Roles(this.Context.Token.UserId.Value, site); 189 | if (rols.Contains(UMC.Security.Membership.AdminRole) == false) 190 | { 191 | this.Prompt("需要管理员权限才能设置"); 192 | } 193 | 194 | } 195 | switch (setting) 196 | { 197 | case "Rolename": 198 | { 199 | var Rolename = this.AsyncDialog("Rolename", r => this.DialogValue("none")); 200 | Data.DataFactory.Instance().Delete(new UserToRole { user_id = userId, Rolename = Rolename, Site = site }); 201 | 202 | this.Context.Send($"{request.Model}.{request.Command}", true); 203 | } 204 | break; 205 | case "Role": 206 | { 207 | 208 | var rolename = this.AsyncDialog("Rolename", "Settings", "SelectRole", new WebMeta().Put("Site", site)); 209 | Data.DataFactory.Instance().Put(new UserToRole 210 | { 211 | user_id = userId, 212 | Site = site, 213 | Rolename = rolename 214 | }); 215 | 216 | this.Context.Send($"{request.Model}.{request.Command}", true); 217 | } 218 | break; 219 | case "Auth": 220 | { 221 | var user = Data.DataFactory.Instance().User(userId); 222 | response.Redirect("Settings", "Auth", new UMC.Web.WebMeta().Put("Type", "User", "Value", user.Username).Put("Site", site), true); 223 | } 224 | break; 225 | } 226 | 227 | } 228 | 229 | } 230 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Apiumc Gateway

2 |

它一个工具等于 Nginx + Https证书 + 内网穿透 + 图片切割水印 + 网关登录

3 | 4 |

5 | star 6 | 7 | 8 | 9 | ### 介绍说明 10 | 11 | Apiumc Gateway 是高性能的Web网关,它从底层Socket原始通信层开始,采用多线程、多任务模式从新构建Web服务,充分发挥当下多核的CPU的多任务并行性能,达到不输nginx的性能表现,而多线程、多任务天生比多进程模式更有编程可控性,基于这此原理,为Apiumc带来丰富多的基于网关深度应用,是网关功能集大成者;它一个工具等于`Nginx` +` 网关登录` + `图片处理` + `内网穿透` + `免费Ssl证书`,且配置全程界面化,让你告别难懂、难记易出错的指令配置; 12 | 13 | 在追求功能多样性上性能也无语伦比,拥有多种措施大幅度改善源应用性能,是企业和从业者非常值得掌握的的Web应用托管工具,是F5国产替代首先。 14 | 15 | 16 | 17 | ### 下载安装 18 | 19 | 1. 从发行版处或官网上下载对应操作系统下的版本,解压运行即可。 20 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682142694/image.png) 21 | 2. 在浏览器中输入监听地址中的网址,用管理员进行登录, 按提示完成注册登记,默认管理员为admin,密码也是admin。 22 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682142739/image.png) 23 | 24 | 25 | ### Https证书 26 | Apiumc内置了Https证书管理,因DV类型域名证书可以通过文件验证来签发证书,只要域名解释到Apiumc就自然能通过文件验证,利用此特性,Apiumc团队与知名证书机构达成合作,为各位免费签发DV域名证书,为建设更安全的网络环境,让网络更安全贡献自己的一份力量。 27 | 28 | 注册后,可以免费申请Https证书,两种方式如下。 29 | 1. 在Apiumc指令窗口 输入 `ssl [domain]`,如下图: 30 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682583887/image.png) 31 | 2. 在`云桌面`--`应用设置`--`网关服务`中申请,如下图: 32 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682584153/image.png) 33 | 34 | Apiumc不但可以免费签发域名证书,也支持过期自动签发新证书、并自动部署证书,帮助各运维从复杂证书部署更新解放出来。 35 | 36 | ### 内网穿透 37 | Apiumc内置内网穿透支持,Apiumc是Web的反向代理,只要把外网服务器的请求通过Host域名来区分进行点对点的转发到本地Apiumc,对Apiumc来说转发的请求数据和平常网络防问没有区别,再把响应的数据以点对点的转发外网服务器,完成Http协议的内网穿透,这样外网就可通过Web形式防问本机或内网应用。 38 | 39 | 注册后,也可以开启Web VPN(内网穿透),开启方式两种: 40 | 1. 在Apiumc指令窗口,输入 `vpn start`,如下图: 41 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682585479/image.png) 42 | 2. 在`云桌面`--`应用设置`的Web VPN中状态栏,点击则可启动Web VPN了,如下图: 43 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682584037/image.png) 44 | 45 | 开启后,会分配一个永久不变的域名,用此域名防问将会联通本机的Apiumc了,内网穿透支持绑定域名,只要域名用CNAME解释到分配永久不变的域名,就完成了绑定,从而让内部应用被外网用Web浏览器防问了。[了解更多](WebVPN.md) ... 46 | 47 | 48 | ### 图片切割 49 | 50 | Apiumc内置图片切割水印,原理是通过代理响应后,根据参数转化图片,并缓存,所以在不改变原应用的情况下做到来调整图片尺寸、添加水印、格式转码等等功能,支持根据浏览器从avif格式、webp格式、png格式智能适配,从而让图片网络流量减少60%-90%,节省大量流量费用,还让应用快如闪电,大幅改善原应用的交互质量。[了解更多](ImageCast.md) ... 51 | 52 | 53 | 54 | ### 网关登录 55 | 56 | 网关登录是相比单点登录形式来说,它无需改造第三方应用,帮助企业各应用快速实现统一登录。与应用身份对接是通过网关技术来兼容企业现有应用,让各应用身份对接在线配置即可,配置过程中原应用无感知,对企业来说协调各应用负责人更容易,整体拥有成本更低。 57 | 58 | 59 | 相对于Https证书、内网穿透、图片切割是从网关出发对具体事务创新性实现,而企业的统一登录是企业身份体系和各应用的梳理和诊断,并根据Apiumc提供的7种登录方式提练出Api,配置身份配置转化,是一个专业性实施性解决方案,相对来说我们开拓的网关登录技术路线比传统经典单点登录更有优势,因为网关登录方案不用改造第三方应用,少了各应用适配登录协议的二次开发工具,还有节省更多的是企业协调成本更,还想更进一步了解网关登录,欢迎咨询我们,乐意与各位分享我们在各企业实施统一登录的研究成果。 60 | 61 | ### 加入我们 62 | 63 | Apiumc是用网关形式来加强应用,这是一个新的场景,目前我们用.net core完成了核心部分,性能也相当不错;还有很多场景需要专业人员加入,我们才能丰富,例如日志分析,虽然Apiumc已经能很方便的能收集日志,也能收集其它收集需要数据埋点才能收集的用户维度,但目前来说,我们只是按身份收集全日志,从分析层面来讲,还还远远不够,如何去丰富这个带用户维度日志模型,需要各位加入一起加入完善; 64 | 65 | Apiumc有免费的Https证书和内网穿透,不要忘记给你的 ⭐️ Star ⭐️哦,你们关注是我们推出免费服务动力。 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Resources/Auth/dingtalk.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 钉钉登录 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 59 | 73 | 112 | 113 | 114 | 115 |

116 | 117 | 118 |
119 |

120 | 你正在使用钉钉登录 121 |

122 |
123 | 124 | 125 | 126 |
127 |
128 | 129 |

130 | PC端将采用移动端身份登录应用 131 |

132 |
133 |
134 | 139 |
140 | 141 | 142 | -------------------------------------------------------------------------------- /Resources/Auth/nosupport.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 登录授权 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 |
38 |

39 | 权限不足或终端不支持 40 |

41 |
42 | 43 | 44 | 45 |
46 |
47 | 48 |

49 | 请换可授权移动终端来扫一扫 50 |

51 |
52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /Resources/Auth/wxwork.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 企微授权 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 59 | 72 | 73 | 89 | 90 | 91 | 92 |
93 | 94 | 95 |
96 |

97 | 你正在使用企微登录 98 |

99 |
100 | 101 | 102 | 103 |
104 |
105 | 106 |

107 | PC端将采用企微身份登录应用 108 |

109 |
110 |
111 | 116 |
117 | 118 | 119 | -------------------------------------------------------------------------------- /Resources/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apiumc/Gateway/e7b6594c5f9fd4015170701b3e14c5a65b929b27/Resources/app.png -------------------------------------------------------------------------------- /Resources/check.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 浏览器检测 6 | 7 | 8 | 41 | 65 | 66 | 67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 | 75 | 76 | 77 |
78 |
79 | 80 |

81 | {desc} 82 |

83 |
建议在对应浏览器中,使用免密地址(点击复制
84 |
85 |
86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /Resources/close.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 应用提示 6 | 7 | 8 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 |

40 | 应用现处于维护或关停阶段,紧急请联系应用管理员 41 |

42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /Resources/desktop.doc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 帮助文档 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 53 | 54 | 55 | 56 | 57 | 62 |
63 | 76 | 77 |
78 | 79 |
80 | 81 |
82 |
83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Resources/desktop.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Apiumc · 云桌面 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 |
32 |
33 |
34 |
35 | 59 |
60 | 61 | 62 |
63 | 64 | 65 |
66 | 70 |
71 | 72 | 73 |
74 | 91 | 92 |
93 |
94 | 95 | 96 | 97 | 114 |
115 |
116 | 117 | 136 |
137 |
138 |
    139 |
140 | 141 |
142 |
143 | 窗口 144 |
    145 |
146 |
147 |
148 | 149 |
150 |
151 | 152 | 157 |
158 |
159 | 160 | 161 | -------------------------------------------------------------------------------- /Resources/desktop.page.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | API UMC 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 27 | 135 | 136 | 137 | 138 |
139 | 140 |
141 |
142 |
143 | 144 |
145 |
146 | 147 | 148 | -------------------------------------------------------------------------------- /Resources/desktop.site.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 应用设置 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 27 | 162 | 163 | 164 | 165 |
166 | 167 |
168 |
169 |
170 | 171 |
172 |
173 | 174 | 175 | -------------------------------------------------------------------------------- /Resources/desktop.ui.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | API UMC 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 |
32 | 47 | 48 |
49 | 72 |
73 | 74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /Resources/desktop.umc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | API UMC 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 32 | 201 | 202 | 203 | 204 |
205 |
206 |
207 |
208 |
209 |
210 | 211 | 212 | -------------------------------------------------------------------------------- /Resources/dingtalk.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 钉钉登录 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 |
77 |

78 | 你正在使用钉钉登录 79 |

80 |
81 | 82 | 84 | 87 | 88 | 89 |
90 |
91 | 92 |

93 | 只有在钉钉环境中,才能正常工作 94 |

95 |
96 |
97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /Resources/dir.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 69 | 70 | API UMC 71 | 72 | 73 | 74 | 75 |

目录索引

76 | 81 | 82 | 83 | 84 | 85 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Resources/error.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {title} 6 | 7 | 8 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 |

40 | {msg} 41 |

42 | 43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /Resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apiumc/Gateway/e7b6594c5f9fd4015170701b3e14c5a65b929b27/Resources/favicon.ico -------------------------------------------------------------------------------- /Resources/login-html.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {title} 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/login.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {title} 6 | 7 | 8 | 9 | 10 | 16 | 95 | 96 | 97 | 98 | 99 | 114 | 115 | -------------------------------------------------------------------------------- /Resources/pc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {title} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 130 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Resources/pwd-Compel.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 |
-------------------------------------------------------------------------------- /Resources/pwd-Select.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 |
-------------------------------------------------------------------------------- /Resources/pwd-Selected.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 |
-------------------------------------------------------------------------------- /Resources/umc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | API UMC授权 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |

42 | 你正在使用自家客户端授权 43 |

44 |
45 | 46 | 47 | 48 |
49 |
50 | 51 |

52 | 只有在API UMC客户端环境中,才有效果 53 |

54 |
55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /Resources/user.html: -------------------------------------------------------------------------------- 1 |  2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
-------------------------------------------------------------------------------- /Resources/weixin.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 微信登录 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 38 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 |
61 |

62 | 你正在使用企业微信登录 63 |

64 |
65 | 66 | 67 | 68 |
69 |
70 | 71 |

72 | 只有在微信环境中,才能正常工作 73 |

74 |
75 |
76 | 77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /Resources/wxwork.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 企微登录 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 39 | 56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 |
64 |

65 | 你正在使用企业微信登录 66 |

67 |
68 | 69 | 70 | 71 |
72 |
73 | 74 |

75 | 只有在微信或企微环境中,才能正常工作 76 |

77 |
78 |
79 | 80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /UMC.Host.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net7.0 5 | 6 | UMC.Proxy 7 | 8 | 11 | 12 | 4 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | -------------------------------------------------------------------------------- /WebVPN.md: -------------------------------------------------------------------------------- 1 | 2 |

Apiumc Gateway

3 |

Web VPN内网穿透组件

4 | 5 |

6 | star 7 | 8 | 9 | 10 | 11 | 什么是WebVPN,WebVPN是Apiumc的内网穿透组件,一种不需要服务器,就能内网资源能被外网访问的新型技术,是构建Web形式防问内部资源的VPN,相比部署传统VPN需要消耗大量人力成本,带来复杂的IT运营压力,且不稳定、易掉线、容易被打穿导致内网渗透,给企业带来一定的外部攻击风险。而WebVPN它简化了VPN网络搭建运维管理,十分钟就能让外网防问内网应用,再结合Apiumc网关安全体系,就可运营高安全、低成本、稳定、可靠用浏览器访问内网资源的的Web VPN了。 12 | 13 | ### 开启穿透 14 | 15 | WebVPN是Apiumc网关的穿透组件,应用注册登记成功后,就可以启用WebVPN了。开启Web VPN有两种方式 16 | 17 | 1. 在线开启 18 | 在云桌面上点击“应用设置”,如下图:(需要管理员权限)
19 | ![图片](https://www.apiumc.com/UserResources/1usm4ih/1644735048416/image.png!m400)
20 | 如果显示未开启,则点击开启,再点击则会关停穿透,开启后,点击Web VPN网址,则就可以看到Apiumc云桌面了。 21 | 22 | 2. 命令开启 23 | 运行Apiumc指令窗口,输入指令`vpn start`,则开启内网穿透,如下图:
24 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682249241/image.png!m400)
25 | 在浏览器输入Web VPN的服务网址,则就可以看到的穿透的Apiumc网关云桌面。 26 | 27 | ### 自定义域名 28 | 29 | 只要把域名用CName解释到Web VPN的服务网址域名就可以了,就可以此域名防问穿透内容了,如下图:
30 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682237863/image.png!m400) 31 | 32 | ### 缓存配置 33 | 34 | 为了提高Web页面打开的速度,在WebVPN上也采用了动静分离的缓存机制,全局默认对.gif,.bmp,.png,.jpg,.jpeg,.ico,.webp,.svg,.css,.less,.sass,.scss,.js,.jsx,.coffee,.ts,.ttf,.woff,.woff2,.wasm页面进行缓存。 35 | 36 | 1. 在浏览器的地址栏输入路径再加上`?umc=cache`,则把此路径配对的资源进行缓存,如下图:
37 | ![image](https://www.apiumc.com/UserResources/7124914603020058625/1682251583/image.png!m400) 38 | 39 | 2. 在浏览器的地址栏输入路径再加上`?umc=cache.none`,则关闭此路径缓存机制 40 | 41 | 3. 在浏览器的地址栏输入路径再加上`?umc=cache.clear`,则清除此路径自定义缓存配置,只采用全局缓存机制 42 | 43 | ### 刷新缓存 44 | 有缓存就有可能与服务器资源版本不一致,需要一致性就需要我们来刷新缓存了,刷新缓存有两种方式 45 | 46 | 1. 删除所有缓存 47 | 方式是:在根路径上输入`/?umc`,则打开如下图:
48 | ![图片](https://www.apiumc.com/UserResources/1mwlgcz/1666740721/image.png!m400)
49 | 点击清空缓存,则清空域名下所有缓存。 50 | 51 | 2. 手动刷新单页缓存 52 | 方式是:在url最后追加`?umc=src`或者`&umc=src`,则可刷新此url的缓存 53 | 54 | ### 常见问题 55 | 56 | 1. 浏览器不加载新版本
57 | WebVPN有缓存机制,请参考上例改变此路径下缓存配置,选择最合适自己的。 58 | 59 | 2. 图片验证码失效
60 | 需要关闭图片验证码路径下缓存机制。 61 | 62 | 63 | --------------------------------------------------------------------------------

名称 86 | 大小 87 | 89 | 修改日期 90 |