The world is changing, and I’m just trying to keep up. ----Arthur Morgan
UniTask
https://github.com/Cysharp/UniTask
BestHttp v3,这个插件有v1,v2,v3版本,每个版本的文档都不相同!
https://assetstore.unity.com/packages/tools/network/best-http-267636
//快速开始
//https://bestdocshub.pages.dev/HTTP/getting-started/#1-create-request
//BestHttp 总文档
//https://bestdocshub.pages.dev/
//测试网站
//httpbin.org
//文件下载部分的文档
//https://bestdocshub.pages.dev/HTTP/getting-started/downloads/#progress-tracking
//BestHttp v1的文档(过时)
//https://besthttp-documentation.readthedocs.io/en/dev/1.HTTP/HTTPRequest/
using Best.HTTP;
using Best.HTTP.Request.Upload.Forms;
using Best.HTTP.Response;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Cysharp.Threading.Tasks;
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
public class BestHttp_ECode : MonoBehaviour
{
string tmp_postURL = "http://www.baidu.com";
private string path_bigFilePatg = "http://n.sinaimg.cn/sinacn/w480h306/20180118/6452-fyqtwzu4786357.jpg";
private string path_imageURL = "http://n.sinaimg.cn/sinacn/w480h306/20180118/6452-fyqtwzu4786357.jpg";
void Start()
{
StartCoroutine(nameof(Test_Get));
StartCoroutine(nameof(ShowPictureByHttpRequest));
StartCoroutine(nameof(Test_SendPost));
StartCoroutine(nameof(DownBigFile_WithBlicking));
StartCoroutine(nameof(Test_DownLoadABigFile_WithPolling));
}
private IEnumerator Test_Get()
{
HTTPRequest httpRequest = new HTTPRequest(new System.Uri(tmp_postURL), HTTPMethods.Get
, (request, response) =>
{
if (response.IsSuccess)
{
Debug.Log(response.DataAsText);
}
else
{
Debug.Log("发送失败");
}
});
httpRequest.Send();
yield return null;
}
/// <summary>
/// Post测试方法
/// </summary>
private IEnumerator Test_SendPost()
{
HTTPRequest tmp_post = new HTTPRequest(new System.Uri(tmp_postURL), HTTPMethods.Post,
(request, response) => { Debug.Log(response.DataAsText); });
yield return tmp_post.Send();
}
private IEnumerator AddFormData()
{
HTTPRequest tmp_post = new HTTPRequest(new System.Uri(tmp_postURL), HTTPMethods.Post, RequestFinishedCallback);
tmp_post.UploadSettings.UploadStream = new MultipartFormDataStream()
.AddField("Field Name 1", "Field value 1")
.AddField("Field Name 2", "Field value 2");
MultipartFormDataStream tmp_mfds = new MultipartFormDataStream();
tmp_mfds.AddField("ddd", "qqqq");
tmp_post.UploadSettings.UploadStream = tmp_mfds;
yield return tmp_post.Send();
}
// 4. This callback is called when the request is finished. It might finished because of an error!
private void RequestFinishedCallback(HTTPRequest req, HTTPResponse resp)
{
switch (req.State)
{
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
Debug.Log("Upload finished succesfully!");
}
else
{
Debug.Log($"Server sent an error: {resp.StatusCode}-{resp.Message}");
}
break;
default:
Debug.LogError($"Request finished with error! Request state: {req.State}");
break;
}
}
private IEnumerator StartHttpRequestsByCoroutine()
{
// 1. Create request
var request = HTTPRequest.CreatePost("https://httpbin.org/post");
// 2. Setup
request.UploadSettings.UploadStream = new MultipartFormDataStream()
.AddField("Field Name 1", "Field value 1")
.AddField("Field Name 2", "Field value 2");
// 3. Send
request.Send();
// 4. Wait for completion
yield return request;
switch (request.State)
{
case HTTPRequestStates.Finished:
// 5. Process response
if (request.Response.IsSuccess)
{
Debug.Log("Upload finished succesfully!");
}
else
{
// 6. Error handling
Debug.LogError($"Server sent an error: {request.Response.StatusCode}-{request.Response.Message}");
}
break;
// 6. Error handling
default:
Debug.LogError($"Request finished with error! Request state: {request.State}");
break;
}
}
/// <summary>
/// 异步发送http请求
/// </summary>
/// <returns></returns>
private async UniTask SendRqeustAsync()
{
// 1. Create request
var request = HTTPRequest.CreatePost("https://httpbin.org/post");
// 2. Setup
request.UploadSettings.UploadStream = new MultipartFormDataStream()
.AddField("Field Name 1", "Field value 1")
.AddField("Field Name 2", "Field value 2");
try
{
// 3. Send & wait for completion
var response = await request.GetHTTPResponseAsync();
// 5. Process response
if (response.IsSuccess)
{
Debug.Log("Upload finished succesfully!");
}
else
{
Debug.LogError($"Server sent an error: {response.StatusCode}-{response.Message}");
}
}
catch (AsyncHTTPException e)
{
// 6. Error handling
Debug.LogError($"Request finished with error! Error: {e.Message}");
}
}
/// <summary>
/// 异步发送http请求获取AB包_
/// GetAssetBundleAsync, GetAsStringAsync,
/// GetAsTexture2DAsync, GetRawDataAsync,
/// GetFromJsonResultAsync<T>
/// </summary>
/// <returns></returns>
private async UniTask SendRqeustGetABPackageAsync()
{
// 1. Create request
var request = HTTPRequest.CreatePost("https://httpbin.org/post");
// 2. Setup
request.UploadSettings.UploadStream = new MultipartFormDataStream()
.AddField("Field Name 1", "Field value 1")
.AddField("Field Name 2", "Field value 2");
try
{
// 3. Send & wait for completion
var response = await request.GetAssetBundleAsync();
// 5. Process response
if (response != null)
{
Debug.Log("Upload finished succesfully!");
Debug.Log("AB包下载完毕");
}
else
{
Debug.Log("ab包下载失败");
}
}
catch (AsyncHTTPException e)
{
// 6. Error handling
Debug.LogError($"Request finished with error! Error: {e.Message}");
}
}
//Even if the request's State is HTTPRequestStates.Finished, the server might sent an error and the response's content isn't what we expect! By testing the response's StatusCode or checking IsSuccess we can make sure that all possibilities are handled.
//即使请求的 State 为 HTTPRequestStates.Finished,服务器也可能会发送错误,并且响应的内容不是我们所期望的!
//通过测试响应的 StatusCode 或检查 IsSuccess,我们可以确保处理所有可能性。
//对于较小的资源,HTTPResponse类提供了一些专门的属性:byte[]Data、字符串DataAsText和Texture2D DataAsTexture2D。这些可用于访问作为原始字节、转换为utf-8字符串或Unity Texture2D的下载数据。
/// <summary>
/// 通过HttpRequest显示图片
/// </summary>
private IEnumerator ShowPictureByHttpRequest()
{
GameObject tmp_Plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
tmp_Plane.name = "tmp_Plane";
tmp_Plane.transform.position = Vector3.zero;
new HTTPRequest(new System.Uri(path_imageURL),
(request, response) =>
{
var tex = new Texture2D(0, 0);
tex.LoadImage(response.Data);
//System.IO.File.WriteAllBytes("path", response.Data);//保存到本地
tmp_Plane.GetComponent<Renderer>().material.mainTexture = tex;
//tmp_Plane.GetComponent<Renderer>().material.mainTexture = response.DataAsTexture2D;//直接加载图片,不自己转换
}).Send();
yield return null;
}
/// <summary>
/// 使用轮询进行流式处理
/// </summary>
/// <returns></returns>
private IEnumerator Test_DownLoadABigFile_WithPolling()
{
var request = new HTTPRequest(new Uri(path_bigFilePatg), HTTPMethods.Get);
request.DownloadSettings.OnDownloadStarted += OnBigFileDownloadStarted;
request.DownloadSettings.OnDownloadProgress += OnDownloadProgress;
void OnDownloadProgress(HTTPRequest req, long progress, long length)
=> Debug.Log($"{progress:N0}/{length:N0}");
yield return request.Send();
}
/// <summary>
/// 当大文件开始下载的时候_轮询进行流处理
/// </summary>
/// <param name="req"></param>
/// <param name="resp"></param>
/// <param name="stream"></param>
private void OnBigFileDownloadStarted(HTTPRequest req, HTTPResponse resp, DownloadContentStream stream)
{
//只有当响应的状态代码为2xx时,才会调用此事件,并且不会针对重定向、请求和服务器错误(4xx和5xx状态代码)进行调用。进一步减少您必须处理的边缘情况数量和必须管理的代码复杂性。
Debug.Log("Download Started");
StartCoroutine(ParseContent(stream));
}
/// <summary>
/// 轮询进行流处理
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
IEnumerator ParseContent(DownloadContentStream stream)
{
string filePath = Path.Combine(Application.streamingAssetsPath, "downloadedFile.png");
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
try
{
while (!stream.IsCompleted)
{
// Try to take out a download segment from the Download Stream.
if (stream.TryTake(out var buffer))
{
// 将数据写入文件
fileStream.Write(buffer.Data, 0, buffer.Data.Length);
// Make sure that the buffer is released back to the BufferPool.
BufferPool.Release(buffer);
}
yield return null;
}
}
finally
{
// Don't forget to Dispose the stream!
stream.Dispose();
// 下载本身始终在非 Unity 主线程上完成,它不会影响正在运行的游戏逻辑和渲染!
}
}
Debug.Log($"File saved to: {filePath}");
}
/// <summary>
/// 使用阻塞式下载大文件
/// </summary>
private void DownBigFile_WithBlicking()
{
var request = HTTPRequest.CreateGet(path_imageURL, OnRequestFinishedCallack_WithBlocking);
request.DownloadSettings.OnDownloadStarted += OnDownloadStarted_WithBlocking;
request.DownloadSettings.DownloadStreamFactory = (req, resp, bufferAvailableHandler)
=> new BlockingDownloadContentStream(resp, req.DownloadSettings.ContentStreamMaxBuffered, bufferAvailableHandler);
request.DownloadSettings.OnDownloadProgress += OnDownloadProgress;
void OnDownloadProgress(HTTPRequest req, long progress, long length)
=> Debug.Log($"{progress:N0}/{length:N0}");
request.Send();
}
private void OnRequestFinishedCallack_WithBlocking(HTTPRequest req, HTTPResponse resp)
{
Debug.Log("下载本身始终在非 Unity 主线程上完成,它不会影响正在运行的游戏逻辑和渲染!");
}
private async void OnDownloadStarted_WithBlocking(HTTPRequest req, HTTPResponse resp, DownloadContentStream stream)
{
//StartCoroutine();
UniTask<byte[]> downloadedData_unitask = await Task.Run(() => ConsumeDownloadStream(stream as BlockingDownloadContentStream));
byte[] downloadedData = await downloadedData_unitask;
string filePath = Path.Combine(Application.streamingAssetsPath, "downloadedFile2.png");
await SaveFileAsync(filePath, downloadedData);
}
private async UniTask SaveFileAsync(string filePath, byte[] data)
{
// 确保目录存在
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
// 将字节数组写入文件
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
await fs.WriteAsync(data, 0, data.Length);
}
Debug.Log($"File saved to: {filePath}");
}
public async UniTask<Byte[]> ConsumeDownloadStream(BlockingDownloadContentStream blockingStream)
{
List<Byte[]> segments = new List<Byte[]>();
try
{
while (!blockingStream.IsCompleted)
{
// Take out a segment from the downloaded
if (blockingStream.TryTake(out var buffer))
{
try
{
// Save the segment for later concatenation
Byte[] segment = new Byte[buffer.Count];
Array.Copy(buffer.Data, buffer.Offset, segment, 0, buffer.Count);
segments.Add(segment);
}
finally
{
// Return the buffer to the pool
ArrayPool<byte>.Shared.Return(buffer.Data);
}
}
}
}
finally
{
blockingStream.Dispose();
}
// Concatenate all segments into one array
int totalLength = segments.Sum(s => s.Length);
Byte[] result = new Byte[totalLength];
int offset = 0;
foreach (var segment in segments)
{
Array.Copy(segment, 0, result, offset, segment.Length);
offset += segment.Length;
}
return result;
}
}