natsbot.go (18145B)
1 /* 2 * Copyright (c) 2025, Natacha Porté 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 package natsbot 18 19 import ( 20 "errors" 21 "fmt" 22 "log" 23 "strings" 24 "time" 25 26 "github.com/nats-io/nats.go" 27 "github.com/yuin/gopher-lua" 28 ) 29 30 type NatsBot interface { 31 Setup(L *lua.LState) 32 Teardown(L *lua.LState) 33 } 34 35 type internalEvent struct { 36 name string 37 nc *nats.Conn 38 subs *nats.Subscription 39 err error 40 } 41 42 func Loop(cb NatsBot, mainScript string, capacity int) { 43 evtChan := make(chan *internalEvent, capacity) 44 msgChan := make(chan *nats.Msg, capacity) 45 toClean := make(map[*nats.Subscription]bool) 46 47 L := lua.NewState() 48 defer L.Close() 49 50 cb.Setup(L) 51 defer cb.Teardown(L) 52 53 registerConnType(L) 54 registerTimerType(L) 55 registerState(L, evtChan, msgChan) 56 57 if err := L.DoFile(mainScript); err != nil { 58 panic(err) 59 } 60 61 timer := time.NewTimer(0) 62 defer timer.Stop() 63 64 log.Println("natsbot started") 65 66 for { 67 select { 68 case evt, ok := <-evtChan: 69 if !ok { 70 log.Println("evtChan is closed") 71 break 72 } 73 74 processEvt(L, evt) 75 76 case msg, ok := <-msgChan: 77 78 if !ok { 79 log.Println("msgChan is closed") 80 break 81 } 82 83 processMsg(L, msg) 84 85 if !msg.Sub.IsValid() { 86 toClean[msg.Sub] = true 87 } 88 89 case <-timer.C: 90 } 91 92 runTimers(L, timer) 93 94 if len(msgChan) == 0 { 95 for s := range toClean { 96 if !s.IsValid() { 97 log.Printf("Pruning subscription %q", s.Subject) 98 tbl, idx := stateSubsTable(L) 99 L.RawSetInt(tbl, idx[s], lua.LNil) 100 delete(idx, s) 101 } else { 102 log.Printf("Subscription %q is still valid", s.Subject) 103 } 104 } 105 toClean = make(map[*nats.Subscription]bool) 106 } 107 108 if tableWithIndexIsEmpty(stateSubsTable(L)) && tableIsEmpty(stateTimerTable(L)) { 109 break 110 } 111 } 112 113 log.Println("natsbot finished") 114 } 115 116 func processEvt(L *lua.LState, evt *internalEvent) { 117 tbl, idx := stateConnTable(L) 118 connLua := L.RawGetInt(tbl, idx[evt.nc]) 119 fn := L.GetField(L.GetField(L.GetMetatable(connLua), "__index").(*lua.LTable), evt.name) 120 if lua.LVIsFalse(fn) { 121 return 122 } 123 124 var subLua lua.LValue 125 if evt.subs == nil { 126 subLua = lua.LNil 127 } else { 128 tbl, idx := stateSubsTable(L) 129 subLua = L.RawGetInt(tbl, idx[evt.subs]) 130 } 131 132 var errLua lua.LValue 133 if evt.err == nil { 134 errLua = lua.LNil 135 } else { 136 errLua = lua.LString(evt.err.Error()) 137 } 138 139 err := L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true}, 140 connLua, 141 subLua, 142 errLua) 143 if err != nil { 144 panic(err) 145 } 146 } 147 148 func processMsg(L *lua.LState, msg *nats.Msg) { 149 tbl, idx := stateSubsTable(L) 150 subs := L.RawGetInt(tbl, idx[msg.Sub]) 151 fn := L.GetField(L.GetMetatable(subs), "__call") 152 err := L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true}, 153 subs, 154 lua.LString(msg.Subject), 155 lua.LString(string(msg.Data)), 156 luaHeaders(L, msg.Reply, msg.Header)) 157 if err != nil { 158 panic(err) 159 } 160 } 161 162 /********** State Object in the Lua Interpreter **********/ 163 164 const luaStateName = "_natsbot" 165 const ( 166 _ = iota 167 keyEvtChan 168 keyMsgChan 169 keyCfgMap 170 keyConnTable 171 keySubsTable 172 keyTimerTable 173 ) 174 175 type subsMap map[*nats.Subscription]int 176 177 func registerState(L *lua.LState, evtChan chan *internalEvent, msgChan chan *nats.Msg) { 178 conns := L.NewTable() 179 L.RawSetInt(conns, 1, newUserData(L, make(connMap))) 180 181 subs := L.NewTable() 182 L.RawSetInt(subs, 1, newUserData(L, make(subsMap))) 183 184 st := L.NewTable() 185 L.RawSetInt(st, keyEvtChan, newUserData(L, evtChan)) 186 L.RawSetInt(st, keyMsgChan, newUserData(L, msgChan)) 187 L.RawSetInt(st, keyCfgMap, newUserData(L, make(natsConfigMap))) 188 L.RawSetInt(st, keyConnTable, conns) 189 L.RawSetInt(st, keySubsTable, subs) 190 L.RawSetInt(st, keyTimerTable, L.NewTable()) 191 stateSet(L, st) 192 } 193 194 func stateUncheckedGet(L *lua.LState) *lua.LTable { 195 v := L.GetField(L.Get(lua.RegistryIndex), luaStateName) 196 if result, ok := v.(*lua.LTable); ok { 197 return result 198 } else { 199 return nil 200 } 201 } 202 203 func stateGet(L *lua.LState) *lua.LTable { 204 result := stateUncheckedGet(L) 205 if result == nil { 206 panic("Missing internal state object") 207 } 208 return result 209 } 210 211 func stateSet(L *lua.LState, newState *lua.LTable) { 212 if stateUncheckedGet(L) != nil { 213 panic("Overwriting internal state object") 214 } 215 L.SetField(L.Get(lua.RegistryIndex), luaStateName, newState) 216 } 217 218 func stateValue(L *lua.LState, key int) lua.LValue { 219 return L.RawGetInt(stateGet(L), key) 220 } 221 222 func stateEvtChan(L *lua.LState) chan *internalEvent { 223 ud := stateValue(L, keyEvtChan) 224 return ud.(*lua.LUserData).Value.(chan *internalEvent) 225 } 226 227 func stateMsgChan(L *lua.LState) chan *nats.Msg { 228 ud := stateValue(L, keyMsgChan) 229 return ud.(*lua.LUserData).Value.(chan *nats.Msg) 230 } 231 232 func stateCfgMap(L *lua.LState) natsConfigMap { 233 return stateValue(L, keyCfgMap).(*lua.LUserData).Value.(natsConfigMap) 234 } 235 236 func stateConnTable(L *lua.LState) (*lua.LTable, connMap) { 237 tbl := stateValue(L, keyConnTable).(*lua.LTable) 238 idx := L.RawGetInt(tbl, keyIndex).(*lua.LUserData).Value.(connMap) 239 return tbl, idx 240 } 241 242 func stateSubsTable(L *lua.LState) (*lua.LTable, subsMap) { 243 tbl := stateValue(L, keySubsTable).(*lua.LTable) 244 idx := L.RawGetInt(tbl, keyIndex).(*lua.LUserData).Value.(subsMap) 245 return tbl, idx 246 } 247 248 func stateTimerTable(L *lua.LState) *lua.LTable { 249 return stateValue(L, keyTimerTable).(*lua.LTable) 250 } 251 252 /********** NATS Connection Configuration **********/ 253 254 type natsConfig struct { 255 url string 256 name string 257 nkey string 258 user string 259 password string 260 token string 261 } 262 263 type natsConfigMap map[natsConfig]*nats.Conn 264 265 type natsCbMap map[string]*lua.LFunction 266 267 func connConfig(L *lua.LState) (*natsConfig, natsCbMap, error) { 268 arg := L.Get(1) 269 if url, ok := arg.(lua.LString); ok { 270 if cfg, cbmap, err := toConfig(L, L.Get(2)); err != nil { 271 return nil, nil, err 272 } else if cfg.url == "" { 273 cfg.url = string(url) 274 return cfg, cbmap, nil 275 } else if cfg.url != string(url) { 276 return nil, nil, fmt.Errorf("incompatible URLs %q and %q", cfg.url, string(url)) 277 } else { 278 return cfg, cbmap, nil 279 } 280 } else { 281 return toConfig(L, arg) 282 } 283 } 284 285 func toConfig(L *lua.LState, lv lua.LValue) (*natsConfig, natsCbMap, error) { 286 tbl, ok := lv.(*lua.LTable) 287 if !ok { 288 return nil, nil, errors.New("configuration is not a table") 289 } 290 291 var result natsConfig 292 cbmap := make(natsCbMap) 293 var errStr []string 294 295 L.ForEach(tbl, func(key, value lua.LValue) { 296 skey, ok := key.(lua.LString) 297 if !ok { 298 errStr = append(errStr, fmt.Sprintf("bad key: %q", lua.LVAsString(key))) 299 return 300 } 301 302 switch skey { 303 case "closed": 304 fallthrough 305 case "disconnected": 306 fallthrough 307 case "error": 308 fallthrough 309 case "reconnect_error": 310 fallthrough 311 case "reconnected": 312 if s, ok := value.(*lua.LFunction); ok { 313 cbmap[string(skey)] = s 314 } else { 315 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 316 } 317 318 case "url": 319 if s, ok := value.(lua.LString); ok { 320 result.url = string(s) 321 } else { 322 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 323 } 324 325 case "name": 326 if s, ok := value.(lua.LString); ok { 327 result.name = string(s) 328 } else { 329 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 330 } 331 332 case "nkey": 333 if s, ok := value.(lua.LString); ok { 334 result.nkey = string(s) 335 } else { 336 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 337 } 338 339 case "user": 340 if s, ok := value.(lua.LString); ok { 341 result.user = string(s) 342 } else { 343 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 344 } 345 346 case "password": 347 if s, ok := value.(lua.LString); ok { 348 result.password = string(s) 349 } else { 350 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 351 } 352 353 case "token": 354 if s, ok := value.(lua.LString); ok { 355 result.token = string(s) 356 } else { 357 errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value))) 358 } 359 360 default: 361 errStr = append(errStr, fmt.Sprintf("Unknown key %q", skey)) 362 } 363 }) 364 365 if len(errStr) > 0 { 366 return nil, nil, errors.New(strings.Join(errStr, ", ")) 367 } else { 368 return &result, cbmap, nil 369 } 370 } 371 372 func newConn(evtChan chan *internalEvent, cfg *natsConfig) (*nats.Conn, error) { 373 opt := []nats.Option{ 374 nats.DisconnectErrHandler(newConnErrHandler(evtChan, "disconnected")), 375 nats.ReconnectHandler(newConnHandler(evtChan, "reconnected")), 376 nats.ReconnectErrHandler(newConnErrHandler(evtChan, "reconnect_error")), 377 nats.ClosedHandler(newConnHandler(evtChan, "closed")), 378 nats.ErrorHandler(newErrHandler(evtChan, "error")), 379 } 380 381 if cfg.name != "" { 382 opt = append(opt, nats.Name(cfg.name)) 383 } 384 if cfg.nkey != "" { 385 if o, err := nats.NkeyOptionFromSeed(cfg.nkey); err != nil { 386 return nil, err 387 } else { 388 opt = append(opt, o) 389 } 390 } 391 if cfg.user != "" || cfg.password != "" { 392 opt = append(opt, nats.UserInfo(cfg.user, cfg.password)) 393 } 394 if cfg.token != "" { 395 opt = append(opt, nats.Token(cfg.token)) 396 } 397 398 return nats.Connect(cfg.url, opt...) 399 } 400 401 /********** Lua Object for NATS connection **********/ 402 403 const luaNatsConnTypeName = "natsconn" 404 const keyIndex = 1 405 406 type connMap map[*nats.Conn]int 407 408 func registerConnType(L *lua.LState) { 409 L.SetGlobal(luaNatsConnTypeName, L.NewFunction(natsConnect)) 410 } 411 412 func natsConnect(L *lua.LState) int { 413 pcfg, cbmap, err := connConfig(L) 414 if err != nil { 415 log.Println(err) 416 L.Push(lua.LNil) 417 L.Push(lua.LString(err.Error())) 418 return 2 419 } 420 cfg := *pcfg 421 422 cfgMap := stateCfgMap(L) 423 if nc, found := cfgMap[cfg]; found { 424 tbl, idx := stateConnTable(L) 425 if id, ok := idx[nc]; ok { 426 res := L.RawGetInt(tbl, id) 427 if lua.LVIsFalse(res) { 428 panic("Inconsistent connection table") 429 } 430 L.Push(res) 431 return 1 432 } else { 433 panic("Inconsistent connection table") 434 } 435 } 436 437 nc, err := newConn(stateEvtChan(L), pcfg) 438 if err != nil { 439 log.Println("newConn", err) 440 L.Push(lua.LNil) 441 L.Push(lua.LString(err.Error())) 442 return 2 443 } 444 445 cfgMap[cfg] = nc 446 L.Push(wrapConn(L, nc, cbmap)) 447 return 1 448 } 449 450 func wrapConn(L *lua.LState, nc *nats.Conn, cbmap natsCbMap) lua.LValue { 451 luaConn := newUserData(L, nc) 452 453 index := L.NewTable() 454 L.SetField(index, "publish", L.NewFunction(natsPublish)) 455 L.SetField(index, "subscribe", L.NewFunction(natsSubscribe)) 456 457 for key, fn := range cbmap { 458 L.SetField(index, key, fn) 459 } 460 461 mt := L.NewTable() 462 L.SetField(mt, "__index", index) 463 L.SetField(mt, "__newindex", L.NewFunction(connNewIndex)) 464 L.SetMetatable(luaConn, mt) 465 466 tbl, idx := stateConnTable(L) 467 id := tbl.Len() + 1 468 L.RawSetInt(tbl, id, luaConn) 469 470 if _, found := idx[nc]; found { 471 panic("id already in connection table") 472 } 473 idx[nc] = id 474 475 return luaConn 476 } 477 478 func connNewIndex(L *lua.LState) int { 479 ud := L.CheckUserData(1) 480 481 if _, ok := ud.Value.(*nats.Conn); !ok { 482 L.ArgError(1, "connection expected") 483 return 0 484 } 485 486 event := L.CheckString(2) 487 if event != "disconnected" && 488 event != "reconnected" && 489 event != "reconnect_error" && 490 event != "closed" && 491 event != "error" { 492 L.ArgError(2, "unsupported callback name") 493 } 494 495 fn := L.Get(3) 496 if _, ok := fn.(*lua.LFunction); ok && fn != lua.LNil { 497 L.ArgError(3, "function expected") 498 } 499 500 mt := L.GetMetatable(ud) 501 idx := L.GetField(mt, "__index").(*lua.LTable) 502 L.SetField(idx, event, fn) 503 return 0 504 } 505 506 func newConnHandler(evtChan chan *internalEvent, name string) nats.ConnHandler { 507 return func(nc *nats.Conn) { 508 evtChan <- &internalEvent{name: name, nc: nc} 509 } 510 } 511 512 func newConnErrHandler(evtChan chan *internalEvent, name string) nats.ConnErrHandler { 513 return func(nc *nats.Conn, err error) { 514 evtChan <- &internalEvent{name: name, nc: nc, err: err} 515 } 516 } 517 518 func newErrHandler(evtChan chan *internalEvent, name string) nats.ErrHandler { 519 return func(nc *nats.Conn, s *nats.Subscription, err error) { 520 evtChan <- &internalEvent{name: name, nc: nc, subs: s, err: err} 521 } 522 } 523 524 func checkConn(L *lua.LState, index int) *nats.Conn { 525 ud := L.CheckUserData(index) 526 527 if v, ok := ud.Value.(*natsSubs); ok { 528 return v.nc 529 } 530 531 if v, ok := ud.Value.(*nats.Conn); ok { 532 return v 533 } 534 535 L.ArgError(index, "connection expected") 536 return nil 537 } 538 539 func natsPublish(L *lua.LState) int { 540 nc := checkConn(L, 1) 541 subject := L.CheckString(2) 542 data := L.OptString(3, "") 543 544 if err := nc.Publish(subject, []byte(data)); err != nil { 545 log.Println("Publish:", err) 546 L.Push(lua.LNil) 547 L.Push(lua.LString(err.Error())) 548 return 2 549 } else { 550 L.Push(lua.LTrue) 551 return 1 552 } 553 } 554 555 func natsSubscribe(L *lua.LState) int { 556 nc := checkConn(L, 1) 557 subject := L.CheckString(2) 558 fn := L.CheckFunction(3) 559 560 if s, err := nc.ChanSubscribe(subject, stateMsgChan(L)); err != nil { 561 log.Println("Subscribe:", err) 562 L.Push(lua.LNil) 563 L.Push(lua.LString(err.Error())) 564 return 2 565 } else { 566 L.Push(wrapSubs(L, fn, s, nc)) 567 return 1 568 } 569 } 570 571 /********** Lua Object for NATS subscription **********/ 572 573 type natsSubs struct { 574 id int 575 nc *nats.Conn 576 subs *nats.Subscription 577 } 578 579 func wrapSubs(L *lua.LState, fn lua.LValue, ns *nats.Subscription, nc *nats.Conn) lua.LValue { 580 tbl, subsIdx := stateSubsTable(L) 581 id := tbl.Len() + 1 582 luaSub := newUserData(L, &natsSubs{id: id, nc: nc, subs: ns}) 583 584 subsIdx[ns] = id 585 L.RawSetInt(tbl, id, luaSub) 586 587 index := L.NewTable() 588 L.SetField(index, "callback", fn) 589 L.SetField(index, "id", lua.LNumber(id)) 590 L.SetField(index, "subject", lua.LString(string(ns.Subject))) 591 592 L.SetField(index, "publish", L.NewFunction(natsPublish)) 593 L.SetField(index, "subscribe", L.NewFunction(natsSubscribe)) 594 L.SetField(index, "unsubscribe", L.NewFunction(natsUnsubscribe)) 595 596 mt := L.NewTable() 597 L.SetField(mt, "__call", fn) 598 L.SetField(mt, "__index", index) 599 L.SetField(mt, "__newindex", L.NewFunction(subsNewIndex)) 600 601 L.SetMetatable(luaSub, mt) 602 return luaSub 603 } 604 605 func subsNewIndex(L *lua.LState) int { 606 _ = checkSubs(L, 1) 607 mt := L.GetMetatable(L.Get(1)) 608 key := L.Get(2) 609 610 if s, ok := key.(lua.LString); !ok || string(s) != "callback" { 611 L.RaiseError("attempt to change bad subscription field") 612 return 0 613 } 614 615 fn := L.CheckFunction(3) 616 L.SetField(mt, "__call", fn) 617 index := L.GetField(mt, "__index").(*lua.LTable) 618 L.SetField(index, "callback", fn) 619 return 0 620 } 621 622 func checkSubs(L *lua.LState, index int) *natsSubs { 623 ud := L.CheckUserData(index) 624 625 if v, ok := ud.Value.(*natsSubs); ok { 626 return v 627 } 628 629 L.ArgError(index, "subscription expected") 630 return nil 631 } 632 633 func natsUnsubscribe(L *lua.LState) int { 634 s := checkSubs(L, 1) 635 count := L.OptInt(2, 0) 636 637 if err := s.subs.AutoUnsubscribe(count); err != nil { 638 log.Println("Unsubscribe:", err) 639 L.Push(lua.LNil) 640 L.Push(lua.LString(err.Error())) 641 return 2 642 } else { 643 L.Push(lua.LTrue) 644 return 1 645 } 646 } 647 648 /********** Lua Object for timers **********/ 649 650 const luaTimerTypeName = "timer" 651 652 func registerTimerType(L *lua.LState) { 653 mt := L.NewTypeMetatable(luaTimerTypeName) 654 L.SetGlobal(luaTimerTypeName, mt) 655 L.SetField(mt, "new", L.NewFunction(newTimer)) 656 L.SetField(mt, "schedule", L.NewFunction(timerSchedule)) 657 L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), timerMethods)) 658 } 659 660 func newTimer(L *lua.LState) int { 661 atTime := L.Get(1) 662 cb := L.CheckFunction(2) 663 L.Pop(2) 664 L.SetMetatable(cb, L.GetTypeMetatable(luaTimerTypeName)) 665 L.Push(cb) 666 L.Push(atTime) 667 return timerSchedule(L) 668 } 669 670 var timerMethods = map[string]lua.LGFunction{ 671 "cancel": timerCancel, 672 "schedule": timerSchedule, 673 } 674 675 func timerCancel(L *lua.LState) int { 676 timer := L.CheckFunction(1) 677 L.RawSet(stateTimerTable(L), timer, lua.LNil) 678 return 0 679 } 680 681 func timerSchedule(L *lua.LState) int { 682 timer := L.CheckFunction(1) 683 atTime := lua.LNil 684 if L.Get(2) != lua.LNil { 685 atTime = L.CheckNumber(2) 686 } 687 688 L.RawSet(stateTimerTable(L), timer, atTime) 689 return 0 690 } 691 692 func toTime(lsec lua.LNumber) time.Time { 693 fsec := float64(lsec) 694 sec := int64(fsec) 695 nsec := int64((fsec - float64(sec)) * 1.0e9) 696 697 return time.Unix(sec, nsec) 698 } 699 700 func runTimers(L *lua.LState, parentTimer *time.Timer) { 701 hasNext := false 702 var nextTime time.Time 703 704 now := time.Now() 705 timers := stateTimerTable(L) 706 707 timer, luaT := timers.Next(lua.LNil) 708 for timer != lua.LNil { 709 t := toTime(luaT.(lua.LNumber)) 710 if t.Compare(now) <= 0 { 711 L.RawSet(timers, timer, lua.LNil) 712 err := L.CallByParam(lua.P{Fn: timer, NRet: 0, Protect: true}, timer, luaT) 713 if err != nil { 714 panic(err) 715 } 716 timer = lua.LNil 717 hasNext = false 718 } else if !hasNext || t.Compare(nextTime) < 0 { 719 hasNext = true 720 nextTime = t 721 } 722 723 timer, luaT = timers.Next(timer) 724 } 725 726 if hasNext { 727 parentTimer.Reset(time.Until(nextTime)) 728 } else { 729 parentTimer.Stop() 730 } 731 } 732 733 /********** Tools **********/ 734 735 func luaHeader(L *lua.LState, header []string) lua.LValue { 736 switch len(header) { 737 case 0: 738 return lua.LNil 739 case 1: 740 return lua.LString(header[0]) 741 default: 742 result := L.CreateTable(len(header), 0) 743 for i, v := range header { 744 L.RawSetInt(result, i+1, lua.LString(v)) 745 } 746 return result 747 } 748 } 749 750 func luaHeaders(L *lua.LState, reply string, headers map[string][]string) lua.LValue { 751 result := L.NewTable() 752 753 if reply != "" { 754 L.RawSetInt(result, 1, lua.LString(reply)) 755 } 756 757 for key, values := range headers { 758 L.SetField(result, key, luaHeader(L, values)) 759 } 760 761 return result 762 } 763 764 func newUserData(L *lua.LState, v interface{}) *lua.LUserData { 765 res := L.NewUserData() 766 res.Value = v 767 return res 768 } 769 770 func tableIsEmpty(t *lua.LTable) bool { 771 key, _ := t.Next(lua.LNil) 772 return key == lua.LNil 773 } 774 775 func tableWithIndexIsEmpty(t *lua.LTable, idx interface{}) bool { 776 key, _ := t.Next(lua.LNil) 777 778 if n, ok := key.(lua.LNumber); ok && n == lua.LNumber(keyIndex) { 779 key, _ = t.Next(key) 780 } 781 782 return key == lua.LNil 783 }