namespace Timelapse.Desktop; using System; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text; using Eto.Forms; using FlashCap; using FlashCap.Devices; using FlashCap.Utilities; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Tesses.WebServer; public class proxsvr : Tesses.WebServer.IServer { public proxsvr(IServer svr) { dest=svr; } IServer dest; public void AddCors(ServerContext ctx) { dest.AddCors(ctx); } public async Task BeforeAsync(ServerContext ctx) { return await dest.BeforeAsync(ctx); } public async Task GetAsync(ServerContext ctx) { await dest.GetAsync(ctx); if(ctx.RawUrl != "/api/stream.jpg") { await ctx.NetworkStream.DisposeAsync(); } } public async Task OptionsAsync(ServerContext ctx) { await dest.OptionsAsync(ctx); await ctx.NetworkStream.DisposeAsync(); } public async Task OtherAsync(ServerContext ctx) { await dest.OtherAsync(ctx); await ctx.NetworkStream.DisposeAsync(); } public async Task PostAsync(ServerContext ctx) { await dest.PostAsync(ctx); await ctx.NetworkStream.DisposeAsync(); } } public class EmbeadedServer : Server { public EmbeadedServer(string extname) { this.extname=extname; } string extname; public override async Task GetAsync(ServerContext ctx) { var asm=Assembly.GetExecutingAssembly(); if(asm != null) { string j=$"{extname}{ctx.UrlPath.Replace('/','.')}"; var strm=asm.GetManifestResourceStream(j); if(strm != null) { await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(ctx.UrlPath)); return; } j=j.TrimEnd('.') + ".index.html"; strm=asm.GetManifestResourceStream(j); if(strm != null) { await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType("index.html")); return; } } await NotFoundServer.ServerNull.GetAsync(ctx); } } public class TimelapseWebServer { MainForm frm; public TimelapseWebServer(MainForm frm) { this.frm=frm; } CancellationTokenSource? tokenSrc; TcpListener? _listener; private async Task CommunicateHostAsync(HttpServerListener listener,TcpClient clt) { try{ // HTTP/1.1\r\n //HEADER1\r\n //HEADER2\r\n //...... //HEADERN\r\n //\r\n //OPTIONAL REQUEST BODY //RESPONSE var strm = listener.GetStream(clt); await listener.PushAsync(strm,clt.Client.LocalEndPoint,clt.Client.RemoteEndPoint); }catch(Exception ex) { _=ex; } } public async Task ListenAsync(HttpServerListener svr,CancellationToken token,Action endpoint) { if(_listener == null) return; _listener.Start(); if(endpoint != null) { endpoint((IPEndPoint)_listener.LocalEndpoint); } using (var r = token.Register(() => _listener.Stop())) { while (!token.IsCancellationRequested) { try{ var socket=await _listener.AcceptTcpClientAsync(); await CommunicateHostAsync(svr,socket).ConfigureAwait(false); }catch(Exception ex) { _=ex; } } } } public async void Listen() { if(tokenSrc != null) { tokenSrc.Cancel(); tokenSrc.Dispose(); } if(!frm.Instance.Model.enableWebServer) return; tokenSrc=new CancellationTokenSource(); //#if (!DEBUG) EmbeadedServer esvr=new EmbeadedServer("Timelapse.ServerFiles"); // #else // StaticServer esvr=new StaticServer("ServerFiles"); //#endif RouteServer rs=new RouteServer(); rs.Add("/stream.jpg",StreamImage); rs.Add("/state.json",State); rs.Add("/setstate.cgi",SetState); MountableServer isvr=new MountableServer(esvr); isvr.Mount("/api/",rs);var p=new System.Net.IPEndPoint(System.Net.IPAddress.Any,frm.Instance.Model.timelapsePort); _listener=new TcpListener(p); HttpServerListener svr=new HttpServerListener(p,new proxsvr(isvr)); Console.WriteLine("Almost ready to listen"); await ListenAsync(svr,tokenSrc.Token,(e)=>{ int port=e.Port; string dns=Dns.GetHostName(); var localIPs =string.Join('\n', Dns.GetHostAddresses(dns).Where(e=>{return !IPAddress.IsLoopback(e);}).Select(e=>{ return $"http://{e.ToString()}:{port}/"; })); if(frm.synchContext != null) frm.synchContext.Post((e)=>{ MessageBox.Show($"Listening on:\n{localIPs}\nhttp://localhost:{port}\nhttp://{dns}:{port}/",MessageBoxButtons.OK); },null); }); } private async Task SetState(ServerContext ctx) { string rec; if(ctx.QueryParams.TryGetFirst("rec",out rec)) { frm.Instance.Recording= rec != "false"; } string real; if(ctx.QueryParams.TryGetFirst("real",out real)) { frm.Instance.RealTime= real != "false"; } await ctx.SendRedirectAsync("./state.json"); } private async Task State(ServerContext ctx) { await ctx.SendJsonAsync(new{Recording=frm.Instance.Recording,RealTime=frm.Instance.RealTime}); } private byte[] GetBytes(Image img) { using(var ms=new MemoryStream()) { img.SaveAsJpeg(ms); return ms.ToArray(); } } private async Task StreamImage(ServerContext ctx) { ctx.ResponseHeaders.Add("Content-Type","multipart/x-mixed-replace; boundary=--boundary"); await ctx.WriteHeadersAsync(); frm.NewFrame += (sender,e)=>{ try{ var img=e.Image.Clone(); Thread t =new Thread(()=>{ try{ lock(ctx){ StringBuilder b = new StringBuilder(); byte[] data = GetBytes(img); img.Dispose(); b.Append("\r\n"); b.Append("--boundary\r\n"); b.Append("Content-Type: image/jpeg\r\n"); b.Append($"Content-Length: {data.Length}\r\n"); b.Append("\r\n"); byte[] hdr= Encoding.ASCII.GetBytes(b.ToString()); byte[] crlf = Encoding.ASCII.GetBytes("\r\n"); ctx.NetworkStream.Write(hdr,0,hdr.Length); ctx.NetworkStream.Write(data,0,data.Length); ctx.NetworkStream.Write(crlf,0,crlf.Length); ctx.NetworkStream.Flush(); } }catch(Exception ex) { try{ ctx.NetworkStream.Dispose(); }catch(Exception ex2) { _=ex2; } _=ex; } }); t.Start(); }catch(Exception ex) { _=ex; } }; } }