Set long cache-control and add a hash to css and js urls, clean some things up

This commit is contained in:
Ken-Håvard Lieng 2016-01-25 06:01:40 +01:00
parent 2ccca3a778
commit df02d27674
9 changed files with 171 additions and 77 deletions

View file

@ -13,6 +13,37 @@ import (
"github.com/khlieng/dispatch/storage"
)
const (
cookieName = "dispatch"
)
var (
hmacKey []byte
)
func initAuth() {
var err error
hmacKey, err = getHMACKey()
if err != nil {
log.Fatal(err)
}
}
func getHMACKey() ([]byte, error) {
key, err := ioutil.ReadFile(storage.Path.HMACKey())
if err != nil {
key = make([]byte, 32)
rand.Read(key)
err = ioutil.WriteFile(storage.Path.HMACKey(), key, 0600)
if err != nil {
return nil, err
}
}
return key, nil
}
func handleAuth(w http.ResponseWriter, r *http.Request) *Session {
var session *Session
@ -21,22 +52,14 @@ func handleAuth(w http.ResponseWriter, r *http.Request) *Session {
authLog(r, "No cookie set")
session = newUser(w, r)
} else {
token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return hmacKey, nil
})
token, err := parseToken(cookie.Value)
if err == nil && token.Valid {
userID := uint64(token.Claims["UserID"].(float64))
log.Println(r.RemoteAddr, "[Auth] GET", r.URL.Path, "| Valid token | User ID:", userID)
sessionLock.Lock()
session = sessions[userID]
sessionLock.Unlock()
session = sessions.get(userID)
if session == nil {
// A previous anonymous session has been cleaned up, create a new one
session = newUser(w, r)
@ -47,6 +70,7 @@ func handleAuth(w http.ResponseWriter, r *http.Request) *Session {
} else {
authLog(r, "Invalid token")
}
session = newUser(w, r)
}
}
@ -63,11 +87,7 @@ func newUser(w http.ResponseWriter, r *http.Request) *Session {
log.Println(r.RemoteAddr, "[Auth] Create session | User ID:", user.ID)
session := NewSession(user)
sessionLock.Lock()
sessions[user.ID] = session
sessionLock.Unlock()
sessions.set(user.ID, session)
go session.run()
token := jwt.New(jwt.SigningMethodHS256)
@ -89,19 +109,14 @@ func newUser(w http.ResponseWriter, r *http.Request) *Session {
return session
}
func getHMACKey() ([]byte, error) {
key, err := ioutil.ReadFile(storage.Path.HMACKey())
if err != nil {
key = make([]byte, 32)
rand.Read(key)
err = ioutil.WriteFile(storage.Path.HMACKey(), key, 0600)
if err != nil {
return nil, err
func parseToken(cookie string) (*jwt.Token, error) {
return jwt.Parse(cookie, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
}
return hmacKey, nil
})
return key, nil
}
func authLog(r *http.Request, s string) {

View file

@ -8,8 +8,10 @@ import (
)
var (
index_start = []byte(`<!DOCTYPE html><html lang=en><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Dispatch</title><link href="https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto+Mono:400,700" rel=stylesheet><link href=/bundle.css rel=stylesheet></head><body><div id=root></div><script>window.__ENV__=`)
index_end = []byte(`;</script><script src=/bundle.js></script></body></html>`)
index_0 = []byte(`<!DOCTYPE html><html lang=en><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Dispatch</title><link href="https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto+Mono:400,700" rel=stylesheet><link href=/`)
index_1 = []byte(` rel=stylesheet></head><body><div id=root></div><script>window.__ENV__=`)
index_2 = []byte(`;</script><script src=/`)
index_3 = []byte(`></script></body></html>`)
)
type connectDefaults struct {
@ -25,7 +27,9 @@ type indexData struct {
}
func renderIndex(w io.Writer, session *Session) {
w.Write(index_start)
w.Write(index_0)
w.Write([]byte(files[1].Path))
w.Write(index_1)
json.NewEncoder(w).Encode(indexData{
Defaults: connectDefaults{
@ -37,5 +41,7 @@ func renderIndex(w io.Writer, session *Session) {
},
})
w.Write(index_end)
w.Write(index_2)
w.Write([]byte(files[0].Path))
w.Write(index_3)
}

View file

@ -13,7 +13,7 @@ import (
func reconnectIRC() {
for _, user := range storage.LoadUsers() {
session := NewSession(user)
sessions[user.ID] = session
sessions.set(user.ID, session)
go session.run()
channels := user.GetChannels()

View file

@ -41,7 +41,7 @@ func dispatchMessage(msg *irc.Message) WSResponse {
newIRCHandler(c, s).dispatchMessage(msg)
return <-s.out
return <-s.broadcast
}
func checkResponse(t *testing.T, expectedType string, expectedData interface{}, res WSResponse) {
@ -194,7 +194,7 @@ func TestHandleIRCWhois(t *testing.T) {
Realname: "realname",
Server: "srv.com",
Channels: []string{"#chan", "#chan1"},
}, <-s.out)
}, <-s.broadcast)
}
func TestHandleIRCTopic(t *testing.T) {
@ -236,7 +236,7 @@ func TestHandleIRCNames(t *testing.T) {
Server: "host.com",
Channel: "#chan",
Users: []string{"a", "b", "c", "d"},
}, <-s.out)
}, <-s.broadcast)
}
func TestHandleIRCMotd(t *testing.T) {
@ -263,5 +263,5 @@ func TestHandleIRCMotd(t *testing.T) {
Server: "host.com",
Title: "motd title",
Content: []string{"line 1", "line 2"},
}, <-s.out)
}, <-s.broadcast)
}

View file

@ -3,6 +3,8 @@ package server
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/base64"
"io/ioutil"
"log"
"net/http"
@ -10,22 +12,71 @@ import (
"strings"
"time"
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
"github.com/khlieng/dispatch/assets"
)
var files = []File{
File{"vendor.js", "text/javascript"},
File{"bundle.js", "text/javascript"},
File{"bundle.css", "text/css"},
File{"font/fontello.woff", "application/font-woff"},
File{"font/fontello.ttf", "application/x-font-ttf"},
File{"font/fontello.eot", "application/vnd.ms-fontobject"},
File{"font/fontello.svg", "image/svg+xml"},
File{
Path: "bundle.js",
Asset: "bundle.js.gz",
ContentType: "text/javascript",
CacheControl: "max-age=31536000",
},
File{
Path: "bundle.css",
Asset: "bundle.css.gz",
ContentType: "text/css",
CacheControl: "max-age=31536000",
},
File{
Path: "font/fontello.woff",
Asset: "font/fontello.woff.gz",
ContentType: "application/font-woff",
},
File{
Path: "font/fontello.ttf",
Asset: "font/fontello.ttf.gz",
ContentType: "application/x-font-ttf",
},
File{
Path: "font/fontello.eot",
Asset: "font/fontello.eot.gz",
ContentType: "application/vnd.ms-fontobject",
},
File{
Path: "font/fontello.svg",
Asset: "font/fontello.svg.gz",
ContentType: "image/svg+xml",
},
}
type File struct {
Path string
ContentType string
Path string
Asset string
ContentType string
CacheControl string
}
func initFileServer() {
if !viper.GetBool("dev") {
data, err := assets.Asset(files[0].Asset)
if err != nil {
log.Fatal(err)
}
hash := md5.Sum(data)
files[0].Path = "bundle." + base64.RawURLEncoding.EncodeToString(hash[:]) + ".js"
data, err = assets.Asset(files[1].Asset)
if err != nil {
log.Fatal(err)
}
hash = md5.Sum(data)
files[1].Path = "bundle." + base64.RawURLEncoding.EncodeToString(hash[:]) + ".css"
}
}
func serveFiles(w http.ResponseWriter, r *http.Request) {
@ -41,7 +92,11 @@ func serveFiles(w http.ResponseWriter, r *http.Request) {
for _, file := range files {
if strings.HasSuffix(r.URL.Path, file.Path) {
serveFile(w, r, file.Path+".gz", file.ContentType)
if file.CacheControl != "" {
w.Header().Set("Cache-Control", file.CacheControl)
}
serveFile(w, r, file.Asset, file.ContentType)
return
}
}
@ -57,6 +112,7 @@ func serveIndex(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/html")
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {

View file

@ -7,7 +7,6 @@ import (
"net/http/httputil"
"net/url"
"strings"
"sync"
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/gorilla/websocket"
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
@ -17,16 +16,9 @@ import (
"github.com/khlieng/dispatch/storage"
)
const (
cookieName = "dispatch"
)
var (
sessions *sessionStore
channelStore *storage.ChannelStore
sessions map[uint64]*Session
sessionLock sync.Mutex
hmacKey []byte
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
@ -38,21 +30,13 @@ var (
)
func Run() {
defer storage.Close()
sessions = newSessionStore()
channelStore = storage.NewChannelStore()
sessions = make(map[uint64]*Session)
var err error
hmacKey, err = getHMACKey()
if err != nil {
log.Fatal(err)
}
reconnectIRC()
initAuth()
initFileServer()
startHTTP()
select {}
}
func startHTTP() {

View file

@ -17,9 +17,9 @@ type Session struct {
connectionState map[string]bool
ircLock sync.Mutex
ws map[string]*wsConn
wsLock sync.Mutex
out chan WSResponse
ws map[string]*wsConn
wsLock sync.Mutex
broadcast chan WSResponse
user *storage.User
expiration *time.Timer
@ -31,7 +31,7 @@ func NewSession(user *storage.User) *Session {
irc: make(map[string]*irc.Client),
connectionState: make(map[string]bool),
ws: make(map[string]*wsConn),
out: make(chan WSResponse, 32),
broadcast: make(chan WSResponse, 32),
user: user,
expiration: time.NewTimer(AnonymousSessionExpiration),
reset: make(chan time.Duration, 1),
@ -115,7 +115,7 @@ func (s *Session) numWS() int {
}
func (s *Session) sendJSON(t string, v interface{}) {
s.out <- WSResponse{t, v}
s.broadcast <- WSResponse{t, v}
}
func (s *Session) sendError(err error, server string) {
@ -134,7 +134,7 @@ func (s *Session) resetExpirationIfEmpty() {
func (s *Session) run() {
for {
select {
case res := <-s.out:
case res := <-s.broadcast:
s.wsLock.Lock()
for _, ws := range s.ws {
ws.out <- res
@ -142,9 +142,7 @@ func (s *Session) run() {
s.wsLock.Unlock()
case <-s.expiration.C:
sessionLock.Lock()
delete(sessions, s.user.ID)
sessionLock.Unlock()
sessions.delete(s.user.ID)
s.user.Remove()
return
@ -157,3 +155,33 @@ func (s *Session) run() {
}
}
}
type sessionStore struct {
sessions map[uint64]*Session
lock sync.Mutex
}
func newSessionStore() *sessionStore {
return &sessionStore{
sessions: make(map[uint64]*Session),
}
}
func (s *sessionStore) get(userid uint64) *Session {
s.lock.Lock()
session := s.sessions[userid]
s.lock.Unlock()
return session
}
func (s *sessionStore) set(userid uint64, session *Session) {
s.lock.Lock()
s.sessions[userid] = session
s.lock.Unlock()
}
func (s *sessionStore) delete(userid uint64) {
s.lock.Lock()
delete(s.sessions, userid)
s.lock.Unlock()
}