Capstan is a Golang web framework that shares some similarities with others in its segment.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
2.7KB

  1. package capstan
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. "sync"
  7. "syscall"
  8. )
  9. type PreLaunchFunc func() error
  10. type Listener interface {
  11. net.Listener
  12. syscall.Conn
  13. }
  14. type file struct {
  15. fp *os.File
  16. fd uintptr
  17. }
  18. type fdkey struct {
  19. network string
  20. addr string
  21. }
  22. func (k *fdkey) String() string {
  23. return k.network + ":" + k.addr
  24. }
  25. type fdencode struct {
  26. Name string `json:"fp"`
  27. FD uintptr `json:"fd"`
  28. }
  29. type fdenv struct {
  30. Sockets []*fdencode `json:"sockets"`
  31. ParentPID int `json:"parent_pid"`
  32. }
  33. type fdmap struct {
  34. sockets map[string]Listener
  35. descriptors map[string]uintptr
  36. sync.RWMutex
  37. }
  38. func (f *fdmap) Files() ([]*file, error) {
  39. i := 0
  40. files := make([]*file, len(f.sockets))
  41. for key, listener := range f.sockets {
  42. conn, ok := listener.(syscall.Conn)
  43. if !ok {
  44. return nil, fmt.Errorf("listener doesn't implement syscall.Conn")
  45. }
  46. fd, err := duplicate(conn)
  47. if err != nil {
  48. return nil, err
  49. }
  50. files[i] = &file{
  51. fp: os.NewFile(fd, key),
  52. fd: fd,
  53. }
  54. i++
  55. }
  56. return files, nil
  57. }
  58. var fds *fdmap
  59. var parentPID int
  60. var childPID int
  61. var prelaunchFunc PreLaunchFunc
  62. func listenerKey(listener net.Listener) string {
  63. if listener.Addr().Network() != "unix" {
  64. host, port, _ := net.SplitHostPort(listener.Addr().String())
  65. if host == "::" {
  66. host = ""
  67. }
  68. return netKey(listener.Addr().Network(), net.JoinHostPort(host, port))
  69. }
  70. return netKey(listener.Addr().Network(), listener.Addr().String())
  71. }
  72. func netKey(network, addr string) string {
  73. if network != "unix" {
  74. host, port, _ := net.SplitHostPort(addr)
  75. return network + "::" + net.JoinHostPort(host, port)
  76. }
  77. return addr
  78. }
  79. func AddPreLaunchFunc(fn PreLaunchFunc) {
  80. if prelaunchFunc != nil {
  81. oldfn := prelaunchFunc
  82. prelaunchFunc = func() error {
  83. if err := oldfn(); err != nil {
  84. return err
  85. }
  86. return fn()
  87. }
  88. }
  89. prelaunchFunc = fn
  90. }
  91. func (f fdmap) addListener(listener Listener) error {
  92. var fd uintptr
  93. conn, ok := listener.(syscall.Conn)
  94. if !ok {
  95. return fmt.Errorf("listener does not implement syscall.Conn")
  96. }
  97. raw, err := conn.SyscallConn()
  98. if err != nil {
  99. return err
  100. }
  101. raw.Control(func(ptr uintptr) {
  102. fd = ptr
  103. })
  104. f.Lock()
  105. f.sockets[listenerKey(listener)] = listener
  106. f.descriptors[listenerKey(listener)] = fd
  107. f.Unlock()
  108. return nil
  109. }
  110. // Removes the listener associated with the specified network and addr tuple.
  111. func (f fdmap) removeListener(listener net.Listener) {
  112. f.Lock()
  113. delete(f.sockets, listenerKey(listener))
  114. delete(f.descriptors, listenerKey(listener))
  115. f.Unlock()
  116. }
  117. // Resume and return a listener defined by the file descriptor `fd`.
  118. func resumeListener(network, addr string, fd uintptr) (net.Listener, error) {
  119. file := os.NewFile(fd, network+":"+addr)
  120. defer file.Close()
  121. return net.FileListener(file)
  122. }