github.com/openshift-online/ocm-sdk-go@v0.1.473/send.go (about)

     1  /*
     2  Copyright (c) 2018 Red Hat, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8    http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This file contains the implementation of the methods of the connection that are used to send HTTP
    18  // requests and receive HTTP responses.
    19  
    20  package sdk
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"net/http"
    26  	"path"
    27  
    28  	"github.com/openshift-online/ocm-sdk-go/internal"
    29  )
    30  
    31  // RoundTrip is the implementation of the http.RoundTripper interface.
    32  func (c *Connection) RoundTrip(request *http.Request) (response *http.Response, err error) {
    33  	// Check if the connection is closed:
    34  	err = c.checkClosed()
    35  	if err != nil {
    36  		return
    37  	}
    38  
    39  	// Get the context from the request:
    40  	ctx := request.Context()
    41  
    42  	// Check the request URL:
    43  	if request.URL.Path == "" {
    44  		err = fmt.Errorf("request path is mandatory")
    45  		return
    46  	}
    47  	if request.URL.Scheme != "" || request.URL.Host != "" || !path.IsAbs(request.URL.Path) {
    48  		err = fmt.Errorf("request URL '%s' isn't absolute", request.URL)
    49  		return
    50  	}
    51  
    52  	// Select the target server add the base URL to the request URL:
    53  	server, err := c.selectServer(ctx, request)
    54  	if err != nil {
    55  		return
    56  	}
    57  	request.URL = server.URL.ResolveReference(request.URL)
    58  
    59  	// Check the request method and body:
    60  	switch request.Method {
    61  	case http.MethodGet, http.MethodDelete:
    62  		if request.Body != nil {
    63  			c.logger.Warn(ctx,
    64  				"Request body is not allowed for the '%s' method",
    65  				request.Method,
    66  			)
    67  		}
    68  	case http.MethodPost, http.MethodPatch, http.MethodPut:
    69  		// POST and PATCH and PUT don't need to have a body. It is up to the server to decide if
    70  		// this is acceptable.
    71  	default:
    72  		err = fmt.Errorf("method '%s' is not allowed", request.Method)
    73  		return
    74  	}
    75  
    76  	// Add the default headers:
    77  	if request.Header == nil {
    78  		request.Header = make(http.Header)
    79  	}
    80  	if c.agent != "" {
    81  		request.Header.Set("User-Agent", c.agent)
    82  	}
    83  	switch request.Method {
    84  	case http.MethodPost, http.MethodPatch, http.MethodPut:
    85  		request.Header.Set("Content-Type", "application/json")
    86  	}
    87  	request.Header.Set("Accept", "application/json")
    88  
    89  	// Select the client:
    90  	client, err := c.clientSelector.Select(ctx, server)
    91  	if err != nil {
    92  		return
    93  	}
    94  
    95  	// Send the request:
    96  	response, err = client.Do(request)
    97  	if err != nil {
    98  		return
    99  	}
   100  
   101  	// Check that the response content type is JSON:
   102  	err = internal.CheckContentType(response)
   103  	if err != nil {
   104  		return
   105  	}
   106  
   107  	return
   108  }
   109  
   110  // selectServer selects the server that should be used for the given request, according its path and
   111  // the alternative URLs configured when the connection was created.
   112  func (c *Connection) selectServer(ctx context.Context,
   113  	request *http.Request) (base *internal.ServerAddress, err error) {
   114  	// Select the server corresponding to the longest matching prefix. Note that it is enough to
   115  	// pick the first match because the entries have already been sorted by descending prefix
   116  	// length when the connection was created.
   117  	for _, entry := range c.urlTable {
   118  		if entry.re.MatchString(request.URL.Path) {
   119  			base = entry.url
   120  			return
   121  		}
   122  	}
   123  	if base == nil {
   124  		err = fmt.Errorf(
   125  			"can't find any matching URL for request path '%s'",
   126  			request.URL.Path,
   127  		)
   128  	}
   129  	return
   130  }