Skip to content

Custom Endpoints

Daisuke Tsukada edited this page Aug 26, 2025 · 1 revision

Custom Endpoints

Learn how to extend Unity Remote Server with custom API endpoints for your specific needs.

Basic Custom Endpoint

Create a simple custom endpoint:

using DSGarage.UnityRemoteServer;
using UnityEditor;
using System.Net;

[InitializeOnLoad]
public static class MyCustomEndpoints
{
    static MyCustomEndpoints()
    {
        // Register when Unity Editor starts
        RemoteServer.RegisterCustomEndpoint("/custom/hello", HandleHello);
    }
    
    static void HandleHello(HttpListenerContext context)
    {
        var response = new { 
            message = "Hello from custom endpoint!",
            time = System.DateTime.Now
        };
        
        RemoteServer.SendJsonResponse(context, response);
    }
}

Test your endpoint:

curl http://127.0.0.1:8787/custom/hello

Handling Different HTTP Methods

static void HandleCustomEndpoint(HttpListenerContext context)
{
    switch (context.Request.HttpMethod)
    {
        case "GET":
            HandleGet(context);
            break;
        case "POST":
            HandlePost(context);
            break;
        case "DELETE":
            HandleDelete(context);
            break;
        default:
            context.Response.StatusCode = 405; // Method Not Allowed
            context.Response.Close();
            break;
    }
}

static void HandleGet(HttpListenerContext context)
{
    // Handle GET request
    var data = GetSomeData();
    RemoteServer.SendJsonResponse(context, data);
}

static void HandlePost(HttpListenerContext context)
{
    // Read POST data
    var json = RemoteServer.ReadRequestBody(context);
    var data = JsonUtility.FromJson<MyData>(json);
    
    // Process data
    ProcessData(data);
    
    // Send response
    RemoteServer.SendJsonResponse(context, new { ok = true });
}

Query Parameters

Handle query parameters in your endpoints:

static void HandleSearch(HttpListenerContext context)
{
    var query = context.Request.QueryString;
    
    var searchTerm = query["q"] ?? "";
    var limit = int.Parse(query["limit"] ?? "10");
    var offset = int.Parse(query["offset"] ?? "0");
    
    var results = SearchAssets(searchTerm, limit, offset);
    
    RemoteServer.SendJsonResponse(context, new
    {
        query = searchTerm,
        results = results,
        count = results.Length,
        limit = limit,
        offset = offset
    });
}

Real-World Examples

1. Asset Search Endpoint

Search for assets in the project:

[InitializeOnLoad]
public static class AssetSearchEndpoint
{
    static AssetSearchEndpoint()
    {
        RemoteServer.RegisterCustomEndpoint("/assets/search", HandleAssetSearch);
    }
    
    static void HandleAssetSearch(HttpListenerContext context)
    {
        var query = context.Request.QueryString["q"] ?? "";
        var type = context.Request.QueryString["type"] ?? "all";
        
        var guids = AssetDatabase.FindAssets(query);
        var assets = new List<object>();
        
        foreach (var guid in guids)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
            
            if (type == "all" || asset.GetType().Name.ToLower().Contains(type.ToLower()))
            {
                assets.Add(new
                {
                    guid = guid,
                    path = path,
                    name = asset.name,
                    type = asset.GetType().Name
                });
            }
        }
        
        RemoteServer.SendJsonResponse(context, new
        {
            query = query,
            type = type,
            count = assets.Count,
            assets = assets
        });
    }
}

2. Scene Management Endpoint

Control scene loading and management:

[InitializeOnLoad]
public static class SceneEndpoints
{
    static SceneEndpoints()
    {
        RemoteServer.RegisterCustomEndpoint("/scene/list", ListScenes);
        RemoteServer.RegisterCustomEndpoint("/scene/open", OpenScene);
        RemoteServer.RegisterCustomEndpoint("/scene/save", SaveScene);
    }
    
    static void ListScenes(HttpListenerContext context)
    {
        var scenes = new List<object>();
        
        // Get all scenes in build settings
        foreach (var scene in EditorBuildSettings.scenes)
        {
            scenes.Add(new
            {
                path = scene.path,
                enabled = scene.enabled,
                name = System.IO.Path.GetFileNameWithoutExtension(scene.path)
            });
        }
        
        // Add current scene info
        var currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
        
        RemoteServer.SendJsonResponse(context, new
        {
            current = new
            {
                name = currentScene.name,
                path = currentScene.path,
                isDirty = currentScene.isDirty
            },
            buildScenes = scenes
        });
    }
    
    static void OpenScene(HttpListenerContext context)
    {
        var json = RemoteServer.ReadRequestBody(context);
        var request = JsonUtility.FromJson<SceneRequest>(json);
        
        EditorApplication.delayCall += () =>
        {
            UnityEditor.SceneManagement.EditorSceneManager.OpenScene(request.path);
        };
        
        RemoteServer.SendJsonResponse(context, new { ok = true });
    }
    
    [System.Serializable]
    class SceneRequest
    {
        public string path;
    }
}

3. Prefab Operations

Create and modify prefabs:

[InitializeOnLoad]
public static class PrefabEndpoints
{
    static PrefabEndpoints()
    {
        RemoteServer.RegisterCustomEndpoint("/prefab/create", CreatePrefab);
        RemoteServer.RegisterCustomEndpoint("/prefab/instantiate", InstantiatePrefab);
    }
    
    static void CreatePrefab(HttpListenerContext context)
    {
        var json = RemoteServer.ReadRequestBody(context);
        var request = JsonUtility.FromJson<CreatePrefabRequest>(json);
        
        EditorApplication.delayCall += () =>
        {
            var go = new GameObject(request.name);
            
            // Add components
            if (request.addCamera) go.AddComponent<Camera>();
            if (request.addLight) go.AddComponent<Light>();
            if (request.addRigidbody) go.AddComponent<Rigidbody>();
            
            // Save as prefab
            var path = $"Assets/Prefabs/{request.name}.prefab";
            PrefabUtility.SaveAsPrefabAsset(go, path);
            GameObject.DestroyImmediate(go);
            
            AssetDatabase.Refresh();
        };
        
        RemoteServer.SendJsonResponse(context, new { ok = true });
    }
    
    [System.Serializable]
    class CreatePrefabRequest
    {
        public string name;
        public bool addCamera;
        public bool addLight;
        public bool addRigidbody;
    }
}

4. Test Runner Integration

Run Unity tests remotely:

[InitializeOnLoad]
public static class TestEndpoints
{
    static TestEndpoints()
    {
        RemoteServer.RegisterCustomEndpoint("/test/run", RunTests);
        RemoteServer.RegisterCustomEndpoint("/test/results", GetTestResults);
    }
    
    static void RunTests(HttpListenerContext context)
    {
        var json = RemoteServer.ReadRequestBody(context);
        var request = JsonUtility.FromJson<TestRequest>(json);
        
        EditorApplication.delayCall += () =>
        {
            var testRunner = new UnityEditor.TestTools.TestRunner.TestRunner();
            
            if (request.runAll)
            {
                UnityEditor.TestTools.TestRunner.Api.RunTests();
            }
            else
            {
                UnityEditor.TestTools.TestRunner.Api.RunTests(request.filter);
            }
        };
        
        RemoteServer.SendJsonResponse(context, new { ok = true });
    }
    
    [System.Serializable]
    class TestRequest
    {
        public bool runAll;
        public string filter;
    }
}

Thread Safety

Unity API calls must be made on the main thread:

static void HandleEndpoint(HttpListenerContext context)
{
    // This runs on HTTP server thread
    var request = RemoteServer.ReadRequestBody(context);
    
    // Queue for main thread execution
    EditorApplication.delayCall += () =>
    {
        // This runs on Unity's main thread
        DoUnityApiCall();
    };
    
    // Or use RemoteOps for thread-safe execution
    RemoteOps.ExecuteOnMainThread(() =>
    {
        DoUnityApiCall();
    });
    
    RemoteServer.SendJsonResponse(context, new { ok = true });
}

Error Handling

Properly handle errors in custom endpoints:

static void HandleEndpoint(HttpListenerContext context)
{
    try
    {
        // Your logic here
        var result = DoSomething();
        
        RemoteServer.SendJsonResponse(context, new
        {
            ok = true,
            data = result
        });
    }
    catch (System.Exception e)
    {
        context.Response.StatusCode = 500;
        RemoteServer.SendJsonResponse(context, new
        {
            ok = false,
            error = e.Message,
            stack = e.StackTrace
        });
    }
}

Authentication

Add authentication to your endpoints:

static bool ValidateApiKey(HttpListenerContext context)
{
    var apiKey = context.Request.Headers["X-API-Key"];
    return apiKey == "your-secret-key";
}

static void SecureEndpoint(HttpListenerContext context)
{
    if (!ValidateApiKey(context))
    {
        context.Response.StatusCode = 401; // Unauthorized
        RemoteServer.SendJsonResponse(context, new
        {
            ok = false,
            error = "Invalid API key"
        });
        return;
    }
    
    // Handle authenticated request
    HandleRequest(context);
}

Streaming Responses

For large data, use streaming:

static void StreamLargeData(HttpListenerContext context)
{
    context.Response.ContentType = "application/octet-stream";
    
    using (var output = context.Response.OutputStream)
    {
        // Stream data in chunks
        byte[] buffer = new byte[4096];
        int bytesRead;
        
        using (var input = File.OpenRead("large-file.dat"))
        {
            while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, bytesRead);
            }
        }
    }
    
    context.Response.Close();
}

Best Practices

  1. Use Meaningful URLs: /assets/texture/list instead of /list_tex
  2. Version Your API: /api/v1/build for future compatibility
  3. Return Consistent Responses: Always include ok status
  4. Document Your Endpoints: Add comments and examples
  5. Validate Input: Check parameters before processing
  6. Handle Errors Gracefully: Return meaningful error messages
  7. Use Appropriate HTTP Methods: GET for reading, POST for actions
  8. Keep Endpoints Focused: One endpoint, one responsibility

Testing Custom Endpoints

Test your endpoints with curl:

# GET request
curl http://127.0.0.1:8787/custom/status

# POST with JSON
curl -X POST http://127.0.0.1:8787/custom/action \
  -H "Content-Type: application/json" \
  -d '{"param": "value"}'

# With custom header
curl http://127.0.0.1:8787/custom/secure \
  -H "X-API-Key: your-secret-key"

Or with Python:

import requests

# Test endpoint
response = requests.get("http://127.0.0.1:8787/custom/hello")
print(response.json())

# POST data
response = requests.post(
    "http://127.0.0.1:8787/custom/create",
    json={"name": "TestObject"}
)
print(response.json())