forked from lug/matterbridge
		
	
		
			
				
	
	
		
			194 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"flag"
 | |
| 	"html/template"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gorilla/websocket"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// Time allowed to write the file to the client.
 | |
| 	writeWait = 10 * time.Second
 | |
| 
 | |
| 	// Time allowed to read the next pong message from the client.
 | |
| 	pongWait = 60 * time.Second
 | |
| 
 | |
| 	// Send pings to client with this period. Must be less than pongWait.
 | |
| 	pingPeriod = (pongWait * 9) / 10
 | |
| 
 | |
| 	// Poll file for changes with this period.
 | |
| 	filePeriod = 10 * time.Second
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	addr      = flag.String("addr", ":8080", "http service address")
 | |
| 	homeTempl = template.Must(template.New("").Parse(homeHTML))
 | |
| 	filename  string
 | |
| 	upgrader  = websocket.Upgrader{
 | |
| 		ReadBufferSize:  1024,
 | |
| 		WriteBufferSize: 1024,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
 | |
| 	fi, err := os.Stat(filename)
 | |
| 	if err != nil {
 | |
| 		return nil, lastMod, err
 | |
| 	}
 | |
| 	if !fi.ModTime().After(lastMod) {
 | |
| 		return nil, lastMod, nil
 | |
| 	}
 | |
| 	p, err := ioutil.ReadFile(filename)
 | |
| 	if err != nil {
 | |
| 		return nil, fi.ModTime(), err
 | |
| 	}
 | |
| 	return p, fi.ModTime(), nil
 | |
| }
 | |
| 
 | |
| func reader(ws *websocket.Conn) {
 | |
| 	defer ws.Close()
 | |
| 	ws.SetReadLimit(512)
 | |
| 	ws.SetReadDeadline(time.Now().Add(pongWait))
 | |
| 	ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
 | |
| 	for {
 | |
| 		_, _, err := ws.ReadMessage()
 | |
| 		if err != nil {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func writer(ws *websocket.Conn, lastMod time.Time) {
 | |
| 	lastError := ""
 | |
| 	pingTicker := time.NewTicker(pingPeriod)
 | |
| 	fileTicker := time.NewTicker(filePeriod)
 | |
| 	defer func() {
 | |
| 		pingTicker.Stop()
 | |
| 		fileTicker.Stop()
 | |
| 		ws.Close()
 | |
| 	}()
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-fileTicker.C:
 | |
| 			var p []byte
 | |
| 			var err error
 | |
| 
 | |
| 			p, lastMod, err = readFileIfModified(lastMod)
 | |
| 
 | |
| 			if err != nil {
 | |
| 				if s := err.Error(); s != lastError {
 | |
| 					lastError = s
 | |
| 					p = []byte(lastError)
 | |
| 				}
 | |
| 			} else {
 | |
| 				lastError = ""
 | |
| 			}
 | |
| 
 | |
| 			if p != nil {
 | |
| 				ws.SetWriteDeadline(time.Now().Add(writeWait))
 | |
| 				if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		case <-pingTicker.C:
 | |
| 			ws.SetWriteDeadline(time.Now().Add(writeWait))
 | |
| 			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func serveWs(w http.ResponseWriter, r *http.Request) {
 | |
| 	ws, err := upgrader.Upgrade(w, r, nil)
 | |
| 	if err != nil {
 | |
| 		if _, ok := err.(websocket.HandshakeError); !ok {
 | |
| 			log.Println(err)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var lastMod time.Time
 | |
| 	if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil {
 | |
| 		lastMod = time.Unix(0, n)
 | |
| 	}
 | |
| 
 | |
| 	go writer(ws, lastMod)
 | |
| 	reader(ws)
 | |
| }
 | |
| 
 | |
| func serveHome(w http.ResponseWriter, r *http.Request) {
 | |
| 	if r.URL.Path != "/" {
 | |
| 		http.Error(w, "Not found", 404)
 | |
| 		return
 | |
| 	}
 | |
| 	if r.Method != "GET" {
 | |
| 		http.Error(w, "Method not allowed", 405)
 | |
| 		return
 | |
| 	}
 | |
| 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 | |
| 	p, lastMod, err := readFileIfModified(time.Time{})
 | |
| 	if err != nil {
 | |
| 		p = []byte(err.Error())
 | |
| 		lastMod = time.Unix(0, 0)
 | |
| 	}
 | |
| 	var v = struct {
 | |
| 		Host    string
 | |
| 		Data    string
 | |
| 		LastMod string
 | |
| 	}{
 | |
| 		r.Host,
 | |
| 		string(p),
 | |
| 		strconv.FormatInt(lastMod.UnixNano(), 16),
 | |
| 	}
 | |
| 	homeTempl.Execute(w, &v)
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	flag.Parse()
 | |
| 	if flag.NArg() != 1 {
 | |
| 		log.Fatal("filename not specified")
 | |
| 	}
 | |
| 	filename = flag.Args()[0]
 | |
| 	http.HandleFunc("/", serveHome)
 | |
| 	http.HandleFunc("/ws", serveWs)
 | |
| 	if err := http.ListenAndServe(*addr, nil); err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const homeHTML = `<!DOCTYPE html>
 | |
| <html lang="en">
 | |
|     <head>
 | |
|         <title>WebSocket Example</title>
 | |
|     </head>
 | |
|     <body>
 | |
|         <pre id="fileData">{{.Data}}</pre>
 | |
|         <script type="text/javascript">
 | |
|             (function() {
 | |
|                 var data = document.getElementById("fileData");
 | |
|                 var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}");
 | |
|                 conn.onclose = function(evt) {
 | |
|                     data.textContent = 'Connection closed';
 | |
|                 }
 | |
|                 conn.onmessage = function(evt) {
 | |
|                     console.log('file updated');
 | |
|                     data.textContent = evt.data;
 | |
|                 }
 | |
|             })();
 | |
|         </script>
 | |
|     </body>
 | |
| </html>
 | |
| `
 | 
