Add session lock

HTTP requests can be processed in parallel, but we only have one IMAP
connection per session.

Closes: https://todo.sr.ht/~sircmpwn/koushin/12
This commit is contained in:
Simon Ser 2019-12-03 17:18:17 +01:00
parent ec03c60dff
commit 48d6d5d227
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
3 changed files with 45 additions and 16 deletions

View file

@ -21,10 +21,18 @@ func generateToken() (string, error) {
var ErrSessionExpired = errors.New("session expired") var ErrSessionExpired = errors.New("session expired")
type Session struct { type Session struct {
locker sync.Mutex
imapConn *imapclient.Client imapConn *imapclient.Client
username, password string username, password string
} }
func (s *Session) Do(f func(*imapclient.Client) error) error {
s.locker.Lock()
defer s.locker.Unlock()
return f(s.imapConn)
}
// TODO: expiration timer // TODO: expiration timer
type ConnPool struct { type ConnPool struct {
locker sync.Mutex locker sync.Mutex

View file

@ -9,7 +9,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/emersion/go-imap"
imapclient "github.com/emersion/go-imap/client" imapclient "github.com/emersion/go-imap/client"
"github.com/emersion/go-message"
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -95,7 +97,6 @@ type context struct {
echo.Context echo.Context
server *Server server *Server
session *Session session *Session
conn *imapclient.Client
} }
var aLongTimeAgo = time.Unix(233431200, 0) var aLongTimeAgo = time.Unix(233431200, 0)
@ -152,7 +153,15 @@ func handleGetPart(ctx *context, raw bool) error {
return echo.NewHTTPError(http.StatusBadRequest, err) return echo.NewHTTPError(http.StatusBadRequest, err)
} }
msg, part, err := getMessagePart(ctx.conn, mboxName, uid, partPath) var msg *imapMessage
var part *message.Entity
var mbox *imap.MailboxStatus
err = ctx.session.Do(func(c *imapclient.Client) error {
var err error
msg, part, err = getMessagePart(c, mboxName, uid, partPath)
mbox = c.Mailbox()
return err
})
if err != nil { if err != nil {
return err return err
} }
@ -192,7 +201,7 @@ func handleGetPart(ctx *context, raw bool) error {
} }
return ctx.Render(http.StatusOK, "message.html", map[string]interface{}{ return ctx.Render(http.StatusOK, "message.html", map[string]interface{}{
"Mailbox": ctx.conn.Mailbox(), "Mailbox": mbox,
"Message": msg, "Message": msg,
"Body": body, "Body": body,
"PartPath": partPathString, "PartPath": partPathString,
@ -290,7 +299,6 @@ func New(imapURL, smtpURL string) *echo.Echo {
} else if err != nil { } else if err != nil {
return err return err
} }
ctx.conn = ctx.session.imapConn
return next(ctx) return next(ctx)
} }
@ -304,18 +312,26 @@ func New(imapURL, smtpURL string) *echo.Echo {
e.GET("/mailbox/:mbox", func(ectx echo.Context) error { e.GET("/mailbox/:mbox", func(ectx echo.Context) error {
ctx := ectx.(*context) ctx := ectx.(*context)
mailboxes, err := listMailboxes(ctx.conn) var mailboxes []*imap.MailboxInfo
if err != nil { var msgs []imapMessage
return err var mbox *imap.MailboxStatus
} err = ctx.session.Do(func(c *imapclient.Client) error {
var err error
msgs, err := listMessages(ctx.conn, ctx.Param("mbox")) if mailboxes, err = listMailboxes(c); err != nil {
return err
}
if msgs, err = listMessages(c, ctx.Param("mbox")); err != nil {
return err
}
mbox = c.Mailbox()
return nil
})
if err != nil { if err != nil {
return err return err
} }
return ctx.Render(http.StatusOK, "mailbox.html", map[string]interface{}{ return ctx.Render(http.StatusOK, "mailbox.html", map[string]interface{}{
"Mailbox": ctx.conn.Mailbox(), "Mailbox": mbox,
"Mailboxes": mailboxes, "Mailboxes": mailboxes,
"Messages": msgs, "Messages": msgs,
}) })
@ -335,9 +351,14 @@ func New(imapURL, smtpURL string) *echo.Echo {
e.GET("/logout", func(ectx echo.Context) error { e.GET("/logout", func(ectx echo.Context) error {
ctx := ectx.(*context) ctx := ectx.(*context)
if err := ctx.conn.Logout(); err != nil {
err := ctx.session.Do(func(c *imapclient.Client) error {
return c.Logout()
})
if err != nil {
return fmt.Errorf("failed to logout: %v", err) return fmt.Errorf("failed to logout: %v", err)
} }
ctx.setToken("") ctx.setToken("")
return ctx.Redirect(http.StatusFound, "/login") return ctx.Redirect(http.StatusFound, "/login")
}) })

View file

@ -2,8 +2,8 @@ package koushin
import ( import (
"fmt" "fmt"
"time"
"io" "io"
"time"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
@ -34,10 +34,10 @@ func (s *Server) connectSMTP() (*smtp.Client, error) {
} }
type OutgoingMessage struct { type OutgoingMessage struct {
From string From string
To []string To []string
Subject string Subject string
Text string Text string
} }
func (msg *OutgoingMessage) WriteTo(w io.Writer) error { func (msg *OutgoingMessage) WriteTo(w io.Writer) error {