main.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package main
  2. import (
  3. "compress/gzip"
  4. "context"
  5. "crypto/tls"
  6. "fmt"
  7. "html/template"
  8. "io"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "path/filepath"
  13. "sort"
  14. "strings"
  15. "github.com/gorilla/mux"
  16. "github.com/nats-io/nats.go"
  17. "nhooyr.io/websocket"
  18. )
  19. var natsConn *nats.Conn
  20. func Initialize() error {
  21. var err error
  22. natsConn, err = nats.Connect(nats.DefaultURL)
  23. if err != nil {
  24. return err
  25. }
  26. return nil
  27. }
  28. func wsLoop(ctx context.Context, cancelFunc context.CancelFunc, ws *websocket.Conn, topic string, userID string) {
  29. defer closeWS(ws)
  30. for {
  31. if _, message, err := ws.Read(ctx); err != nil {
  32. log.Printf("Error reading message %s", err)
  33. break
  34. } else {
  35. msg := &nats.Msg{Subject: topic, Data: message, Reply: userID}
  36. if err = natsConn.PublishMsg(msg); err != nil {
  37. log.Printf("Could not publish message: %s", err)
  38. return
  39. }
  40. }
  41. }
  42. cancelFunc()
  43. }
  44. func natsConnLoop(cctx, ctx context.Context, ws *websocket.Conn, topic string, userID string) {
  45. _, err := natsConn.Subscribe(topic, func(m *nats.Msg) {
  46. m.Ack()
  47. if m.Reply == userID {
  48. return
  49. }
  50. if err := ws.Write(ctx, websocket.MessageText, m.Data); err != nil {
  51. log.Printf("Error writing message to %s: %s", userID, err)
  52. return
  53. }
  54. })
  55. if err != nil {
  56. panic(err)
  57. }
  58. }
  59. func closeWS(ws *websocket.Conn) {
  60. // can check if already closed here
  61. if err := ws.Close(websocket.StatusNormalClosure, ""); err != nil {
  62. log.Printf("Error closing: %s", err)
  63. }
  64. }
  65. func VideoConnections(w http.ResponseWriter, r *http.Request) {
  66. ws, err := websocket.Accept(w, r, nil)
  67. if err != nil {
  68. log.Fatal(err)
  69. }
  70. userID := strings.ToLower(r.URL.Query().Get("userID"))
  71. peerID := strings.ToLower(r.URL.Query().Get("peerID"))
  72. peers := []string{userID, peerID}
  73. sort.Strings(peers)
  74. topicName := fmt.Sprintf("video-%s-%s", peers[0], peers[1])
  75. ctx := context.Background()
  76. cctx, cancelFunc := context.WithCancel(ctx)
  77. go wsLoop(ctx, cancelFunc, ws, topicName, userID)
  78. natsConnLoop(cctx, ctx, ws, topicName, userID)
  79. }
  80. const TEMPLATE = "layouts/layout.html"
  81. const STATIC_DIR = "/static/"
  82. type API struct {
  83. }
  84. type PageData struct {
  85. Title string
  86. Content string
  87. CanonicalURL string
  88. OGTitle string
  89. OGDescription string
  90. OGType string
  91. OGImage string
  92. }
  93. type gzipResponseWriter struct {
  94. io.Writer
  95. http.ResponseWriter
  96. }
  97. func (w gzipResponseWriter) Write(b []byte) (int, error) {
  98. return w.Writer.Write(b)
  99. }
  100. func makeGzipHandler(h http.Handler) http.Handler {
  101. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  102. if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  103. h.ServeHTTP(w, r)
  104. return
  105. }
  106. w.Header().Set("Content-Encoding", "gzip")
  107. gz := gzip.NewWriter(w)
  108. defer gz.Close()
  109. gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
  110. h.ServeHTTP(gzr, r)
  111. })
  112. }
  113. func notFound(w http.ResponseWriter, r *http.Request) {
  114. t, _ := template.ParseFiles(TEMPLATE, "content/custom_404.html")
  115. w.WriteHeader(http.StatusNotFound)
  116. t.ExecuteTemplate(w, "layout", &PageData{
  117. Title: "DUMMY", Content: ""})
  118. }
  119. func fileHandler(w http.ResponseWriter, r *http.Request) {
  120. http.ServeFile(w, r, "."+STATIC_DIR+r.URL.Path[1:])
  121. }
  122. func maxAgeHandler(seconds int, h http.Handler) http.Handler {
  123. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  124. w.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d, public, immutable", seconds))
  125. h.ServeHTTP(w, r)
  126. })
  127. }
  128. func compileTemplates(ua string, filenames ...string) (*template.Template, error) {
  129. var tmpl *template.Template
  130. for _, filename := range filenames {
  131. name := filepath.Base(filename)
  132. if tmpl == nil {
  133. tmpl = template.New(name).Funcs(template.FuncMap{})
  134. } else {
  135. tmpl = tmpl.New(name).Funcs(template.FuncMap{})
  136. }
  137. b, err := ioutil.ReadFile(filename)
  138. if err != nil {
  139. return nil, err
  140. }
  141. tmpl.Parse(string(b))
  142. }
  143. return tmpl, nil
  144. }
  145. type HomePageData struct {
  146. *PageData
  147. }
  148. func (api *API) index(w http.ResponseWriter, r *http.Request) {
  149. ua := r.Header.Get("User-Agent")
  150. t, err := compileTemplates(ua, TEMPLATE, "content/index.html")
  151. if err != nil {
  152. panic(err)
  153. }
  154. err = t.ExecuteTemplate(w, "layout", &HomePageData{
  155. PageData: &PageData{
  156. CanonicalURL: "",
  157. Title: "",
  158. Content: "",
  159. OGTitle: "",
  160. OGDescription: "",
  161. OGImage: "",
  162. OGType: "",
  163. }})
  164. if err != nil {
  165. panic(err)
  166. }
  167. }
  168. func main() {
  169. api := &API{}
  170. err := Initialize()
  171. if err != nil {
  172. panic(err)
  173. }
  174. router := mux.NewRouter().StrictSlash(true)
  175. router.
  176. PathPrefix("/static/").
  177. Handler(http.StripPrefix(STATIC_DIR, makeGzipHandler(maxAgeHandler(2629746, http.FileServer(http.Dir("."+STATIC_DIR))))))
  178. router.HandleFunc("/robots.txt", fileHandler).Methods("GET")
  179. router.HandleFunc("/", api.index).Methods("GET")
  180. router.NotFoundHandler = http.HandlerFunc(notFound)
  181. router.HandleFunc("/video/connections", VideoConnections).Methods(http.MethodGet)
  182. tls.LoadX509KeyPair("localhost.crt", "localhost.key")
  183. log.Fatal(http.ListenAndServe(":8000", router))
  184. }