mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-12-14 18:15:44 +00:00
Added websocket support
This commit is contained in:
parent
79c232a4ac
commit
b06ab003e0
2 changed files with 187 additions and 15 deletions
89
Capy64/LuaRuntime/Handlers/WebSocketHandle.cs
Normal file
89
Capy64/LuaRuntime/Handlers/WebSocketHandle.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using KeraLua;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capy64.LuaRuntime.Handlers;
|
||||
|
||||
class WebSocketHandle : IHandle
|
||||
{
|
||||
private ClientWebSocket _client;
|
||||
private long _requestId;
|
||||
private static IGame _game;
|
||||
public WebSocketHandle(ClientWebSocket client, long requestId, IGame game)
|
||||
{
|
||||
_client = client;
|
||||
_requestId = requestId;
|
||||
_game = game;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, LuaFunction> functions = new()
|
||||
{
|
||||
["send"] = L_Send,
|
||||
["closeAsync"] = L_CloseAsync,
|
||||
};
|
||||
|
||||
public void Push(Lua L, bool newTable = true)
|
||||
{
|
||||
if (newTable)
|
||||
L.NewTable();
|
||||
|
||||
foreach (var pair in functions)
|
||||
{
|
||||
L.PushString(pair.Key);
|
||||
L.PushCFunction(pair.Value);
|
||||
L.SetTable(-3);
|
||||
}
|
||||
|
||||
L.PushString("_handle");
|
||||
L.PushObject(this);
|
||||
L.SetTable(-3);
|
||||
}
|
||||
|
||||
private static WebSocketHandle GetHandle(Lua L, bool gc = true)
|
||||
{
|
||||
L.CheckType(1, LuaType.Table);
|
||||
L.PushString("_handle");
|
||||
L.GetTable(1);
|
||||
return L.ToObject<WebSocketHandle>(-1, gc);
|
||||
}
|
||||
|
||||
private static int L_Send(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var data = L.CheckString(2);
|
||||
|
||||
var h = GetHandle(L, false);
|
||||
|
||||
if (h is null || h._client.State == WebSocketState.Closed)
|
||||
L.Error("connection is closed");
|
||||
|
||||
h._client.SendAsync(Encoding.ASCII.GetBytes(data), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_CloseAsync(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var h = GetHandle(L, true);
|
||||
|
||||
if (h is null || h._client.State == WebSocketState.Closed)
|
||||
return 0;
|
||||
|
||||
h._client.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None)
|
||||
.ContinueWith(async task =>
|
||||
{
|
||||
await task;
|
||||
_game.LuaRuntime.PushEvent("websocket_close", h._requestId);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,18 +7,28 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Capy64.LuaRuntime.Libraries;
|
||||
#nullable enable
|
||||
public class HTTP : IPlugin
|
||||
{
|
||||
private static IGame _game;
|
||||
private static HttpClient _client;
|
||||
private static long RequestId;
|
||||
private static HttpClient _httpClient;
|
||||
private static long _requestId;
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly LuaRegister[] HttpLib = new LuaRegister[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
name = "checkURL",
|
||||
function = L_CheckUrl,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "requestAsync",
|
||||
|
|
@ -26,17 +36,17 @@ public class HTTP : IPlugin
|
|||
},
|
||||
new()
|
||||
{
|
||||
name = "checkURL",
|
||||
function = L_CheckUrl,
|
||||
name = "websocketAsync",
|
||||
function = L_WebsocketAsync,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
public HTTP(IGame game, IConfiguration configuration)
|
||||
{
|
||||
_game = game;
|
||||
RequestId = 0;
|
||||
_client = new();
|
||||
_client.DefaultRequestHeaders.Add("User-Agent", $"Capy64/{Capy64.Version}");
|
||||
_requestId = 0;
|
||||
_httpClient = new();
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", $"Capy64/{Capy64.Version}");
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
|
@ -53,10 +63,29 @@ public class HTTP : IPlugin
|
|||
return 1;
|
||||
}
|
||||
|
||||
public static bool TryGetUri(string url, out Uri? uri)
|
||||
private static readonly string[] _allowedSchemes = new[]
|
||||
{
|
||||
return (Uri.TryCreate(url, UriKind.Absolute, out uri)
|
||||
&& uri?.Scheme == Uri.UriSchemeHttp) || uri?.Scheme == Uri.UriSchemeHttps;
|
||||
Uri.UriSchemeHttp,
|
||||
Uri.UriSchemeHttps,
|
||||
Uri.UriSchemeWs,
|
||||
Uri.UriSchemeWss,
|
||||
};
|
||||
public static bool TryGetUri(string url, out Uri uri)
|
||||
{
|
||||
return Uri.TryCreate(url, UriKind.Absolute, out uri!) && _allowedSchemes.Contains(uri.Scheme);
|
||||
}
|
||||
|
||||
private static int L_CheckUrl(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var url = L.CheckString(1);
|
||||
|
||||
var isValid = TryGetUri(url, out _);
|
||||
|
||||
L.PushBoolean(isValid);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int L_Request(IntPtr state)
|
||||
|
|
@ -151,9 +180,9 @@ public class HTTP : IPlugin
|
|||
? new HttpMethod((string)value)
|
||||
: request.Content is not null ? HttpMethod.Post : HttpMethod.Get;
|
||||
|
||||
var requestId = RequestId++;
|
||||
var requestId = _requestId++;
|
||||
|
||||
var reqTask = _client.SendAsync(request);
|
||||
var reqTask = _httpClient.SendAsync(request);
|
||||
reqTask.ContinueWith(async (task) =>
|
||||
{
|
||||
|
||||
|
|
@ -218,15 +247,69 @@ public class HTTP : IPlugin
|
|||
return 1;
|
||||
}
|
||||
|
||||
private static int L_CheckUrl(IntPtr state)
|
||||
private static int L_WebsocketAsync(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var url = L.CheckString(1);
|
||||
if (!TryGetUri(url, out var uri))
|
||||
{
|
||||
L.ArgumentError(1, "invalid request url");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var isValid = TryGetUri(url, out _);
|
||||
var requestId = _requestId++;
|
||||
|
||||
L.PushBoolean(isValid);
|
||||
var wsClient = new ClientWebSocket();
|
||||
var connectTask = wsClient.ConnectAsync(uri, CancellationToken.None);
|
||||
connectTask.ContinueWith(async task =>
|
||||
{
|
||||
if (task.IsFaulted || task.IsCanceled)
|
||||
{
|
||||
_game.LuaRuntime.PushEvent("websocket_failure", requestId, task.Exception?.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
await task;
|
||||
|
||||
var handle = new WebSocketHandle(wsClient, requestId, _game);
|
||||
|
||||
_game.LuaRuntime.PushEvent("websocket_connect", L =>
|
||||
{
|
||||
L.PushInteger(requestId);
|
||||
|
||||
handle.Push(L, true);
|
||||
|
||||
return 2;
|
||||
});
|
||||
|
||||
var buffer = new byte[4096];
|
||||
var builder = new StringBuilder();
|
||||
while (wsClient.State == WebSocketState.Open)
|
||||
{
|
||||
var result = await wsClient.ReceiveAsync(buffer, CancellationToken.None);
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
Console.WriteLine("Closing");
|
||||
await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
_game.LuaRuntime.PushEvent("websocket_close", requestId);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = Encoding.ASCII.GetString(buffer, 0, result.Count);
|
||||
builder.Append(data);
|
||||
}
|
||||
|
||||
if(result.EndOfMessage)
|
||||
{
|
||||
_game.LuaRuntime.PushEvent("websocket_message", requestId, builder.ToString());
|
||||
builder.Clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
L.PushInteger(requestId);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue