http.go

  1package plugin
  2
  3import (
  4	"io"
  5	"net/http"
  6	"strings"
  7	"time"
  8
  9	lua "github.com/yuin/gopher-lua"
 10)
 11
 12const (
 13	httpTimeout     = 10 * time.Second
 14	httpMaxBodySize = 1 << 20 // 1 MB
 15)
 16
 17var httpClient = &http.Client{
 18	Timeout: httpTimeout,
 19}
 20
 21// luaHTTP implements matcha.http(options) — make an HTTP request.
 22//
 23// options is a table with fields:
 24//   - url     (string, required)
 25//   - method  (string, optional, default "GET")
 26//   - headers (table, optional)
 27//   - body    (string, optional)
 28//
 29// Returns (response_table, nil) on success or (nil, error_string) on failure.
 30// response_table has fields: status (number), body (string), headers (table).
 31func (m *Manager) luaHTTP(L *lua.LState) int {
 32	opts := L.CheckTable(1)
 33
 34	// URL (required).
 35	urlVal := opts.RawGetString("url")
 36	if urlVal == lua.LNil {
 37		L.Push(lua.LNil)
 38		L.Push(lua.LString("missing required field: url"))
 39		return 2
 40	}
 41	rawURL := urlVal.String()
 42
 43	// Scheme validation.
 44	if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
 45		L.Push(lua.LNil)
 46		L.Push(lua.LString("unsupported URL scheme: only http and https are allowed"))
 47		return 2
 48	}
 49
 50	// Method (optional, default GET).
 51	method := "GET"
 52	if v := opts.RawGetString("method"); v != lua.LNil {
 53		method = strings.ToUpper(v.String())
 54	}
 55
 56	// Body (optional).
 57	var bodyReader io.Reader
 58	if v := opts.RawGetString("body"); v != lua.LNil {
 59		bodyReader = strings.NewReader(v.String())
 60	}
 61
 62	req, err := http.NewRequest(method, rawURL, bodyReader)
 63	if err != nil {
 64		L.Push(lua.LNil)
 65		L.Push(lua.LString(err.Error()))
 66		return 2
 67	}
 68
 69	// Headers (optional).
 70	if v := opts.RawGetString("headers"); v != lua.LNil {
 71		if tbl, ok := v.(*lua.LTable); ok {
 72			tbl.ForEach(func(k, v lua.LValue) {
 73				req.Header.Set(k.String(), v.String())
 74			})
 75		}
 76	}
 77
 78	resp, err := httpClient.Do(req)
 79	if err != nil {
 80		L.Push(lua.LNil)
 81		L.Push(lua.LString(err.Error()))
 82		return 2
 83	}
 84	defer resp.Body.Close()
 85
 86	body, err := io.ReadAll(io.LimitReader(resp.Body, httpMaxBodySize))
 87	if err != nil {
 88		L.Push(lua.LNil)
 89		L.Push(lua.LString(err.Error()))
 90		return 2
 91	}
 92
 93	// Build response table.
 94	result := L.NewTable()
 95	result.RawSetString("status", lua.LNumber(resp.StatusCode))
 96	result.RawSetString("body", lua.LString(string(body)))
 97
 98	headers := L.NewTable()
 99	for k, vals := range resp.Header {
100		if len(vals) > 0 {
101			headers.RawSetString(strings.ToLower(k), lua.LString(vals[0]))
102		}
103	}
104	result.RawSetString("headers", headers)
105
106	L.Push(result)
107	L.Push(lua.LNil)
108	return 2
109}