51.HTTP上传数据

51.网络通信-超文本传输HTTP-上传数据


51.1 知识点

上传文件到 HTTP 资源服务器需要遵守的规则

  1. ContentType = “multipart/form-data; boundary=边界字符串”;
  2. 上传的数据必须按照格式写入流中

//  --边界字符串
//  Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
//  Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)

//  空一行
//  (这里直接写入传入的内容 即文件数据体)

//  --边界字符串--
  1. 保证服务器允许上传
  2. 写入流前需要先设置 ContentLength 内容长度

HFS服务器可以设置所有人可上传,也可以设置用户上传权限,否则可能没有上传权限



上传文件

创建 HttpWebRequest 对象,不需写要上传的文件名

//1.创建HttpWebRequest对象 不用写要上传的文件名
HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://192.168.1.101:8000/HTTPRoot/") as HttpWebRequest;

相关设置(请求类型、内容类型、超时、身份验证等)

//2.相关设置(请求类型,内容类型,超时,身份验证等)
httpWebRequest.Method = WebRequestMethods.Http.Post;
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";//复合文件类型 自定义边界字符串是MrTao
httpWebRequest.Timeout = 500000;
httpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");//服务器身份验证信息
httpWebRequest.PreAuthenticate = true;//先验证身份 再上传数据 标识设置为True

按格式拼接字符串并转为字节数组用于上传

文件数据前的头部信息
//3-1.文件数据前的头部信息

//其中\r\n是多平台换行 自定义边界字符串是MrTao 字段名字传入file 声明是文件 字段名可以自定义的 

//  --边界字符串 
//  Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
//  Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
//  空一行

string head = 
    "--MrTao\r\n" +
    "Content-Disposition:form-data;name=\"file\";filename=\"http上传的文件.jpg\"\r\n" +
    "Content-Type:application/octet-stream\r\n\r\n";

//头部拼接字符串规则信息的字节数组
byte[] headBytes = Encoding.UTF8.GetBytes(head);
结束的边界信息
//3-2.结束的边界信息
//  --边界字符串--
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");

写入上传流

//4.写入上传流
using (FileStream localFileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
    //4-1.设置上传长度
    //总长度 是前部分头部字符串 + 文件本身有多大 + 后部分边界字符串
    httpWebRequest.ContentLength = headBytes.Length + localFileStream.Length + endBytes.Length;
    //用于上传的流
    Stream uploadRequestStream = httpWebRequest.GetRequestStream();

    //4-2.先写入前部分头部信息
    uploadRequestStream.Write(headBytes, 0, headBytes.Length);

    //4-3.再写入文件数据
    byte[] bytes = new byte[2048];//字节数组
    //不停的读取
    int contentLength = localFileStream.Read(bytes, 0, bytes.Length);
    while (contentLength != 0)
    {
        uploadRequestStream.Write(bytes, 0, contentLength);
        contentLength = localFileStream.Read(bytes, 0, bytes.Length);
    }

    //4-4.在写入结束的边界信息
    uploadRequestStream.Write(endBytes, 0, endBytes.Length);

    uploadRequestStream.Close();
    localFileStream.Close();
}

上传数据,获取响应

//5.上传数据,获取响应
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
    print("上传通信成功");
else
    print("上传失败" + httpWebResponse.StatusCode);

测试结果


总结

HTTP 上传文件相对比较麻烦,需要按照指定的规则进行内容拼接达到上传文件的目的。其中相对重要的知识点是上传文件时的规则:

  • 边界字符串
  • Content-Disposition: form-data; name=”file”; filename=”传到服务器上使用的文件名”
  • Content-Type: application/octet-stream(由于我们传2进制文件,所以这里使用2进制)
  • 空行
  • (这里直接写入传入的内容)
  • 边界字符串

关于更多规则,可前往官网查看详细说明。
有关 ContentType 的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type
有关媒体类型的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
有关 Content-Disposition 的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition


51.2 知识点代码

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

public class Lesson51_网络通信_超文本传输HTTP_上传数据 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 上传文件到HTTP资源服务器需要遵守的规则

        //上传文件时内容的必备规则

        //  1:ContentType = "multipart/form-data; boundary=边界字符串";

        //  2:上传的数据必须按照格式写入流中

        //  --边界字符串
        //  Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
        //  Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
        //  空一行
        //  (这里直接写入传入的内容 即文件数据体)
        //  --边界字符串--

        //  3:保证服务器允许上传

        //  4:写入流前需要先设置ContentLength内容长度

        #endregion

        #region 知识点二 上传文件

        //1.创建HttpWebRequest对象 不用写要上传的文件名
        HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://192.168.1.101:8000/HTTPRoot/") as HttpWebRequest;


        //2.相关设置(请求类型,内容类型,超时,身份验证等)
        httpWebRequest.Method = WebRequestMethods.Http.Post;
        httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";//复合文件类型 自定义边界字符串是MrTao
        httpWebRequest.Timeout = 500000;
        httpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");//服务器身份验证信息
        httpWebRequest.PreAuthenticate = true;//先验证身份 再上传数据 标识设置为True


        //3.按格式拼接字符串并且转为字节数组之后用于上传

        //3-1.文件数据前的头部信息

        //其中\r\n是多平台换行 自定义边界字符串是MrTao 字段名字传入file 声明是文件 字段名可以自定义的 

        //  --边界字符串 
        //  Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
        //  Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
        //  空一行

        string head = 
            "--MrTao\r\n" +
            "Content-Disposition:form-data;name=\"file\";filename=\"http上传的文件.png\"\r\n" +
            "Content-Type:application/octet-stream\r\n\r\n";

        //头部拼接字符串规则信息的字节数组
        byte[] headBytes = Encoding.UTF8.GetBytes(head);

        //3-2.结束的边界信息
        //  --边界字符串--
        byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");


        //4.写入上传流
        using (FileStream localFileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
        {
            //4-1.设置上传长度
            //总长度 是前部分头部字符串 + 文件本身有多大 + 后部分边界字符串
            httpWebRequest.ContentLength = headBytes.Length + localFileStream.Length + endBytes.Length;
            //用于上传的流
            Stream uploadRequestStream = httpWebRequest.GetRequestStream();

            //4-2.先写入前部分头部信息
            uploadRequestStream.Write(headBytes, 0, headBytes.Length);

            //4-3.再写入文件数据
            byte[] bytes = new byte[2048];//字节数组
            //不停的读取
            int contentLength = localFileStream.Read(bytes, 0, bytes.Length);
            while (contentLength != 0)
            {
                uploadRequestStream.Write(bytes, 0, contentLength);
                contentLength = localFileStream.Read(bytes, 0, bytes.Length);
            }

            //4-4.在写入结束的边界信息
            uploadRequestStream.Write(endBytes, 0, endBytes.Length);

            uploadRequestStream.Close();
            localFileStream.Close();
        }


        //5.上传数据,获取响应
        HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
        if (httpWebResponse.StatusCode == HttpStatusCode.OK)
            print("上传通信成功");
        else
            print("上传失败" + httpWebResponse.StatusCode);


        #endregion

        #region 总结

        //HTTP上传文件相对比较麻烦
        //需要按照指定的规则进行内容拼接达到上传文件的目的
        //其中相对重要的知识点是 
        //上传文件时的规则
        //  --边界字符串
        //  Content-Disposition: form-data; name="file";filename="传到服务器上使用的文件名"
        //  Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
        //  空行
        //  (这里直接写入传入的内容)
        //  --边界字符串--

        //关于其更多的规则,可以前往官网查看详细说明
        //关于ContentType更多内容可以前往
        //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type
        //关于媒体类型可以前往
        //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
        //关于Content-Disposition更多内容可以前往
        //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition

        #endregion
    }
}

51.3 练习题

在之前的Http管理器当中,封装一个上传的相关方法,同样还是以多线程进行上传,避免影响主线程逻辑

在Http管理器声明上传文件方法

private string USER_NAME = "MrTao";
private string PASS_WORD = "MrTao";

/// <summary>
/// 上传文件
/// </summary>
/// <param name="fileName">传到远端服务器上的文件名</param>
/// <param name="loacalFilePath">本地的文件路径</param>
/// <param name="action">上传结束后的回调函数</param>
public async void UpLoadFile(string fileName, string loacalFilePath, UnityAction<HttpStatusCode> action)
{
    //上面状态码变量 默认请求失败
    HttpStatusCode httpStatusCodeResult = HttpStatusCode.BadRequest;

    await Task.Run(() =>
    {
        try
        {
            // 创建HTTP请求对象,用于与服务器通信
            HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH) as HttpWebRequest;

            // 设置HTTP请求方法为POST,用于上传数据
            httpWebRequest.Method = WebRequestMethods.Http.Post;

            // 设置HTTP请求的内容类型为multipart/form-data,其中boundary参数指定分隔符
            httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";

            // 设置HTTP请求的超时时间为500,000毫秒(500秒)
            httpWebRequest.Timeout = 500000;

            // 设置HTTP请求的凭据,包括用户名和密码
            httpWebRequest.Credentials = new NetworkCredential(USER_NAME, PASS_WORD);

            // 启用预身份验证,以在请求之前进行身份验证
            httpWebRequest.PreAuthenticate = true;


            //拼接字符串 头部
            string head = 
            "--MrTao\r\n" +
            "Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\n" +
            "Content-Type:application/octet-stream\r\n\r\n";

            //替换文件名
            head = string.Format(head, fileName);

            //得到头部字节数组
            byte[] headBytes = Encoding.UTF8.GetBytes(head);

            //尾部的边界字符串
            byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");

            //读入指定路径文件资源
            using (FileStream localStream = File.OpenRead(loacalFilePath))
            {
                //设置长度
                httpWebRequest.ContentLength = headBytes.Length + localStream.Length + endBytes.Length;

                //写入流
                Stream uploadRequestStream = httpWebRequest.GetRequestStream();

                //写入头部
                uploadRequestStream.Write(headBytes, 0, headBytes.Length);

                //创建字节数组 不停的写入上传文件
                byte[] bytes = new byte[4096];
                int contentLenght = localStream.Read(bytes, 0, bytes.Length);
                while (contentLenght != 0)
                {
                    uploadRequestStream.Write(bytes, 0, contentLenght);
                    contentLenght = localStream.Read(bytes, 0, bytes.Length);
                }

                //写入尾部
                uploadRequestStream.Write(endBytes, 0, endBytes.Length);

                //关闭流
                uploadRequestStream.Close();
                localStream.Close();
            }

            //得到响应结果
            HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;

            //让外部去处理结果 
            httpStatusCodeResult = httpWebResponse.StatusCode;

            //关闭流
            httpWebResponse.Close();
        }
        catch (WebException w)
        {
            Debug.Log("上传出错" + w.Status + w.Message);
        }
    });

    //执行回调
    action?.Invoke(httpStatusCodeResult);
}

进行上传测试

HttpManager.Instance.UpLoadFile("HttpManager上传.png", Application.streamingAssetsPath + "/test.png", (code) =>
{
    if (code == HttpStatusCode.OK)
        print("上传指令成功");
    else
        print("上传指令失败" + code);
});

测试结果


51.4 练习题代码

HttpManager

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

public class HttpManager : BaseSingletonInCSharp<HttpManager>
{
    private string HTTP_PATH = "http://192.168.1.101:8000/HTTPRoot/";

    private string USER_NAME = "MrTao";
    private string PASS_WORD = "MrTao";

    /// <summary>
    /// 下载指定文件到本地指定路径中
    /// </summary>
    /// <param name="fileName">远程文件名</param>
    /// <param name="loacFilePath">本地路径</param>
    /// <param name="action">下载结束后的回调函数</param>
    public async void DownLoadFile(string fileName, string loacFilePath, UnityAction<HttpStatusCode> action)
    {
        // 创建一个临时变量,用于存储HTTP响应的状态码,默认为OK。
        HttpStatusCode httpStatusCodeResult = HttpStatusCode.OK;

        // 使用异步任务来执行下载操作,以避免阻塞主线程。
        await Task.Run(() =>
        {
            try
            {
                // 判断文件是否存在 Head 

                // 1. 创建HTTP连接对象,用于发送HTTP请求到服务器。
                HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH + fileName) as HttpWebRequest;

                // 2. 设置请求类型为HEAD,这样我们可以检查文件是否存在,以及设置其他相关参数。
                httpWebRequest.Method = WebRequestMethods.Http.Head;
                httpWebRequest.Timeout = 2000;

                // 3. 发送请求并获取HTTP响应。
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;

                // 如果文件存在才下载
                if (httpWebResponse.StatusCode == HttpStatusCode.OK)
                {
                    Debug.Log("文件存在且可用");
                    Debug.Log(httpWebResponse.ContentLength); // 打印文件大小
                    Debug.Log(httpWebResponse.ContentType); // 打印文件的MIME类型
                    
                    // 关闭判断文件是否存在的请求。
                    httpWebResponse.Close();

                    // 下载文件 Get 

                    // 1. 创建新的HTTP连接对象,用于下载文件。
                    httpWebRequest = HttpWebRequest.Create(HTTP_PATH + fileName) as HttpWebRequest;

                    // 2. 设置请求类型为GET,以获取文件内容,并设置其他相关参数。
                    httpWebRequest.Method = WebRequestMethods.Http.Get;
                    httpWebRequest.Timeout = 2000;

                    // 3. 发送请求并获取HTTP响应。
                    httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;

                    // 4. 存储数据到本地
                    if (httpWebResponse.StatusCode == HttpStatusCode.OK)
                    {
                        // 创建本地文件并准备将下载的数据写入其中。
                        using (FileStream fileStream = File.Create(loacFilePath))
                        {
                            // 获取HTTP响应的数据流,从中读取数据。
                            Stream downloadResponseStream = httpWebResponse.GetResponseStream();

                            // 创建一个字节数组,用于存储从数据流中读取的数据。
                            byte[] bytes = new byte[4096];
                            // 从数据流中读取一部分数据,并返回读取的字节数。
                            int contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);

                            // 使用循环将数据写入本地文件,直到数据流读取完毕。
                            while (contentLength != 0)
                            {
                                fileStream.Write(bytes, 0, contentLength);
                                contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
                            }

                            // 关闭本地文件和数据流。
                            fileStream.Close();
                            downloadResponseStream.Close();
                        }
                        // 如果下载成功,将HTTP响应状态码设置为OK。
                        httpStatusCodeResult = HttpStatusCode.OK;
                    }
                    else
                    {
                        // 如果下载失败,将HTTP响应状态码存储在 httpStatusCodeResult 变量中。
                        httpStatusCodeResult = httpWebResponse.StatusCode;
                    }

                }
                else
                {
                    // 如果文件不存在,将HTTP响应状态码存储在 httpStatusCodeResult 变量中。
                    httpStatusCodeResult = httpWebResponse.StatusCode;
                }

                // 关闭HTTP响应。
                httpWebResponse.Close();
            }
            catch (WebException w)
            {
                // 如果发生Web异常,将状态码设置为服务器内部错误,并记录错误消息。
                httpStatusCodeResult = HttpStatusCode.InternalServerError;
                Debug.Log("下载出错" + w.Message + w.Status);
            }
        });

        // 调用传递进来的 UnityAction 委托,将HTTP响应状态码传递给调用者。
        action?.Invoke(httpStatusCodeResult);
    }

    /// <summary>
    /// 上传文件
    /// </summary>
    /// <param name="fileName">传到远端服务器上的文件名</param>
    /// <param name="loacalFilePath">本地的文件路径</param>
    /// <param name="action">上传结束后的回调函数</param>
    public async void UpLoadFile(string fileName, string loacalFilePath, UnityAction<HttpStatusCode> action)
    {
        //上面状态码变量 默认请求失败
        HttpStatusCode httpStatusCodeResult = HttpStatusCode.BadRequest;

        await Task.Run(() =>
        {
            try
            {
                // 创建HTTP请求对象,用于与服务器通信
                HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH) as HttpWebRequest;

                // 设置HTTP请求方法为POST,用于上传数据
                httpWebRequest.Method = WebRequestMethods.Http.Post;

                // 设置HTTP请求的内容类型为multipart/form-data,其中boundary参数指定分隔符
                httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";

                // 设置HTTP请求的超时时间为500,000毫秒(500秒)
                httpWebRequest.Timeout = 500000;

                // 设置HTTP请求的凭据,包括用户名和密码
                httpWebRequest.Credentials = new NetworkCredential(USER_NAME, PASS_WORD);

                // 启用预身份验证,以在请求之前进行身份验证
                httpWebRequest.PreAuthenticate = true;


                //拼接字符串 头部
                string head = 
                "--MrTao\r\n" +
                "Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\n" +
                "Content-Type:application/octet-stream\r\n\r\n";

                //替换文件名
                head = string.Format(head, fileName);

                //得到头部字节数组
                byte[] headBytes = Encoding.UTF8.GetBytes(head);

                //尾部的边界字符串
                byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");

                //读入指定路径文件资源
                using (FileStream localStream = File.OpenRead(loacalFilePath))
                {
                    //设置长度
                    httpWebRequest.ContentLength = headBytes.Length + localStream.Length + endBytes.Length;

                    //写入流
                    Stream uploadRequestStream = httpWebRequest.GetRequestStream();

                    //写入头部
                    uploadRequestStream.Write(headBytes, 0, headBytes.Length);

                    //创建字节数组 不停的写入上传文件
                    byte[] bytes = new byte[4096];
                    int contentLenght = localStream.Read(bytes, 0, bytes.Length);
                    while (contentLenght != 0)
                    {
                        uploadRequestStream.Write(bytes, 0, contentLenght);
                        contentLenght = localStream.Read(bytes, 0, bytes.Length);
                    }

                    //写入尾部
                    uploadRequestStream.Write(endBytes, 0, endBytes.Length);

                    //关闭流
                    uploadRequestStream.Close();
                    localStream.Close();
                }

                //得到响应结果
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;

                //让外部去处理结果 
                httpStatusCodeResult = httpWebResponse.StatusCode;

                //关闭流
                httpWebResponse.Close();
            }
            catch (WebException w)
            {
                Debug.Log("上传出错" + w.Status + w.Message);
            }
        });

        //执行回调
        action?.Invoke(httpStatusCodeResult);
    }

}

Lesson51_练习题

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

public class Lesson51_练习题 : MonoBehaviour
{
    void Start()
    {
        HttpManager.Instance.UpLoadFile("HttpManager上传.png", Application.streamingAssetsPath + "/test.png", (code) =>
        {
            if (code == HttpStatusCode.OK)
                print("上传指令成功");
            else
                print("上传指令失败" + code);
        });
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏