server.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package uiserver
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net"
  6. "os"
  7. )
  8. // Worker handles a single connection, providing updates to the clients
  9. type Worker struct {
  10. Update chan interface{} // this is called whenever a worker should update
  11. Exit chan interface{} // this is called to force the worker to Close
  12. Conn net.Conn
  13. }
  14. // NewWorker creates a new Worker from an ongoing connection
  15. func NewWorker(conn net.Conn) Worker {
  16. w := Worker{Conn: conn}
  17. w.Update = make(chan interface{}, 10)
  18. return w
  19. }
  20. func (w *Worker) send(obj interface{}) error {
  21. marshalled, err := json.Marshal(obj)
  22. if err != nil {
  23. return err
  24. }
  25. marshalled = append(marshalled, byte('\n'))
  26. _, err = w.Conn.Write(marshalled)
  27. return err
  28. }
  29. // Work loops on Worker channel to do what is needed
  30. func (w *Worker) Work() {
  31. for {
  32. select {
  33. case <-w.Exit:
  34. w.Conn.Close()
  35. return
  36. case obj := <-w.Update:
  37. err := w.send(obj)
  38. if err != nil {
  39. w.Conn.Close()
  40. close(w.Update)
  41. close(w.Exit)
  42. return
  43. }
  44. }
  45. }
  46. }
  47. // NetUI handles a server for the protocol documented in this package
  48. // the API is designed so to make it possible to attach it to multiple
  49. // listeners, despite this is NOT possible at the moment
  50. type NetUI struct {
  51. Object interface{}
  52. workers []Worker
  53. sock *net.Listener
  54. exit chan interface{}
  55. }
  56. // protocolHello is the hello message that the server will give to clients
  57. // a server doesn't expect any introduction from clients
  58. // the hello message is useful to clients to recognize protocol version cleanly
  59. // the clients are guaranteed that the hello message will always be present in
  60. // future versions of the protocol, always terminated by a newline and of
  61. // exactly 15bytes
  62. var protocolHello = "DIRETTOFORO V1\n"
  63. // NewNetUI creates a NetUI object. Don't create it otherwise!
  64. // the parameter "obj" must be a pointer; if it's not a pointer, no error will
  65. // be raised but bad things could happen later
  66. func NewNetUI(obj interface{}) NetUI {
  67. exitchan := make(chan interface{})
  68. return NetUI{exit: exitchan, Object: obj}
  69. }
  70. // Update sends the new state to every connected client
  71. func (n *NetUI) Update() {
  72. for _, w := range n.workers {
  73. select {
  74. case w.Update <- n.Object:
  75. default:
  76. fmt.Fprintln(os.Stderr, "error pushing message to worker on", w.Conn.RemoteAddr())
  77. // TODO: shall we remove w from the pool on first error?
  78. }
  79. }
  80. }
  81. // Close shuts the TCPserver down synchronously
  82. func (n *NetUI) Close() {
  83. if n.sock != nil {
  84. (*n.sock).Close()
  85. }
  86. for _, w := range n.workers {
  87. close(w.Exit)
  88. close(w.Update)
  89. }
  90. }
  91. // Run does the heavy work, and is blocking. It expects a Listener (typically
  92. // got with net.Listener)
  93. // It will exit when NetUI.Close() is called or when an error on the socket
  94. // happens
  95. func (n *NetUI) Run(sock net.Listener) error {
  96. if n.sock != nil {
  97. return fmt.Errorf("More than one Run for a single NetUI is currently unsupported")
  98. }
  99. n.sock = &sock
  100. defer sock.Close()
  101. for {
  102. conn, err := sock.Accept()
  103. if err != nil {
  104. return nil
  105. }
  106. // FIXME: performance problem here: we're waiting each client to be
  107. // validated before passing to sth else; we'd better run this in a
  108. // goroutine, but then the access to n.workers would be concurrent,
  109. // which is not allowed
  110. err = validateRequest(conn)
  111. if err != nil {
  112. continue
  113. }
  114. connworker := NewWorker(conn)
  115. connworker.Update <- n.Object
  116. n.workers = append(n.workers, connworker)
  117. go connworker.Work()
  118. }
  119. }
  120. func validateRequest(conn net.Conn) error {
  121. _, err := conn.Write([]byte(protocolHello))
  122. if err != nil {
  123. conn.Close()
  124. return err
  125. }
  126. return nil
  127. }