github.com/devcamcar/cli@v0.0.0-20181107134215-706a05759d18/client/invoke.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"os"
    12  	"strings"
    13  
    14  	"github.com/fnproject/fn_go/provider"
    15  	"github.com/go-openapi/runtime/logger"
    16  )
    17  
    18  const (
    19  	FN_CALL_ID             = "Fn-Call-Id"
    20  	MaximumRequestBodySize = 5 * 1024 * 1024 // bytes
    21  )
    22  
    23  func EnvAsHeader(req *http.Request, selectedEnv []string) {
    24  	detectedEnv := os.Environ()
    25  	if len(selectedEnv) > 0 {
    26  		detectedEnv = selectedEnv
    27  	}
    28  
    29  	for _, e := range detectedEnv {
    30  		kv := strings.Split(e, "=")
    31  		name := kv[0]
    32  		req.Header.Set(name, os.Getenv(name))
    33  	}
    34  }
    35  
    36  type apiErr struct {
    37  	Message string `json:"message"`
    38  }
    39  
    40  type callID struct {
    41  	CallID string `json:"call_id"`
    42  	Error  apiErr `json:"error"`
    43  }
    44  
    45  func Invoke(provider provider.Provider, invokeUrl string, content io.Reader, output io.Writer, method string, env []string, contentType string, includeCallID bool) error {
    46  
    47  	method = "POST"
    48  
    49  	// Read the request body (up to the maximum size), as this is used in the
    50  	// authentication signature
    51  	var req *http.Request
    52  	if content != nil {
    53  		b, err := ioutil.ReadAll(io.LimitReader(content, MaximumRequestBodySize))
    54  		buffer := bytes.NewBuffer(b)
    55  		req, err = http.NewRequest(method, invokeUrl, buffer)
    56  		if err != nil {
    57  			return fmt.Errorf("Error creating request to service: %s", err)
    58  		}
    59  	} else {
    60  		var err error
    61  		req, err = http.NewRequest(method, invokeUrl, nil)
    62  		if err != nil {
    63  			return fmt.Errorf("Error creating request to service: %s", err)
    64  		}
    65  	}
    66  
    67  	if contentType != "" {
    68  		req.Header.Set("Content-Type", contentType)
    69  	} else {
    70  		req.Header.Set("Content-Type", "text/plain")
    71  	}
    72  
    73  	if len(env) > 0 {
    74  		EnvAsHeader(req, env)
    75  	}
    76  
    77  	transport := provider.WrapCallTransport(http.DefaultTransport)
    78  	httpClient := http.Client{Transport: transport}
    79  
    80  	if logger.DebugEnabled() {
    81  		b, err := httputil.DumpRequestOut(req, content != nil)
    82  		if err != nil {
    83  			return err
    84  		}
    85  		fmt.Printf(string(b) + "\n")
    86  	}
    87  
    88  	resp, err := httpClient.Do(req)
    89  
    90  	if err != nil {
    91  		return fmt.Errorf("Error invoking fn: %s", err)
    92  	}
    93  
    94  	if logger.DebugEnabled() {
    95  		b, err := httputil.DumpResponse(resp, true)
    96  		if err != nil {
    97  			return err
    98  		}
    99  		fmt.Printf(string(b) + "\n")
   100  	}
   101  
   102  	// for sync calls
   103  	if call_id, found := resp.Header[FN_CALL_ID]; found {
   104  		if includeCallID {
   105  			fmt.Fprint(os.Stderr, fmt.Sprintf("Call ID: %v\n", call_id[0]))
   106  		}
   107  		io.Copy(output, resp.Body)
   108  	} else {
   109  		// for async calls and error discovering
   110  		c := &callID{}
   111  		err = json.NewDecoder(resp.Body).Decode(c)
   112  		if err == nil {
   113  			// decode would not fail in both cases:
   114  			// - call id in body
   115  			// - error in body
   116  			// that's why we need to check values of attributes
   117  			if c.CallID != "" {
   118  				fmt.Fprint(os.Stderr, fmt.Sprintf("Call ID: %v\n", c.CallID))
   119  			} else {
   120  				fmt.Fprint(output, fmt.Sprintf("Error: %v\n", c.Error.Message))
   121  			}
   122  		} else {
   123  			return err
   124  		}
   125  	}
   126  
   127  	if resp.StatusCode >= 400 {
   128  		// TODO: parse out error message
   129  		return fmt.Errorf("Error calling function: status %v", resp.StatusCode)
   130  	}
   131  
   132  	return nil
   133  }