PopulateCacheManuallySample.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading.Tasks;
  5. using Best.HTTP.Examples.Helpers;
  6. using Best.HTTP.Response;
  7. using Best.HTTP.Shared;
  8. using Best.HTTP.Shared.Extensions;
  9. using Best.HTTP.Shared.PlatformSupport.Memory;
  10. using UnityEngine;
  11. using UnityEngine.Scripting;
  12. namespace Best.HTTP.Examples
  13. {
  14. /// <summary>
  15. /// Example demonstrating usage of BeginCache, EndCache functions and HTTPCacheContentWriter class to manually store content for an url.
  16. /// </summary>
  17. class PopulateCacheManuallySample : SampleBase
  18. {
  19. // Constants passed to the API endpoint stored in _baseAddress
  20. const int FromUserId = 1;
  21. const int ToUserId = 10;
  22. /// <summary>
  23. /// String template used for generating unique uris for every user stored individually in the local cache.
  24. /// </summary>
  25. const string LocalCacheUserURLTemplate = "httpcache://userapi/user/{0}";
  26. #pragma warning disable 0649, 0169
  27. [Header("Sample Fields")]
  28. /// <summary>
  29. /// GameObject that will be used as a root for new UI objects.
  30. /// </summary>
  31. [SerializeField]
  32. private RectTransform _contentRoot;
  33. /// <summary>
  34. /// Prefab of a UI object with two Text fields.
  35. /// </summary>
  36. [SerializeField]
  37. private MultiTextListItem _listItemPrefab;
  38. #pragma warning restore
  39. /// <summary>
  40. /// Address of the used end point.
  41. /// </summary>
  42. private string _baseAddress = "https://besthttpwebgldemo.azurewebsites.net/users/{0}/{1}";
  43. protected override async void Start()
  44. {
  45. base.Start();
  46. CreateUIItem("Loading users...");
  47. await PopulateLocalCache();
  48. await LoadUsersFromLocalCache();
  49. }
  50. async Task PopulateLocalCache()
  51. {
  52. try
  53. {
  54. // Load a list of users from the server
  55. var users = await HTTPRequest.CreateGet(string.Format(_baseAddress, FromUserId, ToUserId))
  56. .GetFromJsonResultAsync<List<User>>();
  57. CreateUIItem($"Received {users.Count} users from /users/{FromUserId}/{ToUserId}");
  58. StoreUsersInLocalCache(users);
  59. }
  60. catch (AsyncHTTPException ex)
  61. {
  62. CreateUIItem($"/Users request failed: {ex.Message}");
  63. }
  64. }
  65. void StoreUsersInLocalCache(List<User> users)
  66. {
  67. // Go over all the users and save them to the local cache with a custom uri
  68. foreach (var user in users)
  69. {
  70. var userUri = new Uri(string.Format(LocalCacheUserURLTemplate, user.Id));
  71. // convert the user object to json string and get the string's bytes
  72. var content = UserToByteArray(user);
  73. // BeginCache expects at least one caching header ("cache-control" with "max-age" directive, "etag", "expires" or "last-modified").
  74. // A cache-control with a max-age directive is a good choice for fabricated urls as the plugin will not try to valide the content's freshness.
  75. var headers = new Dictionary<string, List<string>> { { "cache-control", new List<string> { $"max-age={TimeSpan.FromDays(360).TotalSeconds}" } } };
  76. // Start the caching procedure by calling BeginCache and pass all the required parameters.
  77. var cacheWriter = HTTPManager.LocalCache.BeginCache(HTTPMethods.Get, userUri, HTTPStatusCodes.OK, headers, null);
  78. // If the writer is null, something prevents caching, these can be one of the following:
  79. // - caching itself isn't supported
  80. // - userUri doesn't start with 'http'
  81. // - can't acquire a write lock on the resource (a request currently reading or writing the same uri)
  82. // - IO error
  83. // - If the headers passed to BeginCache has a "content-length" header too, BeginCache checks whether it's in the limits of the cache. If it can make enough room to fit a null value will be returned.
  84. if (cacheWriter == null)
  85. {
  86. CreateUIItem($"Can't cache user('{user.Id}')!");
  87. continue;
  88. }
  89. try
  90. {
  91. // Write content to the cache.
  92. // Under the hood this writes to a file, if presumably it will take a long time (large content and/or slow media) it's advised to use a Thread.
  93. cacheWriter.Write(content);
  94. // Finish the caching process by calling EndCache.
  95. // This call will do all housekeeping, like disposing internal streams and releasing the write lock on the uri.
  96. // After this call, requests to this very same uri will load and serve from the local cache.
  97. HTTPManager.LocalCache.EndCache(cacheWriter, true, null);
  98. CreateUIItem($"User cached as '{userUri}'");
  99. }
  100. catch (Exception ex)
  101. {
  102. Debug.LogException(ex);
  103. // We must call EndCache even if there's an error, otherwise not all resources are disposed and
  104. // new attempts to write to this cache entry will fail!
  105. HTTPManager.LocalCache.EndCache(cacheWriter, false, null);
  106. CreateUIItem($"Writing to cache failed with user '{user.Id}'");
  107. }
  108. finally
  109. {
  110. // content's byte[] is borrowed from the BufferPool, it's a nice thing to return it!
  111. BufferPool.Release(content);
  112. }
  113. }
  114. }
  115. /// <summary>
  116. /// This function will test/emulate requests loading cached individual users
  117. /// </summary>
  118. async Task LoadUsersFromLocalCache()
  119. {
  120. for (int id = FromUserId; id < ToUserId; id++)
  121. {
  122. // Create and execute a HTTP request to get one individual user's data from the local cache
  123. var user = await HTTPRequest.CreateGet(string.Format(LocalCacheUserURLTemplate, id))
  124. .GetFromJsonResultAsync<User>();
  125. CreateUIItem($"User '{user.Id}' loaded from local cache (uri: '{string.Format(LocalCacheUserURLTemplate, id)}')!");
  126. }
  127. }
  128. /// <summary>
  129. /// Converts a User object to JSon string and returns with the byte representation of the string
  130. /// </summary>
  131. BufferSegment UserToByteArray(User user)
  132. {
  133. string json = JSON.LitJson.JsonMapper.ToJson(user);
  134. int length = Encoding.UTF8.GetByteCount(json);
  135. byte[] result = BufferPool.Get(length, true);
  136. Encoding.UTF8.GetBytes(json, 0, json.Length, result, 0);
  137. return result.AsBuffer(length);
  138. }
  139. MultiTextListItem CreateUIItem(string str)
  140. => Instantiate<MultiTextListItem>(this._listItemPrefab, this._contentRoot)
  141. .SetText(str) as MultiTextListItem;
  142. }
  143. [Preserve]
  144. class User
  145. {
  146. [Preserve] public string Id { get; set; }
  147. [Preserve] public string Name { get; set; }
  148. [Preserve] public DateTime Joined { get; set; }
  149. }
  150. }