archat-server/server/server.go

295 lines
7.0 KiB
Go
Raw Normal View History

package server
import (
"encoding/json"
2024-03-24 13:09:36 +00:00
"net/http"
2024-03-24 16:10:02 +00:00
"os"
2024-03-24 13:09:36 +00:00
"strings"
"sync"
"time"
2024-03-24 16:10:02 +00:00
"github.com/charmbracelet/log"
2024-03-24 13:09:36 +00:00
"github.com/gorilla/websocket"
"golang.org/x/crypto/bcrypt"
"krzyzanowski.dev/p2pchat/common"
)
type Account struct {
nickname string
passHash []byte
}
type ServerContext struct {
2024-03-24 13:09:36 +00:00
idCounter int
idCounterLock sync.RWMutex
peersList []*Peer
peersListLock sync.RWMutex
accounts map[string]*Account
accountsLock sync.RWMutex
}
type HandlerContext struct {
2024-03-24 13:09:36 +00:00
peer *Peer
*ServerContext
}
type Peer struct {
id int
2024-03-24 13:09:36 +00:00
conn *websocket.Conn
hasAccount bool
account *Account
}
2024-03-24 16:10:02 +00:00
var logger = log.NewWithOptions(os.Stdout, log.Options{
ReportTimestamp: true,
TimeFormat: time.TimeOnly,
Prefix: "⚙️ Server",
})
func init() {
if common.IsProd {
logger.SetLevel(log.InfoLevel)
} else {
logger.SetLevel(log.DebugLevel)
}
}
2024-03-24 13:09:36 +00:00
func NewPeer(conn *websocket.Conn) *Peer {
return &Peer{-1, conn, false, nil}
}
func peerSliceIndexOf(s []*Peer, id int) int {
i := 0
var p *Peer
for i, p = range s {
if p.id == id {
break
}
}
return i
}
func peerSliceRemove(s *[]*Peer, i int) {
(*s)[i] = (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
}
2024-03-24 13:09:36 +00:00
func (srvCtx *ServerContext) removePeer(peer *Peer) {
srvCtx.peersListLock.Lock()
peerSliceRemove(&srvCtx.peersList, peerSliceIndexOf(srvCtx.peersList, peer.id))
srvCtx.peersListLock.Unlock()
}
2024-03-24 13:09:36 +00:00
func handleDisconnection(handlerCtx *HandlerContext) {
handlerCtx.removePeer(handlerCtx.peer)
2024-03-24 16:10:02 +00:00
logger.Infof("%s disconnected", handlerCtx.peer.conn.RemoteAddr())
}
2024-03-24 13:09:36 +00:00
func (hdlCtx *HandlerContext) handleEcho(reqFrame *common.RequestFrame) (res common.Response, err error) {
echoReq, err := common.RequestFromFrame[common.EchoRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
echoRes := common.EchoResponse(echoReq)
2024-03-24 13:09:36 +00:00
return echoRes, nil
}
2024-03-24 13:09:36 +00:00
func (hdlCtx *HandlerContext) handleListPeers(reqFrame *common.RequestFrame) (res common.Response, err error) {
// Currently list peers request is empty, so we can ignore it - we won't use it
_, err = common.RequestFromFrame[common.ListPeersRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
2024-03-24 13:09:36 +00:00
hdlCtx.peersListLock.RLock()
peersFreeze := make([]*Peer, len(hdlCtx.peersList))
copy(peersFreeze, hdlCtx.peersList)
hdlCtx.peersListLock.RUnlock()
listPeersRes := common.ListPeersResponse{PeersInfo: make([]common.PeerInfo, 0)}
for _, peer := range peersFreeze {
listPeersRes.PeersInfo = append(
listPeersRes.PeersInfo,
common.PeerInfo{
ID: peer.id,
Addr: peer.conn.RemoteAddr().String(),
HasNickaname: peer.hasAccount,
Nickname: peer.account.nickname,
},
)
}
2024-03-24 13:09:36 +00:00
return listPeersRes, nil
}
2024-03-24 13:09:36 +00:00
func (hdlCtx *HandlerContext) handleAuth(reqFrame *common.RequestFrame) (res common.Response, err error) {
authReq, err := common.RequestFromFrame[common.AuthRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
// Check if account already exists
2024-03-24 13:09:36 +00:00
hdlCtx.accountsLock.RLock()
account, ok := hdlCtx.accounts[authReq.Nickname]
hdlCtx.accountsLock.RUnlock()
var authRes *common.AuthResponse
if ok {
// Check if password matches
if bcrypt.CompareHashAndPassword(account.passHash, []byte(authReq.Password)) == nil {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: true}
hdlCtx.peersListLock.Lock()
hdlCtx.peer.hasAccount = true
hdlCtx.peer.account = account
hdlCtx.peersListLock.Unlock()
} else {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: false}
}
} else {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: true}
passHash, err := bcrypt.GenerateFromPassword([]byte(authReq.Password), bcrypt.DefaultCost)
if err != nil {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: false}
} else {
newAcc := Account{authReq.Nickname, passHash}
2024-03-24 13:09:36 +00:00
hdlCtx.accountsLock.Lock()
hdlCtx.accounts[newAcc.nickname] = &newAcc
hdlCtx.accountsLock.Unlock()
hdlCtx.peersListLock.Lock()
hdlCtx.peer.hasAccount = true
hdlCtx.peer.account = &newAcc
hdlCtx.peersListLock.Unlock()
}
}
2024-03-24 13:09:36 +00:00
return authRes, nil
}
2024-03-24 13:09:36 +00:00
func (srvCtx *ServerContext) printConnectedPeers() {
srvCtx.peersListLock.RLock()
2024-03-24 16:10:02 +00:00
logger.Debug("displaying all connections:")
for _, p := range srvCtx.peersList {
nick := "-"
if p.hasAccount {
nick = p.account.nickname
}
2024-03-24 16:10:02 +00:00
log.Debugf("ID#%d, Addr:%s, Auth:%t, Nick:%s", p.id, p.conn.RemoteAddr(), p.hasAccount, nick)
}
srvCtx.peersListLock.RUnlock()
}
2024-03-24 13:09:36 +00:00
func (hdlCtx *HandlerContext) handleRequest(reqJsonBytes []byte) error {
2024-03-24 16:10:02 +00:00
logger.Debugf("got message text: %s", strings.Trim(string(reqJsonBytes), "\n"))
2024-03-24 13:09:36 +00:00
var reqFrame common.RequestFrame
json.Unmarshal(reqJsonBytes, &reqFrame)
2024-03-24 16:10:02 +00:00
log.Debugf("unmarshalled request frame (ID=%d)", reqFrame.ID)
2024-03-24 13:09:36 +00:00
var res common.Response
var err error
if reqFrame.ID == common.AuthRID {
res, err = hdlCtx.handleAuth(&reqFrame)
} else if reqFrame.ID == common.ListPeersRID {
res, err = hdlCtx.handleListPeers(&reqFrame)
} else if reqFrame.ID == common.EchoRID {
res, err = hdlCtx.handleEcho(&reqFrame)
}
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("could not handle request ID=%d", reqFrame.ID)
2024-03-24 13:09:36 +00:00
return err
}
2024-03-24 13:09:36 +00:00
resFrame, err := common.ResponseFrameFrom(res)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("could not create frame from response")
2024-03-24 13:09:36 +00:00
return err
}
resJsonBytes, err := json.Marshal(resFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("error marshalling frame to json")
2024-03-24 13:09:36 +00:00
return err
}
2024-03-24 16:10:02 +00:00
logger.Debugf("sending %s", string(resJsonBytes))
2024-03-24 13:09:36 +00:00
err = hdlCtx.peer.conn.WriteMessage(websocket.TextMessage, resJsonBytes)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("error writing response frame")
2024-03-24 13:09:36 +00:00
return err
}
return nil
}
func (srvCtx *ServerContext) addPeer(peer *Peer) {
srvCtx.idCounterLock.Lock()
srvCtx.idCounter++
peer.id = srvCtx.idCounter
srvCtx.idCounterLock.Unlock()
srvCtx.peersListLock.Lock()
srvCtx.peersList = append(srvCtx.peersList, peer)
srvCtx.peersListLock.Unlock()
}
func (srvCtx *ServerContext) wsapiHandler(w http.ResponseWriter, r *http.Request) {
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("upgrade failed")
2024-03-24 13:09:36 +00:00
return
}
peer := NewPeer(conn)
srvCtx.addPeer(peer)
handlerCtx := &HandlerContext{peer, srvCtx}
defer handleDisconnection(handlerCtx)
defer conn.Close()
2024-03-24 16:10:02 +00:00
logger.Infof("%s connected", conn.RemoteAddr())
for {
2024-03-24 13:09:36 +00:00
messType, messBytes, err := conn.ReadMessage()
if err != nil {
break
}
if messType != 1 {
err := conn.WriteMessage(websocket.CloseUnsupportedData, []byte("Only JSON text is supported"))
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Debugf("[Server] error sending close message due to unsupported data")
2024-03-24 13:09:36 +00:00
}
2024-03-24 13:09:36 +00:00
return
}
err = handlerCtx.handleRequest(messBytes)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Debug(err)
break
}
}
}
2024-03-24 13:09:36 +00:00
func RunServer() {
srvCtx := &ServerContext{peersList: make([]*Peer, 0), accounts: make(map[string]*Account)}
go func() {
for {
srvCtx.printConnectedPeers()
time.Sleep(time.Second * 5)
}
}()
http.HandleFunc("/wsapi", srvCtx.wsapiHandler)
2024-03-24 16:10:02 +00:00
logger.Info("Starting server...")
2024-03-24 13:09:36 +00:00
http.ListenAndServe(":8080", nil)
}