204 lines
3.9 KiB
Go
204 lines
3.9 KiB
Go
package lrserver
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"context"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// Server contains a single lrserver instance's data
|
|
type Server struct {
|
|
name string
|
|
port uint16
|
|
server *http.Server
|
|
connSet *connSet
|
|
js string
|
|
statusLog *log.Logger
|
|
liveCSS bool
|
|
}
|
|
|
|
// New creates a new Server instance
|
|
func New(name string, port uint16) *Server {
|
|
// Create router
|
|
router := http.NewServeMux()
|
|
|
|
logPrefix := "[" + name + "] "
|
|
|
|
// Create server
|
|
s := &Server{
|
|
name: name,
|
|
server: &http.Server{
|
|
Handler: router,
|
|
ErrorLog: log.New(os.Stderr, logPrefix, 0),
|
|
},
|
|
connSet: &connSet{conns: make(map[*conn]struct{})},
|
|
statusLog: log.New(os.Stdout, logPrefix, 0),
|
|
liveCSS: true,
|
|
}
|
|
s.setPort(port)
|
|
|
|
// Handle JS
|
|
router.HandleFunc("/livereload.js", jsHandler(s))
|
|
|
|
// Handle reload requests
|
|
router.HandleFunc("/livereload", webSocketHandler(s))
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *Server) Close() error {
|
|
if s.server != nil {
|
|
return s.server.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
if s.server != nil {
|
|
return s.server.Shutdown(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) ListenAndServe() error {
|
|
// Create listener
|
|
l, err := net.Listen("tcp", makeAddr(s.port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set assigned port if necessary
|
|
if s.port == 0 {
|
|
port, err := makePort(l.Addr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.setPort(port)
|
|
}
|
|
|
|
s.logStatus("listening on " + s.server.Addr)
|
|
return s.server.Serve(l)
|
|
}
|
|
|
|
// Reload sends a reload message to the client
|
|
func (s *Server) Reload(file string) {
|
|
s.logStatus("requesting reload: " + file)
|
|
for conn := range s.connSet.conns {
|
|
conn.reloadChan <- file
|
|
}
|
|
}
|
|
|
|
// Alert sends an alert message to the client
|
|
func (s *Server) Alert(msg string) {
|
|
s.logStatus("requesting alert: " + msg)
|
|
for conn := range s.connSet.conns {
|
|
conn.alertChan <- msg
|
|
}
|
|
}
|
|
|
|
// Name gets the server name
|
|
func (s *Server) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
// Port gets the port that the server is listening on
|
|
func (s *Server) Port() uint16 {
|
|
return s.port
|
|
}
|
|
|
|
// LiveCSS gets the live CSS preference
|
|
func (s *Server) LiveCSS() bool {
|
|
return s.liveCSS
|
|
}
|
|
|
|
// StatusLog gets the server's status logger,
|
|
// which writes to os.Stdout by default
|
|
func (s *Server) StatusLog() *log.Logger {
|
|
return s.statusLog
|
|
}
|
|
|
|
// ErrorLog gets the server's error logger,
|
|
// which writes to os.Stderr by default
|
|
func (s *Server) ErrorLog() *log.Logger {
|
|
return s.server.ErrorLog
|
|
}
|
|
|
|
// SetLiveCSS sets the live CSS preference
|
|
func (s *Server) SetLiveCSS(n bool) {
|
|
s.liveCSS = n
|
|
}
|
|
|
|
// SetStatusLog sets the server's status logger,
|
|
// which can be set to nil
|
|
func (s *Server) SetStatusLog(l *log.Logger) {
|
|
s.statusLog = l
|
|
}
|
|
|
|
// SetErrorLog sets the server's error logger,
|
|
// which can be set to nil
|
|
func (s *Server) SetErrorLog(l *log.Logger) {
|
|
s.server.ErrorLog = l
|
|
}
|
|
|
|
func (s *Server) setPort(port uint16) {
|
|
s.port = port
|
|
s.server.Addr = makeAddr(port)
|
|
|
|
if port != 0 {
|
|
s.js = fmt.Sprintf(js, s.port)
|
|
}
|
|
}
|
|
|
|
func (s *Server) newConn(wsConn *websocket.Conn) {
|
|
c := &conn{
|
|
conn: wsConn,
|
|
|
|
server: s,
|
|
handshake: false,
|
|
|
|
reloadChan: make(chan string),
|
|
alertChan: make(chan string),
|
|
closeChan: make(chan closeSignal),
|
|
}
|
|
s.connSet.add(c)
|
|
go c.start()
|
|
}
|
|
|
|
func (s *Server) logStatus(msg ...interface{}) {
|
|
if s.statusLog != nil {
|
|
s.statusLog.Println(msg...)
|
|
}
|
|
}
|
|
|
|
func (s *Server) logError(msg ...interface{}) {
|
|
if s.server.ErrorLog != nil {
|
|
s.server.ErrorLog.Println(msg...)
|
|
}
|
|
}
|
|
|
|
// makeAddr converts uint16(x) to ":x"
|
|
func makeAddr(port uint16) string {
|
|
return fmt.Sprintf(":%d", port)
|
|
}
|
|
|
|
// makePort converts ":x" to uint16(x)
|
|
func makePort(addr string) (uint16, error) {
|
|
_, portString, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
port64, err := strconv.ParseUint(portString, 10, 16)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint16(port64), nil
|
|
}
|