blocks4u/Assets/Blocks4u/Editor/BlocklyServer.cs

196 lines
6.1 KiB
C#

#if UNITY_EDITOR
using System;
using System.IO;
using System.Net;
using System.Text;
using Blocks4u.Editor.DTO;
using Unity.Plastic.Newtonsoft.Json;
using Unity.Plastic.Newtonsoft.Json.Linq;
using UnityEngine;
namespace Blocks4u.Editor
{
public class BlocklyServer : IDisposable
{
private string _assetsPath;
public string URL { get; private set; }
private HttpListener _listener;
public string SessionToken { get; private set; }
private bool _running;
public BlocklyServer(string assetsPath, string url, string sessionToken)
{
_assetsPath = assetsPath;
URL = url;
SessionToken = sessionToken;
_listener = new HttpListener();
_listener.Prefixes.Add(url);
_listener.Start();
_running = true;
Listen();
}
private async void Listen()
{
while (_running)
{
ProcessRequest(await _listener.GetContextAsync());
}
}
private void ProcessRequest(HttpListenerContext context)
{
var request = context.Request;
var url = request.Url;
var path = url.AbsolutePath;
// if (path == "/")
// {
// foreach (string kek in context.Request.QueryString)
// {
// Debug.LogError(kek);
// }
//
// HandleRedirect($"/blocks4u/dist/index.html?{context.Request.QueryString}", context);
// return;
// }
if (path != "/rpc")
{
HandleAsset(path, context);
}
else
{
try
{
HandleRpc(context);
}
catch (Exception e)
{
var response = context.Response;
response.StatusCode = 500;
response.OutputStream.Write(Encoding.UTF8.GetBytes(e.Message));
response.Close();
Debug.LogException(e);
}
}
}
private void HandleRpc(HttpListenerContext context)
{
var request = context.Request;
if (request.HttpMethod != "POST")
{
throw new Exception("Invalid HTTP method");
}
var headers = request.Headers;
if (headers["X-Auth"] != SessionToken)
{
throw new Exception("Invalid session token");
}
var response = context.Response;
using var reader = new StreamReader(request.InputStream);
var invocation = JsonConvert.DeserializeObject<MethodInvocation>(reader.ReadToEnd());
var method = typeof(RPC).GetMethod(invocation.method, new[] {typeof(JArray)});
var result = method.Invoke(null, new object[] {invocation.@params});
if (result is not JToken token)
{
throw new Exception("Invalid RPC method signature. Expected public static JToken name(JArray @params)");
}
response.ContentType = "application/json";
response.StatusCode = 200;
var json = new JObject
{
["result"] = (JToken) result
};
response.OutputStream.Write(Encoding.UTF8.GetBytes(json.ToString(Formatting.Indented)));
response.Close();
}
private void HandleAsset(string path, HttpListenerContext context)
{
// if (path.StartsWith("/assets/js/") && !path.EndsWith(".js"))
// {
// path += ".js"; //handle js module import
// }
var response = context.Response;
var file = Path.Combine(_assetsPath, path.Substring(1));
if (path.Contains("..") || !path.StartsWith("/") || !File.Exists(file))
{
response.StatusCode = 404;
response.Close();
return;
}
response.StatusCode = 200;
response.ContentType = GetContentType(Path.GetExtension(file));
var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
fileStream.CopyTo(response.OutputStream);
fileStream.Close();
response.Close();
}
private void HandleRedirect(string path, HttpListenerContext context)
{
var response = context.Response;
response.StatusCode = 302;
response.AddHeader("Location", path);
response.Close();
}
private static string GetContentType(string ext)
{
if (string.IsNullOrEmpty(ext))
{
return "application/octet-stream";
}
// Убираем точку если есть
if (ext.StartsWith("."))
{
ext = ext.Substring(1);
}
return ext.ToLower() switch
{
"html" or "htm" => "text/html",
"js" => "application/javascript",
"css" => "text/css",
"svg" => "image/svg+xml",
"png" => "image/png",
"jpeg" or "jpg" => "image/jpeg",
"ico" => "image/x-icon",
"gif" => "image/gif",
"bmp" => "image/bmp",
"webp" => "image/webp",
"txt" => "text/plain",
"json" => "application/json",
"xml" => "application/xml",
"pdf" => "application/pdf",
"zip" => "application/zip",
"mp3" => "audio/mpeg",
"mp4" => "video/mp4",
"woff" => "font/woff",
"woff2" => "font/woff2",
"ttf" => "font/ttf",
"otf" => "font/otf",
_ => "application/octet-stream"
};
}
public void Dispose()
{
_running = false;
_listener?.Stop();
}
}
}
#endif