Skip to content

Instantly share code, notes, and snippets.

@amimaro
Last active August 9, 2024 11:41
Show Gist options
  • Save amimaro/10e879ccb54b2cacae4b81abea455b10 to your computer and use it in GitHub Desktop.
Save amimaro/10e879ccb54b2cacae4b81abea455b10 to your computer and use it in GitHub Desktop.
Listen for Http Requests with Unity
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.IO;
using System.Net;
using System.Threading;
public class UnityHttpListener : MonoBehaviour
{
private HttpListener listener;
private Thread listenerThread;
void Start ()
{
listener = new HttpListener ();
listener.Prefixes.Add ("http://localhost:4444/");
listener.Prefixes.Add ("http://127.0.0.1:4444/");
listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
listener.Start ();
listenerThread = new Thread (startListener);
listenerThread.Start ();
Debug.Log ("Server Started");
}
void Update ()
{
}
private void startListener ()
{
while (true) {
var result = listener.BeginGetContext (ListenerCallback, listener);
result.AsyncWaitHandle.WaitOne ();
}
}
private void ListenerCallback (IAsyncResult result)
{
var context = listener.EndGetContext (result);
Debug.Log ("Method: " + context.Request.HttpMethod);
Debug.Log ("LocalUrl: " + context.Request.Url.LocalPath);
if (context.Request.QueryString.AllKeys.Length > 0)
foreach (var key in context.Request.QueryString.AllKeys) {
Debug.Log ("Key: " + key + ", Value: " + context.Request.QueryString.GetValues (key) [0]);
}
if (context.Request.HttpMethod == "POST") {
Thread.Sleep (1000);
var data_text = new StreamReader (context.Request.InputStream,
context.Request.ContentEncoding).ReadToEnd ();
Debug.Log (data_text);
}
context.Response.Close ();
}
}
@ThatOtherVRGuy
Copy link

Will this work on Android?

@amimaro
Copy link
Author

amimaro commented Nov 13, 2019

Android built with Unity? I believe so.

@ThatOtherVRGuy
Copy link

Yes, confirmed. Maybe I'm doing something wrong, but for it to listen to the LAN IP address it's on, like 192.168.1.12, I had to find that address and add it to the prefixes. Do you know if there's a 'generic' way of doing this, so I don't need to know my own IP address?

@amimaro
Copy link
Author

amimaro commented Nov 13, 2019

I think it's a network thing. If you setup the listener at localhost, I believe you can access this listener from another device if it is in the same LAN.

@thoulegend
Copy link

There a way to turn the listener into a coroutine?...trying to get it working with wwwform and post

@amimaro
Copy link
Author

amimaro commented Mar 8, 2020

I haven't used unity in a while 😅

@thoulegend
Copy link

Ya turns out unity and wwwforms doesn't like threads or something, I changed the code to getmethodasync and it worked

@iLyxa3D
Copy link

iLyxa3D commented Jul 30, 2020

Unity WebGL not supported.

@FUTC-Coding
Copy link

Do you know if there's a 'generic' way of doing this, so I don't need to know my own IP address?

you can simply use "http://*:4444/" as a prefix. And yes definitely works on android, I have a similar script running on an oculus go unity app.

@BarboraAtVResearch
Copy link

It'd be good to see how to return a json response from that code example.

yes

@BarboraAtVResearch
Copy link

It'd be good to see how to return a json response from that code example.

private void ListenerCallback(IAsyncResult result)
{
var context = listener.EndGetContext(result);

    if (context.Request.HttpMethod == "GET")
    {
        string logContent = // your json string here
        context.Response.ContentType = "application/json";
        byte[] encodedPayload = new UTF8Encoding().GetBytes(logContent);
        context.Response.ContentLength64 = encodedPayload.Length;
        System.IO.Stream output = context.Response.OutputStream;
        output.Write(encodedPayload, 0, encodedPayload.Length);
    }

    context.Response.Close();
}

@janick187
Copy link

Thannk you brother, this was exactly what I was looking for!

@Slock81
Copy link

Slock81 commented Jun 24, 2021

This was unbelievably helpful, thank you!!

@TomSmith27
Copy link

This is great but if i try and do this it the method never finishes does anyone know why?
var transforms = GameObject.FindObjectsOfType<Transform>();

I think its because Unity cannot access gameobjects when not on the main thread?

@AG-Ej-Business
Copy link

Im trying to understand how this code works exactly, since you use multi threading, if i make it run on 1 thread it basically just crashes my unity entirely forever waiting for a response i think, so my question is how exactly does this work?

@BarboraAtVResearch
Copy link

BarboraAtVResearch commented Jul 27, 2021

Im trying to understand how this code works exactly, since you use multi threading, if i make it run on 1 thread it basically just crashes my unity entirely forever waiting for a response i think, so my question is how exactly does this work?

This is great but if i try and do this it the method never finishes does anyone know why?
var transforms = GameObject.FindObjectsOfType<Transform>();

I think its because Unity cannot access gameobjects when not on the main thread?

Yes, the listener uses threading. Therefore you can call only functions which are thread-safe directly from it (which unfortunately not all the Unity functions are, for instance, GameObject.Find is not thread-safe). What I did was to buffer all the objects into the collection and only used JsonUtility.ToJson(myObject) directly from the listener, which is thread-safe. You may use a similar approach depending on what you want.

I've created a simple GitHub repository that buffers app events, sends them, and flushes them on demand. It will not provide you with exhaustive knowledge, but it is at least a working code example of the approach mentioned above.

@felipeggrod
Copy link

Very helpful gist, thanks everyone

@lejeanf
Copy link

lejeanf commented May 2, 2022

Hey, thx for this script.
Just a quick question: where do you set the default folder to open your .html pages?

@mwitz8
Copy link

mwitz8 commented Jun 4, 2022

Does anyone know why he put Thread.Sleep (1000); on line 52? Why do you need a Thread.Sleep() there?

@shoeboxwargamer
Copy link

It appears to work when running in editor but not on published builds, any thoughts?

@vitosbat
Copy link

vitosbat commented Jan 12, 2023

It appears to work when running in editor but not on published builds, any thoughts?

I had the same issue. In my case it was because I'm not kill the tread in Unity Editor. When I stopped the play mode it continue to listen 4444 port. Then I started my build and it had a conflict for the listening the same port. You need to close Editor before start the build or add some code to kill the thread before exit the play mode in Editor .
Anyway, check the 127.0.0.1:4444 port (or your port number) in Windows console before starting the build :
$ netstat -aot

@lejeanf
Copy link

lejeanf commented Jan 12, 2023

To get arround that problem I randomize the port on Awake. you can send an event with the new port used so that whatever script who needs it can grab the event.

@vitosbat
Copy link

vitosbat commented Jan 12, 2023

I found this solution for me now - just added to Start method:

EditorApplication.playModeStateChanged += delegate(PlayModeStateChange state) 

        {
            if (state == PlayModeStateChange.ExitingPlayMode)
            {
                listener.Stop();
                listenerThread.Abort();
                Debug.Log("Server stoped");
            }
        };

But recommend to add checking of listening to StartListener method, cause it throws an error at finish playmode:

while (true)
        {
            if (listener.IsListening)
            {
                var result = listener.BeginGetContext(ListenerCallback, listener);
                result.AsyncWaitHandle.WaitOne();
            }
        }

@PikaChokeMe
Copy link

Would it not be better or possible to do this with a coroutine instead of an entirely separate System.Thread?

@PikaChokeMe
Copy link

using UnityEngine;
using UnityEngine.Networking;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;

public class UnityHttpListener : MonoBehaviour
{

  private HttpListener listener;

  void Start()
  {
    listener = new HttpListener();
    listener.Prefixes.Add("http://localhost:4444/");
    listener.Prefixes.Add("http://127.0.0.1:4444/");
    listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
    listener.Start();
    
    StartCoroutine("startListenerCoroutine");
		
    Debug.Log("Server Started");
  }

  private System.Collections.IEnumerator startListenerCoroutine()
  {
    while(listener.IsListening)
    {
      Task<HttpListenerContext> task = listener.GetContextAsync();
      yield return new WaitUntil(() => task.IsCompleted);
      ProcessRequest(task.Result);
    }
  }

  private void ProcessRequest(HttpListenerContext context)
  {
    Debug.Log("Method: " + context.Request.HttpMethod);
    Debug.Log("LocalUrl: " + context.Request.Url.LocalPath);

    if (context.Request.QueryString.AllKeys.Length > 0) {
      foreach (var key in context.Request.QueryString.AllKeys) {
        Debug.Log ("Key: " + key + ", Value: " + context.Request.QueryString.GetValues(key)[0]);
      }
    }

    if (context.Request.HttpMethod == "POST") {	
      var data_text = new StreamReader (context.Request.InputStream, context.Request.ContentEncoding).ReadToEnd();
      Debug.Log (data_text);
    }

    context.Response.StatusCode = 200;
    context.Response.StatusCode = "OK";
    context.Response.Close ();
  }
  
  void OnApplicationQuit() {
    listener.Stop();
    Debug.Log("Server stoped");
  }  
}

https://gist.github.com/PikaChokeMe/7604fa19596ea6f66019898a948a8dd2

I will say that I don't know the performance implications of this over the dedicated thread; however.

@TenebreGaming
Copy link

TenebreGaming commented Dec 18, 2023

ThatOtherVRGuy Check out the IPManager class in this thread on StackExchange. It will grab the IP address for you.

https://stackoverflow.com/questions/51975799/how-to-get-ip-address-of-device-in-unity-2018

You can also bind to ip address 127.0.0.1 (localhost) or simply or use a wildcard (though it's not always a good thing to bind to all IP addresses on a system.)

@TenebreGaming
Copy link

I would also like to comment on the topic of treads vs. coroutines. Coroutines are great for in-game things that you can guarantee will happen quickly and/or can be sliced neatly into sections to spread across frames. The nature of HTTP, however, cannot guarantee any sort of timing and from my experience should generally be done in a separate thread. This, of course, does depend a lot on what you're doing with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment