mirror of
https://github.com/AfterShip/email-verifier.git
synced 2025-02-06 09:26:19 +00:00
improve timeout configuration
This commit is contained in:
parent
833d301dc1
commit
61b48c0805
78
smtp.go
78
smtp.go
@ -1,6 +1,7 @@
|
||||
package emailverifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@ -38,7 +39,7 @@ func (v *Verifier) CheckSMTP(domain, username string) (*SMTP, error) {
|
||||
email := fmt.Sprintf("%s@%s", username, domain)
|
||||
|
||||
// Dial any SMTP server that will accept a connection
|
||||
client, mx, err := newSMTPClient(domain, v.proxyURI)
|
||||
client, mx, err := newSMTPClient(domain, v.proxyURI, v.connectTimeout, v.operationTimeout)
|
||||
if err != nil {
|
||||
return &ret, ParseSMTPError(err)
|
||||
}
|
||||
@ -112,7 +113,7 @@ func (v *Verifier) CheckSMTP(domain, username string) (*SMTP, error) {
|
||||
}
|
||||
|
||||
// newSMTPClient generates a new available SMTP client
|
||||
func newSMTPClient(domain, proxyURI string) (*smtp.Client, *net.MX, error) {
|
||||
func newSMTPClient(domain, proxyURI string, connectTimeout, operationTimeout time.Duration) (*smtp.Client, *net.MX, error) {
|
||||
domain = domainToASCII(domain)
|
||||
mxRecords, err := net.LookupMX(domain)
|
||||
if err != nil {
|
||||
@ -137,7 +138,7 @@ func newSMTPClient(domain, proxyURI string) (*smtp.Client, *net.MX, error) {
|
||||
addr := r.Host + smtpPort
|
||||
index := i
|
||||
go func() {
|
||||
c, err := dialSMTP(addr, proxyURI)
|
||||
c, err := dialSMTP(addr, proxyURI, connectTimeout, operationTimeout)
|
||||
if err != nil {
|
||||
if !done {
|
||||
ch <- err
|
||||
@ -181,48 +182,28 @@ func newSMTPClient(domain, proxyURI string) (*smtp.Client, *net.MX, error) {
|
||||
// dialSMTP is a timeout wrapper for smtp.Dial. It attempts to dial an
|
||||
// SMTP server (socks5 proxy supported) and fails with a timeout if timeout is reached while
|
||||
// attempting to establish a new connection
|
||||
func dialSMTP(addr, proxyURI string) (*smtp.Client, error) {
|
||||
// Channel holding the new smtp.Client or error
|
||||
ch := make(chan interface{}, 1)
|
||||
|
||||
func dialSMTP(addr, proxyURI string, connectTimeout, operationTimeout time.Duration) (*smtp.Client, error) {
|
||||
// Dial the new smtp connection
|
||||
go func() {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if proxyURI != "" {
|
||||
conn, err = establishProxyConnection(addr, proxyURI)
|
||||
} else {
|
||||
conn, err = establishConnection(addr)
|
||||
}
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
ch <- client
|
||||
}()
|
||||
|
||||
// Retrieve the smtp client from our client channel or timeout
|
||||
select {
|
||||
case res := <-ch:
|
||||
switch r := res.(type) {
|
||||
case *smtp.Client:
|
||||
return r, nil
|
||||
case error:
|
||||
return nil, r
|
||||
default:
|
||||
return nil, errors.New("Unexpected response dialing SMTP server")
|
||||
}
|
||||
case <-time.After(smtpTimeout):
|
||||
return nil, errors.New("Timeout connecting to mail-exchanger")
|
||||
if proxyURI != "" {
|
||||
conn, err = establishProxyConnection(addr, proxyURI, connectTimeout)
|
||||
} else {
|
||||
conn, err = establishConnection(addr, connectTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set specific timeouts for writing and reading
|
||||
err = conn.SetDeadline(time.Now().Add(operationTimeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
return smtp.NewClient(conn, host)
|
||||
}
|
||||
|
||||
// GenerateRandomEmail generates a random email address using the domain passed. Used
|
||||
@ -237,13 +218,13 @@ func GenerateRandomEmail(domain string) string {
|
||||
}
|
||||
|
||||
// establishConnection connects to the address on the named network address.
|
||||
func establishConnection(addr string) (net.Conn, error) {
|
||||
return net.Dial("tcp", addr)
|
||||
func establishConnection(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout("tcp", addr, timeout)
|
||||
}
|
||||
|
||||
// establishProxyConnection connects to the address on the named network address
|
||||
// via proxy protocol
|
||||
func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
|
||||
func establishProxyConnection(addr, proxyURI string, timeout time.Duration) (net.Conn, error) {
|
||||
u, err := url.Parse(proxyURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -252,5 +233,10 @@ func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.Dial("tcp", addr)
|
||||
|
||||
// https://github.com/golang/go/issues/37549#issuecomment-1178745487
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", addr)
|
||||
}
|
||||
|
16
smtp_test.go
16
smtp_test.go
@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -214,7 +215,8 @@ func TestCheckSMTPOK_HostNotExists(t *testing.T) {
|
||||
|
||||
func TestNewSMTPClientOK(t *testing.T) {
|
||||
domain := "gmail.com"
|
||||
ret, _, err := newSMTPClient(domain, "")
|
||||
timeout := 5 * time.Second
|
||||
ret, _, err := newSMTPClient(domain, "", timeout, timeout)
|
||||
assert.NotNil(t, ret)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@ -222,21 +224,24 @@ func TestNewSMTPClientOK(t *testing.T) {
|
||||
func TestNewSMTPClientFailed_WithInvalidProxy(t *testing.T) {
|
||||
domain := "gmail.com"
|
||||
proxyURI := "socks5://user:password@127.0.0.1:1080?timeout=5s"
|
||||
ret, _, err := newSMTPClient(domain, proxyURI)
|
||||
timeout := 5 * time.Second
|
||||
ret, _, err := newSMTPClient(domain, proxyURI, timeout, timeout)
|
||||
assert.Nil(t, ret)
|
||||
assert.Error(t, err, syscall.ECONNREFUSED)
|
||||
}
|
||||
|
||||
func TestNewSMTPClientFailed(t *testing.T) {
|
||||
domain := "zzzz171777.com"
|
||||
ret, _, err := newSMTPClient(domain, "")
|
||||
timeout := 5 * time.Second
|
||||
ret, _, err := newSMTPClient(domain, "", timeout, timeout)
|
||||
assert.Nil(t, ret)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDialSMTPFailed_NoPortIsConfigured(t *testing.T) {
|
||||
disposableDomain := "zzzz1717.com"
|
||||
ret, err := dialSMTP(disposableDomain, "")
|
||||
timeout := 5 * time.Second
|
||||
ret, err := dialSMTP(disposableDomain, "", timeout, timeout)
|
||||
assert.Nil(t, ret)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), "missing port"))
|
||||
@ -244,7 +249,8 @@ func TestDialSMTPFailed_NoPortIsConfigured(t *testing.T) {
|
||||
|
||||
func TestDialSMTPFailed_NoSuchHost(t *testing.T) {
|
||||
disposableDomain := "zzzzyyyyaaa123.com:25"
|
||||
ret, err := dialSMTP(disposableDomain, "")
|
||||
timeout := 5 * time.Second
|
||||
ret, err := dialSMTP(disposableDomain, "", timeout, timeout)
|
||||
assert.Nil(t, ret)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), "no such host"))
|
||||
|
18
verifier.go
18
verifier.go
@ -17,6 +17,10 @@ type Verifier struct {
|
||||
schedule *schedule // schedule represents a job schedule
|
||||
proxyURI string // use a SOCKS5 proxy to verify the email,
|
||||
apiVerifiers map[string]smtpAPIVerifier // currently support gmail & yahoo, further contributions are welcomed.
|
||||
|
||||
// Timeouts
|
||||
connectTimeout time.Duration // Timeout for establishing connections
|
||||
operationTimeout time.Duration // Timeout for SMTP operations (e.g., EHLO, MAIL FROM, etc.)
|
||||
}
|
||||
|
||||
// Result is the result of Email Verification
|
||||
@ -50,6 +54,8 @@ func NewVerifier() *Verifier {
|
||||
helloName: defaultHelloName,
|
||||
catchAllCheckEnabled: true,
|
||||
apiVerifiers: map[string]smtpAPIVerifier{},
|
||||
connectTimeout: 10 * time.Second,
|
||||
operationTimeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,6 +229,18 @@ func (v *Verifier) Proxy(proxyURI string) *Verifier {
|
||||
return v
|
||||
}
|
||||
|
||||
// ConnectTimeout sets the timeout for establishing connections.
|
||||
func (v *Verifier) ConnectTimeout(timeout time.Duration) *Verifier {
|
||||
v.connectTimeout = timeout
|
||||
return v
|
||||
}
|
||||
|
||||
// OperationTimeout sets the timeout for SMTP operations (e.g., EHLO, MAIL FROM, etc.).
|
||||
func (v *Verifier) OperationTimeout(timeout time.Duration) *Verifier {
|
||||
v.operationTimeout = timeout
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Verifier) calculateReachable(s *SMTP) string {
|
||||
if !v.smtpCheckEnabled {
|
||||
return reachableUnknown
|
||||
|
Loading…
x
Reference in New Issue
Block a user