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  }