gopkg.in/goose.v2@v2.0.1/testservices/swiftservice/service_http.go (about)

     1  // Swift double testing service - HTTP API implementation
     2  
     3  package swiftservice
     4  
     5  import (
     6  	"bytes"
     7  	"crypto/md5"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"net/url"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  // verbatim real Swift responses
    19  const (
    20  	notFoundResponse = `404 Not Found
    21  
    22  The resource could not be found.
    23  
    24  
    25  `
    26  	createdResponse = `201 Created
    27  
    28  
    29  
    30  
    31  `
    32  	acceptedResponse = `202 Accepted
    33  
    34  The request is accepted for processing.
    35  
    36  
    37  `
    38  )
    39  
    40  // handleContainers processes HTTP requests for container management.
    41  func (s *Swift) handleContainers(container string, w http.ResponseWriter, r *http.Request) {
    42  	var err error
    43  	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
    44  	exists := s.HasContainer(container)
    45  	if !exists && r.Method != "PUT" {
    46  		w.WriteHeader(http.StatusNotFound)
    47  		w.Write([]byte(notFoundResponse))
    48  		return
    49  	}
    50  	switch r.Method {
    51  	case "GET":
    52  		urlParams, err := url.ParseQuery(r.URL.RawQuery)
    53  		if err != nil {
    54  			w.WriteHeader(http.StatusInternalServerError)
    55  			w.Write([]byte(err.Error()))
    56  			return
    57  		}
    58  		params := make(map[string]string, len(urlParams))
    59  		for k := range urlParams {
    60  			params[k] = urlParams.Get(k)
    61  		}
    62  		contents, err := s.ListContainer(container, params)
    63  		var objdata []byte
    64  		if err == nil {
    65  			objdata, err = json.Marshal(contents)
    66  		}
    67  		if err != nil {
    68  			w.WriteHeader(http.StatusInternalServerError)
    69  			w.Write([]byte(err.Error()))
    70  		} else {
    71  			w.WriteHeader(http.StatusOK)
    72  			w.Header().Set("Content-Type", "application/json; charset=UF-8")
    73  			w.Write([]byte(objdata))
    74  		}
    75  	case "DELETE":
    76  		if err = s.RemoveContainer(container); err != nil {
    77  			w.WriteHeader(http.StatusInternalServerError)
    78  			w.Write([]byte(err.Error()))
    79  		} else {
    80  			w.Header().Set("Content-Length", "0")
    81  			w.WriteHeader(http.StatusNoContent)
    82  		}
    83  	case "HEAD":
    84  		urlParams, err := url.ParseQuery(r.URL.RawQuery)
    85  		if err != nil {
    86  			w.WriteHeader(http.StatusInternalServerError)
    87  			w.Write([]byte(err.Error()))
    88  			return
    89  		}
    90  		params := make(map[string]string, len(urlParams))
    91  		for k := range urlParams {
    92  			params[k] = urlParams.Get(k)
    93  		}
    94  		_, err = s.ListContainer(container, params)
    95  		if err != nil {
    96  			w.WriteHeader(http.StatusInternalServerError)
    97  			w.Write([]byte(err.Error()))
    98  		} else {
    99  			w.WriteHeader(http.StatusOK)
   100  			w.Header().Set("Content-Type", "application/json; charset=UF-8")
   101  		}
   102  	case "PUT":
   103  		if exists {
   104  			w.WriteHeader(http.StatusAccepted)
   105  			w.Write([]byte(acceptedResponse))
   106  		} else {
   107  			if err = s.AddContainer(container); err != nil {
   108  				w.WriteHeader(http.StatusInternalServerError)
   109  				w.Write([]byte(err.Error()))
   110  			} else {
   111  				w.WriteHeader(http.StatusCreated)
   112  				w.Write([]byte(createdResponse))
   113  			}
   114  		}
   115  	case "POST":
   116  		// [sodre]: we don't implement changing ACLs, so this always succeeds.
   117  		w.WriteHeader(http.StatusAccepted)
   118  		w.Write([]byte(createdResponse))
   119  	default:
   120  		panic("not implemented request type: " + r.Method)
   121  	}
   122  }
   123  
   124  // handleObjects processes HTTP requests for object management.
   125  func (s *Swift) handleObjects(container, object string, w http.ResponseWriter, r *http.Request) {
   126  	var err error
   127  	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
   128  	if exists := s.HasContainer(container); !exists {
   129  		w.WriteHeader(http.StatusNotFound)
   130  		w.Write([]byte(notFoundResponse))
   131  		return
   132  	}
   133  	objdata, err := s.GetObject(container, object)
   134  	if err != nil && r.Method != "PUT" {
   135  		w.WriteHeader(http.StatusNotFound)
   136  		w.Write([]byte(notFoundResponse))
   137  		return
   138  	}
   139  	exists := err == nil
   140  	switch r.Method {
   141  	case "GET":
   142  		// Note: even though the real Swift service does not
   143  		// quote the Etag header value, we need to quote it here
   144  		// because http.ServeContent requires the quotes, and
   145  		// this means that we can have a naive client that puts
   146  		// the exact Etag value in (for example) an If-Match
   147  		// header and it will work with both the real Swift and
   148  		// the local mock server. Note also that the HTTP
   149  		// standard does require the Etag value to be quoted;
   150  		// see https://tools.ietf.org/html/rfc7232#section-2.3
   151  		// TODO maintain modification time.
   152  		w.Header().Set("Etag", fmt.Sprintf(`"%x"`, md5.Sum(objdata)))
   153  		http.ServeContent(w, r, object, time.Now(), bytes.NewReader(objdata))
   154  	case "DELETE":
   155  		if err = s.RemoveObject(container, object); err != nil {
   156  			w.WriteHeader(http.StatusInternalServerError)
   157  			w.Write([]byte(err.Error()))
   158  		} else {
   159  			w.Header().Set("Content-Length", "0")
   160  			w.WriteHeader(http.StatusNoContent)
   161  		}
   162  	case "HEAD":
   163  		w.Header().Set("Content-Length", strconv.Itoa(len(objdata)))
   164  		w.Header().Set("Etag", fmt.Sprintf(`"%x"`, md5.Sum(objdata)))
   165  		w.WriteHeader(http.StatusOK)
   166  	case "PUT":
   167  		bodydata, err := ioutil.ReadAll(r.Body)
   168  		if err != nil {
   169  			w.WriteHeader(http.StatusInternalServerError)
   170  			w.Write([]byte(err.Error()))
   171  			return
   172  		}
   173  		if exists {
   174  			err = s.RemoveObject(container, object)
   175  			if err != nil {
   176  				w.WriteHeader(http.StatusInternalServerError)
   177  				w.Write([]byte(err.Error()))
   178  			}
   179  		}
   180  		if err = s.AddObject(container, object, bodydata); err != nil {
   181  			w.WriteHeader(http.StatusInternalServerError)
   182  			w.Write([]byte(err.Error()))
   183  		} else {
   184  			w.WriteHeader(http.StatusCreated)
   185  			w.Write([]byte(createdResponse))
   186  		}
   187  	default:
   188  		panic("not implemented request type: " + r.Method)
   189  	}
   190  }
   191  
   192  // ServeHTTP is the main entry point in the HTTP request processing.
   193  func (s *Swift) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   194  	// TODO(wallyworld) - 2013-02-11 bug=1121682
   195  	// we need to support container ACLs so we can have pubic containers.
   196  	// For public containers, the token is not required to access the files. For now, if the request
   197  	// does not provide a token, we will let it through and assume a public container is being accessed.
   198  	token := r.Header.Get("X-Auth-Token")
   199  	_, err := s.IdentityService.FindUser(token)
   200  	if err != nil && s.FallbackIdentityService != nil {
   201  		_, err = s.FallbackIdentityService.FindUser(token)
   202  	}
   203  	if token != "" && err != nil {
   204  		w.WriteHeader(http.StatusUnauthorized)
   205  		return
   206  	}
   207  	path := strings.TrimRight(r.URL.Path, "/")
   208  	path = strings.Trim(path, "/")
   209  	parts := strings.SplitN(path, "/", 4)
   210  	if len(parts) > 2 {
   211  		parts = parts[2:]
   212  		if len(parts) == 1 {
   213  			container := parts[0]
   214  			s.handleContainers(container, w, r)
   215  		} else if len(parts) == 2 {
   216  			container := parts[0]
   217  			object := parts[1]
   218  			s.handleObjects(container, object, w, r)
   219  		}
   220  	} else {
   221  		panic("not implemented request: " + r.URL.Path)
   222  	}
   223  }
   224  
   225  // setupHTTP attaches all the needed handlers to provide the HTTP API.
   226  func (s *Swift) SetupHTTP(mux *http.ServeMux) {
   227  	mux.Handle("/", s)
   228  }
   229  
   230  func (s *Swift) Stop() {
   231  	// noop
   232  }