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~