timelapsenow/Timelapse/WebServer.cs

268 lines
7.5 KiB
C#

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<bool> 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{
//<METHOD> <PATH> 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<IPEndPoint> 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<IPAddress,string>(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<Rgb24> 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;
}
};
}
}