Web, WebSocket and images
An FL# script reaches the internet with the classes of .NET: System.Net.Http.HttpClient (with HttpRequestMessage, HttpResponseMessage, HttpContent and its kinds) and System.Net.WebSockets.ClientWebSocket. They have the signatures of .NET 10, so code and libraries written for .NET work as they are. Images come with Texture.LoadFromUrl and ImageConversion.LoadImage.
On a player's device, every address goes through the network gate of the game: the first time a world contacts a site, the player chooses Always, This time or Deny in a window (see Sites a world contacts). Your request waits for that answer. On the server, every address is allowed and nobody is asked.
HTTP
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using NodeWorld.Flux;
namespace MyWorld
{
public class Scores : FluxScript
{
private static readonly HttpClient http = new HttpClient { BaseAddress = new Uri("https://api.example.com/") };
protected override void Start()
{
_ = LoadAsync();
}
private async Task LoadAsync()
{
try
{
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string json = await http.GetStringAsync("scores?top=10");
Debug.Log("Scores: " + json);
var body = new StringContent("{\"player\":\"" + Player.name + "\",\"score\":42}", Encoding.UTF8, "application/json");
HttpResponseMessage answer = await http.PostAsync("scores", body);
Debug.Log("Saved: " + (int)answer.StatusCode);
}
catch (HttpRequestException exception)
{
Debug.LogWarning("The scores are not available: " + exception.Message);
}
}
}
}What you can do:
- Every method (
GET,POST,PUT,PATCH,DELETE…), every header exceptHostand the connection ones, bodies in bytes (ByteArrayContent,StreamContent,ReadOnlyMemoryContent), text (StringContent), forms (FormUrlEncodedContent,MultipartFormDataContent). - The status, the headers and the body of the answer:
ReadAsStringAsync,ReadAsByteArrayAsync,ReadAsStreamAsync. - Redirects are followed (50 at most, as .NET), never from
httpstohttp. A redirect to another site goes through the gate again. - gzip and deflate answers are decoded when you ask for it:
new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }). HttpClient.Timeout(100 s by default) and aCancellationTokenstop a request.
What is different from .NET on a PC:
- Only
httpandhttps. The body of an answer arrives whole:ResponseHeadersReadgives it whole too. - No cookies (
UseCookiesstaysfalse), no proxy, no credentials, no client certificate, no custom certificate check: a site's certificate must be valid. - Only the asynchronous methods:
HttpClient.SendthrowsNotSupportedException(a script never waits on its thread). - When the player denies the site, the request fails with
HttpRequestException,HttpRequestError.ConnectionError, and a message that names the site.
WebSocket
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NodeWorld.Flux;
namespace MyWorld
{
public class Chat : FluxScript
{
private ClientWebSocket socket;
protected override void Start()
{
_ = RunAsync();
}
private async Task RunAsync()
{
socket = new ClientWebSocket();
socket.Options.AddSubProtocol("chat");
await socket.ConnectAsync(new Uri("wss://chat.example.com/room"), CancellationToken.None);
await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("hello")), WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[4096];
while (socket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close) break;
Debug.Log(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
}
protected override void OnDestroy()
{
socket?.Abort();
}
}
}ws and wss, text and binary messages, messages in pieces (endOfMessage), the close handshake (CloseAsync, CloseOutputAsync), Abort. Options.KeepAliveInterval (30 s by default) keeps the connection awake. No compression extension; a server that answers anything but 101 fails the connection, as in .NET.
Images
Texture picture = await Texture.LoadFromUrl("https://example.com/poster.png");
GetComponent<Renderer>().sharedMaterial.SetTexture("_BaseMap", picture);
Debug.Log(picture.width + " x " + picture.height);Texture.LoadFromUrl loads a PNG or JPG through the gate and decodes it into a texture of the world. ImageConversion.LoadImage(bytes) decodes bytes you already have (from GetByteArrayAsync, a WebSocket…) and gives null when they are not an image. The textures are destroyed with the world, or by Object.Destroy. On the server there is no picture: LoadImage gives null.
What the player is asked
- The choice is kept per site: the domain and its subdomains. Allowing
example.comalso allowsapi.example.comandcdn.example.com. Where a domain starts comes from the Public Suffix List: allowingmyname.github.iodoes not allow the rest ofgithub.io. A port that is not the standard one is part of the site:example.com:8443. - One window per site: all your requests to it wait for the same answer, as long as the player has not answered, or until they leave the world.
- Always and Deny are kept on the player's device, for every world, until they remove them in Settings, Sites. This time lasts until they leave the world.
- The big video sites (
youtube.com,youtu.be,twitch.tv,vimeo.com,soundcloud.com,dailymotion.com) are allowed without a window; the player can change that list. - An IP address of the player's machine or local network written in your address (
192.168.1.20,127.0.0.1…) shows the window with a warning. A name that points there is always refused. - The game resolves the name itself and connects to the address it checked: a name that changes its answer after the check reaches nothing new.
- In Play mode of the Unity editor, everything is allowed, without a window.
Videos
A VideoPlayer asks the gate for the site of its address before it opens it (VideoState.Loading meanwhile). A denied site gives VideoState.Error with VideoError.Refused. Every network protocol of the video player works (http, https, rtmp, rtsp, udp, mms, srt…); a file, a screen or a capture device does not.
On the server
Scripts marked [ServerOnly] have the same HttpClient and ClientWebSocket, with every address allowed: no player is there to answer. They also reach the server machine and its local network: the host decides what they may reach with its firewall (see Ports and firewall).
Logs
Each request writes one [Web] line with the site only, never the path: [Web] GET api.example.com: 200, 512 bytes. The window, the player's answer and each WebSocket opening and closing write theirs too.
