Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ protected async Task<string> Call(
return HandleOfflineRequest(uri, method, content, allHeaders);
}

byte[] json = new System.Text.UTF8Encoding().GetBytes(content);
byte[] json = RequestBodyEncoder.Encode(content, Talo.Settings.compressRequests, out var gzipped);

using (UnityWebRequest www = new(uri, method))
{
Expand All @@ -94,6 +94,11 @@ protected async Task<string> Call(
www.SetRequestHeader(header.key, header.value);
}

if (gzipped)
{
www.SetRequestHeader("Content-Encoding", RequestBodyEncoder.GzipEncoding);
}

var op = www.SendWebRequest();

while (!op.isDone)
Expand Down
3 changes: 3 additions & 0 deletions Assets/Talo Game Services/Talo/Runtime/TaloSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ public class TaloSettings : ScriptableObject

[Tooltip("The value for the verification key version")]
public string verificationKeyValue = "";

[Tooltip("Gzip request bodies above 1KB")]
public bool compressRequests = true;
}
}
41 changes: 41 additions & 0 deletions Assets/Talo Game Services/Talo/Runtime/Utils/RequestBodyEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.IO;
using System.IO.Compression;
using System.Text;

namespace TaloGameServices
{
public static class RequestBodyEncoder
{
public const int MinCompressBytes = 1024;

public const string GzipEncoding = "gzip";

public static byte[] Encode(string content, bool compress, out bool gzipped)
{
gzipped = false;

if (string.IsNullOrEmpty(content))
{
return new byte[0];
}

var raw = new UTF8Encoding().GetBytes(content);

if (!compress || raw.Length < MinCompressBytes)
{
return raw;
}

using (var output = new MemoryStream())
{
using (var gzip = new GZipStream(output, CompressionLevel.Fastest))
{
gzip.Write(raw, 0, raw.Length);
}

gzipped = true;
return output.ToArray();
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Assets/Talo Game Services/Talo/Tests/RequestBodyEncoder.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace TaloGameServices.Test
{
internal class RequestBodyEncoderTest
{
// every gzip stream starts with 0x1F 0x8B
private const byte FirstGzipByte = 0x1F;
private const byte SecondGzipByte = 0x8B;

private static readonly UTF8Encoding Utf8 = new();

private static string BuildJson(int size)
{
var padding = new string('a', size);

return $"{{\"name\":\"event\",\"props\":[{{\"key\":\"pad\",\"value\":\"{padding}\"}}]}}";
}

private static byte[] Decompress(byte[] body)
{
using (var input = new MemoryStream(body))
using (var gzip = new GZipStream(input, CompressionMode.Decompress))
using (var output = new MemoryStream())
{
gzip.CopyTo(output);
return output.ToArray();
}
}

[Test]
public void Encode_EmptyContent_ReturnsNoBytes()
{
var body = RequestBodyEncoder.Encode("", true, out var gzipped);

Assert.AreEqual(0, body.Length);
Assert.IsFalse(gzipped);
}

[Test]
public void Encode_SmallBody_IsNotCompressed()
{
var content = BuildJson(100);

var body = RequestBodyEncoder.Encode(content, true, out var gzipped);

Assert.IsFalse(gzipped);
CollectionAssert.AreEqual(Utf8.GetBytes(content), body);
}

[Test]
public void Encode_LargeBody_StartsWithGzipHeader()
{
var body = RequestBodyEncoder.Encode(BuildJson(4096), true, out var gzipped);

Assert.IsTrue(gzipped);
Assert.Greater(body.Length, 2);
Assert.AreEqual(FirstGzipByte, body[0]);
Assert.AreEqual(SecondGzipByte, body[1]);
}

[Test]
public void Encode_LargeBody_DecompressesToOriginal()
{
var content = BuildJson(4096);

var body = RequestBodyEncoder.Encode(content, true, out _);

CollectionAssert.AreEqual(Utf8.GetBytes(content), Decompress(body));
}

[Test]
public void Encode_NonAsciiBody_DecompressesToOriginal()
{
// 2000 two-byte chars and an emoji: wrong encoding would break the round trip
var content = $"{{\"text\":\"{new string('\u00fc', 2000)} \U0001F600\"}}";

var body = RequestBodyEncoder.Encode(content, true, out var gzipped);

Assert.IsTrue(gzipped);
CollectionAssert.AreEqual(Utf8.GetBytes(content), Decompress(body));
}

[Test]
public void Encode_CompressDisabled_IsNotCompressed()
{
var content = BuildJson(4096);

var body = RequestBodyEncoder.Encode(content, false, out var gzipped);

Assert.IsFalse(gzipped);
CollectionAssert.AreEqual(Utf8.GetBytes(content), body);
}

[Test]
public void Encode_RepeatedEvents_IsSmallerThanInput()
{
var eventJson = BuildJson(380);
var content = $"[{string.Join(",", Enumerable.Repeat(eventJson, 50))}]";

var body = RequestBodyEncoder.Encode(content, true, out var gzipped);

Assert.IsTrue(gzipped);
Assert.Less(body.Length, content.Length);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading