mirror of
https://github.com/nabbar/golib.git
synced 2025-09-26 20:01:15 +08:00
- Add Package Pooler For SMTP :
* implement SMTP lib interface * check number of sent mail in a time lapse before sending new mail * if time since last reset is over, reset counter * add feature to call a user function on each counter reset - Pkg SMTP : Fix race detection & segFault - Pkg Mailer : add new function to parse all template value with the given map[string]string
This commit is contained in:
@@ -39,16 +39,17 @@ const (
|
||||
MinPkgLDAP = 1000
|
||||
MinPkgMail = 1100
|
||||
MinPkgMailer = 1200
|
||||
MinPkgNetwork = 1300
|
||||
MinPkgNats = 1400
|
||||
MinPkgNutsDB = 1500
|
||||
MinPkgOAuth = 1600
|
||||
MinPkgAws = 1700
|
||||
MinPkgRouter = 1800
|
||||
MinPkgSemaphore = 1900
|
||||
MinPkgSMTP = 2000
|
||||
MinPkgStatic = 2100
|
||||
MinPkgVersion = 2200
|
||||
MinPkgMailPooler = 1300
|
||||
MinPkgNetwork = 1400
|
||||
MinPkgNats = 1500
|
||||
MinPkgNutsDB = 1600
|
||||
MinPkgOAuth = 1700
|
||||
MinPkgAws = 1800
|
||||
MinPkgRouter = 1900
|
||||
MinPkgSemaphore = 2000
|
||||
MinPkgSMTP = 2100
|
||||
MinPkgStatic = 2200
|
||||
MinPkgVersion = 2300
|
||||
|
||||
MinAvailable = 4000
|
||||
|
||||
|
44
mailPooler/config.go
Normal file
44
mailPooler/config.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package mailPooler
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
liberr "github.com/nabbar/golib/errors"
|
||||
)
|
||||
|
||||
type FuncCaller func() liberr.Error
|
||||
|
||||
type Config struct {
|
||||
Max int `json:"max" yaml:"max" toml:"max" mapstructure:"max"`
|
||||
Wait time.Duration `json:"wait" yaml:"wait" toml:"wait" mapstructure:"wait"`
|
||||
_fct FuncCaller
|
||||
}
|
||||
|
||||
func (c *Config) SetFuncCaller(fct FuncCaller) {
|
||||
c._fct = fct
|
||||
}
|
129
mailPooler/counter.go
Normal file
129
mailPooler/counter.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package mailPooler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
liberr "github.com/nabbar/golib/errors"
|
||||
)
|
||||
|
||||
type Counter interface {
|
||||
Pool(ctx context.Context) liberr.Error
|
||||
Reset() liberr.Error
|
||||
Clone() Counter
|
||||
}
|
||||
|
||||
type counter struct {
|
||||
m sync.Mutex
|
||||
num int
|
||||
|
||||
max int
|
||||
dur time.Duration
|
||||
tim time.Time
|
||||
|
||||
fct FuncCaller
|
||||
}
|
||||
|
||||
func newCounter(max int, dur time.Duration, fct FuncCaller) Counter {
|
||||
return &counter{
|
||||
m: sync.Mutex{},
|
||||
num: max,
|
||||
max: max,
|
||||
dur: dur,
|
||||
tim: time.Time{},
|
||||
fct: fct,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *counter) Pool(ctx context.Context) liberr.Error {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.max <= 0 || c.dur <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if e := ctx.Err(); e != nil {
|
||||
return ErrorMailPoolerContext.ErrorParent(e)
|
||||
}
|
||||
|
||||
if c.tim.IsZero() {
|
||||
c.num = c.max
|
||||
} else if time.Since(c.tim) > c.dur {
|
||||
c.num = c.max
|
||||
c.tim = time.Time{}
|
||||
}
|
||||
|
||||
if c.num > 0 {
|
||||
c.num--
|
||||
c.tim = time.Now()
|
||||
} else {
|
||||
time.Sleep(c.dur - time.Since(c.tim))
|
||||
|
||||
c.num = c.max - 1
|
||||
c.tim = time.Now()
|
||||
|
||||
if e := ctx.Err(); e != nil {
|
||||
return ErrorMailPoolerContext.ErrorParent(e)
|
||||
} else if err := c.fct(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counter) Reset() liberr.Error {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.max <= 0 || c.dur <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.num = c.max
|
||||
c.tim = time.Time{}
|
||||
|
||||
if err := c.fct(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counter) Clone() Counter {
|
||||
return &counter{
|
||||
m: sync.Mutex{},
|
||||
num: c.num,
|
||||
max: c.max,
|
||||
dur: c.dur,
|
||||
tim: time.Time{},
|
||||
fct: c.fct,
|
||||
}
|
||||
}
|
60
mailPooler/error.go
Normal file
60
mailPooler/error.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package mailPooler
|
||||
|
||||
import "github.com/nabbar/golib/errors"
|
||||
|
||||
const (
|
||||
ErrorParamsEmpty errors.CodeError = iota + errors.MinPkgMailPooler
|
||||
ErrorMailPooler
|
||||
ErrorMailPoolerContext
|
||||
)
|
||||
|
||||
var isCodeError = false
|
||||
|
||||
func IsCodeError() bool {
|
||||
return isCodeError
|
||||
}
|
||||
|
||||
func init() {
|
||||
isCodeError = errors.ExistInMapMessage(ErrorParamsEmpty)
|
||||
errors.RegisterIdFctMessage(ErrorParamsEmpty, getMessage)
|
||||
}
|
||||
|
||||
func getMessage(code errors.CodeError) (message string) {
|
||||
switch code {
|
||||
case errors.UNK_ERROR:
|
||||
return ""
|
||||
case ErrorParamsEmpty:
|
||||
return "given parameters is empty"
|
||||
case ErrorMailPooler:
|
||||
return "generic mail pooler error"
|
||||
case ErrorMailPoolerContext:
|
||||
return "context has trigger error"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
125
mailPooler/pooler.go
Normal file
125
mailPooler/pooler.go
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package mailPooler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
liberr "github.com/nabbar/golib/errors"
|
||||
libsmtp "github.com/nabbar/golib/smtp"
|
||||
"io"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type Pooler interface {
|
||||
Reset() liberr.Error
|
||||
NewPooler() Pooler
|
||||
|
||||
libsmtp.SMTP
|
||||
}
|
||||
|
||||
type pooler struct {
|
||||
s libsmtp.SMTP
|
||||
c Counter
|
||||
}
|
||||
|
||||
func New(cfg *Config, cli libsmtp.SMTP) Pooler {
|
||||
if cli == nil {
|
||||
return &pooler{
|
||||
s: nil,
|
||||
c: newCounter(cfg.Max, cfg.Wait, cfg._fct),
|
||||
}
|
||||
} else {
|
||||
return &pooler{
|
||||
s: cli.Clone(),
|
||||
c: newCounter(cfg.Max, cfg.Wait, cfg._fct),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pooler) Reset() liberr.Error {
|
||||
if p.s == nil {
|
||||
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
|
||||
}
|
||||
|
||||
if err := p.c.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pooler) NewPooler() Pooler {
|
||||
if p.s == nil {
|
||||
return &pooler{
|
||||
s: nil,
|
||||
c: p.c.Clone(),
|
||||
}
|
||||
} else {
|
||||
return &pooler{
|
||||
s: p.s.Clone(),
|
||||
c: p.c.Clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pooler) Send(ctx context.Context, from string, to []string, data io.WriterTo) liberr.Error {
|
||||
if p.s == nil {
|
||||
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
|
||||
}
|
||||
|
||||
if err := p.c.Pool(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.s.Send(ctx, from, to, data)
|
||||
}
|
||||
|
||||
func (p *pooler) Client(ctx context.Context) (*smtp.Client, liberr.Error) {
|
||||
if p.s == nil {
|
||||
return nil, ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
|
||||
}
|
||||
|
||||
return p.s.Client(ctx)
|
||||
}
|
||||
|
||||
func (p *pooler) Close() {
|
||||
if p.s != nil {
|
||||
p.s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pooler) Check(ctx context.Context) liberr.Error {
|
||||
if p.s == nil {
|
||||
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
|
||||
}
|
||||
|
||||
return p.s.Check(ctx)
|
||||
}
|
||||
|
||||
func (p *pooler) Clone() libsmtp.SMTP {
|
||||
return p.NewPooler()
|
||||
}
|
@@ -61,6 +61,8 @@ type Mailer interface {
|
||||
SetTroubleText(text string)
|
||||
GetTroubleText() string
|
||||
|
||||
ParseData(data map[string]string)
|
||||
|
||||
GenerateHTML() (*bytes.Buffer, liberr.Error)
|
||||
GeneratePlainText() (*bytes.Buffer, liberr.Error)
|
||||
}
|
||||
|
@@ -27,11 +27,58 @@ package mailer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/matcornic/hermes/v2"
|
||||
liberr "github.com/nabbar/golib/errors"
|
||||
)
|
||||
|
||||
func (e *email) ParseData(data map[string]string) {
|
||||
for k, v := range data {
|
||||
e.p.Copyright = strings.ReplaceAll(e.p.Copyright, k, v)
|
||||
e.p.Link = strings.ReplaceAll(e.p.Link, k, v)
|
||||
e.p.Logo = strings.ReplaceAll(e.p.Logo, k, v)
|
||||
e.p.Name = strings.ReplaceAll(e.p.Name, k, v)
|
||||
e.p.TroubleText = strings.ReplaceAll(e.p.TroubleText, k, v)
|
||||
|
||||
e.b.Greeting = strings.ReplaceAll(e.b.Greeting, k, v)
|
||||
e.b.Name = strings.ReplaceAll(e.b.Name, k, v)
|
||||
e.b.Signature = strings.ReplaceAll(e.b.Signature, k, v)
|
||||
e.b.Title = strings.ReplaceAll(e.b.Title, k, v)
|
||||
|
||||
e.b.FreeMarkdown = hermes.Markdown(strings.ReplaceAll(string(e.b.FreeMarkdown), k, v))
|
||||
|
||||
for i := range e.b.Intros {
|
||||
e.b.Intros[i] = strings.ReplaceAll(e.b.Intros[i], k, v)
|
||||
}
|
||||
|
||||
for i := range e.b.Outros {
|
||||
e.b.Outros[i] = strings.ReplaceAll(e.b.Outros[i], k, v)
|
||||
}
|
||||
|
||||
for i := range e.b.Dictionary {
|
||||
e.b.Dictionary[i].Key = strings.ReplaceAll(e.b.Dictionary[i].Key, k, v)
|
||||
e.b.Dictionary[i].Value = strings.ReplaceAll(e.b.Dictionary[i].Value, k, v)
|
||||
}
|
||||
|
||||
for i := range e.b.Table.Data {
|
||||
for j := range e.b.Table.Data[i] {
|
||||
e.b.Table.Data[i][j].Key = strings.ReplaceAll(e.b.Table.Data[i][j].Key, k, v)
|
||||
e.b.Table.Data[i][j].Value = strings.ReplaceAll(e.b.Table.Data[i][j].Value, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range e.b.Actions {
|
||||
e.b.Actions[i].Instructions = strings.ReplaceAll(e.b.Actions[i].Instructions, k, v)
|
||||
e.b.Actions[i].InviteCode = strings.ReplaceAll(e.b.Actions[i].InviteCode, k, v)
|
||||
e.b.Actions[i].Button.Link = strings.ReplaceAll(e.b.Actions[i].Button.Link, k, v)
|
||||
e.b.Actions[i].Button.Text = strings.ReplaceAll(e.b.Actions[i].Button.Text, k, v)
|
||||
e.b.Actions[i].Button.Color = strings.ReplaceAll(e.b.Actions[i].Button.Color, k, v)
|
||||
e.b.Actions[i].Button.TextColor = strings.ReplaceAll(e.b.Actions[i].Button.TextColor, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *email) GenerateHTML() (*bytes.Buffer, liberr.Error) {
|
||||
return e.generated(e.genHtml)
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nabbar/golib/errors"
|
||||
)
|
||||
@@ -90,6 +91,7 @@ func NewSMTP(cfg Config, tlsConfig *tls.Config) (SMTP, errors.Error) {
|
||||
return nil, ErrorParamsEmpty.Error(nil)
|
||||
} else {
|
||||
return &smtpClient{
|
||||
mut: sync.Mutex{},
|
||||
cfg: cfg,
|
||||
tls: tlsConfig,
|
||||
}, nil
|
||||
|
152
smtp/model.go
152
smtp/model.go
@@ -33,11 +33,13 @@ import (
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nabbar/golib/errors"
|
||||
)
|
||||
|
||||
type smtpClient struct {
|
||||
mut sync.Mutex
|
||||
con net.Conn
|
||||
cli *smtp.Client
|
||||
tls *tls.Config
|
||||
@@ -47,6 +49,7 @@ type smtpClient struct {
|
||||
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
||||
func (s smtpClient) Clone() SMTP {
|
||||
return &smtpClient{
|
||||
mut: sync.Mutex{},
|
||||
con: nil,
|
||||
cli: nil,
|
||||
tls: s.tls,
|
||||
@@ -56,30 +59,22 @@ func (s smtpClient) Clone() SMTP {
|
||||
|
||||
// Close Terminate SMTP negotiation client and close connection.
|
||||
func (s *smtpClient) Close() {
|
||||
if s.cli != nil {
|
||||
if e := s.cli.Quit(); e != nil {
|
||||
_ = s.cli.Close()
|
||||
}
|
||||
s.cli = nil
|
||||
}
|
||||
|
||||
if s.con != nil {
|
||||
_ = s.con.Close()
|
||||
s.con = nil
|
||||
}
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
s._close()
|
||||
}
|
||||
|
||||
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
||||
func (s *smtpClient) Check(ctx context.Context) errors.Error {
|
||||
defer s.Close()
|
||||
s.mut.Lock()
|
||||
defer func() {
|
||||
s._close()
|
||||
s.mut.Unlock()
|
||||
}()
|
||||
|
||||
if s.cli == nil {
|
||||
if _, e := s.Client(ctx); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
if e := s.cli.Noop(); e != nil {
|
||||
if c, err := s._client(ctx); err != nil {
|
||||
return err
|
||||
} else if e := c.Noop(); e != nil {
|
||||
return ErrorSMTPClientNoop.ErrorParent(e)
|
||||
}
|
||||
|
||||
@@ -88,36 +83,10 @@ func (s *smtpClient) Check(ctx context.Context) errors.Error {
|
||||
|
||||
// Client Get SMTP Client interface.
|
||||
func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
|
||||
if s.cli == nil {
|
||||
var (
|
||||
addr = s.cfg.GetHost()
|
||||
tlsc = s.tls.Clone()
|
||||
)
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
||||
if s.cfg.GetTlSServerName() != "" && s.cfg.GetNet() != NET_UNIX {
|
||||
tlsc.ServerName = s.cfg.GetTlSServerName()
|
||||
}
|
||||
|
||||
if s.cfg.IsTLSSkipVerify() && s.cfg.GetNet() != NET_UNIX {
|
||||
tlsc.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if s.cfg.GetPort() > 0 {
|
||||
addr = fmt.Sprintf("%s:%v", s.cfg.GetHost(), s.cfg.GetPort())
|
||||
}
|
||||
|
||||
if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil {
|
||||
return nil, err
|
||||
} else if err := s.auth(cli, addr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
s.Close()
|
||||
s.con = con
|
||||
s.cli = cli
|
||||
}
|
||||
}
|
||||
|
||||
return s.cli, nil
|
||||
return s._client(ctx)
|
||||
}
|
||||
|
||||
// validateLine checks to see if a line has CR or LF as per RFC 5321.
|
||||
@@ -134,46 +103,54 @@ func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io
|
||||
|
||||
var (
|
||||
e error
|
||||
c *smtp.Client
|
||||
w io.WriteCloser
|
||||
|
||||
err errors.Error
|
||||
)
|
||||
|
||||
s.mut.Lock()
|
||||
|
||||
defer func() {
|
||||
if w != nil {
|
||||
_ = w.Close()
|
||||
}
|
||||
|
||||
s.Close()
|
||||
//mandatory for SMTP protocol
|
||||
s._close()
|
||||
|
||||
s.mut.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.validateLine(from); err != nil {
|
||||
if err = s.validateLine(from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, recp := range to {
|
||||
if err := s.validateLine(recp); err != nil {
|
||||
if err = s.validateLine(recp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Check(ctx); err != nil {
|
||||
return err
|
||||
if c, err = s._client(ctx); err != nil {
|
||||
return ErrorSMTPClientInit.Error(err)
|
||||
}
|
||||
|
||||
if _, e = s.Client(ctx); e != nil {
|
||||
return ErrorSMTPClientInit.ErrorParent(e)
|
||||
if e = c.Noop(); e != nil {
|
||||
return ErrorSMTPClientNoop.ErrorParent(e)
|
||||
}
|
||||
|
||||
if e = s.cli.Mail(from); e != nil {
|
||||
if e = c.Mail(from); e != nil {
|
||||
return ErrorSMTPClientMail.ErrorParent(e)
|
||||
}
|
||||
|
||||
for _, addr := range to {
|
||||
if e = s.cli.Rcpt(addr); e != nil {
|
||||
if e = c.Rcpt(addr); e != nil {
|
||||
return ErrorSMTPClientRcpt.ErrorParent(e)
|
||||
}
|
||||
}
|
||||
|
||||
if w, e = s.cli.Data(); e != nil {
|
||||
if w, e = c.Data(); e != nil {
|
||||
return ErrorSMTPClientData.ErrorParent(e)
|
||||
}
|
||||
|
||||
@@ -183,3 +160,62 @@ func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *smtpClient) _client(ctx context.Context) (*smtp.Client, errors.Error) {
|
||||
if s.cli != nil && s.con != nil {
|
||||
return s.cli, nil
|
||||
}
|
||||
|
||||
if s.con == nil && s.cli != nil {
|
||||
if e := s.cli.Quit(); e != nil {
|
||||
_ = s.cli.Close()
|
||||
}
|
||||
} else if s.con != nil && s.cli == nil {
|
||||
_ = s.con.Close()
|
||||
}
|
||||
|
||||
s.cli = nil
|
||||
s.con = nil
|
||||
|
||||
var (
|
||||
addr = s.cfg.GetHost()
|
||||
tlsc = s.tls.Clone()
|
||||
)
|
||||
|
||||
if s.cfg.GetTlSServerName() != "" && s.cfg.GetNet() != NET_UNIX {
|
||||
tlsc.ServerName = s.cfg.GetTlSServerName()
|
||||
}
|
||||
|
||||
if s.cfg.IsTLSSkipVerify() && s.cfg.GetNet() != NET_UNIX {
|
||||
tlsc.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if s.cfg.GetPort() > 0 {
|
||||
addr = fmt.Sprintf("%s:%v", s.cfg.GetHost(), s.cfg.GetPort())
|
||||
}
|
||||
|
||||
if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil {
|
||||
return nil, err
|
||||
} else if err = s.auth(cli, addr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
s.con = con
|
||||
s.cli = cli
|
||||
}
|
||||
|
||||
return s.cli, nil
|
||||
}
|
||||
|
||||
func (s *smtpClient) _close() {
|
||||
if s.cli != nil {
|
||||
if e := s.cli.Quit(); e != nil {
|
||||
_ = s.cli.Close()
|
||||
}
|
||||
s.cli = nil
|
||||
}
|
||||
|
||||
if s.con != nil {
|
||||
_ = s.con.Close()
|
||||
s.con = nil
|
||||
}
|
||||
}
|
||||
|
@@ -35,15 +35,17 @@ import (
|
||||
libiot "github.com/nabbar/golib/ioutils"
|
||||
liblog "github.com/nabbar/golib/logger"
|
||||
libsnd "github.com/nabbar/golib/mail"
|
||||
libpool "github.com/nabbar/golib/mailPooler"
|
||||
libtpl "github.com/nabbar/golib/mailer"
|
||||
libsem "github.com/nabbar/golib/semaphore"
|
||||
libsmtp "github.com/nabbar/golib/smtp"
|
||||
)
|
||||
|
||||
const (
|
||||
CONFIG_SMTP_DSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
|
||||
CONFIG_EMAIL_FROM = "email@example.com"
|
||||
CONFIG_EMAIL_TO = "email@example.com"
|
||||
CONFIG_SUBJECT = "Testing Send Mail"
|
||||
_ConfigSmtpDSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
|
||||
_ConfigEmailFrom = "email@example.com"
|
||||
_ConfigEmailTo = "email@example.com"
|
||||
_ConfigSubject = "Testing Send Mail"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -71,22 +73,36 @@ func init() {
|
||||
|
||||
func main() {
|
||||
var (
|
||||
cli libsmtp.SMTP
|
||||
cli libpool.Pooler
|
||||
err liberr.Error
|
||||
sem = libsem.NewSemaphoreWithContext(ctx, 0)
|
||||
)
|
||||
|
||||
defer func() {
|
||||
sem.DeferMain()
|
||||
cnl()
|
||||
if cli != nil {
|
||||
cli.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
cli = getSmtp()
|
||||
//cli = getSmtp()
|
||||
cli = getPool()
|
||||
|
||||
err = getSendMail(getTemplate()).SendClose(ctx, cli)
|
||||
snd := getSendMail(getTemplate())
|
||||
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "sending email", err)
|
||||
// err = getSendMail(getTemplate()).SendClose(ctx, cli)
|
||||
for i := 0; i < 5; i++ {
|
||||
_ = sem.NewWorker()
|
||||
go func() {
|
||||
defer sem.DeferWorker()
|
||||
err = snd.Send(ctx, cli)
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[sender] sending email", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}()
|
||||
}
|
||||
|
||||
_ = sem.WaitAll()
|
||||
}
|
||||
|
||||
func getTemplate() libtpl.Mailer {
|
||||
@@ -127,27 +143,43 @@ func getTemplate() libtpl.Mailer {
|
||||
}
|
||||
|
||||
func getSmtp() libsmtp.SMTP {
|
||||
cfg, err := libsmtp.NewConfig(CONFIG_SMTP_DSN)
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp config parsing", err)
|
||||
cfg, err := libsmtp.NewConfig(_ConfigSmtpDSN)
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] config parsing", err)
|
||||
|
||||
/* #nosec */
|
||||
//nolint #nosec
|
||||
s, err := libsmtp.NewSMTP(cfg, &tls.Config{})
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp create client", err)
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp checking working", s.Check(ctx))
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] init", err)
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] checking working", s.Check(ctx))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func getPool() libpool.Pooler {
|
||||
cfg := &libpool.Config{
|
||||
Max: 2,
|
||||
Wait: 10 * time.Second,
|
||||
}
|
||||
cfg.SetFuncCaller(func() liberr.Error {
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[mail pooler] reset counter", nil)
|
||||
return nil
|
||||
})
|
||||
|
||||
p := libpool.New(cfg, getSmtp())
|
||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[mail pooler] init", nil)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func getSendMail(tpl libtpl.Mailer) libsnd.Sender {
|
||||
m := libsnd.New()
|
||||
|
||||
m.Email().SetFrom(CONFIG_EMAIL_FROM)
|
||||
m.Email().SetRecipients(libsnd.RecipientTo, CONFIG_EMAIL_TO)
|
||||
m.Email().SetFrom(_ConfigEmailFrom)
|
||||
m.Email().SetRecipients(libsnd.RecipientTo, _ConfigEmailTo)
|
||||
|
||||
m.SetCharset("UTF-8")
|
||||
m.SetPriority(libsnd.PriorityNormal)
|
||||
m.SetSubject(CONFIG_SUBJECT)
|
||||
m.SetSubject(_ConfigSubject)
|
||||
m.SetEncoding(libsnd.EncodingBinary)
|
||||
m.SetDateTime(time.Now())
|
||||
|
||||
|
Reference in New Issue
Block a user