diff --git a/Qiniu/Fusion/FusionManager.cs b/Qiniu/Fusion/FusionManager.cs
index a0ddaf21..7f86148c 100644
--- a/Qiniu/Fusion/FusionManager.cs
+++ b/Qiniu/Fusion/FusionManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Security.Cryptography;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -7,6 +8,7 @@
using System.Net;
using Qiniu.Http;
using Newtonsoft.Json;
+using System.Text.RegularExpressions;
namespace Qiniu.Fusion
{
@@ -206,5 +208,23 @@ public LogListResult LogList(LogListRequest request)
return result;
}
+
+ ///
+ /// 时间戳防盗链
+ ///
+ ///
+ ///
+ public string HotLink(HotLinkRequest request)
+ {
+ string RAW = request.RawUrl;
+
+ string key = request.Key;
+ string path = Uri.EscapeUriString(request.Path);
+ string file = request.File;
+ string ts = (int.Parse(request.Timestamp)).ToString("x");
+ string SIGN = StringUtils.md5Hash(key + path + file + ts);
+
+ return string.Format("{0}&sign={1}&t={2}", RAW, SIGN, ts);
+ }
}
}
diff --git a/Qiniu/Fusion/Model/HotLinkRequest.cs b/Qiniu/Fusion/Model/HotLinkRequest.cs
new file mode 100644
index 00000000..07abb3ae
--- /dev/null
+++ b/Qiniu/Fusion/Model/HotLinkRequest.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Qiniu.Util;
+
+namespace Qiniu.Fusion.Model
+{
+ public class HotLinkRequest
+ {
+ public string RawUrl
+ {
+ get
+ {
+ return Host + Path + File + Query;
+ }
+ }
+
+ public string Host { get; set; }
+
+ public string Path { get; set; }
+
+ public string File { get; set; }
+
+ public string Query { get; set; }
+
+ public string Key { get; set; }
+
+ public string Timestamp { get; set; }
+
+ public HotLinkRequest()
+ {
+ Host = "";
+ Path = "";
+ File = "";
+ Query = "";
+ Key = "";
+ Timestamp = "";
+ }
+
+ public HotLinkRequest(string url, string key, int expire)
+ {
+ string host, path, file, query;
+ UrlHelper.UrlSplit(url, out host, out path, out file, out query);
+
+ Host = host;
+ Path = path;
+ File = file;
+ Query = query;
+ Key = key;
+
+ SetLinkExpire(expire);
+ }
+
+ public void SetLinkExpire(int seconds)
+ {
+ DateTime dt0 = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
+ DateTime dt1 = DateTime.Now.AddSeconds(seconds);
+ TimeSpan tsx = dt1.Subtract(dt0);
+ string sts = tsx.Ticks.ToString();
+ Timestamp = sts.Substring(0, sts.Length - 7);
+ }
+
+ public void SetLinkExpire(DateTime stopAt)
+ {
+ DateTime dt0 = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
+ TimeSpan tsx = stopAt.Subtract(dt0);
+ string sts = tsx.Ticks.ToString();
+ Timestamp = sts.Substring(0, sts.Length - 7);
+ }
+ }
+}
diff --git a/Qiniu/Fusion/Model/UrlHelper.cs b/Qiniu/Fusion/Model/UrlHelper.cs
index 67cf511d..dde2ab54 100644
--- a/Qiniu/Fusion/Model/UrlHelper.cs
+++ b/Qiniu/Fusion/Model/UrlHelper.cs
@@ -31,5 +31,26 @@ public static string GetNormalUrl(string _url)
return m.Value;
}
+ public static void UrlSplit(string url, out string host, out string path, out string file, out string query)
+ {
+ int start = 0;
+
+ Regex regHost = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+");
+ host = regHost.Match(url, start).Value;
+ start += host.Length;
+
+ Regex regPath = new Regex(@"(/(\w|\-)*)+/");
+ path = regPath.Match(url, start).Value;
+ if(string.IsNullOrEmpty(path))
+ {
+ path = "/";
+ }
+ start += path.Length;
+
+ int index = url.IndexOf('?', start);
+ file = url.Substring(start, index - start);
+
+ query = url.Substring(index);
+ }
}
}
diff --git a/Qiniu/Http/CompletionHandler.cs b/Qiniu/Http/CompletionHandler.cs
index 7c13de8f..17eb318c 100644
--- a/Qiniu/Http/CompletionHandler.cs
+++ b/Qiniu/Http/CompletionHandler.cs
@@ -7,4 +7,12 @@ namespace Qiniu.Http
/// 请求回复信息
/// 请求回复内容
public delegate void CompletionHandler(ResponseInfo respInfo, string response);
+
+ ///
+ /// HTTP请求结果(非json/二进制数据)
+ ///
+ ///
+ ///
+ public delegate void RecvDataHandler(ResponseInfo respInfo, byte[] respData);
+
}
\ No newline at end of file
diff --git a/Qiniu/Http/HttpManager.cs b/Qiniu/Http/HttpManager.cs
index b9cc337e..367a244d 100644
--- a/Qiniu/Http/HttpManager.cs
+++ b/Qiniu/Http/HttpManager.cs
@@ -84,7 +84,7 @@ public void get(string pUrl, Dictionary pHeaders,
vWebResp = (HttpWebResponse)vWebReq.GetResponse();
handleWebResponse(vWebResp, pCompletionHandler);
}
- catch(WebException wexp)
+ catch (WebException wexp)
{
// FIX-HTTP 4xx/5xx Error 2016-11-22, 17:00 @fengyh
HttpWebResponse xWebResp = wexp.Response as HttpWebResponse;
@@ -94,6 +94,71 @@ public void get(string pUrl, Dictionary pHeaders,
{
handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
}
+ finally
+ {
+ if(vWebResp!=null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if(vWebReq!=null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
+ }
+
+ public void getRaw(string pUrl,RecvDataHandler pRecvDataHandler)
+ {
+ HttpWebRequest vWebReq = null;
+ HttpWebResponse vWebResp = null;
+ try
+ {
+ vWebReq = (HttpWebRequest)WebRequest.Create(pUrl);
+ }
+ catch (Exception ex)
+ {
+ if (pRecvDataHandler != null)
+ {
+ pRecvDataHandler(ResponseInfo.invalidRequest(ex.Message), null);
+ }
+ return;
+ }
+
+ try
+ {
+ vWebReq.AllowAutoRedirect = false;
+ vWebReq.Method = "GET";
+ vWebReq.UserAgent = this.getUserAgent();
+
+ //fire request
+ vWebResp = (HttpWebResponse)vWebReq.GetResponse();
+ handleWebResponse(vWebResp, pRecvDataHandler);
+ }
+ catch (Exception exp)
+ {
+
+ if (pRecvDataHandler != null)
+ {
+ pRecvDataHandler(ResponseInfo.networkError(exp.Message), null);
+ }
+ }
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
}
///
@@ -174,6 +239,87 @@ public void postForm(string pUrl, Dictionary pHeaders,
{
handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
}
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
+ }
+
+ ///
+ /// post data from raw
+ ///
+ ///
+ ///
+ ///
+ public void postFormRaw(string pUrl, Dictionary pHeaders, RecvDataHandler pRecvDataHandler)
+ {
+ HttpWebRequest vWebReq = null;
+ HttpWebResponse vWebResp = null;
+ try
+ {
+ vWebReq = (HttpWebRequest)WebRequest.Create(pUrl);
+ }
+ catch (Exception ex)
+ {
+ if (pRecvDataHandler != null)
+ {
+ pRecvDataHandler(ResponseInfo.invalidRequest(ex.Message), null);
+ }
+ return;
+ }
+
+ try
+ {
+ vWebReq.UserAgent = this.getUserAgent();
+ vWebReq.AllowAutoRedirect = false;
+ vWebReq.Method = "POST";
+ vWebReq.ContentType = FORM_MIME_URLENCODED;
+ if (pHeaders != null)
+ {
+ foreach (KeyValuePair kvp in pHeaders)
+ {
+ if (!kvp.Key.Equals("Content-Type"))
+ {
+ vWebReq.Headers.Add(kvp.Key, kvp.Value);
+ }
+ }
+ }
+
+ //fire request
+ vWebResp = (HttpWebResponse)vWebReq.GetResponse();
+ handleWebResponse(vWebResp, pRecvDataHandler);
+ }
+ catch (Exception exp)
+ {
+ if (pRecvDataHandler != null)
+ {
+ pRecvDataHandler(ResponseInfo.networkError(exp.Message), null);
+ }
+ }
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
}
@@ -249,6 +395,20 @@ public void postData(string pUrl, Dictionary pHeaders,
{
handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
}
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
}
///
@@ -325,6 +485,20 @@ public void postData(string pUrl, Dictionary pHeaders,
{
handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
}
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
}
///
@@ -479,25 +653,40 @@ public void postMultipartDataForm(string pUrl, Dictionary pHeade
{
handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
}
+ finally
+ {
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
+ }
}
///
/// post multi-part data form to remote server
- /// used to upload data
+ /// used to upload file
///
///
///
+ ///
///
///
///
public void postMultipartDataRaw(string pUrl, Dictionary pHeaders,
- HttpFormFile pFormFile, ProgressHandler pProgressHandler, CompletionHandler pCompletionHandler)
+ HttpFormFile pFormFile, ProgressHandler pProgressHandler, RecvDataHandler pRecvDataHandler)
{
if (pFormFile == null)
{
- if (pCompletionHandler != null)
+ if (pRecvDataHandler != null)
{
- pCompletionHandler(ResponseInfo.fileError(new Exception("no file specified")), "");
+ pRecvDataHandler(ResponseInfo.fileError(new Exception("no file specified")), null);
}
return;
}
@@ -511,9 +700,9 @@ public void postMultipartDataRaw(string pUrl, Dictionary pHeader
}
catch (Exception ex)
{
- if (pCompletionHandler != null)
+ if (pRecvDataHandler != null)
{
- pCompletionHandler(ResponseInfo.invalidRequest(ex.Message), "");
+ pRecvDataHandler(ResponseInfo.invalidRequest(ex.Message), null);
}
return;
}
@@ -580,9 +769,9 @@ public void postMultipartDataRaw(string pUrl, Dictionary pHeader
}
catch (Exception fex)
{
- if (pCompletionHandler != null)
+ if (pRecvDataHandler != null)
{
- pCompletionHandler(ResponseInfo.fileError(fex), "");
+ pRecvDataHandler(ResponseInfo.fileError(fex), null);
}
}
break;
@@ -603,17 +792,28 @@ public void postMultipartDataRaw(string pUrl, Dictionary pHeader
//fire request
vWebResp = (HttpWebResponse)vWebReq.GetResponse();
- handleWebResponse(vWebResp, pCompletionHandler);
+ handleWebResponse(vWebResp, pRecvDataHandler);
}
- catch (WebException wexp)
+ catch (Exception exp)
{
- // FIX-HTTP 4xx/5xx Error 2016-11-22, 17:00 @fengyh
- HttpWebResponse xWebResp = wexp.Response as HttpWebResponse;
- handleErrorWebResponse(xWebResp, pCompletionHandler, wexp);
+ if(pRecvDataHandler!=null)
+ {
+ pRecvDataHandler(ResponseInfo.networkError(exp.Message), null);
+ }
}
- catch (Exception exp)
+ finally
{
- handleErrorWebResponse(vWebResp, pCompletionHandler, exp);
+ if (vWebResp != null)
+ {
+ vWebResp.Close();
+ vWebResp = null;
+ }
+
+ if (vWebReq != null)
+ {
+ vWebReq.Abort();
+ vWebReq = null;
+ }
}
}
@@ -643,14 +843,13 @@ private void handleWebResponse(HttpWebResponse pWebResp, CompletionHandler pComp
string host = null;
string respData = null;
int contentLength = -1;
- bool recvInvalid = false;
if (pWebResp != null)
{
statusCode = (int)pWebResp.StatusCode;
if (pWebResp.Headers != null)
- {
+ {
WebHeaderCollection respHeaders = pWebResp.Headers;
foreach (string headerName in respHeaders.AllKeys)
{
@@ -685,47 +884,44 @@ private void handleWebResponse(HttpWebResponse pWebResp, CompletionHandler pComp
}
}
- using (StreamReader respStream = new StreamReader(pWebResp.GetResponseStream()))
+ if (contentLength > 0)
{
- respData = respStream.ReadToEnd();
-
- if (contentLength > 0)
+ Stream ps = pWebResp.GetResponseStream();
+ byte[] raw = new byte[contentLength];
+ int bytesRead = 0; // 已读取的字节数
+ int bytesLeft = contentLength; // 剩余字节数
+ while (bytesLeft > 0)
{
- if (respData.Length != contentLength)
- {
- recvInvalid = true;
- }
+ bytesRead = ps.Read(raw, contentLength - bytesLeft, bytesLeft);
+ bytesLeft -= bytesRead;
}
- }
- try
- {
- /////////////////////////////////////////////////////////////
- // 改进Response的error解析, 根据HttpStatusCode
- // @fengyh 2016-08-17 18:29
- /////////////////////////////////////////////////////////////
- if (statusCode != (int)HCODE.OK)
- {
- bool isOtherCode = HttpCode.GetErrorMessage(statusCode, out error);
+ respData = Encoding.UTF8.GetString(raw);
- if (isOtherCode)
+ try
+ {
+ /////////////////////////////////////////////////////////////
+ // 改进Response的error解析, 根据HttpStatusCode
+ // @fengyh 2016-08-17 18:29
+ /////////////////////////////////////////////////////////////
+ if (statusCode != (int)HCODE.OK)
{
- Dictionary errorDict = JsonConvert.DeserializeObject>(respData);
- error = errorDict["error"];
+ bool isOtherCode = HttpCode.GetErrorMessage(statusCode, out error);
+
+ if (isOtherCode)
+ {
+ Dictionary errorDict = JsonConvert.DeserializeObject>(respData);
+ error = errorDict["error"];
+ }
}
}
+ catch (Exception) { }
}
- catch (Exception) { }
-
- if (recvInvalid)
+ else
{
- statusCode = -1;
- string err = string.Format("response-recv is not complete RECV={0},TOTAL={1} {2}", respData.Length, contentLength, error);
- Console.WriteLine(err);
- error = err;
+ //statusCode = -1;
+ //error = "response err";
}
-
- pWebResp.Close();
}
else
{
@@ -770,7 +966,6 @@ private void handleErrorWebResponse(HttpWebResponse pWebResp, CompletionHandler
string host = null;
string respData = null;
int contentLength = -1;
- bool recvInvalid = false;
if (pWebResp != null)
{
@@ -810,47 +1005,44 @@ private void handleErrorWebResponse(HttpWebResponse pWebResp, CompletionHandler
}
}
- using (StreamReader respStream = new StreamReader(pWebResp.GetResponseStream()))
+ if (contentLength > 0)
{
- respData = respStream.ReadToEnd();
-
- if (contentLength > 0)
+ Stream ps = pWebResp.GetResponseStream();
+ byte[] raw = new byte[contentLength];
+ int bytesRead = 0; // 已读取的字节数
+ int bytesLeft = contentLength; // 剩余字节数
+ while (bytesLeft > 0)
{
- if (respData.Length != contentLength)
- {
- recvInvalid = true;
- }
+ bytesRead = ps.Read(raw, contentLength - bytesLeft, bytesLeft);
+ bytesLeft -= bytesRead;
}
- }
- try
- {
- /////////////////////////////////////////////////////////////
- // 改进Response的error解析, 根据HttpStatusCode
- // @fengyh 2016-08-17 18:29
- /////////////////////////////////////////////////////////////
- if (statusCode != (int)HCODE.OK)
- {
- bool isOtherCode = HttpCode.GetErrorMessage(statusCode, out error);
+ respData = Encoding.UTF8.GetString(raw);
- if (isOtherCode)
+ try
+ {
+ /////////////////////////////////////////////////////////////
+ // 改进Response的error解析, 根据HttpStatusCode
+ // @fengyh 2016-08-17 18:29
+ /////////////////////////////////////////////////////////////
+ if (statusCode != (int)HCODE.OK)
{
- Dictionary errorDict = JsonConvert.DeserializeObject>(respData);
- error = errorDict["error"];
+ bool isOtherCode = HttpCode.GetErrorMessage(statusCode, out error);
+
+ if (isOtherCode)
+ {
+ Dictionary errorDict = JsonConvert.DeserializeObject>(respData);
+ error = errorDict["error"];
+ }
}
}
+ catch (Exception) { }
}
- catch (Exception) { }
-
- if (recvInvalid)
+ else
{
statusCode = -1;
- string err = string.Format("response-recv is not complete RECV={0},TOTAL={1} {2}", respData.Length, contentLength, error);
- Console.WriteLine(err);
- error = err;
+ error = "response err";
}
-
- pWebResp.Close();
}
else
{
@@ -865,6 +1057,105 @@ private void handleErrorWebResponse(HttpWebResponse pWebResp, CompletionHandler
}
}
+ private void handleWebResponse(HttpWebResponse pWebResp, RecvDataHandler pRecvDataHandler)
+ {
+ DateTime startTime = DateTime.Now;
+ //check for exception
+ int statusCode = ResponseInfo.NetworkError;
+ string reqId = null;
+ string xlog = null;
+ string ip = null;
+ string xvia = null;
+ string error = null;
+ string host = null;
+ byte[] respData = null;
+ int contentLength = -1;
+
+ if (pWebResp != null)
+ {
+ statusCode = (int)pWebResp.StatusCode;
+
+ if (pWebResp.Headers != null)
+ {
+ WebHeaderCollection respHeaders = pWebResp.Headers;
+ foreach (string headerName in respHeaders.AllKeys)
+ {
+ if (headerName.Equals("X-Reqid"))
+ {
+ reqId = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("X-Log"))
+ {
+ xlog = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("X-Via"))
+ {
+ xvia = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("X-Px"))
+ {
+ xvia = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("Fw-Via"))
+ {
+ xvia = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("Host"))
+ {
+ host = respHeaders[headerName].ToString();
+ }
+ else if (headerName.Equals("Content-Length"))
+ {
+ contentLength = int.Parse(respHeaders["Content-Length"]);
+ }
+ }
+ }
+
+ if (contentLength > 0)
+ {
+ Stream ps = pWebResp.GetResponseStream();
+ respData = new byte[contentLength];
+ int bytesRead = 0; // 已读取的字节数
+ int bytesLeft = contentLength; // 剩余字节数
+ while (bytesLeft > 0)
+ {
+ bytesRead = ps.Read(respData, contentLength - bytesLeft, bytesLeft);
+ bytesLeft -= bytesRead;
+ }
+
+ try
+ {
+ /////////////////////////////////////////////////////////////
+ // 改进Response的error解析, 根据HttpStatusCode
+ // @fengyh 2016-08-17 18:29
+ /////////////////////////////////////////////////////////////
+ if (statusCode != (int)HCODE.OK)
+ {
+ bool isOtherCode = HttpCode.GetErrorMessage(statusCode, out error);
+
+ if (isOtherCode)
+ {
+ string respJson = Encoding.UTF8.GetString(respData);
+ Dictionary errorDict = JsonConvert.DeserializeObject>(respJson);
+ error = errorDict["error"];
+ }
+ }
+ }
+ catch (Exception) { }
+ }
+ else
+ {
+ error = "response error";
+ }
+ }
+
+ double duration = DateTime.Now.Subtract(startTime).TotalSeconds;
+ ResponseInfo respInfo = new ResponseInfo(statusCode, reqId, xlog, xvia, host, ip, duration, error);
+ if (pRecvDataHandler != null)
+ {
+ pRecvDataHandler(respInfo, respData);
+ }
+ }
}
}
diff --git a/Qiniu/Processing/Dfop.cs b/Qiniu/Processing/Dfop.cs
index 377c444c..d9e66493 100644
--- a/Qiniu/Processing/Dfop.cs
+++ b/Qiniu/Processing/Dfop.cs
@@ -32,18 +32,13 @@ public DfopResult dfop(string fop,string url)
Dictionary dfopHeaders = new Dictionary();
dfopHeaders.Add("Authorization", token);
- CompletionHandler dfopCompletionHandler = new CompletionHandler(delegate (ResponseInfo respInfo, string response)
+ RecvDataHandler dfopRecvDataHandler = new RecvDataHandler(delegate (ResponseInfo respInfo, byte[] respData)
{
- if (respInfo.isOk())
- {
- dfopResult = StringUtils.jsonDecode(response);
- }
-
dfopResult.ResponseInfo = respInfo;
- dfopResult.Response = response;
+ dfopResult.ResponseData = respData;
});
- mHttpManager.postForm(dfopUrl, dfopHeaders, null, dfopCompletionHandler);
+ mHttpManager.postFormRaw(dfopUrl, dfopHeaders, dfopRecvDataHandler);
return dfopResult;
}
@@ -59,18 +54,13 @@ public DfopResult dfop(string fop,byte[] data)
Dictionary dfopHeaders = new Dictionary();
dfopHeaders.Add("Authorization", token);
- CompletionHandler dfopCompletionHandler = new CompletionHandler(delegate (ResponseInfo respInfo, string response)
+ RecvDataHandler dfopRecvDataHandler = new RecvDataHandler(delegate (ResponseInfo respInfo, byte[] respData)
{
- if (respInfo.isOk())
- {
- dfopResult = StringUtils.jsonDecode(response);
- }
-
dfopResult.ResponseInfo = respInfo;
- dfopResult.Response = response;
+ dfopResult.ResponseData = respData;
});
- mHttpManager.postMultipartDataRaw(dfopUrl, dfopHeaders, dfopData, null, dfopCompletionHandler);
+ mHttpManager.postMultipartDataRaw(dfopUrl, dfopHeaders, dfopData, null, dfopRecvDataHandler);
return dfopResult;
}
diff --git a/Qiniu/Processing/DfopResult.cs b/Qiniu/Processing/DfopResult.cs
index ff06f157..7c54b964 100644
--- a/Qiniu/Processing/DfopResult.cs
+++ b/Qiniu/Processing/DfopResult.cs
@@ -6,8 +6,15 @@
namespace Qiniu.Processing
{
- public class DfopResult:HttpResult
+ public class DfopResult
{
- public DfopResult() { }
+ public ResponseInfo ResponseInfo { get; set; }
+
+ public byte[] ResponseData { get; set; }
+
+ public string Response()
+ {
+ return Encoding.UTF8.GetString(ResponseData);
+ }
}
}
diff --git a/Qiniu/Properties/AssemblyInfo.cs b/Qiniu/Properties/AssemblyInfo.cs
index ce02dcd4..8df4b5b0 100644
--- a/Qiniu/Properties/AssemblyInfo.cs
+++ b/Qiniu/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("Qiniu (Storage) C# SDK v7.1.0.0 (.NET4.0)")]
-[assembly: AssemblyDescription("Qiniu (Storage) C# SDK v7.1.0.0 (.NET4.0)")]
+[assembly: AssemblyTitle("Qiniu (Storage) C# SDK v7.0.0.5 (.NET4.0)")]
+[assembly: AssemblyDescription("Qiniu (Storage) C# SDK v7.0.0.5 (.NET4.0)")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("QINIU")]
-[assembly: AssemblyProduct("Qiniu (Storage) C# SDK v7.1.0.0 (.NET4.0)")]
+[assembly: AssemblyProduct("Qiniu (Storage) C# SDK v7.0.0.5 (.NET4.0)")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("7.1.0.0")]
-[assembly: AssemblyFileVersion("7.1.0.0")]
+[assembly: AssemblyVersion("7.0.0.5")]
+[assembly: AssemblyFileVersion("7.0.0.5")]
diff --git a/Qiniu/Qiniu.csproj b/Qiniu/Qiniu.csproj
index b12abac0..f9368f04 100644
--- a/Qiniu/Qiniu.csproj
+++ b/Qiniu/Qiniu.csproj
@@ -54,6 +54,7 @@
+
diff --git a/Qiniu/Storage/BucketManager.cs b/Qiniu/Storage/BucketManager.cs
index f0f57eb9..79d0c322 100644
--- a/Qiniu/Storage/BucketManager.cs
+++ b/Qiniu/Storage/BucketManager.cs
@@ -442,6 +442,25 @@ public ListFilesResult listFiles(string bucket,string prefix,string marker,int l
return result;
}
+ public HttpResult updateLifecycle(string bucket,string key,int deleteAfterDays)
+ {
+ HttpResult updateResult = null;
+
+ string updateUrl = string.Format("{0}{1}", Config.ZONE.RsHost, updateLifecycleOp(bucket, key, deleteAfterDays));
+ string accessToken = Auth.createManageToken(updateUrl, null, this.mac);
+ Dictionary updateHeaders = new Dictionary();
+ updateHeaders.Add("Authorization", accessToken);
+ CompletionHandler updateCompletionHandler = new CompletionHandler(delegate (ResponseInfo respInfo, string response)
+ {
+ updateResult = new HttpResult();
+ updateResult.Response = response;
+ updateResult.ResponseInfo = respInfo;
+ });
+
+ this.mHttpManager.postForm(updateUrl, updateHeaders, null, updateCompletionHandler);
+ return updateResult;
+ }
+
public string statOp(string bucket, string key)
{
return string.Format("/stat/{0}", StringUtils.encodedEntry(bucket, key));
@@ -497,5 +516,10 @@ public string prefetchOp(string bucket, string key)
{
return string.Format("/prefetch/{0}", StringUtils.encodedEntry(bucket, key));
}
+
+ public string updateLifecycleOp(string bucket,string key,int deleteAfterDays)
+ {
+ return string.Format("/deleteAfterDays/{0}/{1}", StringUtils.encodedEntry(bucket, key), deleteAfterDays);
+ }
}
}
diff --git a/README.md b/README.md
index b6602015..906387e8 100644
--- a/README.md
+++ b/README.md
@@ -10,22 +10,28 @@
######源码下载
- git clone https://github.com/qiniu/csharp-sdk
+```
+git clone https://github.com/qiniu/csharp-sdk
+```
-**注意**
+**注意**
-当前最新版本为v7(master与v7同步),另请参考 [v7.0.0.3 release](https://github.com/qiniu/csharp-sdk/releases/tag/7.0.0.3)
+当前最新版本为v7(master),另请参考 [v7.0.0.5 release](https://github.com/qiniu/csharp-sdk/releases/tag/v7.0.0.5)
######添加引用
-获取编译好的qiniu.dll后,在项目中添加Qiniu.dll引用
+获取编译好的Qiniu.dll后,在项目中添加Qiniu.dll引用
######附加依赖项
C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json)因此,您需要在项目中引用它,或者使用NuGet安装
- Install-Package Newtonsoft.Json
+```
+Install-Package Newtonsoft.Json
+```
+######NuGet安装
+此外,您也可以使用NuGet来管理SDK包,在Visul Studio中打开包管理器即可搜索到。
####秘钥配置
@@ -42,6 +48,7 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
在QSunSync和qiniulab这两个工具中,都用到了此SDK,研究它们的源代码也能帮助您更好地了解此SDK:
* [QSunSync](https://github.com/qiniu/QSunSync)
+
* [QiniulaLab](https://github.com/qiniu/qiniulab)
您也可以参考官方文档 [C# SDK 使用指南](http://developer.qiniu.com/code/v7/sdk/csharp.html)
@@ -50,18 +57,22 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
不同的空间(bucket)可能位于不同的机房(区域/Zone),因此文件管理等操作需要正确配置Zone(位于Qiniu.Common命名空间),默认配置为"华东机房",用户可以有以下两种方法配置Zone:
-1. 直接配置( 如果确定机房所在Zone,可以使用此方法)
+1.直接配置( 如果确定机房所在Zone,可以使用此方法)
- // XXX是其中之一: CN_East CN_South CN_North US_North
- Qiniu.Common.Config.ZONE = Qiniu.Common.Zone.ZONE_XXX();
+```csharp
+// XXX是其中之一: CN_East CN_South CN_North US_North
+Qiniu.Common.Config.ZONE = Qiniu.Common.Zone.ZONE_XXX();
- // 或者使用ZoneID (CN_East CN_South CN_North US_North)
- Qiniu.Common.Config.ConfigZone(zoneId);
+// 或者使用ZoneID (CN_East CN_South CN_North US_North)
+Qiniu.Common.Config.ConfigZone(zoneId);
+```
-2. 使用AutoZone自动配置(推荐使用这个方法)
+2.使用AutoZone自动配置(推荐使用这个方法)
- // AK = ACCESS_KEY
- Qiniu.Common.Config.ConfigZoneAuto(AK,BUCKET);
+```csharp
+// AK = ACCESS_KEY
+Qiniu.Common.Config.ConfigZoneAuto(AK,BUCKET);
+```
示例代码中没有特别指明有关Zone的设置,请特别注意。
@@ -75,138 +86,140 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
推荐使用UploadManager,以下代码示意了如何使用UploadManager来上传一个本地文件(请注意Zone设置,下同):
- using System;
- using Qiniu.Util;
- using Qiniu.Storage;
- using System.IO;
-
- namespace ConsoleDemo
- {
- class SimpleUploadDemo
- {
- public static void Main(string[] args)
- {
- string AK = "ACCESS_KEY";
- string SK = "SECRET_KEY";
- // 目标空间名
- string bucket = "TARGET_BUCKET";
- // 目标文件名
- string saveKey = "SAVE_KEY";
- // 本地文件
- string localFile = "LOCAL_FILE";
-
- // 上传策略
- PutPolicy putPolicy = new PutPolicy();
- // 设置要上传的目标空间
- putPolicy.Scope = bucket;
- // 上传策略的过期时间(单位:秒)
- putPolicy.SetExpires(3600);
- // 文件上传完毕后,在多少天后自动被删除
- putPolicy.DeleteAfterDays = 1;
-
- // 请注意这里的Zone设置(如果不设置,就默认为华东机房)
- // var zoneId = Qiniu.Common.AutoZone.Query(AK,BUCKET);
- // Qiniu.Common.Config.ConfigZone(zoneId);
-
- Mac mac = new Mac(AK,SK); // Use AK & SK here
- // 生成上传凭证
- string uploadToken = Auth.createUploadToken(putPolicy, mac);
-
- UploadOptions uploadOptions = null;
-
- // 上传完毕事件处理
- UpCompletionHandler uploadCompleted = new UpCompletionHandler(OnUploadCompleted);
-
- // 方式1:使用UploadManager
- //默认设置 Qiniu.Common.Config.PUT_THRESHOLD = 512*1024;
- //可以适当修改,UploadManager会根据这个阈值自动选择是否使用分片(Resumable)上传
- UploadManager um = new UploadManager();
- um.uploadFile(localFile, saveKey, token, uploadOptions, uploadCompleted);
-
- // 方式2:使用FormManager
- //FormUploader fm = new FormUploader();
- //fm.uploadFile(localFile, saveKey, token, uploadOptions, uploadCompleted);
-
- Console.ReadKey();
- }
-
- private static void OnUploadCompleted(string key, ResponseInfo respInfo, string respJson)
- {
- // respInfo.StatusCode
- // respJson是返回的json消息,示例: { "key":"FILE","hash":"HASH","fsize":FILE_SIZE }
- }
- }
- }
-
+```csharp
+using System;
+using Qiniu.Util;
+using Qiniu.Storage;
+using System.IO;
+
+namespace ConsoleDemo
+{
+ class SimpleUploadDemo
+ {
+ public static void Main(string[] args)
+ {
+ string AK = "ACCESS_KEY";
+ string SK = "SECRET_KEY";
+ // 目标空间名
+ string bucket = "TARGET_BUCKET";
+ // 目标文件名
+ string saveKey = "SAVE_KEY";
+ // 本地文件
+ string localFile = "LOCAL_FILE";
+
+ // 上传策略
+ PutPolicy putPolicy = new PutPolicy();
+ // 设置要上传的目标空间
+ putPolicy.Scope = bucket;
+ // 上传策略的过期时间(单位:秒)
+ putPolicy.SetExpires(3600);
+ // 文件上传完毕后,在多少天后自动被删除
+ putPolicy.DeleteAfterDays = 1;
+
+ // 请注意这里的Zone设置(如果不设置,就默认为华东机房)
+ // var zoneId = Qiniu.Common.AutoZone.Query(AK,BUCKET);
+ // Qiniu.Common.Config.ConfigZone(zoneId);
+
+ Mac mac = new Mac(AK, SK); // Use AK & SK here
+ // 生成上传凭证
+ string uploadToken = Auth.createUploadToken(putPolicy, mac);
+
+ UploadOptions uploadOptions = null;
+
+ // 上传完毕事件处理
+ UpCompletionHandler uploadCompleted = new UpCompletionHandler(OnUploadCompleted);
+
+ // 方式1:使用UploadManager
+ //默认设置 Qiniu.Common.Config.PUT_THRESHOLD = 512*1024;
+ //可以适当修改,UploadManager会根据这个阈值自动选择是否使用分片(Resumable)上传
+ UploadManager um = new UploadManager();
+ um.uploadFile(localFile, saveKey, token, uploadOptions, uploadCompleted);
+
+ // 方式2:使用FormManager
+ //FormUploader fm = new FormUploader();
+ //fm.uploadFile(localFile, saveKey, token, uploadOptions, uploadCompleted);
+
+ Console.ReadKey();
+ }
+
+ private static void OnUploadCompleted(string key, ResponseInfo respInfo, string respJson)
+ {
+ // respInfo.StatusCode
+ // respJson是返回的json消息,示例: { "key":"FILE","hash":"HASH","fsize":FILE_SIZE }
+ }
+ }
+}
+```
#####断点续上传
实际上也是分片上传,使用ResumeUploader,参考如下示例:
- using System;
- using Qiniu.Util;
- using Qiniu.Storage;
- using System.IO;
-
- namespace ConsoleDemo
- {
- class ResumableUploadDemo
- {
- public static void Main(string[] args)
- {
- string AK = "ACCESS_KEY";
- string SK = "SECRET_KEY";
- string bucket = "TARGET_BUCKET";
- string saveKey = "SAVE_KEY";
- string localFile = "LOCAL_FILE";
- // 上传进度记录保存的目录
- string recordPath = "RECORD_PATH";
- // 上传进度保存为文件
- string recordFile = "RECORD_FILE";
-
- // 设置上传时的分片大小(单位为字节,已默认设置为2MB,不得大于4MB,一般保留默认即可)
- // Qiniu.Common.CHUNK_SIZE = N_CHUNK_SIZE;
-
- UploadOptions uploadOptions = new UploadOptions(
- null, // ExtraParams
- null, // MimeType
- false, // CheckCrc32
- new UpProgressHandler(OnUploadProgressChanged), // 上传进度
- null // CancelSignal
- );
-
- UpCompletionHandler uploadCompleted = new UpCompletionHandler(OnUploadCompleted); // 上传完毕
-
- // 上传时会将当前进度记录写到文件,下次可以“断点续传”
- ResumeRecorder rr = new ResumeRecorder(recordPath);
- // 开始上传
- ResumeUploader ru = new ResumeUploader(
- rr, // 续传记录
- recordFile, // 续传记录文件
- localFile, // 待上传的本地文件
- saveKey, // 要保存的文件名
- token, // 上传凭证
- uploadOptions, // 上传选项(其中包含进度处理),可为null
- uploadCompleted // 上传完毕事件处理
- );
-
- ru.uploadFile();
- Console.ReadKey();
- }
-
- private static void OnUploadProgressChanged(string key,double percent)
- {
- // percent = [0(开始)~1.0(完成)]
- }
-
- private static void OnUploadCompleted(string key,ResponseInfo respInfo,string respJson)
- {
- // respInfo.StatusCode
- // respJson是返回的json消息,示例: { "key":"FILE","hash":"HASH","fsize":FILE_SIZE }
- }
- }
- }
-
+```csharp
+using System;
+using Qiniu.Util;
+using Qiniu.Storage;
+using System.IO;
+
+namespace ConsoleDemo
+{
+ class ResumableUploadDemo
+ {
+ public static void Main(string[] args)
+ {
+ string AK = "ACCESS_KEY";
+ string SK = "SECRET_KEY";
+ string bucket = "TARGET_BUCKET";
+ string saveKey = "SAVE_KEY";
+ string localFile = "LOCAL_FILE";
+ // 上传进度记录保存的目录
+ string recordPath = "RECORD_PATH";
+ // 上传进度保存为文件
+ string recordFile = "RECORD_FILE";
+
+ // 设置上传时的分片大小(单位为字节,已默认设置为2MB,不得大于4MB,一般保留默认即可)
+ // Qiniu.Common.CHUNK_SIZE = N_CHUNK_SIZE;
+
+ UploadOptions uploadOptions = new UploadOptions(
+ null, // ExtraParams
+ null, // MimeType
+ false, // CheckCrc32
+ new UpProgressHandler(OnUploadProgressChanged), // 上传进度
+ null // CancelSignal
+ );
+
+ UpCompletionHandler uploadCompleted = new UpCompletionHandler(OnUploadCompleted); // 上传完毕
+
+ // 上传时会将当前进度记录写到文件,下次可以“断点续传”
+ ResumeRecorder rr = new ResumeRecorder(recordPath);
+ // 开始上传
+ ResumeUploader ru = new ResumeUploader(
+ rr, // 续传记录
+ recordFile, // 续传记录文件
+ localFile, // 待上传的本地文件
+ saveKey, // 要保存的文件名
+ token, // 上传凭证
+ uploadOptions, // 上传选项(其中包含进度处理),可为null
+ uploadCompleted // 上传完毕事件处理
+ );
+
+ ru.uploadFile();
+ Console.ReadKey();
+ }
+
+ private static void OnUploadProgressChanged(string key, double percent)
+ {
+ // percent = [0(开始)~1.0(完成)]
+ }
+
+ private static void OnUploadCompleted(string key, ResponseInfo respInfo, string respJson)
+ {
+ // respInfo.StatusCode
+ // respJson是返回的json消息,示例: { "key":"FILE","hash":"HASH","fsize":FILE_SIZE }
+ }
+ }
+}
+```
**说明**
@@ -216,10 +229,11 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
*上传域名*
-请设置`UploadFromCDN`参数(true/false,默认为true即使用CDN):
+请设置`UploadFromCDN`参数(true/false,默认为false即不使用CDN):
- // 不使用CDN
- Qiniu.Common.Config.UploadFromCDN = false;
+```csharp
+Qiniu.Common.Config.UploadFromCDN = false;
+```
*关于UpCompletionHandler参数*
@@ -239,6 +253,25 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
5.使用ResumbaleUploader时,**上传不同的文件,请务必使用不同的recordPath/recordFile**,因为断点记录和上传文件是对应的
+*关于上传重试*
+
+上传过程中遇到网络异常(如网络突然断开然后恢复),SDK会自动重试,最大重试次数默认5:
+
+```csharp
+Qiniu.Common.Config.RETRY_MAX = 5;
+```
+
+可以设置是否重试等待(一次重试失败后是否等待一段时间后开始下一次重试,默认不开启)
+
+```csharp
+Qiniu.Common.Config.RetryWaitForNext = true;
+```
+
+重试等待间隔(仅当开启重试等待才有效,默认1000ms)
+
+```csharp
+Qiniu.Common.Config.RETRY_INTERVAL_MILISEC = 1000;
+```
####文件下载
@@ -258,16 +291,17 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
示例代码
- using Qiniu.Util;
- // AK = "ACCESS_KEY"
- // SK = "SECRET_KEY"
- // 加上过期参数,使用 ?e=
- // 如果rawUrl中已包含?,则改用&e=
- // rawURL = "RAW_URL" + "?e=1482207600";
- Mac mac = new Mac(AK,SK);
- string token = Auth.createDownloadToken(rawUrl, mac);
- string signedURL = rawURL + "&token=" + token;
-
+```csharp
+using Qiniu.Util;
+// AK = "ACCESS_KEY"
+// SK = "SECRET_KEY"
+// 加上过期参数,使用 ?e=
+// 如果rawUrl中已包含?,则改用&e=
+// rawURL = "RAW_URL" + "?e=1482207600";
+Mac mac = new Mac(AK, SK);
+string token = Auth.createDownloadToken(rawUrl, mac);
+string signedURL = rawURL + "&token=" + token;
+```
####空间资源管理
@@ -300,33 +334,35 @@ C# SDK引用了第三方的开源项目[Json.NET](http://www.newtonsoft.com/json
以下是一个简单的示例:
- // AK = "ACCESS_KEY"
- // SK = "SECRET_KEY"
- Mac mac = new Mac(AK, SK);
- BucketManager bm = new BucketManager(mac);
-
- string bucket = "BUCKET"; // 目标空间
- string marker = ""; // 首次请求时marker必须为空
- string prefix = null; // 按文件名前缀保留搜索结果
- string delimiter = null; // 目录分割字符(比如"/")
- int limit = 100; // 最大值1000
-
- // 返回结果存储在items中
- List items = new List();
-
- // 由于limit限制,可能需要执行多次操作
- // 返回值中Marker字段若非空,则表示文件数超过了limit
- do
+```csharp
+// AK = "ACCESS_KEY"
+// SK = "SECRET_KEY"
+Mac mac = new Mac(AK, SK);
+BucketManager bm = new BucketManager(mac);
+
+string bucket = "BUCKET"; // 目标空间
+string marker = ""; // 首次请求时marker必须为空
+string prefix = null; // 按文件名前缀保留搜索结果
+string delimiter = null; // 目录分割字符(比如"/")
+int limit = 100; // 最大值1000
+
+// 返回结果存储在items中
+List items = new List();
+
+// 由于limit限制,可能需要执行多次操作
+// 返回值中Marker字段若非空,则表示文件数超过了limit
+do
+{
+ var result = bm.listFiles(bucket, prefix, marker, limit, delimiter);
+ marker = result.Marker;
+ if (result.Items != null)
{
- var result = bm.listFiles(bucket, prefix, marker, limit, delimiter);
- marker = result.Marker;
- if (result.Items != null)
- {
- items.AddRange(result.Items);
- }
- } while (!string.IsNullOrEmpty(marker));
+ items.AddRange(result.Items);
+ }
+} while (!string.IsNullOrEmpty(marker));
- // 在这里处理文件列表items
+// 在这里处理文件列表items
+```
完整示例及其它更多代码可以参考SDK的[examples目录](https://github.com/qiniu/csharp-sdk/tree/master/examples)
@@ -340,19 +376,21 @@ op=`OP1`&op=`OP2`...
如下示例:
- // AK = ACCESS_KEY
- // SK = SECRET_KEY
- Mac mac = new Mac(AK, SK);
- // 批量操作类似于
- // op=&op=&op=...
- string batchOps = "BATCH_OPS";
- BucketManager bm = new BucketManager(mac);
- HttpResult result = bm.batch(batchOps);
- // 或者
- //string[] batch_ops={"","","",...};
- //bm.batch(batch_ops);
- //返回结果在这里result.Response
-
+```csharp
+// AK = ACCESS_KEY
+// SK = SECRET_KEY
+Mac mac = new Mac(AK, SK);
+// 批量操作类似于
+// op=&op=&op=...
+string batchOps = "BATCH_OPS";
+BucketManager bm = new BucketManager(mac);
+HttpResult result = bm.batch(batchOps);
+// 或者
+//string[] batch_ops={"","","",...};
+//bm.batch(batch_ops);
+//返回结果在这里result.Response
+```
+
可参考[examples/BucketManagement.batch()](https://github.com/qiniu/csharp-sdk/blob/master/examples/BucketFileManagement.cs#L129)
#####新特性:force参数
@@ -361,7 +399,120 @@ move/copy支持force参数,另请参阅[资源复制的force参数](http://dev
####持久化操作
-如:fops = vframe/jpg/offset/1/w/480/h/360/rotate/90 表示视频截图。
+如:`fops = vframe/jpg/offset/1/w/480/h/360/rotate/90` 表示视频截图。
+
+####dfop数据处理
+
+使用方法:
+
+`dfop(FOP,URL)` 或者`dfop(FOP,DATA)`
+
+FOP是fop操作字符串,例如"imageInfo",目前不支持saveas、avvod
+
+URL是资源链接,DATA是资源的字节数据,资源最大为20MB
+
+示例:
+
+```csharp
+string AK = "AccessKey";
+string SK = "SecretKey";
+Mac mac = new Mac(AK,SK);
+Dfop dx = new Dfop(mac);
+
+string fop = "imageInfo";
+string url = "http://www.hello.world.net/images/1.jpg";
+string file = "F:\\images\\1.jpg";
+byte[] data = File.ReadAllBytes(file);
+
+DfopResult result1 = dx.dfop(fops,url);
+DfopResult result2 = dx.dfop(fops,data);
+ ```
+
+####Fusion(融合CDN加速)
+
+此模块包括以下几个功能:
+
+* 缓存刷新
+
+* 文件预取
+
+* 流量带宽
+
+* 日志查询
+
+这些功能都包含在`FusionManager`里面,其初始化方式如下:
+
+```csharp
+string AK="ACCESS_KEY";
+string SK="SECRET_KEY";
+Mac mac = new Mac(AK,SK);
+FusionManager fxm = new FusionManager(mac);
+```
+
+#####缓存刷新
+
+```csharp
+string[] urls = new string[] { "URL1", "URL2" };
+string[] dirs = new string[] { "DIR1", "DIR2" };
+RefreshRequest request = new RefreshRequest();
+request.AddUrls(urls);
+request.AddDirs(dirs);
+RefreshResult result = fxm.Refresh(request);
+Console.WriteLine(result);
+```
+
+另请参阅[缓存刷新-接口文档](http://developer.qiniu.com/article/fusion/api/refresh.html)
+
+#####文件预取
+
+```csharp
+string[] urls = new string[] { "URL1", "URL2" };
+PrefetchRequest request = new PrefetchRequest(urls);
+PrefetchResult result = fxm.Prefetch(request);
+Console.WriteLine(result);
+```
+
+另请参阅[文件预取-接口文档](http://developer.qiniu.com/article/fusion/api/refresh.html)
+
+#####流量带宽
+
+带宽查询
+
+```csharp
+BandwidthRequest request = new BandwidthRequest();
+request.StartDate = "START_DATE"; // "2016-09-01"
+request.EndDate = "END_DATE"; // "2016-09-20"
+request.Granularity = "GRANU"; // "day"
+request.Domains = "DOMAIN1;DOMAIN2"; // domains
+BandwidthResult result = fxm.Bandwidth(request);
+Console.WriteLine(result);
+```
+
+流量查询
+
+```csharp
+FluxRequest request = new FluxRequest();
+request.StartDate = "START_DATE"; // "2016-09-01"
+request.EndDate = "END_DATE"; // "2016-09-20"
+request.Granularity = "GRANU"; // "day"
+request.Domains = "DOMAIN1;DOMAIN2"; // domains
+FluxResult result = fxm.Flux(request);
+Console.WriteLine(result);
+```
+
+另请参阅[流量带宽-接口文档](http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html)
+
+#####日志查询
+
+```csharp
+LogListRequest request = new LogListRequest();
+request.Day = "DAY"; // "2016-09-01"
+request.Domains = "DOMAIN1"; // domains
+LogListResult result = fusionMgr.LogList(request);
+Console.WriteLine(result);
+```
+
+另请参阅[日志查询-接口文档](http://developer.qiniu.com/article/fusion/api/log.html)
###SDK结构