Created
April 9, 2023 22:42
-
-
Save to-osaki/815ca55b85bd644185d71a16aa0eb62a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Net; | |
using System.Net.Http; | |
using System.Text.RegularExpressions; | |
using UnityEngine; | |
using ResponseCallback = System.Func<System.Net.HttpListenerRequest, System.Text.RegularExpressions.Match, ResponseValue>; | |
public class ResponseValue | |
{ | |
static public readonly ResponseValue Empty = new ResponseValue | |
{ | |
statusCode = (int)HttpStatusCode.OK, | |
contentType = "text/plain", | |
output = null, | |
}; | |
static public readonly ResponseValue NotFound = new ResponseValue | |
{ | |
statusCode = (int)HttpStatusCode.NotFound, | |
contentType = "text/plain", | |
output = null, | |
}; | |
static public ResponseValue ResponseText(string text) | |
{ | |
return new ResponseValue | |
{ | |
statusCode = (int)HttpStatusCode.OK, | |
contentType = "text/plain", | |
output = System.Text.Encoding.UTF8.GetBytes(text), | |
}; | |
} | |
static public ResponseValue ResponseJson(string json) | |
{ | |
return new ResponseValue | |
{ | |
statusCode = (int)HttpStatusCode.OK, | |
contentType = "application/json", | |
output = System.Text.Encoding.UTF8.GetBytes(json), | |
}; | |
} | |
static public ResponseValue ResponseJson(System.Object obj) | |
{ | |
string json = JsonUtility.ToJson(obj); | |
return ResponseJson(json); | |
} | |
static public ResponseValue ResponseJpeg(byte[] jpg) | |
{ | |
return new ResponseValue | |
{ | |
statusCode = (int)HttpStatusCode.OK, | |
contentType = "image/jpeg", | |
output = jpg, | |
}; | |
} | |
public int statusCode; | |
public string contentType; | |
public byte[] output; | |
} | |
public class HttpServer : IDisposable | |
{ | |
private readonly HttpListener _listener; | |
private readonly Dictionary<HttpMethod, List<(Regex, ResponseCallback)>> _routes; | |
private Queue<HttpListenerContext> _contexts = new Queue<HttpListenerContext>(16); | |
public int ArrivedRequestCount => _contexts.Count; | |
public HttpServer(int port) | |
{ | |
_listener = new HttpListener(); | |
_listener.Prefixes.Add($"http://+:{port}/"); | |
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous; | |
_listener.Start(); | |
_listener.BeginGetContext(ListenerCallback, _listener); | |
_routes = new Dictionary<HttpMethod, List<(Regex, ResponseCallback)>> | |
{ | |
{HttpMethod.Post, new List<(Regex, ResponseCallback)>()}, | |
{HttpMethod.Get, new List<(Regex, ResponseCallback)>()}, | |
{HttpMethod.Put, new List<(Regex, ResponseCallback)>()}, | |
{HttpMethod.Delete, new List<(Regex, ResponseCallback)>()}, | |
}; | |
} | |
public void Update() | |
{ | |
lock (_contexts) | |
{ | |
while (_contexts.Count > 0) | |
{ | |
Response(_contexts.Dequeue()); | |
} | |
} | |
} | |
public void Dispose() | |
{ | |
lock (_contexts) | |
{ | |
while (_contexts.Count > 0) | |
{ | |
var context = _contexts.Dequeue(); | |
context.Response.Abort(); | |
} | |
} | |
if (_listener.IsListening) | |
{ | |
_listener.Stop(); | |
} | |
} | |
public void Route(HttpMethod method, Regex pattern, ResponseCallback callback) | |
{ | |
if (_routes.TryGetValue(method, out var table)) | |
{ | |
table.Add((pattern, callback)); | |
} | |
} | |
public void Route(Regex pattern, ResponseCallback callback) | |
{ | |
Route(HttpMethod.Get, pattern, callback); | |
} | |
public ResponseValue InvokeRequest(HttpMethod method, HttpListenerRequest request) | |
{ | |
if (_routes.TryGetValue(method, out var list)) | |
{ | |
for (int i = 0; i < list.Count; i++) | |
{ | |
(Regex pattern, ResponseCallback callback) = list[i]; | |
var match = pattern.Match(request.Url.LocalPath); | |
if (match.Success) | |
{ | |
return callback?.Invoke(request, match); | |
} | |
} | |
} | |
return ResponseValue.NotFound; | |
} | |
private void ListenerCallback(IAsyncResult result) | |
{ | |
var context = _listener.EndGetContext(result); | |
if (!_listener.IsListening) { context.Response.Abort(); return; } | |
_listener.BeginGetContext(ListenerCallback, _listener); | |
lock (_contexts) | |
{ | |
_contexts.Enqueue(context); | |
} | |
} | |
private void Response(HttpListenerContext context) | |
{ | |
var request = context.Request; | |
var response = context.Response; | |
try | |
{ | |
var value = InvokeRequest(new HttpMethod(request.HttpMethod), request); | |
if (value != null) | |
{ | |
response.ContentType = value.contentType; | |
response.StatusCode = value.statusCode; | |
response.AppendHeader("Cache-Control", "no-cache"); | |
response.AppendHeader("Access-Control-Allow-Origin", "*"); | |
if (value.output != null) | |
{ | |
var bytes = value.output; | |
response.Close(bytes, false); | |
} | |
else | |
{ | |
response.Close(); | |
} | |
} | |
else | |
{ | |
response.Abort(); | |
} | |
} | |
catch (Exception e) | |
{ | |
ResponseInternalError(response, e); | |
} | |
} | |
private void ResponseInternalError(HttpListenerResponse response, Exception e) | |
{ | |
response.StatusCode = (int)HttpStatusCode.InternalServerError; | |
response.ContentType = "text/plain"; | |
try | |
{ | |
using (var writer = new System.IO.StreamWriter(response.OutputStream, System.Text.Encoding.UTF8)) | |
writer.Write(e.ToString()); | |
response.Close(); | |
} | |
catch | |
{ | |
response.Abort(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment