gowebbuild/vendor/github.com/jaschaephraim/lrserver/server.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
}