github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/third/upyun/upyun.go (about) 1 package upyun 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/http/httputil" 12 "os" 13 "strconv" 14 "strings" 15 "time" 16 ) 17 18 type UpYun struct { 19 httpClient *http.Client 20 trans *http.Transport 21 bucketName string 22 userName string 23 passWord string 24 apiDomain string 25 contentMd5 string 26 fileSecret string 27 tmpHeaders map[string]string 28 29 TimeOut int 30 Debug bool 31 } 32 33 /** 34 * 初始化 UpYun 存储接口 35 * @param bucketName 空间名称 36 * @param userName 操作员名称 37 * @param passWord 密码 38 * return UpYun object 39 */ 40 func NewUpYun(bucketName, userName, passWord string) *UpYun { 41 u := new(UpYun) 42 u.TimeOut = 300 43 u.httpClient = &http.Client{} 44 u.httpClient.Transport = &http.Transport{ 45 Dial: timeoutDialer(u.TimeOut), 46 } 47 u.bucketName = bucketName 48 u.userName = userName 49 u.passWord = StringMd5(passWord) 50 u.apiDomain = "v0.api.upyun.com" 51 u.Debug = false 52 return u 53 } 54 55 func (u *UpYun) Version() string { 56 return "1.0.1" 57 } 58 59 /** 60 * 切换 API 接口的域名 61 * @param domain { 62 默认 v0.api.upyun.com 自动识别, 63 v1.api.upyun.com 电信, 64 v2.api.upyun.com 联通, 65 v3.api.upyun.com 移动 66 } 67 * return 无 68 */ 69 func (u *UpYun) SetApiDomain(domain string) { 70 u.apiDomain = domain 71 } 72 73 /** 74 * 设置连接超时时间 75 * @param time 秒 76 * return 无 77 */ 78 func (u *UpYun) SetTimeout(time int) { 79 u.TimeOut = time 80 u.httpClient.Transport = &http.Transport{Dial: timeoutDialer(u.TimeOut)} 81 } 82 83 /** 84 * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致, 85 * 将回报 406 Not Acceptable 错误) 86 * @param str (文件 MD5 校验码) 87 * return 无 88 */ 89 func (u *UpYun) SetContentMD5(str string) { 90 u.contentMd5 = str 91 } 92 93 /** 94 * 连接签名方法 95 * @param method 请求方式 {GET, POST, PUT, DELETE} 96 * return 签名字符串 97 */ 98 func (u *UpYun) sign(method, uri, date string, length int64) string { 99 var bufSign bytes.Buffer 100 bufSign.WriteString(method) 101 bufSign.WriteString("&") 102 bufSign.WriteString(uri) 103 bufSign.WriteString("&") 104 bufSign.WriteString(date) 105 bufSign.WriteString("&") 106 bufSign.WriteString(strconv.FormatInt(length, 10)) 107 bufSign.WriteString("&") 108 bufSign.WriteString(u.passWord) 109 110 var buf bytes.Buffer 111 buf.WriteString("UpYun ") 112 buf.WriteString(u.userName) 113 buf.WriteString(":") 114 buf.WriteString(StringMd5(bufSign.String())) 115 return buf.String() 116 } 117 118 /** 119 * 连接处理逻辑 120 * @param method 请求方式 {GET, POST, PUT, DELETE} 121 * @param uri 请求地址 122 * @param inFile 如果是POST上传文件,传递文件IO数据流 123 * @param outFile 如果是GET下载文件,可传递文件IO数据流,这种情况函数也返回"" 124 * return 请求返回字符串,失败返回""(打开debug状态下遇到错误将中止程序执行) 125 */ 126 func (u *UpYun) httpAction(method, uri string, headers map[string]string, 127 inFile, outFile *os.File) (string, error) { 128 uri = "/" + u.bucketName + uri 129 url := "http://" + u.apiDomain + uri 130 req, err := http.NewRequest(method, url, nil) 131 if err != nil { 132 if u.Debug { 133 fmt.Println("%v", err) 134 panic("http.NewRequest failed: " + err.Error()) 135 } 136 return "", err 137 } 138 139 for k, v := range headers { 140 req.Header.Add(k, v) 141 } 142 143 length := FileSize(inFile) 144 if u.Debug { 145 fmt.Println("inFileSize: ", length) 146 } 147 148 if method == "PUT" || method == "POST" { 149 method = "POST" 150 if inFile != nil { 151 md5, err := IoMd5(inFile) 152 if err != nil { 153 if u.Debug { 154 panic("IoMd5 failed: " + err.Error()) 155 } 156 return "", err 157 } 158 _, err = inFile.Seek(0, 0) 159 if err != nil { 160 if u.Debug { 161 panic("inFile.Seek failed: " + err.Error()) 162 } 163 return "", err 164 } 165 req.Header.Add("Content-MD5", md5) 166 167 if u.fileSecret != "" { 168 req.Header.Add("Content-Secret", u.fileSecret) 169 u.fileSecret = "" 170 } 171 req.Header.Add("Content-Length", strconv.FormatInt(length, 10)) 172 req.Body = inFile 173 req.ContentLength = length 174 } 175 } 176 req.Method = method 177 178 date := time.Now().UTC().Format(time.RFC1123) 179 req.Header.Add("Date", date) 180 req.Header.Add("Authorization", u.sign(method, uri, date, length)) 181 if method == "HEAD" { 182 req.Body = nil 183 } 184 185 if u.Debug { 186 s, err := httputil.DumpRequestOut(req, true) 187 if err != nil { 188 panic("httputil.DumpRequestOut failed: " + err.Error()) 189 } 190 fmt.Println(string(s)) 191 } 192 var resp *http.Response 193 for i := 0; i < 2; i++ { 194 resp, err = u.httpClient.Do(req) 195 if err == nil { 196 break 197 } 198 u.SetTimeout(u.TimeOut) 199 } 200 if err != nil { 201 if u.Debug { 202 panic("httpClient.Do failed: " + err.Error()) 203 } 204 return "", err 205 } 206 207 defer func() { 208 resp.Body.Close() 209 }() 210 211 rc := resp.StatusCode 212 if rc == 200 { 213 u.tmpHeaders = make(map[string]string) 214 for k, v := range resp.Header { 215 if strings.Contains(k, "X-Upyun") { 216 u.tmpHeaders[k] = v[0] 217 } 218 } 219 220 if method == "GET" && outFile != nil { 221 _, err := io.Copy(outFile, resp.Body) 222 if err != nil { 223 if u.Debug { 224 fmt.Printf("%v %v\n", rc, err) 225 panic("write output file failed: ") 226 } 227 return "", err 228 } 229 return "", nil 230 } 231 232 buf := bytes.NewBuffer(make([]byte, 0, 8192)) 233 buf.ReadFrom(resp.Body) 234 return buf.String(), nil 235 } 236 237 return "", errors.New(resp.Status) 238 } 239 240 /** 241 * 获取总体空间的占用信息 242 * return 空间占用量,失败返回0.0 243 */ 244 func (u *UpYun) GetBucketUsage() (float64, error) { 245 return u.GetFolderUsage("/") 246 } 247 248 /** 249 * 获取某个子目录的占用信息 250 * @param $path 目标路径 251 * return 空间占用量和error,失败空间占用量返回0.0 252 */ 253 func (u *UpYun) GetFolderUsage(path string) (float64, error) { 254 r, err := u.httpAction("GET", path+"?usage", nil, nil, nil) 255 if err != nil { 256 return 0.0, err 257 } 258 v, _ := strconv.ParseFloat(r, 64) 259 return v, nil 260 } 261 262 /** 263 * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问) 264 * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac 265 * @param $str (文件 MD5 校验码) 266 * return null; 267 */ 268 func (u *UpYun) SetFileSecret(str string) { 269 u.fileSecret = str 270 } 271 272 /** 273 * 上传文件 274 * @param filePath 文件路径(包含文件名) 275 * @param inFile 文件IO数据流 276 * @param autoMkdir 是否自动创建父级目录(最深10级目录) 277 * return error 278 */ 279 func (u *UpYun) WriteFile(filePath string, inFile *os.File, autoMkdir bool) error { 280 var headers map[string]string 281 if autoMkdir { 282 headers = make(map[string]string) 283 headers["Mkdir"] = "true" 284 } 285 _, err := u.httpAction("PUT", filePath, headers, inFile, nil) 286 return err 287 } 288 289 /** 290 * 获取上传文件后的信息(仅图片空间有返回数据) 291 * @param key 信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file-type) 292 * return string or "" 293 */ 294 func (u *UpYun) GetWritedFileInfo(key string) string { 295 if u.tmpHeaders == nil { 296 return "" 297 } 298 return u.tmpHeaders[strings.ToLower(key)] 299 } 300 301 /** 302 * 读取文件 303 * @param file 文件路径(包含文件名) 304 * @param outFile 可传递文件IO数据流(结果返回true or false) 305 * return error 306 */ 307 func (u *UpYun) ReadFile(file string, outFile *os.File) error { 308 _, err := u.httpAction("GET", file, nil, nil, outFile) 309 return err 310 } 311 312 /** 313 * 获取文件信息 314 * @param file 文件路径(包含文件名) 315 * return array('type': file | folder, 'size': file size, 'date': unix time) 或 nil 316 */ 317 func (u *UpYun) GetFileInfo(file string) (m map[string]string, err error) { 318 _, err = u.httpAction("HEAD", file, nil, nil, nil) 319 if err != nil { 320 return 321 } 322 if u.tmpHeaders == nil { 323 return 324 } 325 m = make(map[string]string) 326 if v, ok := u.tmpHeaders["X-Upyun-File-Type"]; ok { 327 m["type"] = v 328 } 329 if v, ok := u.tmpHeaders["X-Upyun-File-Size"]; ok { 330 m["size"] = v 331 } 332 if v, ok := u.tmpHeaders["X-Upyun-File-Date"]; ok { 333 m["date"] = v 334 } 335 return 336 } 337 338 type DirInfo struct { 339 Name string 340 Type string 341 Size int64 342 Time int64 343 } 344 345 /** 346 * 读取目录列表 347 * @param path 目录路径 348 * return DirInfo数组 或 nil 349 */ 350 func (u *UpYun) ReadDir(path string) ([]*DirInfo, error) { 351 r, err := u.httpAction("GET", path, nil, nil, nil) 352 if err != nil { 353 return nil, err 354 } 355 356 dirs := make([]*DirInfo, 0, 8) 357 rs := strings.Split(r, "\n") 358 for i := 0; i < len(rs); i++ { 359 ri := strings.TrimSpace(rs[i]) 360 if len(ri) == 0 { 361 continue 362 } 363 rid := strings.Split(ri, "\t") 364 d := new(DirInfo) 365 d.Name = rid[0] 366 if len(rid) > 3 && rid[3] != "" { 367 if rid[1] == "N" { 368 d.Type = "file" 369 } else { 370 d.Type = "folder" 371 } 372 d.Time, _ = strconv.ParseInt(rid[3], 10, 64) 373 } 374 if len(rid) > 2 { 375 d.Size, _ = strconv.ParseInt(rid[2], 10, 64) 376 } 377 dirs = append(dirs, d) 378 } 379 return dirs, nil 380 } 381 382 /** 383 * 删除文件 384 * @param file 文件路径(包含文件名) 385 * return error 386 */ 387 func (u *UpYun) DeleteFile(file string) error { 388 _, err := u.httpAction("DELETE", file, nil, nil, nil) 389 return err 390 } 391 392 /** 393 * 创建目录 394 * @param path 目录路径 395 * @param auto_mkdir=false 是否自动创建父级目录 396 * return error 397 */ 398 func (u *UpYun) MkDir(path string, autoMkdir bool) error { 399 var headers map[string]string 400 headers = make(map[string]string) 401 headers["Folder"] = "true" 402 if autoMkdir { 403 headers["Mkdir"] = "true" 404 } 405 _, err := u.httpAction("PUT", path, headers, nil, nil) 406 return err 407 } 408 409 /** 410 * 删除目录 411 * @param path 目录路径 412 * return error 413 */ 414 func (u *UpYun) RmDir(dir string) error { 415 _, err := u.httpAction("DELETE", dir, nil, nil, nil) 416 return err 417 } 418 419 func FileSize(f *os.File) int64 { 420 if f == nil { 421 return 0 422 } 423 if fi, err := f.Stat(); err == nil { 424 return fi.Size() 425 } 426 return 0 427 } 428 429 func StringMd5(s string) string { 430 h := md5.New() 431 io.WriteString(h, s) 432 return fmt.Sprintf("%x", h.Sum(nil)) 433 } 434 435 func IoMd5(r io.Reader) (sum string, err error) { 436 h := md5.New() 437 _, err = io.Copy(h, r) 438 if err != nil { 439 return 440 } 441 sum = fmt.Sprintf("%x", h.Sum(nil)) 442 return 443 } 444 445 func timeoutDialer(timeout int) func(string, string) (net.Conn, error) { 446 return func(netw, addr string) (c net.Conn, err error) { 447 delta := time.Duration(timeout) * time.Second 448 c, err = net.DialTimeout(netw, addr, delta) 449 if err != nil { 450 return nil, err 451 } 452 c.SetDeadline(time.Now().Add(delta)) 453 return c, nil 454 } 455 }