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结构