Unity发送HTTP请求和文件下载
本类库封装基于ulua框架LuaFramework
1. 使用的类库
HttpWebRequest
HttpWebResponse
LuaFramework
2. 实现了哪些功能
- 发送GET、POST请求
- HTTP请求异步化
- 支持Lua回调
- 支持Host,采用Proxy的实现方式
- 支持将HTTP请求内容保存为文件
- HTTP下载文件,支持断电续传
3. HTTP实现思路
-
HTTPRequest
发起HTTP请求,异步回调返回HTTPResponse
HTTPRequest
源码
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections.Generic;
using LuaFramework;
using UnityEngine;
/// <summary>
/// Http请求
/// </summary>
public class HTTPRequest
{
private string url;
private int timeout;
private Action<HTTPResponse> callback;
private HttpWebRequest request;
private string method;
private string contentType;
private KeyValuePair<string, int> proxy;
protected int range = -1;
// post内容
private StringBuilder postBuilder;
/// <summary>
/// 错误代码
/// </summary>
public const int ERR_EXCEPTION = -1;
/// <summary>
/// 构造函数, 构造GET请求
/// </summary>
/// <param name="url">url地址</param>
/// <param name="timeout">超时时间</param>
/// <param name="callback">回调函数</param>
public HTTPRequest (string url, string method, int timeout, Action<HTTPResponse> callback)
{
this.url = url;
this.timeout = timeout;
this.callback = callback;
this.method = method.ToUpper();
}
/// <summary>
/// 设置Post内容
/// </summary>
/// <param name="data">内容</param>
public void SetPostData(string data) {
if (postBuilder == null) {
postBuilder = new StringBuilder (data.Length);
}
if (postBuilder.Length > 0) {
postBuilder.Append ("&");
}
postBuilder.Append (data);
}
/// <summary>
/// 添加Post内容
/// </summary>
/// <param name="key">key值</param>
/// <param name="value">value值</param>
public void AddPostData(string key, string value) {
if (postBuilder == null) {
postBuilder = new StringBuilder ();
}
if (postBuilder.Length > 0) {
postBuilder.Append ("&");
}
postBuilder.Append (key).Append ("=").Append (UrlEncode (value));
}
/// <summary>
/// 设置代理
/// </summary>
/// <param name="ip">ip地址</param>
/// <param name="port">端口号</param>
public void SetProxy(string ip, int port) {
this.proxy = new KeyValuePair<string, int> (ip, port);
}
/// <summary>
/// 设置ContentType
/// </summary>
/// <value>ContentType value</value>
public string ContentType {
set {
this.contentType = value;
}
}
/// <summary>
/// 发动请求
/// </summary>
public void Start() {
Debug.Log ("Handle Http Request Start");
this.request = WebRequest.Create (url) as HttpWebRequest;
this.request.Timeout = timeout;
this.request.Method = method;
if (this.proxy.Key != null) {
this.request.Proxy = new WebProxy(this.proxy.Key, this.proxy.Value);
}
if (this.contentType != null) {
this.request.ContentType = this.contentType;
}
if (this.range != -1) {
this.request.AddRange (this.range);
}
// POST写POST内容
if (method.Equals ("POST")) {
WritePostData ();
}
try {
AsyncCallback callback = new AsyncCallback (OnResponse);
this.request.BeginGetResponse (callback, null);
} catch (Exception e) {
CallBack (ERR_EXCEPTION, e.Message);
if (request != null) {
request.Abort ();
}
}
}
/// <summary>
/// 处理读取Response
/// </summary>
/// <param name="result">异步回到result</param>
protected void OnResponse(IAsyncResult result) {
//Debug.Log ("Handle Http Response");
HttpWebResponse response = null;
try {
// 获取Response
response = request.EndGetResponse (result) as HttpWebResponse;
if (response.StatusCode == HttpStatusCode.OK) {
if ("HEAD".Equals(method)) {
// HEAD请求
long contentLength = response.ContentLength;
CallBack((int)response.StatusCode, contentLength + "");
return;
}
// 读取请求内容
Stream responseStream = response.GetResponseStream();
byte[] buff = new byte[2048];
MemoryStream ms = new MemoryStream();
int len = -1;
while ((len = responseStream.Read(buff, 0, buff.Length)) > 0) {
ms.Write(buff, 0, len);
}
// 清理操作
responseStream.Close();
response.Close();
request.Abort();
// 调用回调
CallBack ((int)response.StatusCode, ms.ToArray());
} else {
CallBack ((int)response.StatusCode, "");
}
} catch (Exception e) {
CallBack (ERR_EXCEPTION, e.Message);
if (request != null) {
request.Abort ();
}
if (response != null) {
response.Close ();
}
}
}
/// <summary>
/// 回调
/// </summary>
/// <param name="code">编码</param>
/// <param name="content">内容</param>
private void CallBack(int code, string content) {
Debug.LogFormat ("Handle Http Callback, code:{0}", code);
if (callback != null) {
HTTPResponse response = new HTTPResponse ();
response.StatusCode = code;
response.Error = content;
callback (response);
}
}
/// <summary>
/// 回调
/// </summary>
/// <param name="code">编码</param>
/// <param name="content">内容</param>
private void CallBack(int code, byte[] content) {
Debug.LogFormat ("Handle Http Callback, code:{0}", code);
if (callback != null) {
HTTPResponse response = new HTTPResponse (content);
response.StatusCode = code;
callback (response);
}
}
/// <summary>
/// 写Post内容
/// </summary>
private void WritePostData() {
if (null == postBuilder || postBuilder.Length <= 0) {
return;
}
byte[] bytes = Encoding.UTF8.GetBytes (postBuilder.ToString ());
Stream stream = request.GetRequestStream ();
stream.Write (bytes, 0, bytes.Length);
stream.Close ();
}
/// <summary>
/// URLEncode
/// </summary>
/// <returns>encode value</returns>
/// <param name="value">要encode的值</param>
private string UrlEncode(string value) {
StringBuilder sb = new StringBuilder();
byte[] byStr = System.Text.Encoding.UTF8.GetBytes(value);
for (int i = 0; i < byStr.Length; i++)
{
sb.Append(@"%" + Convert.ToString(byStr[i], 16));
}
return (sb.ToString());
}
}
HTTPResponse
源码
using System;
using System.IO;
using System.Text;
using UnityEngine;
using LuaFramework;
/// <summary>
/// HTTP返回内容
/// </summary>
public class HTTPResponse
{
// 状态码
private int statusCode;
// 响应字节
private byte[] responseBytes;
// 错误内容
private string error;
/// <summary>
/// 默认构造函数
/// </summary>
public HTTPResponse ()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="content">响应内容</param>
public HTTPResponse (byte[] content)
{
this.responseBytes = content;
}
/// <summary>
/// 获取响应内容
/// </summary>
/// <returns>响应文本内容</returns>
public string GetResponseText() {
if (null == this.responseBytes) {
return null;
}
return Encoding.UTF8.GetString (this.responseBytes);
}
/// <summary>
/// 将响应内容存储到文件
/// </summary>
/// <param name="fileName">文件名称</param>
public void SaveResponseToFile(string fileName) {
if (null == this.responseBytes) {
return;
}
// FIXME 路径跨平台问题
string path = Path.Combine (Application.dataPath +"/StreamingAssets", fileName);
FileStream fs = new FileStream (path, FileMode.Create);
BinaryWriter writer = new BinaryWriter (fs);
writer.Write (this.responseBytes);
writer.Flush ();
writer.Close ();
fs.Close ();
}
/// <summary>
/// 获取状态码
/// </summary>
/// <value>状态码</value>
public int StatusCode {
set {
this.statusCode = value;
}
get {
return this.statusCode;
}
}
/// <summary>
/// 获取错误消息
/// </summary>
/// <value>错误消息</value>
public string Error {
set {
this.error = value;
}
get {
return this.error;
}
}
}
NetworkManager
中封装了一个发送HTTP请求的方法
/// <summary>
/// 创建一个HTTP请求
/// </summary>
/// <param name="url">URL.</param>
/// <param name="callback">Callback</param>
public HTTPRequest CreateHTTPRequest(string url, string method, LuaFunction callback) {
HTTPRequest client = new HTTPRequest(url, method, 5000, (HTTPResponse response)=>{
if (null != callback) {
callbackEvents.Enqueue(() => {
callback.Call(response);
});
}
});
return client;
}
Lua
中使用范例
local request = networkMgr:CreateHTTPRequest("http://www.baidu.com", "GET", function(response)
response:GetResponseText()
end
)
# 支持HOST模式
#request:SetProxy("42.62.46.224", 80)
request:Start()
4. 文件下载实现思路
这里主要借鉴了Unity技术博客 - 客户端断点续传这篇文章的思想
支持了Lua回调
HTTPDownLoad
源码
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using LuaFramework;
using UnityEngine;
/// <summary>
/// 文件下载
/// </summary>
public class HTTPDownLoad
{
// 下载进度
public float Progress {
get;
private set;
}
// 状态 0 正在下载 1 下载完成 -1 下载出错
public int Status {
get;
private set;
}
// 错误信息
public string Error {
get;
set;
}
// 总长度
public long TotalLength {
get;
private set;
}
// 保存路径
private string savePath;
// url地址
private string url;
// 超时时间
private int timeout;
// 子线程
private Thread thread;
// 子线程是否停止标志
private bool isStop;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="url">url地址</param>
/// <param name="timeout">超时时间</param>
/// <param name="callback">回调函数</param>
public HTTPDownLoad (string url, string savePath, int timeout)
{
this.savePath = savePath;
this.url = url;
this.timeout = timeout;
}
/// <summary>
/// 开启下载
/// </summary>
public void DownLoad() {
// 开启线程下载
thread = new Thread (StartDownLoad);
thread.IsBackground = true;
thread.Start ();
}
/// <summary>
/// 开始下载
/// </summary>
private void StartDownLoad() {
try {
// 构建文件流
FileStream fs = new FileStream (this.savePath, FileMode.OpenOrCreate, FileAccess.Write);
// 文件当前长度
long fileLength = fs.Length;
// 文件总长度
TotalLength = GetDownLoadFileSize ();
Debug.LogFormat("fileLen:{0}", TotalLength);
if (fileLength < TotalLength) {
// 没有下载完成
fs.Seek (fileLength, SeekOrigin.Begin);
// 发送请求开始下载
HttpWebRequest request = WebRequest.Create (this.url) as HttpWebRequest;
request.AddRange ((int)fileLength);
request.Timeout = this.timeout;
// 读取文件内容
Stream stream = request.GetResponse().GetResponseStream();
if (stream.CanTimeout) {
stream.ReadTimeout = this.timeout;
}
byte[] buff = new byte[4096];
int len = -1;
while ((len = stream.Read (buff, 0, buff.Length)) > 0) {
if (isStop) {
break;
}
fs.Write (buff, 0, len);
fileLength += len;
Progress = fileLength * 1.0f / TotalLength;
}
stream.Close ();
stream.Dispose ();
} else {
Progress = 1;
}
fs.Close ();
fs.Dispose ();
// 标记下载完成
if (Progress == 1) {
Status = 1;
}
} catch (Exception e) {
Error = e.Message;
Status = -1;
}
}
/// <summary>
/// 获取下载的文件大小
/// </summary>
/// <returns>文件大小</returns>
public long GetDownLoadFileSize() {
HttpWebRequest request = WebRequest.Create (this.url) as HttpWebRequest;
request.Method = "HEAD";
request.Timeout = this.timeout;
HttpWebResponse response = request.GetResponse () as HttpWebResponse;
return response.ContentLength;
}
/// <summary>
/// 关闭下载
/// </summary>
public void Close() {
this.isStop = true;
}
}
NetworkManager
中封装了一个文件下载的方法
/// <summary>
/// 下载文件
/// </summary>
/// <param name="url">url</param>
/// <param name="savePath">保存地址</param>
/// <param name="callback">callback</param>
public HTTPDownLoad DownLoadFile(string url, string savePath, LuaFunction callback) {
// 一次只允许下载一个文件
if (downFileEvent != null) {
callback.Call (-1, 0, "其他文件正在下载");
return null;
}
// 下载文件
HTTPDownLoad download = new HTTPDownLoad (url, savePath, 5000);
download.DownLoad ();
// 回调函数
downFileEvent = delegate(){
callback.Call (download.Status, download.Progress, download.Error);
if (download.Status != 0) {
downFileEvent = null;
}
};
return download;
}
Lua
中使用范例
local path = "/Workspace/Unity/LuaFramework_UGUI/Assets/StreamingAssets/***.zip"
local downloadRequest = networkMgr:DownLoadFile("文件下载url", path, function(status, progress, error)
print("status:" .. status .. ", progress:" .. progress)
if error ~= nil then
print("error:" .. error)
end
end)
4. 交流与讨论
欢迎大家交流和讨论O(∩_∩)O~
网友评论
System.Net.WebException: Error getting response stream (Write: The authentication or decryption has failed.): SendFailure ---> System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from server. Error code: 0xffffffff800b010f
at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates (Mono.Security.X509.X509CertificateCollection certificates) [0x00000] in <filename unknown>:0
at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1 () [0x00000] in <filename unknown>:0
at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000] in <filename unknown>:0
at (wrapper remoting-invoke-with-check) Mono.Security.Protocol.Tls.Handshake.HandshakeMessage:Process ()
at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage (Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00000] in <filename unknown>:0
at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
--- End of inner exception stack trace ---
at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0
大佬 这个经过项目检验了吗