github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/actions/lua/net/http/request.go (about) 1 package http 2 3 import ( 4 "io" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/Shopify/go-lua" 10 "github.com/treeverse/lakefs/pkg/actions/lua/util" 11 ) 12 13 const defaultRequestTimeout = 30 * time.Second 14 15 func Open(l *lua.State) { 16 open := func(l *lua.State) int { 17 lua.NewLibrary(l, httpLibrary) 18 return 1 19 } 20 lua.Require(l, "net/http", open, false) 21 l.Pop(1) 22 } 23 24 var httpLibrary = []lua.RegistryFunction{ 25 {Name: "request", Function: httpRequest}, 26 } 27 28 // httpRequest - perform http request 29 // 30 // Accepts arguments (url, body) or table with url, method, body, headers. Value for url is required. 31 // The `method` is by default GET or POST in case body is set. 32 // Returns code, body, headers, status. 33 func httpRequest(l *lua.State) int { 34 req := prepareRequest(l) 35 client := http.Client{ 36 Timeout: defaultRequestTimeout, 37 } 38 resp, err := client.Do(req) 39 check(l, err) 40 defer func() { _ = resp.Body.Close() }() 41 body, err := io.ReadAll(resp.Body) 42 check(l, err) 43 44 // push return 45 l.PushInteger(resp.StatusCode) 46 l.PushString(string(body)) 47 pushResponseHeader(l, resp.Header) 48 l.PushString(resp.Status) 49 return 4 50 } 51 52 func prepareRequest(l *lua.State) *http.Request { 53 var ( 54 reqMethod = http.MethodGet 55 reqURL string 56 reqBody io.Reader 57 reqHeaders map[string]interface{} 58 ) 59 switch l.TypeOf(1) { 60 case lua.TypeString: 61 // url and optional body 62 reqURL = lua.CheckString(l, 1) 63 if s, ok := l.ToString(2); ok { 64 reqBody = strings.NewReader(s) 65 reqMethod = http.MethodPost 66 } 67 case lua.TypeTable: 68 // extract request parameters from table 69 value, err := util.PullTable(l, 1) 70 check(l, err) 71 tbl := value.(map[string]interface{}) 72 if s, ok := tbl["url"].(string); ok { 73 reqURL = s 74 } 75 if s, ok := tbl["body"].(string); ok { 76 reqBody = strings.NewReader(s) 77 reqMethod = http.MethodPost 78 } 79 if s, ok := tbl["method"].(string); ok { 80 reqMethod = s 81 } 82 if m, ok := tbl["headers"].(map[string]interface{}); ok { 83 reqHeaders = m 84 } 85 default: 86 lua.Errorf(l, "first argument can be url or request table (invalid type: %d)", l.TypeOf(1)) 87 panic("unreachable") 88 } 89 if reqURL == "" { 90 lua.Errorf(l, "missing request url") 91 panic("unreachable") 92 } 93 req, err := http.NewRequest(reqMethod, reqURL, reqBody) 94 check(l, err) 95 requestAddHeader(reqHeaders, req) 96 return req 97 } 98 99 // requestAddHeader add headers to request. each table value can be single a string or array(table) of strings 100 func requestAddHeader(reqHeaders map[string]interface{}, req *http.Request) { 101 for k, v := range reqHeaders { 102 switch vv := v.(type) { 103 case string: 104 req.Header.Add(k, vv) 105 case map[string]interface{}: 106 for _, val := range vv { 107 if s, ok := val.(string); ok { 108 req.Header.Add(k, s) 109 } 110 } 111 } 112 } 113 } 114 115 // pushResponseHeader response headers as table. the result table will include single value or table of values for multiple values 116 func pushResponseHeader(l *lua.State, header http.Header) { 117 m := make(map[string]interface{}, len(header)) 118 for k, v := range header { 119 if len(v) == 1 { 120 m[k] = v[0] 121 } else { 122 m[k] = v 123 } 124 } 125 util.DeepPush(l, m) 126 } 127 128 func check(l *lua.State, err error) { 129 if err != nil { 130 lua.Errorf(l, "%s", err.Error()) 131 panic("unreachable") 132 } 133 }