Skip to content

Commit

Permalink
Merge pull request #10 from ivy/user-defined-timeouts
Browse files Browse the repository at this point in the history
Allow timeout in connections, retries to be configurable
  • Loading branch information
ivy authored Dec 7, 2017
2 parents 875d2d5 + 0fb8809 commit 73cb68f
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 18 deletions.
48 changes: 39 additions & 9 deletions smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,25 @@ type Dialer struct {
// LocalName is the hostname sent to the SMTP server with the HELO command.
// By default, "localhost" is sent.
LocalName string
// Timeout to use for read/write operations. Defaults to 10 seconds, can
// be set to 0 to disable timeouts.
Timeout time.Duration
// Whether we should retry mailing if the connection returned an error,
// defaults to true.
RetryFailure bool
}

// NewDialer returns a new SMTP Dialer. The given parameters are used to connect
// to the SMTP server.
func NewDialer(host string, port int, username, password string) *Dialer {
return &Dialer{
Host: host,
Port: port,
Username: username,
Password: password,
SSL: port == 465,
Host: host,
Port: port,
Username: username,
Password: password,
SSL: port == 465,
Timeout: 10 * time.Second,
RetryFailure: true,
}
}

Expand All @@ -63,7 +71,7 @@ var NetDialTimeout = net.DialTimeout
// Dial dials and authenticates to an SMTP server. The returned SendCloser
// should be closed when done using it.
func (d *Dialer) Dial() (SendCloser, error) {
conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), d.Timeout)
if err != nil {
return nil, err
}
Expand All @@ -77,6 +85,10 @@ func (d *Dialer) Dial() (SendCloser, error) {
return nil, err
}

if d.Timeout > 0 {
conn.SetDeadline(time.Now().Add(d.Timeout))
}

if d.LocalName != "" {
if err := c.Hello(d.LocalName); err != nil {
return nil, err
Expand Down Expand Up @@ -116,7 +128,7 @@ func (d *Dialer) Dial() (SendCloser, error) {
}
}

return &smtpSender{c, d}, nil
return &smtpSender{c, conn, d}, nil
}

func (d *Dialer) tlsConfig() *tls.Config {
Expand Down Expand Up @@ -144,12 +156,29 @@ func (d *Dialer) DialAndSend(m ...*Message) error {

type smtpSender struct {
smtpClient
d *Dialer
conn net.Conn
d *Dialer
}

func (c *smtpSender) retryError(err error) bool {
if !c.d.RetryFailure {
return false
}

if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return true
}

return err == io.EOF
}

func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
if c.d.Timeout > 0 {
c.conn.SetDeadline(time.Now().Add(c.d.Timeout))
}

if err := c.Mail(from); err != nil {
if err == io.EOF {
if c.retryError(err) {
// This is probably due to a timeout, so reconnect and try again.
sc, derr := c.d.Dial()
if derr == nil {
Expand All @@ -159,6 +188,7 @@ func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
}
}
}

return err
}

Expand Down
40 changes: 31 additions & 9 deletions smtp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (

var (
testConn = &net.TCPConn{}
testTLSConn = &tls.Conn{}
testTLSConn = tls.Client(testConn, &tls.Config{InsecureSkipVerify: true})
testConfig = &tls.Config{InsecureSkipVerify: true}
testAuth = smtp.PlainAuth("", testUser, testPwd, testHost)
)
Expand Down Expand Up @@ -118,8 +118,9 @@ func TestDialerNoAuth(t *testing.T) {

func TestDialerTimeout(t *testing.T) {
d := &Dialer{
Host: testHost,
Port: testPort,
Host: testHost,
Port: testPort,
RetryFailure: true,
}
testSendMailTimeout(t, d, []string{
"Extension STARTTLS",
Expand All @@ -138,6 +139,25 @@ func TestDialerTimeout(t *testing.T) {
})
}

func TestDialerTimeoutNoRetry(t *testing.T) {
d := &Dialer{
Host: testHost,
Port: testPort,
RetryFailure: false,
}

err := doTestSendMail(t, d, []string{
"Extension STARTTLS",
"StartTLS",
"Mail " + testFrom,
"Quit",
}, true)

if err.Error() != "gomail: could not send email 1: EOF" {
t.Error("expected to have got EOF, but got:", err)
}
}

type mockClient struct {
t *testing.T
i int
Expand Down Expand Up @@ -232,14 +252,18 @@ func (w *mockWriter) Close() error {
}

func testSendMail(t *testing.T, d *Dialer, want []string) {
doTestSendMail(t, d, want, false)
if err := doTestSendMail(t, d, want, false); err != nil {
t.Error(err)
}
}

func testSendMailTimeout(t *testing.T, d *Dialer, want []string) {
doTestSendMail(t, d, want, true)
if err := doTestSendMail(t, d, want, true); err != nil {
t.Error(err)
}
}

func doTestSendMail(t *testing.T, d *Dialer, want []string, timeout bool) {
func doTestSendMail(t *testing.T, d *Dialer, want []string, timeout bool) error {
testClient := &mockClient{
t: t,
want: want,
Expand Down Expand Up @@ -274,9 +298,7 @@ func doTestSendMail(t *testing.T, d *Dialer, want []string, timeout bool) {
return testClient, nil
}

if err := d.DialAndSend(getTestMessage()); err != nil {
t.Error(err)
}
return d.DialAndSend(getTestMessage())
}

func assertConfig(t *testing.T, got, want *tls.Config) {
Expand Down

0 comments on commit 73cb68f

Please sign in to comment.