aboutsummaryrefslogtreecommitdiff
path: root/request.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2018-01-28 22:47:35 +0100
committerStefan Majewsky <majewsky@gmx.net>2018-01-28 22:47:35 +0100
commit3834e49c90c39f4c95e3b9e7bb52b35204a75625 (patch)
tree480a3e2867dc076c459ee2279e98eb546bc6ca10 /request.go
parenta9595114a691fd04ba043c1b1564741a107bbcdd (diff)
downloadgo-schwift-3834e49c90c39f4c95e3b9e7bb52b35204a75625.tar.gz
sketch out how requests could work
Diffstat (limited to 'request.go')
-rw-r--r--request.go161
1 files changed, 161 insertions, 0 deletions
diff --git a/request.go b/request.go
new file mode 100644
index 0000000..c6bef7b
--- /dev/null
+++ b/request.go
@@ -0,0 +1,161 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* 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.
+*
+******************************************************************************/
+
+package schwift
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ urlmodule "net/url"
+ "strings"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+var okCodes []int
+
+func init() {
+ //prepare input for gophercloud.RequestOpts.OkCodes such that gophercloud's
+ //error handling is fused
+ for code := 100; code < 600; code++ {
+ //as an exception, 401s are handled by Gophercloud because we want to use its
+ //internal token renewal logic
+ if code != 401 {
+ okCodes = append(okCodes, code)
+ }
+ }
+}
+
+//Request contains the parameters that can be set in a request to the Swift API.
+type Request struct {
+ Method string //"GET", "HEAD", "PUT", "POST" or "DELETE"
+ ContainerName string //empty for requests on accounts
+ ObjectName string //empty for requests on accounts/containers
+ AdditionalHeaders map[string]string
+ //ExpectStatusCodes can be left empty to disable this check, otherwise
+ //schwift.UnexpectedStatusCodeError may be returned.
+ ExpectStatusCodes []int
+}
+
+//URL returns the full URL for this request.
+func (r Request) URL(client *gophercloud.ServiceClient) (string, error) {
+ url, err := urlmodule.Parse(client.Endpoint)
+ if err != nil {
+ return "", err
+ }
+ if !strings.HasSuffix(url.Path, "/") {
+ url.Path += "/"
+ }
+
+ if r.ContainerName == "" {
+ if r.ObjectName != "" {
+ return "", ErrNoContainerName
+ }
+ } else {
+ if strings.Contains(r.ContainerName, "/") {
+ return "", ErrMalformedContainerName
+ }
+ url.Path += r.ContainerName + "/" + r.ObjectName
+ }
+
+ return url.String(), nil
+}
+
+//Do executes this request on the given service client.
+func (r Request) Do(client *gophercloud.ServiceClient) (*http.Response, error) {
+ return r.do(client, false)
+}
+
+func (r Request) do(client *gophercloud.ServiceClient, afterReauth bool) (*http.Response, error) {
+ //build URL
+ url, err := r.URL(client)
+ if err != nil {
+ return nil, err
+ }
+
+ //override gophercloud's error handling
+ opts := &gophercloud.RequestOpts{OkCodes: okCodes}
+
+ //override gophercloud's default headers
+ opts.MoreHeaders = map[string]string{
+ "Accept": "",
+ "Content-Type": "",
+ }
+ for key, value := range r.AdditionalHeaders {
+ opts.MoreHeaders[key] = value
+ }
+
+ resp, err := client.ProviderClient.Request(r.Method, url, opts)
+ if err != nil {
+ return resp, err
+ }
+
+ //return success if error code matches expectation
+ if len(r.ExpectStatusCodes) == 0 {
+ //check disabled -> return response unaltered
+ return resp, nil
+ }
+ for _, code := range r.ExpectStatusCodes {
+ if code == resp.StatusCode {
+ return resp, nil
+ }
+ }
+
+ //since we override gophercloud's error handling, we need to handle token
+ //expiry ourselves
+ if resp.StatusCode == http.StatusUnauthorized && !afterReauth {
+ err := drainResponseBody(resp)
+ if err != nil {
+ return nil, err
+ }
+ err = client.Reauthenticate(resp.Request.Header.Get("X-Auth-Token"))
+ if err != nil {
+ return nil, err
+ }
+ //restart request with new token
+ return r.do(client, true)
+ }
+
+ //other unexpected status code -> generate UnexpectedStatusCodeError
+ buf, err := collectResponseBody(resp)
+ if err != nil {
+ return nil, err
+ }
+ return nil, UnexpectedStatusCodeError{
+ ExpectedStatusCodes: r.ExpectStatusCodes,
+ ActualResponse: resp,
+ ResponseBody: buf,
+ }
+}
+
+func drainResponseBody(r *http.Response) error {
+ _, err := io.Copy(ioutil.Discard, r.Body)
+ if err != nil {
+ return err
+ }
+ return r.Body.Close()
+}
+
+func collectResponseBody(r *http.Response) ([]byte, error) {
+ buf, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, err
+ }
+ return buf, r.Body.Close()
+}