Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)

目录

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)

一、简单介绍

二、实现原理

三、注意事项

四、示例效果

五、示例实现简单步骤

六、关键脚本


一、简单介绍

Unity 在开发中,网络访问:

    可以使用 UnityWebRequest 访问,不过好似只能用协程的方式,并且访问只能在主线程中;
    所以这里使用 C# 中的 HttpClient,进行网络访问,而且 HttpClient,允许在子线程中访问,在一些情况下可以大大减少主线程的网络访问压力;

这里使用 HttpClient,进行 Post 访问,发请Content-Type内容类型为 multipart/form-data 的请求,并且Task 结合 async (await) 的进行异步访问,最后使用正常方式以及 Stream 流式的形式获取数据,在这里做一个简单的记录,以便后期使用的时候参考。

示例请求参数如下:

1)Header参数:

参数名 参数值 参数类型 必填 备注

Content-Type

multipart/form-data

String

2)Body参数:

参数名 参数类型 备注 是否必填

id

Integer

应用id

必填

conversationId

String

对话id

必填

deviceId

String

设备号Id

必填

text

String

对话内容

必填

file

File

文件

必填

type

String

对话类型 "text":文本, "image":图片

必填

二、实现原理

1、HttpClient 创建 Http 客户体

2、MultipartFormDataContent 创建 multipart/form-data 数据体,文件使用注意转为二进制数组(ByteArrayContent)

3、使用 HttpClient.PostAsync 发起请求,HttpResponseMessage 接收结果

4、其中 HttpResponseMessage.Content.ReadAsStringAsync().Result 接收正常的数据结果

5、其中 Stream stream = await HttpResponseMessage.Content.ReadAsStreamAsync()流式获取数据,然后 StreamReader reader = new StreamReader(stream) 和

string line = await reader.ReadLineAsync() 流式读取解析每一行数据

三、注意事项

1、根据服务端返回数据的形式,选择合适的读取响应的数据

2、实现形式仅供参考,如有更好的可以留言讨论

四、示例效果

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第1张图片

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第2张图片

五、示例实现简单步骤

1、创建一个 Unity工程,添加一个空物体用来挂载测试脚本

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第3张图片

2、添加脚本,实现 HttpClient 发起 multipart/form-data Post 请求,包括异步发起和流式接收响应数据

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第4张图片

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第5张图片

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第6张图片

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第7张图片

3、把测试脚本挂载到场景物体上

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第8张图片

4、运行场景,可以看到打印信息

Unity HttpClient 之 使用MultipartFormDataContent 发起 内容类型为 multipart/form-data 的数据 Post 请求(正常与流式响应处理)_第9张图片

六、关键脚本

1、HttpClientMultipartFormDataContentRequest


using System.IO;
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Net.Http;

/// 
/// 对话响应结果
/// 
public class ResponeseResult { 
    public string ThreadId { get; set; }
    public string ChatAnswer { get; set; }
}
/// 
/// Http Client 发起 Content-Type 内容类型 multipart / form - data 的请求 实例类
/// 
public class HttpClientMultipartFormDataContentRequest : Singleton, IHttpClientMultipartFormDataContentRequest
{
    #region Data
    /// 
    /// TAG
    /// 
    const string TAG = "[GptChatHttpWebRequest] ";

    /// 
    /// 网址 URL_BASE
    /// 
    const string URL_BASE = "https://YourHttpURL.com";
    /// 
    /// URL_VERSION
    /// 
    const string URL_VERSION = "/xrlauncherhwapi/v1";
    /// 
    /// URL_TARGET
    /// 
    const string URL_TARGET = "/gpts/chat";
    /// 
    /// URL
    /// 
    string URL = URL_BASE + URL_VERSION + URL_TARGET;

    #endregion

    #region SendChatRequest

    /// 
    /// 发起对话请求
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public ResponeseResult SendChatRequest(int id, string conversationId, string deviceId, string chatAsk, string type, string filepath, string filename,
        Action onSuccess, Action onFailed)
    {
        Dictionary keyValues = DataToDictionary(id, conversationId, deviceId, chatAsk, type);

        return PostFormData(URL, keyValues, filepath, filename, onSuccess, onFailed);
    }

    /// 
    /// 发起对话请求
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public ResponeseResult SendChatRequest(int id, string conversationId, string deviceId, string chatAsk, string type, byte[] fileBinaryData, string filename,
        Action onSuccess, Action onFailed)
    {
        Dictionary keyValues = DataToDictionary(id,conversationId,deviceId,chatAsk,type);

        return PostFormData(URL, keyValues, fileBinaryData, filename, onSuccess, onFailed);
    }

    /// 
    /// Post multipart/form-data 数据
    /// 
    /// <服务器地址>
    /// 
    /// <文件的本地储存地址:包括文件名>
    /// <文件的名字>
    /// 
    private ResponeseResult PostFormData(string url, Dictionary dic, string filepath, string filename,
        Action onSuccess, Action onFailed)
    {
        return PostFormData(url, dic, File.ReadAllBytes(filepath), filename, onSuccess, onFailed);
    }

    /// 
    /// Post multipart/form-data 数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    private ResponeseResult PostFormData(string url, Dictionary dic, byte[] fileBinaryData, string filename,
        Action onSuccess, Action onFailed)
    {
        Debug.Log(TAG + " PostFormData fileBinaryData.Length " + fileBinaryData.Length);
        string str = "";
        ResponeseResult chatResponeseResult = null;
        try
        {
            HttpClient client = new HttpClient();
            var postContent = new MultipartFormDataContent();
            string boundary = string.Format("--{0}", DateTime.Now.Ticks.ToString("x"));
            postContent.Headers.Add("ContentType", $"multipart/form-data, boundary={boundary}");
            string filekeyname = "file";
            postContent.Add(new ByteArrayContent(fileBinaryData), filekeyname, filename);
            foreach (var key in dic.Keys)
            {
                postContent.Add(new StringContent(dic[key].ToString()), key);
            }
            Debug.Log(TAG + " PostFormData Url " + url);
            HttpResponseMessage response = client.PostAsync(url, postContent).Result;
            str = response.Content.ReadAsStringAsync().Result;
            Debug.Log(TAG + " PostFormData response  Content " + str);
            chatResponeseResult = HandleResult(str);
            onSuccess?.Invoke(chatResponeseResult);
        }
        catch (Exception ex)
        {
            Debug.LogError(TAG + "PostJsonData:" + ex.ToString());
            onFailed?.Invoke(ex.ToString());
        }
        return chatResponeseResult;
    }

    /// 
    /// 拼凑数据结果
    /// 
    /// 
    /// 
    private ResponeseResult HandleResult(string content) {
        // 将字符串按换行符分割成多行
        string[] lines = content.Split("data");

        Debug.Log(TAG + " HandleResult lines.Length " + lines.Length);

        // 存储结果的列表
        List texts = new List();
        // 存储结果的列表
        List threadIds = new List();

        // 遍历每一行,提取包含 "text":"xx" 的行中的文本内容
        foreach (string line in lines)
        {
            if (line.Contains("text"))
            {
                int startIndex = line.IndexOf("text") + 7; // 找到 "text":" 的位置
                int endIndex = line.IndexOf("\"", startIndex); // 找到下一个引号位置

                string text = line.Substring(startIndex, endIndex - startIndex);
                texts.Add(text);
            }
            if (line.Contains("thread_id"))
            {
                int startIndex = line.IndexOf("thread_id") + 12; // 找到 "thread_id":" 的位置
                int endIndex = line.IndexOf("\"", startIndex); // 找到下一个引号位置

                string threadId = line.Substring(startIndex, endIndex - startIndex);
                threadIds.Add(threadId);
            }
        }

        string stringBuilder = "";
        // 输出结果
        foreach (string text in texts)
        {
            stringBuilder += (text);
        }

        Debug.Log(TAG + " result text " + stringBuilder);
        return new ResponeseResult() { ThreadId = threadIds.Count > 1 ? threadIds[0] : "", ChatAnswer = string.IsNullOrEmpty(stringBuilder) ? "The server returns empty data" : stringBuilder };
    }

    #endregion

    #region SendChatRequestStream

    /// 
    /// 流式处理数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public void SendChatRequestStream(int id, string conversationId, string deviceId, string chatAsk, string type, string filepath, string filename,
        Action onResponesingGettingData, Action onSuccess, Action onFailed)
    {
        Dictionary keyValues = DataToDictionary(id, conversationId, deviceId, chatAsk, type);

        PostFormDataStreamAsync(URL, keyValues, File.ReadAllBytes(filepath), filename, onResponesingGettingData, onSuccess, onFailed);
    }

    /// 
    /// 流式处理数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public void SendChatRequestStream(int id, string conversationId, string deviceId, string chatAsk, string type, byte[] fileBinaryData, string filename,
        Action onResponesingGettingData, Action onSuccess, Action onFailed)
    {
        Dictionary keyValues = DataToDictionary(id, conversationId, deviceId, chatAsk, type);

        PostFormDataStreamAsync(URL, keyValues, fileBinaryData, filename, onResponesingGettingData, onSuccess, onFailed);
    }

    /// 
    /// 异步流式处理数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    private async void PostFormDataStreamAsync(string url, Dictionary dic, byte[] fileBinaryData, string filename,
        Action onResponesingGettingData, Action onSuccess, Action onFailed)
    {
        Debug.Log(TAG + " PostFormDataStreamAsync fileBinaryData.Length " + fileBinaryData.Length);

        try
        {
            using (HttpClient client = new HttpClient())
            using (var postContent = new MultipartFormDataContent())
            {
                string boundary = string.Format("--{0}", DateTime.Now.Ticks.ToString("x"));
                postContent.Headers.Add("ContentType", $"multipart/form-data, boundary={boundary}");
                string filekeyname = "file";
                postContent.Add(new ByteArrayContent(fileBinaryData), filekeyname, filename);
                foreach (var key in dic.Keys)
                {
                    postContent.Add(new StringContent(dic[key].ToString()), key);
                    Debug.Log(TAG + $" PostFormDataStreamAsync StringContent {key} : {dic[key].ToString()} ");
                }

                Debug.Log(TAG + " PostFormDataStreamAsync Url " + url);

                // 使用异步方法发送请求
                using (HttpResponseMessage response = await client.PostAsync(url, postContent))
                {
                    // 检查响应是否成功
                    response.EnsureSuccessStatusCode();

                    // 使用异步方法读取响应流
                    using (Stream stream = await response.Content.ReadAsStreamAsync())
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        ResponeseResult rslt = new ResponeseResult();
                        // 读取每一行并实时处理
                        while (!reader.EndOfStream)
                        {
                            string line = await reader.ReadLineAsync();
                            // 处理当前行的数据
                            ResponeseResult tmp = ProcessResponseLine(line, onResponesingGettingData);
                            if (tmp != null) { rslt.ThreadId = tmp.ThreadId; rslt.ChatAnswer += tmp.ChatAnswer; }
                        }

                        // 处理响应
                        //chatResponeseResult = HandleResult(str);
                        onSuccess?.Invoke(rslt);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError(TAG + "PostFormDataStreamAsync:" + ex.ToString());
            onFailed?.Invoke(ex.ToString());
        }
    }
    /// 
    /// 处理单条数据 (根据自己的数据形式处理)
    /// 
    /// 
    /// 
    private ResponeseResult ProcessResponseLine(string line, Action onChatResponesingGettingResult)
    {
        // 在这里处理每一行的数据
        Debug.Log(TAG + " ProcessResponseLine: Response Line: " + line);
        ResponeseResult rslt = null;

        if (line.Contains("event:close")) { }
        // 可以根据实际需求进行处理
        if (line.Contains("text"))
        {
            int startIndex = line.IndexOf("text") + 7; // 找到 "text":" 的位置
            int endIndex = line.IndexOf("\"", startIndex); // 找到下一个引号位置

            string text = line.Substring(startIndex, endIndex - startIndex);
            string threadId = "";
            if (line.Contains("thread_id"))
            {
                int startIndex_S = line.IndexOf("thread_id") + 12; // 找到 "thread_id":" 的位置
                int endIndex_S = line.IndexOf("\"", startIndex_S); // 找到下一个引号位置

                threadId = line.Substring(startIndex_S, endIndex_S - startIndex_S);
            }

            Debug.Log(TAG + $" ProcessResponseLine: text {text}, threadId {threadId} ");
            rslt = new ResponeseResult() { ThreadId = threadId, ChatAnswer = text };
            onChatResponesingGettingResult.Invoke(rslt);
            return rslt;
        }
        return rslt;
    }

    #endregion

    #region Other
    /// 
    /// 数据转为字典像是
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    Dictionary DataToDictionary(int id, string conversationId, string deviceId, string chatAsk, string type)
    {
        Dictionary keyValues = new Dictionary();
        keyValues.Add("id", id);//id 是服务器返回的凭证  这里的凭证只是针对这个项目后端人员定义的
        keyValues.Add("conversationId", conversationId);//conversationId  这里的凭证只是针对这个项目后端人员定义的
        keyValues.Add("deviceId", deviceId);//deviceId  这里的凭证只是针对这个项目后端人员定义的
        keyValues.Add("text", chatAsk);//text  这里的凭证只是针对这个项目后端人员定义的
        keyValues.Add("type", type);//type  这里的凭证只是针对这个项目后端人员定义的

        return keyValues;
    }
    #endregion
}

2、IHttpClientMultipartFormDataContentRequest

using System;

/// 
/// Http Client 发起 Content-Type 内容类型 multipart / form - data 的请求 接口类
/// 
public interface IHttpClientMultipartFormDataContentRequest
{
    /// 
    /// 发起对话请求
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    ResponeseResult SendChatRequest(int id, string conversationId, string deviceId, string chatAsk, string type, string filepath, string filename, Action onSuccess, Action onFailed);

    /// 
    /// 发起对话请求
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    ResponeseResult SendChatRequest(int id, string conversationId, string deviceId, string chatAsk, string type, byte[] fileBinaryData, string filename,
        Action onSuccess, Action onFailed);

    /// 
    /// 流式处理数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    void SendChatRequestStream(int id, string conversationId, string deviceId, string chatAsk, string type, string filepath, string filename,
        Action onResponesingGettingData, Action onSuccess, Action onFailed);

    /// 
    /// 流式处理数据
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    void SendChatRequestStream(int id, string conversationId, string deviceId, string chatAsk, string type, byte[] fileBinaryData, string filename,
        Action onResponesingGettingData, Action onSuccess, Action onFailed);
}

3、TestHttpClientMultipartFormDataContentRequest

using UnityEngine;

public class TestHttpClientMultipartFormDataContentRequest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        //TestNormal();
        TestStream();
    }


    void TestNormal() {
        int id = 1;
        string conversationId = "";
        string deviceId = "abcdef";
        string chatAsk = "你平时喜欢做什么";
        string type = "text";
        string filepath = "E:\\YourPath\\Imgs\\CameraImag.png";
        string filename = "CameraImag.png";
        HttpClientMultipartFormDataContentRequest.Instance.SendChatRequest(
            id, conversationId, deviceId, chatAsk, type, filepath, filename, (rlt) => {
                Debug.Log($" TestHttpClientMultipartFormDataContentRequest ThreadId = {rlt.ThreadId}, ChatAnswer = {rlt.ChatAnswer}");
            }, (error) => {
                Debug.Log($" TestHttpClientMultipartFormDataContentRequest Fail error = " + error);
            });
    }

    void TestStream() {
        int id = 1;
        string conversationId = "";
        string deviceId = "abcdef";
        string chatAsk = "你平时喜欢做什么";
        string type = "text";
        string filepath = "E:\\YourPath\\Imgs\\CameraImag.png";
        string filename = "CameraImag.png";
        HttpClientMultipartFormDataContentRequest.Instance.SendChatRequestStream(
            id, conversationId, deviceId, chatAsk, type, filepath, filename, (rlt) => {
                Debug.Log($" TestHttpClientMultipartFormDataContentRequest ThreadId = {rlt.ThreadId}, ChatAnswer = {rlt.ChatAnswer}");
            }, (rslt) => {
                Debug.Log($" TestHttpClientMultipartFormDataContentRequest Fail success : ThreadId {rslt.ThreadId}, ChatAnswer {rslt.ChatAnswer}" );
            }, (error) => {
                Debug.Log($" TestHttpClientMultipartFormDataContentRequest Fail error = " + error);
            });
        
    }
}

4、Singleton

using System;

/// 
/// 单例脚本
/// 
/// 
public abstract class Singleton where T : Singleton
{
    private static T m_Instance;

    private static object m_Locker = new object();

    public static T Instance
    {
        get
        {
            if (m_Instance == null)
            {
                lock (m_Locker)
                {
                    if (m_Instance == null)
                    {
                        m_Instance = Activator.CreateInstance();
                        m_Instance.OnSingletonInit();
                    }
                }
            }

            return m_Instance;
        }
    }

    protected virtual void OnSingletonInit()
    {
    }
}

你可能感兴趣的:(unity,HttpClient,form-data,异步,流式)