This commit is contained in:
bel
2020-01-13 03:37:51 +00:00
commit c8eb52f9ba
2023 changed files with 702080 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
out
gen
vendor
coverage
release

View File

@@ -0,0 +1,6 @@
[submodule "specs/qingstor"]
path = specs/qingstor
url = https://github.com/yunify/qingstor-api-specs.git
[submodule "test/features"]
path = test/features
url = https://github.com/yunify/qingstor-sdk-test-scenarios.git

View File

@@ -0,0 +1,55 @@
sudo: required
services:
- docker
language: go
go:
- master
- 1.9
- 1.8
- 1.7
cache:
directories:
- ${HOME}/source
before_install:
- pushd ${HOME}/source
- if [[ ! -d "./make-4.0" ]]; then
wget http://ftp.gnu.org/gnu/make/make-4.0.tar.gz &&
tar -vxzf make-4.0.tar.gz &&
pushd make-4.0 && ./configure && make && popd;
fi
- pushd make-4.0 && sudo make install && popd
- if [[ ! -d "./glide-v0.12.3" ]]; then
wget https://github.com/Masterminds/glide/releases/download/v0.12.3/glide-v0.12.3-linux-amd64.tar.gz &&
tar -vxzf glide-v0.12.3-linux-amd64.tar.gz &&
mv linux-amd64 glide-v0.12.3;
fi
- pushd glide-v0.12.3 && sudo cp glide /usr/local/bin && popd
- if [[ ! -d "./snips-v0.2.8" ]]; then
wget https://github.com/yunify/snips/releases/download/v0.2.8/snips-v0.2.8-linux_amd64.tar.gz &&
mkdir snips-v0.2.8 &&
pushd snips-v0.2.8 &&
tar -vxzf ../snips-v0.2.8-linux_amd64.tar.gz &&
popd;
fi
- pushd snips-v0.2.8 && sudo cp snips /usr/local/bin && popd
- popd
- /usr/local/bin/make --version
- /usr/local/bin/glide --version
install:
- go get -u github.com/golang/lint/golint;
- glide install
- git submodule init
before_script:
- /usr/local/bin/make update
- /usr/local/bin/make generate
script:
- /usr/local/bin/make check
- /usr/local/bin/make release
- /usr/local/bin/make build test test-coverage

View File

@@ -0,0 +1,4 @@
Patches have been contributed by (ordered by the time of the first merged patch):
Jingwen Peng <pengsrc@yunify.com>
Osier Yang <osier@yunify.com>

View File

@@ -0,0 +1,184 @@
# Change Log
All notable changes to QingStor SDK for Go will be documented in this file.
## [v2.2.14] - 2018-6-9
### Fixed
- Fix head application/json file failed
## [v2.2.13] - 2018-5-31
### Added
- Add storage class support
## [v2.2.12] - 2018-4-8
### Changed
- Skip empty header while unpacking
## [v2.2.11] - 2018-3-28
### Changed
- Inject request id for HEAD request
### Fixed
- Fix a read timeout bug introduced in v2.2.10
## [v2.2.10] - 2018-3-14
### Changed
- Close body for every API except GetObject and ImageProcess
- Add correct i/o timeout behavior for http client
## [v2.2.9] - 2017-11-25
### Changed
- Refactor logger.
## [v2.2.8] - 2017-09-25
### Added
- Support setting custom SDK logger.
## [v2.2.7] - 2017-09-01
### Added
- Support image process APIs.
- Add advanced client for image process.
### Changed
- Force the zone ID to be lowercase.
### Fixed
- Add support for the X-QS-Date header.
## [v2.2.6] - 2017-07-21
### Fixed
- Fix concurrency issue in object related operations.
## [v2.2.5] - 2017-05-22
### Fixed
- Fix error in request URL query.
- Fix error in request header value.
## [v2.2.4] - 2017-03-28
### Fixed
- Fix type of Content-Type header.
- Add Content-Length to GetObjectOutput.
- Fix status code of DELETE CORS API.
- Fix type of object size for GET Bucket API.
### BREAKING CHANGES
- The type of content length and object size has been changed from `*int` to `*int64`.
## [v2.2.3] - 2017-03-10
### Added
- Allow user to append additional info to User-Agent.
## [v2.2.2] - 2017-03-08
### Fixed
- Resource is not mandatory in bucket policy statement.
## [v2.2.1] - 2017-03-05
### Changed
- Add "Encrypted" field to "KeyType" struct.
## [v2.2.0] - 2017-02-28
### Added
- Add ListMultipartUploads API.
### Fixed
- Fix request builder & signer.
## [v2.1.2] - 2017-01-16
### Fixed
- Fix request signer.
## [v2.1.1] - 2017-01-05
### Changed
- Fix logger output format, don't parse special characters.
- Rename package "errs" to "errors".
### Added
- Add type converters.
### BREAKING CHANGES
- Change value type in input and output to pointer.
## [v2.1.0] - 2016-12-23
### Changed
- Fix signer bug.
- Add more parameters to sign.
### Added
- Add request parameters for GET Object.
- Add IP address conditions for bucket policy.
## [v2.0.1] - 2016-12-15
### Changed
- Improve the implementation of deleting multiple objects.
## [v2.0.0] - 2016-12-14
### Added
- QingStor SDK for the Go programming language.
[v2.2.14]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.13...v2.2.14
[v2.2.13]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.12...v2.2.13
[v2.2.12]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.11...v2.2.12
[v2.2.11]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.10...v2.2.11
[v2.2.10]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.9...v2.2.10
[v2.2.9]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.8...v2.2.9
[v2.2.8]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.7...v2.2.8
[v2.2.7]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.6...v2.2.7
[v2.2.6]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.5...v2.2.6
[v2.2.5]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.4...v2.2.5
[v2.2.4]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.3...v2.2.4
[v2.2.3]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.2...v2.2.3
[v2.2.2]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.1...v2.2.2
[v2.2.1]: https://github.com/yunify/qingstor-sdk-go/compare/v2.2.0...v2.2.1
[v2.2.0]: https://github.com/yunify/qingstor-sdk-go/compare/v2.1.2...v2.2.0
[v2.1.2]: https://github.com/yunify/qingstor-sdk-go/compare/v2.1.1...v2.1.2
[v2.1.1]: https://github.com/yunify/qingstor-sdk-go/compare/v2.1.0...v2.1.1
[v2.1.0]: https://github.com/yunify/qingstor-sdk-go/compare/v2.0.1...v2.1.0
[v2.0.1]: https://github.com/yunify/qingstor-sdk-go/compare/v2.0.0...v2.0.1
[v2.0.0]: https://github.com/yunify/qingstor-sdk-go/compare/v2.0.0...v2.0.0

201
.rclone_repo/vendor/github.com/yunify/qingstor-sdk-go/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2012 Yunify Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1 @@
Jingwen Peng <pengsrc@yunify.com>

View File

@@ -0,0 +1,124 @@
SHELL := /bin/bash
PREFIX=qingstor-sdk-go
VERSION=$(shell cat version.go | grep "Version\ =" | sed -e s/^.*\ //g | sed -e s/\"//g)
DIRS_TO_CHECK=$(shell ls -d */ | grep -vE "vendor|test")
PKGS_TO_CHECK=$(shell go list ./... | grep -v "/vendor/")
PKGS_TO_RELEASE=$(shell go list ./... | grep -vE "/vendor/|/test")
FILES_TO_RELEASE=$(shell find . -name "*.go" | grep -vE "/vendor/|/test|.*_test.go")
FILES_TO_RELEASE_WITH_VENDOR=$(shell find . -name "*.go" | grep -vE "/test|.*_test.go")
.PHONY: help
help:
@echo "Please use \`make <target>\` where <target> is one of"
@echo " all to check, build, test and release this SDK"
@echo " check to vet and lint the SDK"
@echo " update to update git submodules"
@echo " generate to generate service code"
@echo " build to build the SDK"
@echo " test to run test"
@echo " test-coverage to run test with coverage"
@echo " test-race to run test with race"
@echo " integration-test to run integration test"
@echo " release to build and release current version"
@echo " release-source to pack the source code"
@echo " clean to clean the coverage files"
.PHONY: all
all: check build unit release
.PHONY: check
check: vet lint format
.PHONY: format
format:
@echo "go fmt, skipping vendor packages"
@for pkg in ${PKGS_TO_CHECK}; do go fmt $${pkg}; done;
@echo "ok"
.PHONY: vet
vet:
@echo "Go tool vet, skipping vendor packages"
@go tool vet -all ${DIRS_TO_CHECK}
@echo "Done"
.PHONY: lint
lint:
@echo "Golint, skipping vendor packages"
@lint=$$(for pkg in ${PKGS_TO_CHECK}; do golint $${pkg}; done); \
lint=$$(echo "$${lint}"); \
if [[ -n $${lint} ]]; then echo "$${lint}"; exit 1; fi
@echo "Done"
.PHONY: update
update:
git submodule update --remote
@echo "Done"
.PHONY: generate
generate:
@if [[ ! -f "$$(which snips)" ]]; then \
echo "ERROR: Command \"snips\" not found."; \
fi
snips -f="./specs/qingstor/2016-01-06/swagger/api_v2.0.json" -t="./template" -o="./service"
gofmt -w .
@echo "Done"
.PHONY: build
build: format
@echo "Build the SDK"
go build ${PKGS_TO_RELEASE}
@echo "Done"
.PHONY: test
test:
@echo "Run test"
go test -v ${PKGS_TO_RELEASE}
@echo "Done"
.PHONY: test-coverage
test-coverage:
@echo "Run test with coverage"
for pkg in ${PKGS_TO_RELEASE}; do \
output="coverage$${pkg#github.com/yunify/qingstor-sdk-go}"; \
mkdir -p $${output}; \
go test -v -cover -coverprofile="$${output}/profile.out" $${pkg}; \
if [[ -e "$${output}/profile.out" ]]; then \
go tool cover -html="$${output}/profile.out" -o "$${output}/profile.html"; \
fi; \
done
@echo "Done"
.PHONY: test-race
test-race:
@echo "Run test with race"
go test -v -race -cpu=1,2,4 ${PKGS_TO_RELEASE}
@echo "Done"
.PHONY: integration-test
integration-test:
@echo "Run integration test"
pushd "./test"; go run *.go; popd
@echo "Done"
.PHONY: release
release: release-source release-source-with-vendor
.PHONY: release-source
release-source:
@echo "Pack the source code"
mkdir -p "release"
zip -FS "release/${PREFIX}-source-v${VERSION}.zip" ${FILES_TO_RELEASE}
@echo "Done"
.PHONY: release-source-with-vendor
release-source-with-vendor:
@echo "Pack the source code with vendor"
mkdir -p "release"
zip -FS "release/${PREFIX}-source-with-vendor-v${VERSION}.zip" ${FILES_TO_RELEASE_WITH_VENDOR}
@echo "Done"
.PHONY: clean
clean:
rm -rf $${PWD}/coverage
@echo "Done"

View File

@@ -0,0 +1,84 @@
# QingStor SDK for Go
[![Build Status](https://travis-ci.org/yunify/qingstor-sdk-go.svg?branch=master)](https://travis-ci.org/yunify/qingstor-sdk-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/yunify/qingstor-sdk-go)](https://goreportcard.com/report/github.com/yunify/qingstor-sdk-go)
[![API Reference](http://img.shields.io/badge/api-reference-green.svg)](http://docs.qingcloud.com/qingstor/)
[![License](http://img.shields.io/badge/license-apache%20v2-blue.svg)](https://github.com/yunify/qingstor-sdk-go/blob/master/LICENSE)
The official QingStor SDK for the Go programming language.
## Getting Started
### Installation
Refer to the [Installation Guide](docs/installation.md), and have this SDK installed.
### Preparation
Before your start, please go to [QingCloud Console](https://console.qingcloud.com/access_keys/) to create a pair of QingCloud API AccessKey.
___API AccessKey Example:___
``` yaml
access_key_id: 'ACCESS_KEY_ID_EXAMPLE'
secret_access_key: 'SECRET_ACCESS_KEY_EXAMPLE'
```
### Usage
Now you are ready to code. You can read the detailed guides in the list below to have a clear understanding or just take the quick start code example.
Checkout our [releases](https://github.com/yunify/qingstor-sdk-go/releases) and [change log](https://github.com/yunify/qingstor-sdk-go/blob/master/CHANGELOG.md) for information about the latest features, bug fixes and new ideas.
- [Configuration Guide](docs/configuration.md)
- [QingStor Service Usage Guide](docs/qingstor_service_usage.md)
___Quick Start Code Example:___
``` go
package main
import (
"fmt"
"github.com/yunify/qingstor-sdk-go/config"
qs "github.com/yunify/qingstor-sdk-go/service"
)
func main() {
conf, _ := config.New("ACCESS_KEY_ID", "SECRET_ACCESS_KEY")
// Initialize service object for QingStor.
qsService, _ := qs.Init(conf)
// List all buckets.
qsOutput, _ := qsService.ListBuckets(&qs.ListBucketsInput{})
// Print HTTP status code.
fmt.Println(qs.IntValue(qsOutput.StatusCode))
// Print the count of buckets.
fmt.Println(qs.IntValue(qsOutput.Count))
// Print the first bucket name.
fmt.Println(qs.StringValue(qsOutput.Buckets[0].Name))
}
```
## Reference Documentations
- [QingStor Documentation](https://docs.qingcloud.com/qingstor/index.html)
- [QingStor Guide](https://docs.qingcloud.com/qingstor/guide/index.html)
- [QingStor APIs](https://docs.qingcloud.com/qingstor/api/index.html)
## Contributing
1. Fork it ( https://github.com/yunify/qingstor-sdk-go/fork )
2. Create your feature branch (`git checkout -b new-feature`)
3. Commit your changes (`git commit -asm 'Add some feature'`)
4. Push to the branch (`git push origin new-feature`)
5. Create a new Pull Request
## LICENSE
The Apache License (Version 2.0, January 2004).

View File

@@ -0,0 +1,275 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package config
import (
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"gopkg.in/yaml.v2"
"github.com/yunify/qingstor-sdk-go/logger"
"github.com/yunify/qingstor-sdk-go/utils"
)
// A Config stores a configuration of this sdk.
type Config struct {
AccessKeyID string `yaml:"access_key_id"`
SecretAccessKey string `yaml:"secret_access_key"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Protocol string `yaml:"protocol"`
ConnectionRetries int `yaml:"connection_retries"`
AdditionalUserAgent string `yaml:"additional_user_agent"`
LogLevel string `yaml:"log_level"`
HTTPSettings HTTPClientSettings
Connection *http.Client
}
// HTTPClientSettings is the http client settings.
type HTTPClientSettings struct {
// ConnectTimeout affects making new socket connection
ConnectTimeout time.Duration `yaml:"connect_timeout"`
// ReadTimeout affect each call to HTTPResponse.Body.Read()
ReadTimeout time.Duration `yaml:"read_timeout"`
// WriteTimeout affect each write in io.Copy while sending HTTPRequest
WriteTimeout time.Duration `yaml:"write_timeout" `
// TLSHandshakeTimeout affects https hand shake timeout
TLSHandshakeTimeout time.Duration `yaml:"tls_timeout"`
// IdleConnTimeout affects the time limit to re-use http connections
IdleConnTimeout time.Duration `yaml:"idle_timeout"`
TCPKeepAlive time.Duration `yaml:"tcp_keepalive_time"`
DualStack bool `yaml:"dual_stack"`
// MaxIdleConns affects the idle connections kept for re-use
MaxIdleConns int `yaml:"max_idle_conns"`
MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host"`
ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout"`
}
// DefaultHTTPClientSettings is the default http client settings.
var DefaultHTTPClientSettings = HTTPClientSettings{
ConnectTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
WriteTimeout: time.Second * 30,
TLSHandshakeTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 20,
TCPKeepAlive: 0,
DualStack: false,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
ExpectContinueTimeout: time.Second * 2,
}
// New create a Config with given AccessKeyID and SecretAccessKey.
func New(accessKeyID, secretAccessKey string) (c *Config, err error) {
c, err = NewDefault()
if err != nil {
c = nil
return
}
c.AccessKeyID = accessKeyID
c.SecretAccessKey = secretAccessKey
return
}
// NewDefault create a Config with default configuration.
func NewDefault() (c *Config, err error) {
c = &Config{}
err = c.LoadDefaultConfig()
if err != nil {
c = nil
return
}
return
}
// Check checks the configuration.
func (c *Config) Check() (err error) {
if c.AccessKeyID == "" {
err = errors.New("access key ID not specified")
return
}
if c.SecretAccessKey == "" {
err = errors.New("secret access key not specified")
return
}
if c.Host == "" {
err = errors.New("server host not specified")
return
}
if c.Port <= 0 {
err = errors.New("server port not specified")
return
}
if c.Protocol == "" {
err = errors.New("server protocol not specified")
return
}
if c.AdditionalUserAgent != "" {
for _, x := range c.AdditionalUserAgent {
// Allow space(32) to ~(126) in ASCII Table, exclude "(34).
if int(x) < 32 || int(x) > 126 || int(x) == 32 || int(x) == 34 {
err = errors.New("additional User-Agent contains characters that not allowed")
return
}
}
}
err = logger.CheckLevel(c.LogLevel)
if err != nil {
return
}
return
}
// LoadDefaultConfig loads the default configuration for Config.
// It returns error if yaml decode failed.
func (c *Config) LoadDefaultConfig() (err error) {
c.HTTPSettings = DefaultHTTPClientSettings
err = yaml.Unmarshal([]byte(DefaultConfigFileContent), c)
if err != nil {
logger.Errorf(nil, "Config parse error, %v.", err)
return
}
logger.SetLevel(c.LogLevel)
c.InitHTTPClient()
return
}
// LoadUserConfig loads user configuration in ~/.qingstor/config.yaml for Config.
// It returns error if file not found.
func (c *Config) LoadUserConfig() (err error) {
_, err = os.Stat(GetUserConfigFilePath())
if err != nil {
logger.Warnf(nil, "Installing default config file to %s.", GetUserConfigFilePath())
InstallDefaultUserConfig()
}
return c.LoadConfigFromFilePath(GetUserConfigFilePath())
}
// LoadConfigFromFilePath loads configuration from a specified local path.
// It returns error if file not found or yaml decode failed.
func (c *Config) LoadConfigFromFilePath(filePath string) (err error) {
if strings.Index(filePath, "~/") == 0 {
filePath = strings.Replace(filePath, "~/", getHome()+"/", 1)
}
yamlString, err := ioutil.ReadFile(filePath)
if err != nil {
logger.Errorf(nil, "File not found: %s.", filePath)
return err
}
return c.LoadConfigFromContent(yamlString)
}
// LoadConfigFromContent loads configuration from a given byte slice.
// It returns error if yaml decode failed.
func (c *Config) LoadConfigFromContent(content []byte) (err error) {
c.LoadDefaultConfig()
err = yaml.Unmarshal(content, c)
if err != nil {
logger.Errorf(nil, "Config parse error, %v.", err)
return
}
err = c.Check()
if err != nil {
return
}
logger.SetLevel(c.LogLevel)
c.InitHTTPClient()
return
}
// InitHTTPClient : After modifying Config.HTTPSettings, you should always call this to initialize the HTTP Client.
func (c *Config) InitHTTPClient() {
var emptySettings HTTPClientSettings
if c.HTTPSettings == emptySettings { // User forgot to initialize the settings
c.HTTPSettings = DefaultHTTPClientSettings
} else {
if c.HTTPSettings.ConnectTimeout == 0 {
c.HTTPSettings.ConnectTimeout = DefaultHTTPClientSettings.ConnectTimeout
}
// If ReadTimeout and WriteTimeout is zero, means no read/write timeout
if c.HTTPSettings.TLSHandshakeTimeout == 0 {
c.HTTPSettings.TLSHandshakeTimeout = DefaultHTTPClientSettings.TLSHandshakeTimeout
}
if c.HTTPSettings.ExpectContinueTimeout == 0 {
c.HTTPSettings.ExpectContinueTimeout = DefaultHTTPClientSettings.ExpectContinueTimeout
}
}
dialer := utils.NewDialer(
c.HTTPSettings.ConnectTimeout,
c.HTTPSettings.ReadTimeout,
c.HTTPSettings.WriteTimeout,
)
dialer.KeepAlive = c.HTTPSettings.TCPKeepAlive
// XXX: DualStack enables RFC 6555-compliant "Happy Eyeballs" dialing
// when the network is "tcp" and the destination is a host name
// with both IPv4 and IPv6 addresses. This allows a client to
// tolerate networks where one address family is silently broken
dialer.DualStack = c.HTTPSettings.DualStack
c.Connection = &http.Client{
// We do not use the timeout in http client,
// because this timeout is for the whole http body read/write,
// it's unsuitable for various length of files and network condition.
// We provide a wraper in utils/conn.go of net.Dialer to make io timeout to the http connection
// for individual buffer I/O operation,
Timeout: 0,
Transport: &http.Transport{
DialContext: dialer.DialContext,
MaxIdleConns: c.HTTPSettings.MaxIdleConns,
MaxIdleConnsPerHost: c.HTTPSettings.MaxIdleConnsPerHost,
IdleConnTimeout: c.HTTPSettings.IdleConnTimeout,
TLSHandshakeTimeout: c.HTTPSettings.TLSHandshakeTimeout,
ExpectContinueTimeout: c.HTTPSettings.ExpectContinueTimeout,
},
}
}

View File

@@ -0,0 +1,74 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package config
import (
"io/ioutil"
"os"
"path"
"runtime"
"strings"
)
// DefaultConfigFileContent is the content of default config file.
const DefaultConfigFileContent = `# QingStor services configuration
#access_key_id: ACCESS_KEY_ID
#secret_access_key: SECRET_ACCESS_KEY
host: qingstor.com
port: 443
protocol: https
connection_retries: 3
# Additional User-Agent
additional_user_agent: ""
# Valid log levels are "debug", "info", "warn", "error", and "fatal".
log_level: warn
`
// DefaultConfigFile is the filename of default config file.
const DefaultConfigFile = "~/.qingstor/config.yaml"
// GetUserConfigFilePath returns the user config file path.
func GetUserConfigFilePath() string {
return strings.Replace(DefaultConfigFile, "~/", getHome()+"/", 1)
}
// InstallDefaultUserConfig will install default config file.
func InstallDefaultUserConfig() error {
err := os.MkdirAll(path.Dir(GetUserConfigFilePath()), 0755)
if err != nil {
return err
}
return ioutil.WriteFile(GetUserConfigFilePath(), []byte(DefaultConfigFileContent), 0644)
}
func getHome() string {
home := os.Getenv("HOME")
if runtime.GOOS == "windows" {
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
}
return home
}

View File

@@ -0,0 +1,32 @@
hash: 6e1d4e547cdd58944ef920192480d29edd118cac0e8269d793315066d1249b4e
updated: 2018-05-10T11:43:20.637418+08:00
imports:
- name: github.com/DATA-DOG/godog
version: 272624afcc2fc5f818ce069e2e417e7b4fa8bb6c
subpackages:
- colors
- gherkin
- name: github.com/pengsrc/go-shared
version: db9bcfc423552eef790d989bb81ef055d0d26286
subpackages:
- buffer
- check
- convert
- log
- reopen
- name: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
repo: https://github.com/go-yaml/yaml.git
testImports:
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert

View File

@@ -0,0 +1,11 @@
package: github.com/yunify/qingstor-sdk-go
import:
# YAML
- package: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
repo: https://github.com/go-yaml/yaml.git
# Shared packages
- package: github.com/pengsrc/go-shared
version: db9bcfc423552eef790d989bb81ef055d0d26286

View File

@@ -0,0 +1,102 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
// Package logger provides support for logging to stdout and stderr.
package logger
import (
"context"
"fmt"
"github.com/pengsrc/go-shared/log"
)
// Logger is the interface of SDK logger.
type Logger interface {
Debugf(ctx context.Context, format string, v ...interface{})
Infof(ctx context.Context, format string, v ...interface{})
Warnf(ctx context.Context, format string, v ...interface{})
Errorf(ctx context.Context, format string, v ...interface{})
Fatalf(ctx context.Context, format string, v ...interface{})
Panicf(ctx context.Context, format string, v ...interface{})
}
// CheckLevel checks whether the log level is valid.
func CheckLevel(level string) error {
if _, err := log.ParseLevel(level); err != nil {
return fmt.Errorf(`log level not valid: "%s"`, level)
}
return nil
}
// GetLevel get the log level string.
func GetLevel() string {
if l, ok := instance.(*log.Logger); ok {
return l.GetLevel()
}
return "UNKNOWN"
}
// SetLevel sets the log level.
// Valid levels are "debug", "info", "warn", "error", and "fatal".
func SetLevel(level string) {
if l, ok := instance.(*log.Logger); ok {
err := l.SetLevel(level)
if err != nil {
Fatalf(nil, fmt.Sprintf(`log level not valid: "%s"`, level))
}
}
}
// SetLogger sets the a logger as SDK logger.
func SetLogger(l Logger) {
instance = l
}
// Debugf logs a message with severity DEBUG.
func Debugf(ctx context.Context, format string, v ...interface{}) {
instance.Debugf(ctx, format, v...)
}
// Infof logs a message with severity INFO.
func Infof(ctx context.Context, format string, v ...interface{}) {
instance.Infof(ctx, format, v...)
}
// Warnf logs a message with severity WARN.
func Warnf(ctx context.Context, format string, v ...interface{}) {
instance.Warnf(ctx, format, v...)
}
// Errorf logs a message with severity ERROR.
func Errorf(ctx context.Context, format string, v ...interface{}) {
instance.Errorf(ctx, format, v...)
}
// Fatalf logs a message with severity ERROR followed by a call to os.Exit().
func Fatalf(ctx context.Context, format string, v ...interface{}) {
instance.Fatalf(ctx, format, v...)
}
var instance Logger
func init() {
l, err := log.NewTerminalLogger(log.WarnLevel.String())
if err != nil {
panic(fmt.Sprintf("failed to initialize QingStor SDK logger: %v", err))
}
instance = l
}

View File

@@ -0,0 +1,291 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package builder
import (
"encoding/json"
"errors"
"io"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"unicode"
"github.com/pengsrc/go-shared/convert"
"github.com/yunify/qingstor-sdk-go/request/data"
"github.com/yunify/qingstor-sdk-go/utils"
)
// BaseBuilder is the base builder for all services.
type BaseBuilder struct {
parsedURL string
parsedProperties *map[string]string
parsedQuery *map[string]string
parsedHeaders *map[string]string
parsedBodyString string
parsedBody io.Reader
operation *data.Operation
input *reflect.Value
}
// BuildHTTPRequest builds http request with an operation and an input.
func (b *BaseBuilder) BuildHTTPRequest(o *data.Operation, i *reflect.Value) (*http.Request, error) {
b.operation = o
b.input = i
_, err := b.parse()
if err != nil {
return nil, err
}
return b.build()
}
func (b *BaseBuilder) build() (*http.Request, error) {
httpRequest, err := http.NewRequest(b.operation.RequestMethod, b.parsedURL, b.parsedBody)
if err != nil {
return nil, err
}
err = b.setupHeaders(httpRequest)
if err != nil {
return nil, err
}
return httpRequest, nil
}
func (b *BaseBuilder) parse() (*BaseBuilder, error) {
err := b.parseRequestQueryAndHeaders()
if err != nil {
return b, err
}
err = b.parseRequestBody()
if err != nil {
return b, err
}
err = b.parseRequestProperties()
if err != nil {
return b, err
}
err = b.parseRequestURL()
if err != nil {
return b, err
}
return b, nil
}
func (b *BaseBuilder) parseRequestQueryAndHeaders() error {
requestQuery := map[string]string{}
requestHeaders := map[string]string{}
maps := map[string](map[string]string){
"query": requestQuery,
"headers": requestHeaders,
}
b.parsedQuery = &requestQuery
b.parsedHeaders = &requestHeaders
if !b.input.IsValid() {
return nil
}
fields := b.input.Elem()
if !fields.IsValid() {
return nil
}
for i := 0; i < fields.NumField(); i++ {
tagName := fields.Type().Field(i).Tag.Get("name")
tagLocation := fields.Type().Field(i).Tag.Get("location")
if tagDefault := fields.Type().Field(i).Tag.Get("default"); tagDefault != "" {
maps[tagLocation][tagName] = tagDefault
}
if tagName != "" && tagLocation != "" && maps[tagLocation] != nil {
switch value := fields.Field(i).Interface().(type) {
case *string:
if value != nil {
maps[tagLocation][tagName] = *value
}
case *int:
if value != nil {
maps[tagLocation][tagName] = strconv.Itoa(int(*value))
}
case *int64:
if value != nil {
maps[tagLocation][tagName] = strconv.FormatInt(int64(*value), 10)
}
case *bool:
case *time.Time:
if value != nil {
formatString := fields.Type().Field(i).Tag.Get("format")
format := ""
switch formatString {
case "RFC 822":
format = convert.RFC822
case "ISO 8601":
format = convert.ISO8601
}
maps[tagLocation][tagName] = convert.TimeToString(*value, format)
}
}
}
}
return nil
}
func (b *BaseBuilder) parseRequestBody() error {
requestData := map[string]interface{}{}
if !b.input.IsValid() {
return nil
}
fields := b.input.Elem()
if !fields.IsValid() {
return nil
}
for i := 0; i < fields.NumField(); i++ {
location := fields.Type().Field(i).Tag.Get("location")
if location == "elements" {
name := fields.Type().Field(i).Tag.Get("name")
requestData[name] = fields.Field(i).Interface()
}
}
if len(requestData) != 0 {
dataValue, err := json.Marshal(requestData)
if err != nil {
return err
}
b.parsedBodyString = string(dataValue)
b.parsedBody = strings.NewReader(b.parsedBodyString)
(*b.parsedHeaders)["Content-Type"] = "application/json"
} else {
value := fields.FieldByName("Body")
if value.IsValid() {
switch value.Interface().(type) {
case string:
if value.String() != "" {
b.parsedBodyString = value.String()
b.parsedBody = strings.NewReader(value.String())
}
case io.Reader:
if value.Interface().(io.Reader) != nil {
b.parsedBody = value.Interface().(io.Reader)
}
}
}
}
return nil
}
func (b *BaseBuilder) parseRequestProperties() error {
propertiesMap := map[string]string{}
b.parsedProperties = &propertiesMap
if b.operation.Properties != nil {
fields := reflect.ValueOf(b.operation.Properties).Elem()
if fields.IsValid() {
for i := 0; i < fields.NumField(); i++ {
switch value := fields.Field(i).Interface().(type) {
case *string:
if value != nil {
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = *value
}
case *int:
if value != nil {
numberString := strconv.Itoa(int(*value))
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = numberString
}
}
}
}
}
return nil
}
func (b *BaseBuilder) parseRequestURL() error {
return nil
}
func (b *BaseBuilder) setupHeaders(httpRequest *http.Request) error {
if b.parsedHeaders != nil {
for headerKey, headerValue := range *b.parsedHeaders {
for _, r := range headerValue {
if r > unicode.MaxASCII {
headerValue = utils.URLQueryEscape(headerValue)
break
}
}
httpRequest.Header.Set(headerKey, headerValue)
}
}
if httpRequest.Header.Get("Content-Length") == "" {
var length int64
switch body := b.parsedBody.(type) {
case nil:
length = 0
case io.Seeker:
//start, err := body.Seek(0, io.SeekStart)
start, err := body.Seek(0, 0)
if err != nil {
return err
}
//end, err := body.Seek(0, io.SeekEnd)
end, err := body.Seek(0, 2)
if err != nil {
return err
}
//body.Seek(0, io.SeekStart)
body.Seek(0, 0)
length = end - start
default:
return errors.New("can not get Content-Length")
}
if length > 0 {
httpRequest.ContentLength = length
httpRequest.Header.Set("Content-Length", strconv.Itoa(int(length)))
} else {
httpRequest.Header.Set("Content-Length", "0")
}
}
length, err := strconv.Atoi(httpRequest.Header.Get("Content-Length"))
if err != nil {
return err
}
httpRequest.ContentLength = int64(length)
if httpRequest.Header.Get("Date") == "" {
httpRequest.Header.Set("Date", convert.TimeToString(time.Now(), convert.RFC822))
}
return nil
}

View File

@@ -0,0 +1,175 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package builder
import (
"bytes"
"crypto/md5"
"encoding/base64"
"fmt"
"io/ioutil"
"mime"
"net/http"
"net/url"
"path"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/pengsrc/go-shared/convert"
"github.com/yunify/qingstor-sdk-go"
"github.com/yunify/qingstor-sdk-go/logger"
"github.com/yunify/qingstor-sdk-go/request/data"
"github.com/yunify/qingstor-sdk-go/utils"
)
// QingStorBuilder is the request builder for QingStor service.
type QingStorBuilder struct {
baseBuilder *BaseBuilder
}
// BuildHTTPRequest builds http request with an operation and an input.
func (qb *QingStorBuilder) BuildHTTPRequest(o *data.Operation, i *reflect.Value) (*http.Request, error) {
qb.baseBuilder = &BaseBuilder{}
qb.baseBuilder.operation = o
qb.baseBuilder.input = i
_, err := qb.baseBuilder.parse()
if err != nil {
return nil, err
}
err = qb.parseURL()
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(qb.baseBuilder.operation.RequestMethod,
qb.baseBuilder.parsedURL, qb.baseBuilder.parsedBody)
if err != nil {
return nil, err
}
err = qb.baseBuilder.setupHeaders(httpRequest)
if err != nil {
return nil, err
}
err = qb.setupHeaders(httpRequest)
if err != nil {
return nil, err
}
logger.Infof(nil, fmt.Sprintf(
"Built QingStor request: [%d] %s",
convert.StringToTimestamp(httpRequest.Header.Get("Date"), convert.RFC822),
httpRequest.URL.String(),
))
logger.Infof(nil, fmt.Sprintf(
"QingStor request headers: [%d] %s",
convert.StringToTimestamp(httpRequest.Header.Get("Date"), convert.RFC822),
fmt.Sprint(httpRequest.Header),
))
if qb.baseBuilder.parsedBodyString != "" {
logger.Infof(nil, fmt.Sprintf(
"QingStor request body string: [%d] %s",
convert.StringToTimestamp(httpRequest.Header.Get("Date"), convert.RFC822),
qb.baseBuilder.parsedBodyString,
))
}
return httpRequest, nil
}
func (qb *QingStorBuilder) parseURL() error {
config := qb.baseBuilder.operation.Config
zone := (*qb.baseBuilder.parsedProperties)["zone"]
port := strconv.Itoa(config.Port)
endpoint := config.Protocol + "://" + config.Host + ":" + port
if zone != "" {
endpoint = config.Protocol + "://" + zone + "." + config.Host + ":" + port
}
requestURI := qb.baseBuilder.operation.RequestURI
for key, value := range *qb.baseBuilder.parsedProperties {
endpoint = strings.Replace(endpoint, "<"+key+">", utils.URLQueryEscape(value), -1)
requestURI = strings.Replace(requestURI, "<"+key+">", utils.URLQueryEscape(value), -1)
}
requestURI = regexp.MustCompile(`/+`).ReplaceAllString(requestURI, "/")
requestURL, err := url.Parse(endpoint + requestURI)
if err != nil {
return err
}
if qb.baseBuilder.parsedQuery != nil {
queryValue := requestURL.Query()
for key, value := range *qb.baseBuilder.parsedQuery {
queryValue.Set(key, value)
}
requestURL.RawQuery = queryValue.Encode()
}
qb.baseBuilder.parsedURL = requestURL.String()
return nil
}
func (qb *QingStorBuilder) setupHeaders(httpRequest *http.Request) error {
method := httpRequest.Method
if method == "POST" || method == "PUT" || method == "DELETE" {
if httpRequest.Header.Get("Content-Type") == "" {
mimeType := mime.TypeByExtension(path.Ext(httpRequest.URL.Path))
if mimeType != "" {
httpRequest.Header.Set("Content-Type", mimeType)
}
}
}
if httpRequest.Header.Get("User-Agent") == "" {
version := fmt.Sprintf(`Go v%s`, strings.Replace(runtime.Version(), "go", "", -1))
system := fmt.Sprintf(`%s_%s_%s`, runtime.GOOS, runtime.GOARCH, runtime.Compiler)
ua := fmt.Sprintf(`qingstor-sdk-go/%s (%s; %s)`, sdk.Version, version, system)
if qb.baseBuilder.operation.Config.AdditionalUserAgent != "" {
ua = fmt.Sprintf(`%s %s`, ua, qb.baseBuilder.operation.Config.AdditionalUserAgent)
}
httpRequest.Header.Set("User-Agent", ua)
}
if s := httpRequest.Header.Get("X-QS-Fetch-Source"); s != "" {
u, err := url.Parse(s)
if err != nil {
return fmt.Errorf("invalid HTTP header value: %s", s)
}
httpRequest.Header.Set("X-QS-Fetch-Source", u.String())
}
if qb.baseBuilder.operation.APIName == "Delete Multiple Objects" {
buffer := &bytes.Buffer{}
buffer.ReadFrom(httpRequest.Body)
httpRequest.Body = ioutil.NopCloser(bytes.NewReader(buffer.Bytes()))
md5Value := md5.Sum(buffer.Bytes())
httpRequest.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5Value[:]))
}
return nil
}

View File

@@ -0,0 +1,22 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package data
// Input defines the interfaces that input should implement.
type Input interface {
Validation
}

View File

@@ -0,0 +1,35 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package data
import (
"github.com/yunify/qingstor-sdk-go/config"
)
// Operation stores information of an operation.
type Operation struct {
Config *config.Config
Properties interface{}
APIName string
ServiceName string
RequestMethod string
RequestURI string
StatusCodes []int
}

View File

@@ -0,0 +1,22 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package data
// Validation defines the validate interface.
type Validation interface {
Validate() error
}

View File

@@ -0,0 +1,53 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package errors
import (
"fmt"
"strings"
)
// ParameterRequiredError indicates that the required parameter is missing.
type ParameterRequiredError struct {
ParameterName string
ParentName string
}
// Error returns the description of ParameterRequiredError.
func (e ParameterRequiredError) Error() string {
return fmt.Sprintf(`"%s" is required in "%s"`, e.ParameterName, e.ParentName)
}
// ParameterValueNotAllowedError indicates that the parameter value is not allowed.
type ParameterValueNotAllowedError struct {
ParameterName string
ParameterValue string
AllowedValues []string
}
// Error returns the description of ParameterValueNotAllowedError.
func (e ParameterValueNotAllowedError) Error() string {
allowedValues := []string{}
for _, value := range e.AllowedValues {
allowedValues = append(allowedValues, "\""+value+"\"")
}
return fmt.Sprintf(
`"%s" value "%s" is not allowed, should be one of %s`,
e.ParameterName,
e.ParameterValue,
strings.Join(allowedValues, ", "))
}

View File

@@ -0,0 +1,36 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package errors
import "fmt"
// QingStorError stores information of an QingStor error response.
type QingStorError struct {
StatusCode int
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id"`
ReferenceURL string `json:"url"`
}
// Error returns the description of QingStor error response.
func (qse QingStorError) Error() string {
return fmt.Sprintf(
"QingStor Error: StatusCode \"%d\", Code \"%s\", Message \"%s\", Request ID \"%s\", Reference URL \"%s\"",
qse.StatusCode, qse.Code, qse.Message, qse.RequestID, qse.ReferenceURL)
}

View File

@@ -0,0 +1,253 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package request
import (
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"time"
"github.com/pengsrc/go-shared/convert"
"github.com/yunify/qingstor-sdk-go/logger"
"github.com/yunify/qingstor-sdk-go/request/builder"
"github.com/yunify/qingstor-sdk-go/request/data"
"github.com/yunify/qingstor-sdk-go/request/signer"
"github.com/yunify/qingstor-sdk-go/request/unpacker"
)
// A Request can build, sign, send and unpack API request.
type Request struct {
Operation *data.Operation
Input *reflect.Value
Output *reflect.Value
HTTPRequest *http.Request
HTTPResponse *http.Response
}
// New create a Request from given Operation, Input and Output.
// It returns a Request.
func New(o *data.Operation, i data.Input, x interface{}) (*Request, error) {
input := reflect.ValueOf(i)
if input.IsValid() && input.Elem().IsValid() {
err := i.Validate()
if err != nil {
return nil, err
}
}
output := reflect.ValueOf(x)
return &Request{
Operation: o,
Input: &input,
Output: &output,
}, nil
}
// Send sends API request.
// It returns error if error occurred.
func (r *Request) Send() error {
err := r.Build()
if err != nil {
return err
}
err = r.Sign()
if err != nil {
return err
}
err = r.Do()
if err != nil {
return err
}
return nil
}
// Build checks and builds the API request.
// It returns error if error occurred.
func (r *Request) Build() error {
err := r.check()
if err != nil {
return err
}
err = r.build()
if err != nil {
return err
}
return nil
}
// Do sends and unpacks the API request.
// It returns error if error occurred.
func (r *Request) Do() error {
err := r.send()
if err != nil {
return err
}
err = r.unpack()
if err != nil {
return err
}
return nil
}
// Sign sign the API request by setting the authorization header.
// It returns error if error occurred.
func (r *Request) Sign() error {
err := r.sign()
if err != nil {
return err
}
return nil
}
// SignQuery sign the API request by appending query string.
// It returns error if error occurred.
func (r *Request) SignQuery(timeoutSeconds int) error {
err := r.signQuery(int(time.Now().Unix()) + timeoutSeconds)
if err != nil {
return err
}
return nil
}
// ApplySignature applies the Authorization header.
// It returns error if error occurred.
func (r *Request) ApplySignature(authorization string) error {
r.HTTPRequest.Header.Set("Authorization", authorization)
return nil
}
// ApplyQuerySignature applies the query signature.
// It returns error if error occurred.
func (r *Request) ApplyQuerySignature(accessKeyID string, expires int, signature string) error {
queryValue := r.HTTPRequest.URL.Query()
queryValue.Set("access_key_id", accessKeyID)
queryValue.Set("expires", strconv.Itoa(expires))
queryValue.Set("signature", signature)
r.HTTPRequest.URL.RawQuery = queryValue.Encode()
return nil
}
func (r *Request) check() error {
if r.Operation.Config.AccessKeyID == "" {
return errors.New("access key not provided")
}
if r.Operation.Config.SecretAccessKey == "" {
return errors.New("secret access key not provided")
}
return nil
}
func (r *Request) build() error {
b := &builder.QingStorBuilder{}
httpRequest, err := b.BuildHTTPRequest(r.Operation, r.Input)
if err != nil {
return err
}
r.HTTPRequest = httpRequest
return nil
}
func (r *Request) sign() error {
s := &signer.QingStorSigner{
AccessKeyID: r.Operation.Config.AccessKeyID,
SecretAccessKey: r.Operation.Config.SecretAccessKey,
}
err := s.WriteSignature(r.HTTPRequest)
if err != nil {
return err
}
return nil
}
func (r *Request) signQuery(expires int) error {
s := &signer.QingStorSigner{
AccessKeyID: r.Operation.Config.AccessKeyID,
SecretAccessKey: r.Operation.Config.SecretAccessKey,
}
err := s.WriteQuerySignature(r.HTTPRequest, expires)
if err != nil {
return err
}
return nil
}
func (r *Request) send() error {
var response *http.Response
var err error
if r.Operation.Config.Connection == nil {
r.Operation.Config.InitHTTPClient()
}
retries := r.Operation.Config.ConnectionRetries + 1
for {
if retries > 0 {
logger.Infof(nil, fmt.Sprintf(
"Sending request: [%d] %s %s",
convert.StringToTimestamp(r.HTTPRequest.Header.Get("Date"), convert.RFC822),
r.Operation.RequestMethod,
r.HTTPRequest.Host,
))
response, err = r.Operation.Config.Connection.Do(r.HTTPRequest)
if err == nil {
retries = 0
} else {
retries--
time.Sleep(time.Second)
}
} else {
break
}
}
if err != nil {
return err
}
r.HTTPResponse = response
return nil
}
func (r *Request) unpack() error {
u := &unpacker.QingStorUnpacker{}
err := u.UnpackHTTPRequest(r.Operation, r.HTTPResponse, r.Output)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,266 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package signer
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"sort"
"strings"
"github.com/pengsrc/go-shared/convert"
"github.com/yunify/qingstor-sdk-go/logger"
"github.com/yunify/qingstor-sdk-go/utils"
)
// QingStorSigner is the http request signer for QingStor service.
type QingStorSigner struct {
AccessKeyID string
SecretAccessKey string
}
// WriteSignature calculates signature and write it to http request header.
func (qss *QingStorSigner) WriteSignature(request *http.Request) error {
authorization, err := qss.BuildSignature(request)
if err != nil {
return err
}
request.Header.Set("Authorization", authorization)
return nil
}
// WriteQuerySignature calculates signature and write it to http request url.
func (qss *QingStorSigner) WriteQuerySignature(request *http.Request, expires int) error {
query, err := qss.BuildQuerySignature(request, expires)
if err != nil {
return err
}
if request.URL.RawQuery != "" {
query = "?" + request.URL.RawQuery + "&" + query
} else {
query = "?" + query
}
newRequest, err := http.NewRequest(request.Method,
request.URL.Scheme+"://"+request.URL.Host+utils.URLQueryEscape(request.URL.Path)+query, nil)
if err != nil {
return err
}
request.URL = newRequest.URL
return nil
}
// BuildSignature calculates the signature string.
func (qss *QingStorSigner) BuildSignature(request *http.Request) (string, error) {
stringToSign, err := qss.BuildStringToSign(request)
if err != nil {
return "", err
}
h := hmac.New(sha256.New, []byte(qss.SecretAccessKey))
h.Write([]byte(stringToSign))
signature := strings.TrimSpace(base64.StdEncoding.EncodeToString(h.Sum(nil)))
authorization := "QS " + qss.AccessKeyID + ":" + signature
logger.Debugf(nil, fmt.Sprintf(
"QingStor authorization: [%d] %s",
convert.StringToTimestamp(request.Header.Get("Date"), convert.RFC822),
authorization,
))
return authorization, nil
}
// BuildQuerySignature calculates the signature string for query.
func (qss *QingStorSigner) BuildQuerySignature(request *http.Request, expires int) (string, error) {
stringToSign, err := qss.BuildQueryStringToSign(request, expires)
if err != nil {
return "", err
}
h := hmac.New(sha256.New, []byte(qss.SecretAccessKey))
h.Write([]byte(stringToSign))
signature := strings.TrimSpace(base64.StdEncoding.EncodeToString(h.Sum(nil)))
signature = utils.URLQueryEscape(signature)
query := fmt.Sprintf(
"access_key_id=%s&expires=%d&signature=%s",
qss.AccessKeyID, expires, signature,
)
logger.Debugf(nil, fmt.Sprintf(
"QingStor query signature: [%d] %s",
convert.StringToTimestamp(request.Header.Get("Date"), convert.RFC822),
query,
))
return query, nil
}
// BuildStringToSign build the string to sign.
func (qss *QingStorSigner) BuildStringToSign(request *http.Request) (string, error) {
date := request.Header.Get("Date")
if request.Header.Get("X-QS-Date") != "" {
date = ""
}
stringToSign := fmt.Sprintf(
"%s\n%s\n%s\n%s\n",
request.Method,
request.Header.Get("Content-MD5"),
request.Header.Get("Content-Type"),
date,
)
stringToSign += qss.buildCanonicalizedHeaders(request)
canonicalizedResource, err := qss.buildCanonicalizedResource(request)
if err != nil {
return "", err
}
stringToSign += canonicalizedResource
logger.Debugf(nil, fmt.Sprintf(
"QingStor string to sign: [%d] %s",
convert.StringToTimestamp(request.Header.Get("Date"), convert.RFC822),
stringToSign,
))
return stringToSign, nil
}
// BuildQueryStringToSign build the string to sign for query.
func (qss *QingStorSigner) BuildQueryStringToSign(request *http.Request, expires int) (string, error) {
stringToSign := fmt.Sprintf(
"%s\n%s\n%s\n%d\n",
request.Method,
request.Header.Get("Content-MD5"),
request.Header.Get("Content-Type"),
expires,
)
stringToSign += qss.buildCanonicalizedHeaders(request)
canonicalizedResource, err := qss.buildCanonicalizedResource(request)
if err != nil {
return "", err
}
stringToSign += canonicalizedResource
logger.Debugf(nil, fmt.Sprintf(
"QingStor query string to sign: [%d] %s",
convert.StringToTimestamp(request.Header.Get("Date"), convert.RFC822),
stringToSign,
))
return stringToSign, nil
}
func (qss *QingStorSigner) buildCanonicalizedHeaders(request *http.Request) string {
keys := []string{}
for key := range request.Header {
if strings.HasPrefix(strings.ToLower(key), "x-qs-") {
keys = append(keys, strings.TrimSpace(strings.ToLower(key)))
}
}
sort.Strings(keys)
canonicalizedHeaders := ""
for _, key := range keys {
canonicalizedHeaders += key + ":" + strings.TrimSpace(request.Header.Get(key)) + "\n"
}
return canonicalizedHeaders
}
func (qss *QingStorSigner) buildCanonicalizedResource(request *http.Request) (string, error) {
path := utils.URLQueryEscape(request.URL.Path)
query := request.URL.Query()
keys := []string{}
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
parts := []string{}
for _, key := range keys {
values := query[key]
if qss.queryToSign(key) {
if len(values) > 0 {
if values[0] != "" {
value := strings.TrimSpace(strings.Join(values, ""))
value, err := utils.URLQueryUnescape(value)
if err != nil {
return "", err
}
parts = append(parts, key+"="+value)
} else {
parts = append(parts, key)
}
} else {
parts = append(parts, key)
}
}
}
joinedParts := strings.Join(parts, "&")
if joinedParts != "" {
path = path + "?" + joinedParts
}
logger.Debugf(nil, fmt.Sprintf(
"QingStor canonicalized resource: [%d] %s",
convert.StringToTimestamp(request.Header.Get("Date"), convert.RFC822),
path,
))
return path, nil
}
func (qss *QingStorSigner) queryToSign(key string) bool {
keysMap := map[string]bool{
"acl": true,
"cors": true,
"delete": true,
"mirror": true,
"part_number": true,
"policy": true,
"stats": true,
"upload_id": true,
"uploads": true,
"image": true,
"lifecycle": true,
"logging": true,
"response-expires": true,
"response-cache-control": true,
"response-content-type": true,
"response-content-language": true,
"response-content-encoding": true,
"response-content-disposition": true,
}
return keysMap[key]
}

View File

@@ -0,0 +1,220 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package unpacker
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"reflect"
"strconv"
"time"
"github.com/pengsrc/go-shared/convert"
"github.com/yunify/qingstor-sdk-go/logger"
"github.com/yunify/qingstor-sdk-go/request/data"
)
// BaseUnpacker is the base unpacker for all services.
type BaseUnpacker struct {
operation *data.Operation
httpResponse *http.Response
output *reflect.Value
}
// UnpackHTTPRequest unpacks http response with an operation and an output.
func (b *BaseUnpacker) UnpackHTTPRequest(o *data.Operation, r *http.Response, x *reflect.Value) error {
b.operation = o
b.httpResponse = r
b.output = x
err := b.exposeStatusCode()
if err != nil {
return err
}
err = b.parseResponseHeaders()
if err != nil {
return err
}
err = b.parseResponseBody()
if err != nil {
return err
}
err = b.parseResponseElements()
if err != nil {
return err
}
return nil
}
func (b *BaseUnpacker) exposeStatusCode() error {
value := b.output.Elem().FieldByName("StatusCode")
if value.IsValid() {
switch value.Interface().(type) {
case *int:
logger.Infof(nil, fmt.Sprintf(
"QingStor response status code: [%d] %d",
convert.StringToTimestamp(b.httpResponse.Header.Get("Date"), convert.RFC822),
b.httpResponse.StatusCode,
))
value.Set(reflect.ValueOf(&b.httpResponse.StatusCode))
}
}
return nil
}
func (b *BaseUnpacker) parseResponseHeaders() error {
logger.Infof(nil, fmt.Sprintf(
"QingStor response headers: [%d] %s",
convert.StringToTimestamp(b.httpResponse.Header.Get("Date"), convert.RFC822),
fmt.Sprint(b.httpResponse.Header),
))
if b.isResponseRight() {
fields := b.output.Elem()
for i := 0; i < fields.NumField(); i++ {
field := fields.Field(i)
fieldTagName := fields.Type().Field(i).Tag.Get("name")
fieldTagLocation := fields.Type().Field(i).Tag.Get("location")
fieldStringValue := b.httpResponse.Header.Get(fieldTagName)
// Empty value should be ignored.
if fieldStringValue == "" {
continue
}
if fieldTagName != "" && fieldTagLocation == "headers" {
switch field.Interface().(type) {
case *string:
field.Set(reflect.ValueOf(&fieldStringValue))
case *int:
intValue, err := strconv.Atoi(fieldStringValue)
if err != nil {
return err
}
field.Set(reflect.ValueOf(&intValue))
case *int64:
int64Value, err := strconv.ParseInt(fieldStringValue, 10, 64)
if err != nil {
return err
}
field.Set(reflect.ValueOf(&int64Value))
case *bool:
case *time.Time:
formatString := fields.Type().Field(i).Tag.Get("format")
format := ""
switch formatString {
case "RFC 822":
format = convert.RFC822
case "ISO 8601":
format = convert.ISO8601
}
timeValue, err := convert.StringToTime(fieldStringValue, format)
if err != nil {
return err
}
field.Set(reflect.ValueOf(&timeValue))
}
}
}
}
return nil
}
func (b *BaseUnpacker) parseResponseBody() error {
if b.isResponseRight() {
value := b.output.Elem().FieldByName("Body")
if value.IsValid() {
switch value.Type().String() {
case "string":
buffer := &bytes.Buffer{}
buffer.ReadFrom(b.httpResponse.Body)
b.httpResponse.Body.Close()
logger.Infof(nil, fmt.Sprintf(
"QingStor response body string: [%d] %s",
convert.StringToTimestamp(b.httpResponse.Header.Get("Date"), convert.RFC822),
string(buffer.Bytes()),
))
value.SetString(string(buffer.Bytes()))
case "io.ReadCloser":
value.Set(reflect.ValueOf(b.httpResponse.Body))
}
}
}
return nil
}
func (b *BaseUnpacker) parseResponseElements() error {
if !b.isResponseRight() {
return nil
}
// Do not parse GetObject and ImageProcess's body.
if b.operation.APIName == "GET Object" ||
b.operation.APIName == "Image Process" {
return nil
}
if b.httpResponse.Header.Get("Content-Type") != "application/json" {
return nil
}
buffer := &bytes.Buffer{}
buffer.ReadFrom(b.httpResponse.Body)
b.httpResponse.Body.Close()
if buffer.Len() == 0 {
return nil
}
logger.Infof(nil, fmt.Sprintf(
"QingStor response body string: [%d] %s",
convert.StringToTimestamp(b.httpResponse.Header.Get("Date"), convert.RFC822),
string(buffer.Bytes()),
))
err := json.Unmarshal(buffer.Bytes(), b.output.Interface())
if err != nil {
return err
}
return nil
}
func (b *BaseUnpacker) isResponseRight() bool {
rightStatusCodes := b.operation.StatusCodes
if len(rightStatusCodes) == 0 {
rightStatusCodes = append(rightStatusCodes, 200)
}
flag := false
for _, statusCode := range rightStatusCodes {
if statusCode == b.httpResponse.StatusCode {
flag = true
}
}
return flag
}

View File

@@ -0,0 +1,84 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package unpacker
import (
"bytes"
"encoding/json"
"net/http"
"reflect"
"github.com/yunify/qingstor-sdk-go/request/data"
"github.com/yunify/qingstor-sdk-go/request/errors"
)
// QingStorUnpacker is the response unpacker for QingStor service.
type QingStorUnpacker struct {
baseUnpacker *BaseUnpacker
}
// UnpackHTTPRequest unpack the http response with an operation, http response and an output.
func (qu *QingStorUnpacker) UnpackHTTPRequest(o *data.Operation, r *http.Response, x *reflect.Value) error {
qu.baseUnpacker = &BaseUnpacker{}
err := qu.baseUnpacker.UnpackHTTPRequest(o, r, x)
if err != nil {
return err
}
err = qu.parseError()
if err != nil {
return err
}
// Close body for every API except GetObject and ImageProcess.
if o.APIName != "GET Object" && o.APIName != "Image Process" && r.Body != nil {
err = r.Body.Close()
if err != nil {
return err
}
r.Body = nil
}
return nil
}
func (qu *QingStorUnpacker) parseError() error {
if !qu.baseUnpacker.isResponseRight() {
if qu.baseUnpacker.httpResponse.Header.Get("Content-Type") == "application/json" {
buffer := &bytes.Buffer{}
buffer.ReadFrom(qu.baseUnpacker.httpResponse.Body)
qu.baseUnpacker.httpResponse.Body.Close()
qsError := &errors.QingStorError{}
if buffer.Len() > 0 {
err := json.Unmarshal(buffer.Bytes(), qsError)
if err != nil {
return err
}
}
qsError.StatusCode = qu.baseUnpacker.httpResponse.StatusCode
if qsError.RequestID == "" {
qsError.RequestID = qu.baseUnpacker.httpResponse.Header.Get(http.CanonicalHeaderKey("X-QS-Request-ID"))
}
return qsError
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,266 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package service
import (
"time"
)
// String returns a pointer to the given string value.
func String(v string) *string {
return &v
}
// StringValue returns the value of the given string pointer or
// "" if the pointer is nil.
func StringValue(v *string) string {
if v != nil {
return *v
}
return ""
}
// StringSlice converts a slice of string values into a slice of
// string pointers
func StringSlice(src []string) []*string {
dst := make([]*string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// StringValueSlice converts a slice of string pointers into a slice of
// string values
func StringValueSlice(src []*string) []string {
dst := make([]string, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// StringMap converts a string map of string values into a string
// map of string pointers
func StringMap(src map[string]string) map[string]*string {
dst := make(map[string]*string)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// StringValueMap converts a string map of string pointers into a string
// map of string values
func StringValueMap(src map[string]*string) map[string]string {
dst := make(map[string]string)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Bool returns a pointer to the given bool value.
func Bool(v bool) *bool {
return &v
}
// BoolValue returns the value of the given bool pointer or
// false if the pointer is nil.
func BoolValue(v *bool) bool {
if v != nil {
return *v
}
return false
}
// BoolSlice converts a slice of bool values into a slice of
// bool pointers
func BoolSlice(src []bool) []*bool {
dst := make([]*bool, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// BoolValueSlice converts a slice of bool pointers into a slice of
// bool values
func BoolValueSlice(src []*bool) []bool {
dst := make([]bool, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// BoolMap converts a string map of bool values into a string
// map of bool pointers
func BoolMap(src map[string]bool) map[string]*bool {
dst := make(map[string]*bool)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// BoolValueMap converts a string map of bool pointers into a string
// map of bool values
func BoolValueMap(src map[string]*bool) map[string]bool {
dst := make(map[string]bool)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int returns a pointer to the given int value.
func Int(v int) *int {
return &v
}
// IntValue returns the value of the given int pointer or
// 0 if the pointer is nil.
func IntValue(v *int) int {
if v != nil {
return *v
}
return 0
}
// IntSlice converts a slice of int values into a slice of
// int pointers
func IntSlice(src []int) []*int {
dst := make([]*int, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// IntValueSlice converts a slice of int pointers into a slice of
// int values
func IntValueSlice(src []*int) []int {
dst := make([]int, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// IntMap converts a string map of int values into a string
// map of int pointers
func IntMap(src map[string]int) map[string]*int {
dst := make(map[string]*int)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// IntValueMap converts a string map of int pointers into a string
// map of int values
func IntValueMap(src map[string]*int) map[string]int {
dst := make(map[string]int)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Time returns a pointer to the given time.Time value.
func Time(v time.Time) *time.Time {
return &v
}
// TimeValue returns the value of the given time.Time pointer or
// time.Time{} if the pointer is nil.
func TimeValue(v *time.Time) time.Time {
if v != nil {
return *v
}
return time.Time{}
}
// TimeUnixMilli returns a Unix timestamp in milliseconds from "January 1, 1970 UTC".
// The result is undefined if the Unix time cannot be represented by an int64.
// Which includes calling TimeUnixMilli on a zero Time is undefined.
//
// See Go stdlib https://golang.org/pkg/time/#Time.UnixNano for more information.
func TimeUnixMilli(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond/time.Nanosecond)
}
// TimeSlice converts a slice of time.Time values into a slice of
// time.Time pointers
func TimeSlice(src []time.Time) []*time.Time {
dst := make([]*time.Time, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// TimeValueSlice converts a slice of time.Time pointers into a slice of
// time.Time values
func TimeValueSlice(src []*time.Time) []time.Time {
dst := make([]time.Time, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// TimeMap converts a string map of time.Time values into a string
// map of time.Time pointers
func TimeMap(src map[string]time.Time) map[string]*time.Time {
dst := make(map[string]*time.Time)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// TimeValueMap converts a string map of time.Time pointers into a string
// map of time.Time values
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
dst := make(map[string]time.Time)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
// Package service provides QingStor Service API (API Version 2016-01-06)
package service
import (
"net/http"
"github.com/yunify/qingstor-sdk-go/config"
"github.com/yunify/qingstor-sdk-go/request"
"github.com/yunify/qingstor-sdk-go/request/data"
)
var _ http.Header
// Service QingStor provides low-cost and reliable online storage service with unlimited storage space, high read and write performance, high reliability and data safety, fine-grained access control, and easy to use API.
type Service struct {
Config *config.Config
}
// Init initializes a new service.
func Init(c *config.Config) (*Service, error) {
return &Service{Config: c}, nil
}
// ListBuckets does Retrieve the bucket list.
// Documentation URL: https://docs.qingcloud.com/qingstor/api/service/get.html
func (s *Service) ListBuckets(input *ListBucketsInput) (*ListBucketsOutput, error) {
r, x, err := s.ListBucketsRequest(input)
if err != nil {
return x, err
}
err = r.Send()
if err != nil {
return nil, err
}
requestID := r.HTTPResponse.Header.Get(http.CanonicalHeaderKey("X-QS-Request-ID"))
x.RequestID = &requestID
return x, err
}
// ListBucketsRequest creates request and output object of ListBuckets.
func (s *Service) ListBucketsRequest(input *ListBucketsInput) (*request.Request, *ListBucketsOutput, error) {
if input == nil {
input = &ListBucketsInput{}
}
o := &data.Operation{
Config: s.Config,
APIName: "Get Service",
RequestMethod: "GET",
RequestURI: "/",
StatusCodes: []int{
200, // OK
},
}
x := &ListBucketsOutput{}
r, err := request.New(o, input, x)
if err != nil {
return nil, nil, err
}
return r, x, nil
}
// ListBucketsInput presents input for ListBuckets.
type ListBucketsInput struct {
// Limits results to buckets that in the location
Location *string `json:"Location,omitempty" name:"Location" location:"headers"`
}
// Validate validates the input for ListBuckets.
func (v *ListBucketsInput) Validate() error {
return nil
}
// ListBucketsOutput presents output for ListBuckets.
type ListBucketsOutput struct {
StatusCode *int `location:"statusCode"`
RequestID *string `location:"requestID"`
// Buckets information
Buckets []*BucketType `json:"buckets,omitempty" name:"buckets" location:"elements"`
// Bucket count
Count *int `json:"count,omitempty" name:"count" location:"elements"`
}

View File

@@ -0,0 +1,463 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package service
import (
"fmt"
"time"
"github.com/yunify/qingstor-sdk-go/request/errors"
)
// Properties presents the service properties.
type Properties struct {
// Bucket name
BucketName *string `json:"bucket-name" name:"bucket-name"` // Required
// Object key
ObjectKey *string `json:"object-key" name:"object-key"` // Required
// QingCloud Zone ID
Zone *string `json:"zone" name:"zone"`
}
// ACLType presents ACL.
type ACLType struct {
Grantee *GranteeType `json:"grantee" name:"grantee"` // Required
// Permission for this grantee
// Permission's available values: READ, WRITE, FULL_CONTROL
Permission *string `json:"permission" name:"permission"` // Required
}
// Validate validates the ACL.
func (v *ACLType) Validate() error {
if v.Grantee != nil {
if err := v.Grantee.Validate(); err != nil {
return err
}
}
if v.Grantee == nil {
return errors.ParameterRequiredError{
ParameterName: "Grantee",
ParentName: "ACL",
}
}
if v.Permission == nil {
return errors.ParameterRequiredError{
ParameterName: "Permission",
ParentName: "ACL",
}
}
if v.Permission != nil {
permissionValidValues := []string{"READ", "WRITE", "FULL_CONTROL"}
permissionParameterValue := fmt.Sprint(*v.Permission)
permissionIsValid := false
for _, value := range permissionValidValues {
if value == permissionParameterValue {
permissionIsValid = true
}
}
if !permissionIsValid {
return errors.ParameterValueNotAllowedError{
ParameterName: "Permission",
ParameterValue: permissionParameterValue,
AllowedValues: permissionValidValues,
}
}
}
return nil
}
// BucketType presents Bucket.
type BucketType struct {
// Created time of the bucket
Created *time.Time `json:"created,omitempty" name:"created" format:"ISO 8601"`
// QingCloud Zone ID
Location *string `json:"location,omitempty" name:"location"`
// Bucket name
Name *string `json:"name,omitempty" name:"name"`
// URL to access the bucket
URL *string `json:"url,omitempty" name:"url"`
}
// Validate validates the Bucket.
func (v *BucketType) Validate() error {
return nil
}
// ConditionType presents Condition.
type ConditionType struct {
IPAddress *IPAddressType `json:"ip_address,omitempty" name:"ip_address"`
IsNull *IsNullType `json:"is_null,omitempty" name:"is_null"`
NotIPAddress *NotIPAddressType `json:"not_ip_address,omitempty" name:"not_ip_address"`
StringLike *StringLikeType `json:"string_like,omitempty" name:"string_like"`
StringNotLike *StringNotLikeType `json:"string_not_like,omitempty" name:"string_not_like"`
}
// Validate validates the Condition.
func (v *ConditionType) Validate() error {
if v.IPAddress != nil {
if err := v.IPAddress.Validate(); err != nil {
return err
}
}
if v.IsNull != nil {
if err := v.IsNull.Validate(); err != nil {
return err
}
}
if v.NotIPAddress != nil {
if err := v.NotIPAddress.Validate(); err != nil {
return err
}
}
if v.StringLike != nil {
if err := v.StringLike.Validate(); err != nil {
return err
}
}
if v.StringNotLike != nil {
if err := v.StringNotLike.Validate(); err != nil {
return err
}
}
return nil
}
// CORSRuleType presents CORSRule.
type CORSRuleType struct {
// Allowed headers
AllowedHeaders []*string `json:"allowed_headers,omitempty" name:"allowed_headers"`
// Allowed methods
AllowedMethods []*string `json:"allowed_methods" name:"allowed_methods"` // Required
// Allowed origin
AllowedOrigin *string `json:"allowed_origin" name:"allowed_origin"` // Required
// Expose headers
ExposeHeaders []*string `json:"expose_headers,omitempty" name:"expose_headers"`
// Max age seconds
MaxAgeSeconds *int `json:"max_age_seconds,omitempty" name:"max_age_seconds"`
}
// Validate validates the CORSRule.
func (v *CORSRuleType) Validate() error {
if len(v.AllowedMethods) == 0 {
return errors.ParameterRequiredError{
ParameterName: "AllowedMethods",
ParentName: "CORSRule",
}
}
if v.AllowedOrigin == nil {
return errors.ParameterRequiredError{
ParameterName: "AllowedOrigin",
ParentName: "CORSRule",
}
}
return nil
}
// GranteeType presents Grantee.
type GranteeType struct {
// Grantee user ID
ID *string `json:"id,omitempty" name:"id"`
// Grantee group name
Name *string `json:"name,omitempty" name:"name"`
// Grantee type
// Type's available values: user, group
Type *string `json:"type" name:"type"` // Required
}
// Validate validates the Grantee.
func (v *GranteeType) Validate() error {
if v.Type == nil {
return errors.ParameterRequiredError{
ParameterName: "Type",
ParentName: "Grantee",
}
}
if v.Type != nil {
typeValidValues := []string{"user", "group"}
typeParameterValue := fmt.Sprint(*v.Type)
typeIsValid := false
for _, value := range typeValidValues {
if value == typeParameterValue {
typeIsValid = true
}
}
if !typeIsValid {
return errors.ParameterValueNotAllowedError{
ParameterName: "Type",
ParameterValue: typeParameterValue,
AllowedValues: typeValidValues,
}
}
}
return nil
}
// IPAddressType presents IPAddress.
type IPAddressType struct {
// Source IP
SourceIP []*string `json:"source_ip,omitempty" name:"source_ip"`
}
// Validate validates the IPAddress.
func (v *IPAddressType) Validate() error {
return nil
}
// IsNullType presents IsNull.
type IsNullType struct {
// Refer url
Referer *bool `json:"Referer,omitempty" name:"Referer"`
}
// Validate validates the IsNull.
func (v *IsNullType) Validate() error {
return nil
}
// KeyType presents Key.
type KeyType struct {
// Object created time
Created *time.Time `json:"created,omitempty" name:"created" format:"ISO 8601"`
// Whether this key is encrypted
Encrypted *bool `json:"encrypted,omitempty" name:"encrypted"`
// MD5sum of the object
Etag *string `json:"etag,omitempty" name:"etag"`
// Object key
Key *string `json:"key,omitempty" name:"key"`
// MIME type of the object
MimeType *string `json:"mime_type,omitempty" name:"mime_type"`
// Last modified time in unix time format
Modified *int `json:"modified,omitempty" name:"modified"`
// Object content size
Size *int64 `json:"size,omitempty" name:"size"`
}
// Validate validates the Key.
func (v *KeyType) Validate() error {
return nil
}
// KeyDeleteErrorType presents KeyDeleteError.
type KeyDeleteErrorType struct {
// Error code
Code *string `json:"code,omitempty" name:"code"`
// Object key
Key *string `json:"key,omitempty" name:"key"`
// Error message
Message *string `json:"message,omitempty" name:"message"`
}
// Validate validates the KeyDeleteError.
func (v *KeyDeleteErrorType) Validate() error {
return nil
}
// NotIPAddressType presents NotIPAddress.
type NotIPAddressType struct {
// Source IP
SourceIP []*string `json:"source_ip,omitempty" name:"source_ip"`
}
// Validate validates the NotIPAddress.
func (v *NotIPAddressType) Validate() error {
return nil
}
// ObjectPartType presents ObjectPart.
type ObjectPartType struct {
// Object part created time
Created *time.Time `json:"created,omitempty" name:"created" format:"ISO 8601"`
// MD5sum of the object part
Etag *string `json:"etag,omitempty" name:"etag"`
// Object part number
PartNumber *int `json:"part_number" name:"part_number" default:"0"` // Required
// Object part size
Size *int64 `json:"size,omitempty" name:"size"`
}
// Validate validates the ObjectPart.
func (v *ObjectPartType) Validate() error {
if v.PartNumber == nil {
return errors.ParameterRequiredError{
ParameterName: "PartNumber",
ParentName: "ObjectPart",
}
}
return nil
}
// OwnerType presents Owner.
type OwnerType struct {
// User ID
ID *string `json:"id,omitempty" name:"id"`
// Username
Name *string `json:"name,omitempty" name:"name"`
}
// Validate validates the Owner.
func (v *OwnerType) Validate() error {
return nil
}
// StatementType presents Statement.
type StatementType struct {
// QingStor API methods
Action []*string `json:"action" name:"action"` // Required
Condition *ConditionType `json:"condition,omitempty" name:"condition"`
// Statement effect
// Effect's available values: allow, deny
Effect *string `json:"effect" name:"effect"` // Required
// Bucket policy id, must be unique
ID *string `json:"id" name:"id"` // Required
// The resources to apply bucket policy
Resource []*string `json:"resource,omitempty" name:"resource"`
// The user to apply bucket policy
User []*string `json:"user" name:"user"` // Required
}
// Validate validates the Statement.
func (v *StatementType) Validate() error {
if len(v.Action) == 0 {
return errors.ParameterRequiredError{
ParameterName: "Action",
ParentName: "Statement",
}
}
if v.Condition != nil {
if err := v.Condition.Validate(); err != nil {
return err
}
}
if v.Effect == nil {
return errors.ParameterRequiredError{
ParameterName: "Effect",
ParentName: "Statement",
}
}
if v.Effect != nil {
effectValidValues := []string{"allow", "deny"}
effectParameterValue := fmt.Sprint(*v.Effect)
effectIsValid := false
for _, value := range effectValidValues {
if value == effectParameterValue {
effectIsValid = true
}
}
if !effectIsValid {
return errors.ParameterValueNotAllowedError{
ParameterName: "Effect",
ParameterValue: effectParameterValue,
AllowedValues: effectValidValues,
}
}
}
if v.ID == nil {
return errors.ParameterRequiredError{
ParameterName: "ID",
ParentName: "Statement",
}
}
if len(v.User) == 0 {
return errors.ParameterRequiredError{
ParameterName: "User",
ParentName: "Statement",
}
}
return nil
}
// StringLikeType presents StringLike.
type StringLikeType struct {
// Refer url
Referer []*string `json:"Referer,omitempty" name:"Referer"`
}
// Validate validates the StringLike.
func (v *StringLikeType) Validate() error {
return nil
}
// StringNotLikeType presents StringNotLike.
type StringNotLikeType struct {
// Refer url
Referer []*string `json:"Referer,omitempty" name:"Referer"`
}
// Validate validates the StringNotLike.
func (v *StringNotLikeType) Validate() error {
return nil
}
// UploadsType presents Uploads.
type UploadsType struct {
// Object part created time
Created *time.Time `json:"created,omitempty" name:"created" format:"ISO 8601"`
// Object key
Key *string `json:"key,omitempty" name:"key"`
// Object upload id
UploadID *string `json:"upload_id,omitempty" name:"upload_id"`
}
// Validate validates the Uploads.
func (v *UploadsType) Validate() error {
return nil
}

View File

@@ -0,0 +1,136 @@
package utils
import (
"context"
"net"
"sync"
"time"
)
var connPool sync.Pool
type netConn net.Conn
type netDialer net.Dialer
// Dialer is wrapped dialer provided by qingstor go sdk.
//
// We provide this dialer wrapper ReadTimeout & WriteTimeout attributes into connection object.
// This timeout is for individual buffer I/O operation like other language (python, perl... etc),
// so don't bother with SetDeadline or stupid nethttp.Client timeout.
type Dialer struct {
*net.Dialer
ReadTimeout time.Duration
WriteTimeout time.Duration
}
// NewDialer will create a new dialer.
func NewDialer(connTimeout, readTimeout, writeTimeout time.Duration) *Dialer {
d := &net.Dialer{
DualStack: false,
Timeout: connTimeout,
}
return &Dialer{d, readTimeout, writeTimeout}
}
// Dial connects to the address on the named network.
func (d *Dialer) Dial(network, addr string) (net.Conn, error) {
c, err := d.Dialer.Dial(network, addr)
if err != nil {
return nil, err
}
conn := NewConn(c)
conn.readTimeout = d.ReadTimeout
conn.writeTimeout = d.WriteTimeout
return conn, nil
}
// DialContext connects to the address on the named network using
// the provided context.
func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
c, err := d.Dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
conn := NewConn(c)
conn.readTimeout = d.ReadTimeout
conn.writeTimeout = d.WriteTimeout
return conn, nil
}
// Conn is a generic stream-oriented network connection.
type Conn struct {
netConn
readTimeout time.Duration
writeTimeout time.Duration
timeoutFunc func() bool
}
// NewConn will create a new conn.
func NewConn(c netConn) *Conn {
conn, ok := c.(*Conn)
if ok {
return conn
}
conn, ok = connPool.Get().(*Conn)
if !ok {
conn = new(Conn)
}
conn.netConn = c
return conn
}
// SetReadTimeout will set the conn's read timeout.
func (c *Conn) SetReadTimeout(d time.Duration) {
c.readTimeout = d
}
// SetWriteTimeout will set the conn's write timeout.
func (c *Conn) SetWriteTimeout(d time.Duration) {
c.writeTimeout = d
}
// Read will read from the conn.
func (c Conn) Read(buf []byte) (n int, err error) {
if c.readTimeout > 0 {
c.SetDeadline(time.Now().Add(c.readTimeout))
}
n, err = c.netConn.Read(buf)
if c.readTimeout > 0 {
c.SetDeadline(time.Time{}) // clear timeout
}
return
}
// Write will write into the conn.
func (c Conn) Write(buf []byte) (n int, err error) {
if c.writeTimeout > 0 {
c.SetDeadline(time.Now().Add(c.writeTimeout))
}
n, err = c.netConn.Write(buf)
if c.writeTimeout > 0 {
c.SetDeadline(time.Time{})
}
return
}
// Close will close the conn.
func (c Conn) Close() (err error) {
if c.netConn == nil {
return nil
}
err = c.netConn.Close()
connPool.Put(c)
c.netConn = nil
c.readTimeout = 0
c.writeTimeout = 0
return
}
// IsTimeoutError will check whether the err is a timeout error.
func IsTimeoutError(err error) bool {
e, ok := err.(net.Error)
if ok {
return e.Timeout()
}
return false
}

View File

@@ -0,0 +1,23 @@
package utils
import (
"net/url"
"strings"
)
// URLQueryEscape escapes the original string.
func URLQueryEscape(origin string) string {
escaped := url.QueryEscape(origin)
escaped = strings.Replace(escaped, "%2F", "/", -1)
escaped = strings.Replace(escaped, "%3D", "=", -1)
escaped = strings.Replace(escaped, "+", "%20", -1)
return escaped
}
// URLQueryUnescape unescapes the escaped string.
func URLQueryUnescape(escaped string) (string, error) {
escaped = strings.Replace(escaped, "/", "%2F", -1)
escaped = strings.Replace(escaped, "=", "%3D", -1)
escaped = strings.Replace(escaped, "%20", " ", -1)
return url.QueryUnescape(escaped)
}

View File

@@ -0,0 +1,23 @@
// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
// Package sdk is the official QingStor SDK for the Go programming language.
//
// https://github.com/yunify/qingstor-sdk-go
package sdk
// Version number.
const Version = "2.2.14"