You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
4.4 KiB
212 lines
4.4 KiB
2 years ago
|
package http
|
||
3 years ago
|
|
||
|
import (
|
||
3 years ago
|
"context"
|
||
3 years ago
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
3 years ago
|
"strings"
|
||
|
"unicode"
|
||
3 years ago
|
|
||
3 years ago
|
"github.com/gorilla/websocket"
|
||
3 years ago
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
|
||
2 years ago
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
|
||
3 years ago
|
)
|
||
|
|
||
3 years ago
|
type chatHandler struct {
|
||
|
*http.ServeMux
|
||
|
|
||
|
room chat.Room
|
||
|
userIDCalc *chat.UserIDCalculator
|
||
3 years ago
|
|
||
|
wsUpgrader websocket.Upgrader
|
||
3 years ago
|
}
|
||
|
|
||
|
func newChatHandler(
|
||
|
room chat.Room, userIDCalc *chat.UserIDCalculator,
|
||
|
requirePowMiddleware func(http.Handler) http.Handler,
|
||
|
) http.Handler {
|
||
|
c := &chatHandler{
|
||
|
ServeMux: http.NewServeMux(),
|
||
|
room: room,
|
||
|
userIDCalc: userIDCalc,
|
||
3 years ago
|
|
||
|
wsUpgrader: websocket.Upgrader{},
|
||
3 years ago
|
}
|
||
|
|
||
|
c.Handle("/history", c.historyHandler())
|
||
|
c.Handle("/user-id", requirePowMiddleware(c.userIDHandler()))
|
||
|
c.Handle("/append", requirePowMiddleware(c.appendHandler()))
|
||
3 years ago
|
c.Handle("/listen", c.listenHandler())
|
||
3 years ago
|
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *chatHandler) historyHandler() http.Handler {
|
||
3 years ago
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||
2 years ago
|
limit, err := apiutil.StrToInt(r.PostFormValue("limit"), 0)
|
||
3 years ago
|
if err != nil {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, fmt.Errorf("invalid limit parameter: %w", err))
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
3 years ago
|
cursor := r.PostFormValue("cursor")
|
||
3 years ago
|
|
||
3 years ago
|
cursor, msgs, err := c.room.History(r.Context(), chat.HistoryOpts{
|
||
3 years ago
|
Limit: limit,
|
||
|
Cursor: cursor,
|
||
|
})
|
||
|
|
||
|
if argErr := (chat.ErrInvalidArg{}); errors.As(err, &argErr) {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, argErr.Err)
|
||
3 years ago
|
return
|
||
|
} else if err != nil {
|
||
2 years ago
|
apiutil.InternalServerError(rw, r, err)
|
||
3 years ago
|
}
|
||
|
|
||
2 years ago
|
apiutil.JSONResult(rw, r, struct {
|
||
3 years ago
|
Cursor string `json:"cursor"`
|
||
|
Messages []chat.Message `json:"messages"`
|
||
|
}{
|
||
|
Cursor: cursor,
|
||
|
Messages: msgs,
|
||
|
})
|
||
|
})
|
||
|
}
|
||
3 years ago
|
|
||
3 years ago
|
func (c *chatHandler) userID(r *http.Request) (chat.UserID, error) {
|
||
3 years ago
|
name := r.PostFormValue("name")
|
||
|
if l := len(name); l == 0 {
|
||
|
return chat.UserID{}, errors.New("name is required")
|
||
|
} else if l > 16 {
|
||
|
return chat.UserID{}, errors.New("name too long")
|
||
|
}
|
||
|
|
||
|
nameClean := strings.Map(func(r rune) rune {
|
||
|
if !unicode.IsPrint(r) {
|
||
|
return -1
|
||
|
}
|
||
|
return r
|
||
|
}, name)
|
||
|
|
||
|
if nameClean != name {
|
||
|
return chat.UserID{}, errors.New("name contains invalid characters")
|
||
|
}
|
||
|
|
||
|
password := r.PostFormValue("password")
|
||
|
if l := len(password); l == 0 {
|
||
|
return chat.UserID{}, errors.New("password is required")
|
||
|
} else if l > 128 {
|
||
|
return chat.UserID{}, errors.New("password too long")
|
||
|
}
|
||
|
|
||
3 years ago
|
return c.userIDCalc.Calculate(name, password), nil
|
||
3 years ago
|
}
|
||
|
|
||
3 years ago
|
func (c *chatHandler) userIDHandler() http.Handler {
|
||
3 years ago
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||
3 years ago
|
userID, err := c.userID(r)
|
||
3 years ago
|
if err != nil {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
2 years ago
|
apiutil.JSONResult(rw, r, struct {
|
||
3 years ago
|
UserID chat.UserID `json:"userID"`
|
||
|
}{
|
||
|
UserID: userID,
|
||
|
})
|
||
|
})
|
||
|
}
|
||
3 years ago
|
|
||
|
func (c *chatHandler) appendHandler() http.Handler {
|
||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||
|
userID, err := c.userID(r)
|
||
|
if err != nil {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
|
body := r.PostFormValue("body")
|
||
|
|
||
|
if l := len(body); l == 0 {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, errors.New("body is required"))
|
||
3 years ago
|
return
|
||
|
|
||
|
} else if l > 300 {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, errors.New("body too long"))
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
|
msg, err := c.room.Append(r.Context(), chat.Message{
|
||
|
UserID: userID,
|
||
|
Body: body,
|
||
|
})
|
||
|
|
||
|
if err != nil {
|
||
2 years ago
|
apiutil.InternalServerError(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
2 years ago
|
apiutil.JSONResult(rw, r, struct {
|
||
3 years ago
|
MessageID string `json:"messageID"`
|
||
|
}{
|
||
|
MessageID: msg.ID,
|
||
|
})
|
||
|
})
|
||
|
}
|
||
3 years ago
|
|
||
|
func (c *chatHandler) listenHandler() http.Handler {
|
||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||
|
|
||
|
ctx := r.Context()
|
||
|
sinceID := r.FormValue("sinceID")
|
||
|
|
||
|
conn, err := c.wsUpgrader.Upgrade(rw, r, nil)
|
||
|
if err != nil {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
|
||
|
it, err := c.room.Listen(ctx, sinceID)
|
||
|
|
||
|
if errors.As(err, new(chat.ErrInvalidArg)) {
|
||
2 years ago
|
apiutil.BadRequest(rw, r, err)
|
||
3 years ago
|
return
|
||
|
|
||
|
} else if errors.Is(err, context.Canceled) {
|
||
|
return
|
||
|
|
||
|
} else if err != nil {
|
||
2 years ago
|
apiutil.InternalServerError(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
|
defer it.Close()
|
||
|
|
||
|
for {
|
||
|
|
||
|
msg, err := it.Next(ctx)
|
||
|
if errors.Is(err, context.Canceled) {
|
||
|
return
|
||
|
|
||
|
} else if err != nil {
|
||
2 years ago
|
apiutil.InternalServerError(rw, r, err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
|
||
|
err = conn.WriteJSON(struct {
|
||
|
Message chat.Message `json:"message"`
|
||
|
}{
|
||
|
Message: msg,
|
||
|
})
|
||
|
|
||
|
if err != nil {
|
||
2 years ago
|
apiutil.GetRequestLogger(r).Error(ctx, "couldn't write message", err)
|
||
3 years ago
|
return
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|