のりまき日記

Unityなどの活用リファレンスブログ。「こうしたい時どうする」をまとめたい

unity)かんたんにwebサーバーを立てたい

はじめに

unityでインタラクションコンテンツを作っていると、アプリ間の連携などでhttpによる通信がしたいことがよくあります。よく使う実装をメモとして残しておきます。

tsururin.hatenablog.com

コード

using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using UnityEngine;

public class Server : System.IDisposable
{
    HttpListener m_HttpListener;

    List<IRequestHandler> m_RequestHandlers = new List<IRequestHandler>();

    public Server(int port)
    {
        m_HttpListener = new HttpListener();
        m_HttpListener.Prefixes.Add(string.Format("http://*:{0}/", port));
    }

    public void Start()
    {
        Debug.LogFormat("[Server] start.");
        m_HttpListener.Start();
        var _ = Listen();
    }

    public void Stop()
    {
        Debug.LogFormat("[Server] stop.");
        m_HttpListener.Stop();
    }

    public void Route(IRequestHandler handler)
    {
        m_RequestHandlers.Add(handler);
    }

    async Task Listen()
    {
        while (m_HttpListener.IsListening)
        {
            var context = await m_HttpListener.GetContextAsync();
            var _ = Routing(context);
        }
    }

    async Task Routing(HttpListenerContext context)
    {
        foreach (var handler in m_RequestHandlers)
        {
            if (context.Request.Url.AbsolutePath.StartsWith(handler.path) && context.Request.HttpMethod == handler.method)
            {
                Debug.LogFormat("[Server] routing. path: {0}, method: {1}", handler.path, handler.method);
                try
                {
                    var result = await handler.Handle(context.Request, context.Response);
                    if (result)
                    {
                        return;
                    }
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                    context.Response.StatusCode = 500;
                    context.Response.Close();
                }
            }
        }
        Debug.LogWarningFormat("[Server] 404.");
        context.Response.StatusCode = 404;
        context.Response.Close();
    }

    public void Dispose()
    {
        m_HttpListener.Close();
    }
}
using System.Net;
using System.Threading.Tasks;

public interface IRequestHandler
{
    string path { get; }

    string method { get; }

    Task<bool> Handle(HttpListenerRequest req, HttpListenerResponse res);
}

note

サーバのコードです。

List<IRequestHandler> m_RequestHandlers = new List<IRequestHandler>();

リクエスト毎にハンドラを作成して登録できるようにします。

Debug.LogException(e);
context.Response.StatusCode = 500;
context.Response.Close();

...

Debug.LogWarningFormat("[Server] 404.");
context.Response.StatusCode = 404;
context.Response.Close();

シンプルに500や404を実装しています。

使い方

using UnityEngine;

public class ApiServer : MonoBehaviour
{
    public System.Action onSomethingEvent;

    [SerializeField]
    int m_Port = 3000;

    Server m_Server;

    private void Awake()
    {
        m_Server = new Server(m_Port);
        m_Server.Route(new HelloWorldHandler());
        m_Server.Route(new WebEventHandler(onSomethingEvent));
    }

    private void OnEnable()
    {
        m_Server.Start();
    }

    private void OnDisable()
    {
        m_Server.Stop();
    }
}
using System.Net;
using System.Text;
using System.Threading.Tasks;

public class HelloWorldHandler : IRequestHandler
{
    public string path => "/hello-world";

    public string method => "GET";

    public async Task<bool> Handle(HttpListenerRequest req, HttpListenerResponse res)
    {
        // なにか処理
        await Task.Yield();

        res.StatusCode = 200;
        var text = Encoding.UTF8.GetBytes("hello world!");
        res.OutputStream.Write(text, 0, text.Length);
        res.Close();

        return true;
    }
}
using System.Net;
using System.Text;
using System.Threading.Tasks;

public class WebEventHandler : IRequestHandler
{
    public string path => "/my-event";

    public string method => "GET";

    System.Action m_Callback;

    public WebEventHandler(System.Action callback)
    {
        m_Callback = callback;
    }

    public async Task<bool> Handle(HttpListenerRequest req, HttpListenerResponse res)
    {
        // なにか処理
        await Task.Yield();

        res.StatusCode = 200;
        var text = Encoding.UTF8.GetBytes("ok!");
        res.OutputStream.Write(text, 0, text.Length);
        res.Close();

        m_Callback?.Invoke();

        return true;
    }
}

note

m_Server = new Server(m_Port);
m_Server.Route(new HelloWorldHandler());
m_Server.Route(new WebEventHandler(onSomethingEvent));

サーバを起動してハンドラを登録しています。ハンドラは単純にレスポンスを返すだけでもいいし、リクエストを受けてアプリ内に通知して挙動を変えるなどしてもいいと思います。

おわりに

外部にサーバーを立てたい時はapacheやnode.jsなどで立てるのがラクですが、アプリ間通信くらいでよければこちらも使えると思います。