restic/vendor/github.com/Azure/azure-sdk-for-go/management/http.go

207 lines
6.2 KiB
Go

// +build go1.7
package management
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"crypto/tls"
"fmt"
"net/http"
)
const (
msVersionHeader = "x-ms-version"
requestIDHeader = "x-ms-request-id"
uaHeader = "User-Agent"
contentHeader = "Content-Type"
defaultContentHeaderValue = "application/xml"
)
func (client client) SendAzureGetRequest(url string) ([]byte, error) {
resp, err := client.sendAzureRequest("GET", url, "", nil)
if err != nil {
return nil, err
}
return getResponseBody(resp)
}
func (client client) SendAzurePostRequest(url string, data []byte) (OperationID, error) {
return client.doAzureOperation("POST", url, "", data)
}
func (client client) SendAzurePostRequestWithReturnedResponse(url string, data []byte) ([]byte, error) {
resp, err := client.sendAzureRequest("POST", url, "", data)
if err != nil {
return nil, err
}
return getResponseBody(resp)
}
func (client client) SendAzurePutRequest(url, contentType string, data []byte) (OperationID, error) {
return client.doAzureOperation("PUT", url, contentType, data)
}
func (client client) SendAzureDeleteRequest(url string) (OperationID, error) {
return client.doAzureOperation("DELETE", url, "", nil)
}
func (client client) doAzureOperation(method, url, contentType string, data []byte) (OperationID, error) {
response, err := client.sendAzureRequest(method, url, contentType, data)
if err != nil {
return "", err
}
return getOperationID(response)
}
func getOperationID(response *http.Response) (OperationID, error) {
requestID := response.Header.Get(requestIDHeader)
if requestID == "" {
return "", fmt.Errorf("Could not retrieve operation id from %q header", requestIDHeader)
}
return OperationID(requestID), nil
}
// sendAzureRequest constructs an HTTP client for the request, sends it to the
// management API and returns the response or an error.
func (client client) sendAzureRequest(method, url, contentType string, data []byte) (*http.Response, error) {
if method == "" {
return nil, fmt.Errorf(errParamNotSpecified, "method")
}
if url == "" {
return nil, fmt.Errorf(errParamNotSpecified, "url")
}
httpClient, err := client.createHTTPClient()
if err != nil {
return nil, err
}
response, err := client.sendRequest(httpClient, url, method, contentType, data, 5)
if err != nil {
return nil, err
}
return response, nil
}
// createHTTPClient creates an HTTP Client configured with the key pair for
// the subscription for this client.
func (client client) createHTTPClient() (*http.Client, error) {
cert, err := tls.X509KeyPair(client.publishSettings.SubscriptionCert, client.publishSettings.SubscriptionKey)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
Certificates: []tls.Certificate{cert},
},
},
}, nil
}
// sendRequest sends a request to the Azure management API using the given
// HTTP client and parameters. It returns the response from the call or an
// error.
func (client client) sendRequest(httpClient *http.Client, url, requestType, contentType string, data []byte, numberOfRetries int) (*http.Response, error) {
absURI := client.createAzureRequestURI(url)
for {
request, reqErr := client.createAzureRequest(absURI, requestType, contentType, data)
if reqErr != nil {
return nil, reqErr
}
response, err := httpClient.Do(request)
if err != nil {
if numberOfRetries == 0 {
return nil, err
}
return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1)
}
if response.StatusCode == http.StatusTemporaryRedirect {
// ASM's way of moving traffic around, see https://msdn.microsoft.com/en-us/library/azure/ee460801.aspx
// Only handled automatically for GET/HEAD requests. This is for the rest of the http verbs.
u, err := response.Location()
if err != nil {
return response, fmt.Errorf("Redirect requested but location header could not be retrieved: %v", err)
}
absURI = u.String()
continue // re-issue request
}
if response.StatusCode >= http.StatusBadRequest {
body, err := getResponseBody(response)
if err != nil {
// Failed to read the response body
return nil, err
}
azureErr := getAzureError(body)
if azureErr != nil {
if numberOfRetries == 0 {
return nil, azureErr
}
return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1)
}
}
return response, nil
}
}
// createAzureRequestURI constructs the request uri using the management API endpoint and
// subscription ID associated with the client.
func (client client) createAzureRequestURI(url string) string {
return fmt.Sprintf("%s/%s/%s", client.config.ManagementURL, client.publishSettings.SubscriptionID, url)
}
// createAzureRequest packages up the request with the correct set of headers and returns
// the request object or an error.
func (client client) createAzureRequest(url string, requestType string, contentType string, data []byte) (*http.Request, error) {
var request *http.Request
var err error
if data != nil {
body := bytes.NewBuffer(data)
request, err = http.NewRequest(requestType, url, body)
} else {
request, err = http.NewRequest(requestType, url, nil)
}
if err != nil {
return nil, err
}
request.Header.Set(msVersionHeader, client.config.APIVersion)
request.Header.Set(uaHeader, client.config.UserAgent)
if contentType != "" {
request.Header.Set(contentHeader, contentType)
} else {
request.Header.Set(contentHeader, defaultContentHeaderValue)
}
return request, nil
}