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 }