Fix storage interface and fix dependency mess

This commit is contained in:
Ian Kent 2014-11-23 00:23:39 +00:00
parent 2b58b571cb
commit fc25fce4d2
8 changed files with 56 additions and 44 deletions

View file

@ -1,5 +1,9 @@
package config package config
import (
"github.com/ian-kent/Go-MailHog/mailhog/storage"
)
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
SMTPBindAddr: "0.0.0.0:1025", SMTPBindAddr: "0.0.0.0:1025",
@ -19,6 +23,6 @@ type Config struct {
MongoDb string MongoDb string
MongoColl string MongoColl string
MessageChan chan interface{} MessageChan chan interface{}
Storage interface{} Storage storage.Storage
Assets func(asset string) ([]byte, error) Assets func(asset string) ([]byte, error)
} }

View file

@ -23,10 +23,22 @@ type Protocol struct {
message *data.SMTPMessage message *data.SMTPMessage
hostname string hostname string
// LogHandler is called for each log message. If nil, log messages will
// be output using fmt.Printf instead.
LogHandler func(message string, args ...interface{}) LogHandler func(message string, args ...interface{})
// MessageReceivedHandler is called for each message accepted by the
// SMTP protocol. It must return a MessageID or error. If nil, messages
// will be rejected with an error.
MessageReceivedHandler func(*data.Message) (string, error) MessageReceivedHandler func(*data.Message) (string, error)
// ValidateSenderHandler should return true if the sender is valid,
// otherwise false. If nil, all senders will be accepted.
ValidateSenderHandler func(from string) bool ValidateSenderHandler func(from string) bool
// ValidateRecipientHandler should return true if the recipient is valid,
// otherwise false. If nil, all recipients will be accepted.
ValidateRecipientHandler func(to string) bool ValidateRecipientHandler func(to string) bool
// ValidateAuthenticationhandler should return true if the authentication
// parameters are valid, otherwise false. If nil, all authentication
// attempts will be accepted.
ValidateAuthenticationHandler func(mechanism string, args ...string) bool ValidateAuthenticationHandler func(mechanism string, args ...string) bool
} }
@ -50,7 +62,8 @@ func (proto *Protocol) logf(message string, args ...interface{}) {
} }
} }
// Start begins an SMTP conversation with a 220 reply // Start begins an SMTP conversation with a 220 reply, placing the state
// machine in ESTABLISH state.
func (proto *Protocol) Start(hostname string) *Reply { func (proto *Protocol) Start(hostname string) *Reply {
proto.state = ESTABLISH proto.state = ESTABLISH
proto.hostname = hostname proto.hostname = hostname
@ -60,7 +73,8 @@ func (proto *Protocol) Start(hostname string) *Reply {
// Parse parses a line string and returns any remaining line string // Parse parses a line string and returns any remaining line string
// and a reply, if a command was found. Parse does nothing until a // and a reply, if a command was found. Parse does nothing until a
// new line is found. // new line is found.
// - TODO move this to a buffer inside proto? // - TODO decide whether to move this to a buffer inside Protocol
// sort of like it this way, since it gives control back to the caller
func (proto *Protocol) Parse(line string) (string, *Reply) { func (proto *Protocol) Parse(line string) (string, *Reply) {
var reply *Reply var reply *Reply
@ -266,7 +280,7 @@ func ParseMAIL(mail string) (string, error) {
r := regexp.MustCompile("(?i:From):<([^>]+)>") r := regexp.MustCompile("(?i:From):<([^>]+)>")
match := r.FindStringSubmatch(mail) match := r.FindStringSubmatch(mail)
if len(match) != 2 { if len(match) != 2 {
return "", errors.New("Invalid sender") return "", errors.New("Invalid sender " + mail)
} }
return match[1], nil return match[1], nil
} }
@ -276,7 +290,7 @@ func ParseRCPT(rcpt string) (string, error) {
r := regexp.MustCompile("(?i:To):<([^>]+)>") r := regexp.MustCompile("(?i:To):<([^>]+)>")
match := r.FindStringSubmatch(rcpt) match := r.FindStringSubmatch(rcpt)
if len(match) != 2 { if len(match) != 2 {
return "", errors.New("Invalid recipient") return "", errors.New("Invalid recipient " + rcpt)
} }
return match[1], nil return match[1], nil
} }

View file

@ -1,7 +1,5 @@
package protocol package protocol
// http://www.rfc-editor.org/rfc/rfc5321.txt
// State represents the state of an SMTP conversation // State represents the state of an SMTP conversation
type State int type State int

View file

@ -3,7 +3,6 @@ package server
// http://www.rfc-editor.org/rfc/rfc5321.txt // http://www.rfc-editor.org/rfc/rfc5321.txt
import ( import (
"errors"
"log" "log"
"net" "net"
"strings" "strings"
@ -11,7 +10,6 @@ import (
"github.com/ian-kent/Go-MailHog/mailhog/config" "github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/data" "github.com/ian-kent/Go-MailHog/mailhog/data"
"github.com/ian-kent/Go-MailHog/mailhog/smtp/protocol" "github.com/ian-kent/Go-MailHog/mailhog/smtp/protocol"
"github.com/ian-kent/Go-MailHog/mailhog/storage"
) )
// Session represents a SMTP session using net.TCPConn // Session represents a SMTP session using net.TCPConn
@ -43,6 +41,7 @@ func Accept(conn *net.TCPConn, conf *config.Config) {
func (c *Session) validateAuthentication(mechanism string, args ...string) bool { func (c *Session) validateAuthentication(mechanism string, args ...string) bool {
return true return true
} }
func (c *Session) validateRecipient(to string) bool { func (c *Session) validateRecipient(to string) bool {
return true return true
} }
@ -52,16 +51,8 @@ func (c *Session) validateSender(from string) bool {
} }
func (c *Session) acceptMessage(msg *data.Message) (id string, err error) { func (c *Session) acceptMessage(msg *data.Message) (id string, err error) {
switch c.conf.Storage.(type) { c.logf("Storing message %s", msg.ID)
case *storage.MongoDB: id, err = c.conf.Storage.Store(msg)
c.logf("Storing message using MongoDB")
id, err = c.conf.Storage.(*storage.MongoDB).Store(msg)
case *storage.InMemory:
c.logf("Storing message using Memory")
id, err = c.conf.Storage.(*storage.InMemory).Store(msg)
default:
err = errors.New("Unknown storage stype")
}
c.conf.MessageChan <- msg c.conf.MessageChan <- msg
return return
} }

View file

@ -1,22 +1,17 @@
package storage package storage
import ( import "github.com/ian-kent/Go-MailHog/mailhog/data"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/data"
)
// InMemory is an in memory storage backend // InMemory is an in memory storage backend
type InMemory struct { type InMemory struct {
Config *config.Config
Messages map[string]*data.Message Messages map[string]*data.Message
MessageIndex []string MessageIndex []string
MessageRIndex map[string]int MessageRIndex map[string]int
} }
// CreateInMemory creates a new in memory storage backend // CreateInMemory creates a new in memory storage backend
func CreateInMemory(c *config.Config) *InMemory { func CreateInMemory() *InMemory {
return &InMemory{ return &InMemory{
Config: c,
Messages: make(map[string]*data.Message, 0), Messages: make(map[string]*data.Message, 0),
MessageIndex: make([]string, 0), MessageIndex: make([]string, 0),
MessageRIndex: make(map[string]int, 0), MessageRIndex: make(map[string]int, 0),
@ -32,15 +27,16 @@ func (memory *InMemory) Store(m *data.Message) (string, error) {
} }
// List lists stored messages by index // List lists stored messages by index
func (memory *InMemory) List(start int, limit int) ([]*data.Message, error) { func (memory *InMemory) List(start int, limit int) (*data.Messages, error) {
if limit > len(memory.MessageIndex) { if limit > len(memory.MessageIndex) {
limit = len(memory.MessageIndex) limit = len(memory.MessageIndex)
} }
var messages []*data.Message var messages []data.Message
for _, m := range memory.MessageIndex[start:limit] { for _, m := range memory.MessageIndex[start:limit] {
messages = append(messages, memory.Messages[m]) messages = append(messages, *memory.Messages[m])
} }
return messages, nil msgs := data.Messages(messages)
return &msgs, nil
} }
// DeleteOne deletes an individual message by storage ID // DeleteOne deletes an individual message by storage ID

View file

@ -3,7 +3,6 @@ package storage
import ( import (
"log" "log"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/data" "github.com/ian-kent/Go-MailHog/mailhog/data"
"labix.org/v2/mgo" "labix.org/v2/mgo"
"labix.org/v2/mgo/bson" "labix.org/v2/mgo/bson"
@ -12,22 +11,20 @@ import (
// MongoDB represents MongoDB backed storage backend // MongoDB represents MongoDB backed storage backend
type MongoDB struct { type MongoDB struct {
Session *mgo.Session Session *mgo.Session
Config *config.Config
Collection *mgo.Collection Collection *mgo.Collection
} }
// CreateMongoDB creates a MongoDB backed storage backend // CreateMongoDB creates a MongoDB backed storage backend
func CreateMongoDB(c *config.Config) *MongoDB { func CreateMongoDB(uri, db, coll string) *MongoDB {
log.Printf("Connecting to MongoDB: %s\n", c.MongoUri) log.Printf("Connecting to MongoDB: %s\n", uri)
session, err := mgo.Dial(c.MongoUri) session, err := mgo.Dial(uri)
if err != nil { if err != nil {
log.Printf("Error connecting to MongoDB: %s", err) log.Printf("Error connecting to MongoDB: %s", err)
return nil return nil
} }
return &MongoDB{ return &MongoDB{
Session: session, Session: session,
Config: c, Collection: session.DB(db).C(coll),
Collection: session.DB(c.MongoDb).C(c.MongoColl),
} }
} }

View file

@ -0,0 +1,12 @@
package storage
import "github.com/ian-kent/Go-MailHog/mailhog/data"
// Storage represents a storage backend
type Storage interface {
Store(m *data.Message) (string, error)
List(start int, limit int) (*data.Messages, error)
DeleteOne(id string) error
DeleteAll() error
Load(id string) (*data.Message, error)
}

View file

@ -46,17 +46,17 @@ func configure() {
if storage_type == "mongodb" { if storage_type == "mongodb" {
log.Println("Using MongoDB message storage") log.Println("Using MongoDB message storage")
s := storage.CreateMongoDB(conf) s := storage.CreateMongoDB(conf.MongoUri, conf.MongoDb, conf.MongoColl)
if s == nil { if s == nil {
log.Println("MongoDB storage unavailable, reverting to in-memory storage") log.Println("MongoDB storage unavailable, reverting to in-memory storage")
conf.Storage = storage.CreateInMemory(conf) conf.Storage = storage.CreateInMemory()
} else { } else {
log.Println("Connected to MongoDB") log.Println("Connected to MongoDB")
conf.Storage = s conf.Storage = s
} }
} else if storage_type == "memory" { } else if storage_type == "memory" {
log.Println("Using in-memory message storage") log.Println("Using in-memory message storage")
conf.Storage = storage.CreateInMemory(conf) conf.Storage = storage.CreateInMemory()
} else { } else {
log.Fatalf("Invalid storage type %s", storage_type) log.Fatalf("Invalid storage type %s", storage_type)
} }