mirror of
https://github.com/nabbar/golib.git
synced 2025-10-05 15:56:50 +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
|
MinPkgLDAP = 1000
|
||||||
MinPkgMail = 1100
|
MinPkgMail = 1100
|
||||||
MinPkgMailer = 1200
|
MinPkgMailer = 1200
|
||||||
MinPkgNetwork = 1300
|
MinPkgMailPooler = 1300
|
||||||
MinPkgNats = 1400
|
MinPkgNetwork = 1400
|
||||||
MinPkgNutsDB = 1500
|
MinPkgNats = 1500
|
||||||
MinPkgOAuth = 1600
|
MinPkgNutsDB = 1600
|
||||||
MinPkgAws = 1700
|
MinPkgOAuth = 1700
|
||||||
MinPkgRouter = 1800
|
MinPkgAws = 1800
|
||||||
MinPkgSemaphore = 1900
|
MinPkgRouter = 1900
|
||||||
MinPkgSMTP = 2000
|
MinPkgSemaphore = 2000
|
||||||
MinPkgStatic = 2100
|
MinPkgSMTP = 2100
|
||||||
MinPkgVersion = 2200
|
MinPkgStatic = 2200
|
||||||
|
MinPkgVersion = 2300
|
||||||
|
|
||||||
MinAvailable = 4000
|
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)
|
SetTroubleText(text string)
|
||||||
GetTroubleText() string
|
GetTroubleText() string
|
||||||
|
|
||||||
|
ParseData(data map[string]string)
|
||||||
|
|
||||||
GenerateHTML() (*bytes.Buffer, liberr.Error)
|
GenerateHTML() (*bytes.Buffer, liberr.Error)
|
||||||
GeneratePlainText() (*bytes.Buffer, liberr.Error)
|
GeneratePlainText() (*bytes.Buffer, liberr.Error)
|
||||||
}
|
}
|
||||||
|
@@ -27,11 +27,58 @@ package mailer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/matcornic/hermes/v2"
|
"github.com/matcornic/hermes/v2"
|
||||||
liberr "github.com/nabbar/golib/errors"
|
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) {
|
func (e *email) GenerateHTML() (*bytes.Buffer, liberr.Error) {
|
||||||
return e.generated(e.genHtml)
|
return e.generated(e.genHtml)
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/nabbar/golib/errors"
|
"github.com/nabbar/golib/errors"
|
||||||
)
|
)
|
||||||
@@ -90,6 +91,7 @@ func NewSMTP(cfg Config, tlsConfig *tls.Config) (SMTP, errors.Error) {
|
|||||||
return nil, ErrorParamsEmpty.Error(nil)
|
return nil, ErrorParamsEmpty.Error(nil)
|
||||||
} else {
|
} else {
|
||||||
return &smtpClient{
|
return &smtpClient{
|
||||||
|
mut: sync.Mutex{},
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
tls: tlsConfig,
|
tls: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
|
202
smtp/model.go
202
smtp/model.go
@@ -33,11 +33,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/nabbar/golib/errors"
|
"github.com/nabbar/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type smtpClient struct {
|
type smtpClient struct {
|
||||||
|
mut sync.Mutex
|
||||||
con net.Conn
|
con net.Conn
|
||||||
cli *smtp.Client
|
cli *smtp.Client
|
||||||
tls *tls.Config
|
tls *tls.Config
|
||||||
@@ -47,6 +49,7 @@ type smtpClient struct {
|
|||||||
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
||||||
func (s smtpClient) Clone() SMTP {
|
func (s smtpClient) Clone() SMTP {
|
||||||
return &smtpClient{
|
return &smtpClient{
|
||||||
|
mut: sync.Mutex{},
|
||||||
con: nil,
|
con: nil,
|
||||||
cli: nil,
|
cli: nil,
|
||||||
tls: s.tls,
|
tls: s.tls,
|
||||||
@@ -56,30 +59,22 @@ func (s smtpClient) Clone() SMTP {
|
|||||||
|
|
||||||
// Close Terminate SMTP negotiation client and close connection.
|
// Close Terminate SMTP negotiation client and close connection.
|
||||||
func (s *smtpClient) Close() {
|
func (s *smtpClient) Close() {
|
||||||
if s.cli != nil {
|
s.mut.Lock()
|
||||||
if e := s.cli.Quit(); e != nil {
|
defer s.mut.Unlock()
|
||||||
_ = s.cli.Close()
|
s._close()
|
||||||
}
|
|
||||||
s.cli = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.con != nil {
|
|
||||||
_ = s.con.Close()
|
|
||||||
s.con = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
// Check Try to initiate SMTP dial and negotiation and try to close connection.
|
||||||
func (s *smtpClient) Check(ctx context.Context) errors.Error {
|
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 c, err := s._client(ctx); err != nil {
|
||||||
if _, e := s.Client(ctx); e != nil {
|
return err
|
||||||
return e
|
} else if e := c.Noop(); e != nil {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if e := s.cli.Noop(); e != nil {
|
|
||||||
return ErrorSMTPClientNoop.ErrorParent(e)
|
return ErrorSMTPClientNoop.ErrorParent(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +83,100 @@ func (s *smtpClient) Check(ctx context.Context) errors.Error {
|
|||||||
|
|
||||||
// Client Get SMTP Client interface.
|
// Client Get SMTP Client interface.
|
||||||
func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
|
func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
|
||||||
if s.cli == nil {
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
return s._client(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateLine checks to see if a line has CR or LF as per RFC 5321.
|
||||||
|
func (s smtpClient) validateLine(line string) errors.Error {
|
||||||
|
if strings.ContainsAny(line, "\n\r") {
|
||||||
|
return ErrorSMTPLineCRLF.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) errors.Error {
|
||||||
|
//from smtp.SendMail()
|
||||||
|
|
||||||
|
var (
|
||||||
|
e error
|
||||||
|
c *smtp.Client
|
||||||
|
w io.WriteCloser
|
||||||
|
|
||||||
|
err errors.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
s.mut.Lock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if w != nil {
|
||||||
|
_ = w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
//mandatory for SMTP protocol
|
||||||
|
s._close()
|
||||||
|
|
||||||
|
s.mut.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = s.validateLine(from); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, recp := range to {
|
||||||
|
if err = s.validateLine(recp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c, err = s._client(ctx); err != nil {
|
||||||
|
return ErrorSMTPClientInit.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e = c.Noop(); e != nil {
|
||||||
|
return ErrorSMTPClientNoop.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e = c.Mail(from); e != nil {
|
||||||
|
return ErrorSMTPClientMail.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range to {
|
||||||
|
if e = c.Rcpt(addr); e != nil {
|
||||||
|
return ErrorSMTPClientRcpt.ErrorParent(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w, e = c.Data(); e != nil {
|
||||||
|
return ErrorSMTPClientData.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e = data.WriteTo(w); e != nil {
|
||||||
|
return ErrorSMTPClientWrite.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
var (
|
||||||
addr = s.cfg.GetHost()
|
addr = s.cfg.GetHost()
|
||||||
tlsc = s.tls.Clone()
|
tlsc = s.tls.Clone()
|
||||||
@@ -108,78 +196,26 @@ func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
|
|||||||
|
|
||||||
if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil {
|
if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err := s.auth(cli, addr); err != nil {
|
} else if err = s.auth(cli, addr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
s.Close()
|
|
||||||
s.con = con
|
s.con = con
|
||||||
s.cli = cli
|
s.cli = cli
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return s.cli, nil
|
return s.cli, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateLine checks to see if a line has CR or LF as per RFC 5321.
|
func (s *smtpClient) _close() {
|
||||||
func (s smtpClient) validateLine(line string) errors.Error {
|
if s.cli != nil {
|
||||||
if strings.ContainsAny(line, "\n\r") {
|
if e := s.cli.Quit(); e != nil {
|
||||||
return ErrorSMTPLineCRLF.Error(nil)
|
_ = s.cli.Close()
|
||||||
|
}
|
||||||
|
s.cli = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if s.con != nil {
|
||||||
}
|
_ = s.con.Close()
|
||||||
|
s.con = nil
|
||||||
func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) errors.Error {
|
}
|
||||||
//from smtp.SendMail()
|
|
||||||
|
|
||||||
var (
|
|
||||||
e error
|
|
||||||
w io.WriteCloser
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if w != nil {
|
|
||||||
_ = w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := s.validateLine(from); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, recp := range to {
|
|
||||||
if err := s.validateLine(recp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Check(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, e = s.Client(ctx); e != nil {
|
|
||||||
return ErrorSMTPClientInit.ErrorParent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e = s.cli.Mail(from); e != nil {
|
|
||||||
return ErrorSMTPClientMail.ErrorParent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range to {
|
|
||||||
if e = s.cli.Rcpt(addr); e != nil {
|
|
||||||
return ErrorSMTPClientRcpt.ErrorParent(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if w, e = s.cli.Data(); e != nil {
|
|
||||||
return ErrorSMTPClientData.ErrorParent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, e = data.WriteTo(w); e != nil {
|
|
||||||
return ErrorSMTPClientWrite.ErrorParent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@@ -35,15 +35,17 @@ import (
|
|||||||
libiot "github.com/nabbar/golib/ioutils"
|
libiot "github.com/nabbar/golib/ioutils"
|
||||||
liblog "github.com/nabbar/golib/logger"
|
liblog "github.com/nabbar/golib/logger"
|
||||||
libsnd "github.com/nabbar/golib/mail"
|
libsnd "github.com/nabbar/golib/mail"
|
||||||
|
libpool "github.com/nabbar/golib/mailPooler"
|
||||||
libtpl "github.com/nabbar/golib/mailer"
|
libtpl "github.com/nabbar/golib/mailer"
|
||||||
|
libsem "github.com/nabbar/golib/semaphore"
|
||||||
libsmtp "github.com/nabbar/golib/smtp"
|
libsmtp "github.com/nabbar/golib/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CONFIG_SMTP_DSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
|
_ConfigSmtpDSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
|
||||||
CONFIG_EMAIL_FROM = "email@example.com"
|
_ConfigEmailFrom = "email@example.com"
|
||||||
CONFIG_EMAIL_TO = "email@example.com"
|
_ConfigEmailTo = "email@example.com"
|
||||||
CONFIG_SUBJECT = "Testing Send Mail"
|
_ConfigSubject = "Testing Send Mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -71,22 +73,36 @@ func init() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
cli libsmtp.SMTP
|
cli libpool.Pooler
|
||||||
err liberr.Error
|
err liberr.Error
|
||||||
|
sem = libsem.NewSemaphoreWithContext(ctx, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
sem.DeferMain()
|
||||||
cnl()
|
cnl()
|
||||||
if cli != nil {
|
if cli != nil {
|
||||||
cli.Close()
|
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 {
|
func getTemplate() libtpl.Mailer {
|
||||||
@@ -127,27 +143,43 @@ func getTemplate() libtpl.Mailer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSmtp() libsmtp.SMTP {
|
func getSmtp() libsmtp.SMTP {
|
||||||
cfg, err := libsmtp.NewConfig(CONFIG_SMTP_DSN)
|
cfg, err := libsmtp.NewConfig(_ConfigSmtpDSN)
|
||||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp config parsing", err)
|
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] config parsing", err)
|
||||||
|
|
||||||
/* #nosec */
|
/* #nosec */
|
||||||
//nolint #nosec
|
//nolint #nosec
|
||||||
s, err := libsmtp.NewSMTP(cfg, &tls.Config{})
|
s, err := libsmtp.NewSMTP(cfg, &tls.Config{})
|
||||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp create client", err)
|
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] init", err)
|
||||||
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp checking working", s.Check(ctx))
|
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] checking working", s.Check(ctx))
|
||||||
|
|
||||||
return s
|
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 {
|
func getSendMail(tpl libtpl.Mailer) libsnd.Sender {
|
||||||
m := libsnd.New()
|
m := libsnd.New()
|
||||||
|
|
||||||
m.Email().SetFrom(CONFIG_EMAIL_FROM)
|
m.Email().SetFrom(_ConfigEmailFrom)
|
||||||
m.Email().SetRecipients(libsnd.RecipientTo, CONFIG_EMAIL_TO)
|
m.Email().SetRecipients(libsnd.RecipientTo, _ConfigEmailTo)
|
||||||
|
|
||||||
m.SetCharset("UTF-8")
|
m.SetCharset("UTF-8")
|
||||||
m.SetPriority(libsnd.PriorityNormal)
|
m.SetPriority(libsnd.PriorityNormal)
|
||||||
m.SetSubject(CONFIG_SUBJECT)
|
m.SetSubject(_ConfigSubject)
|
||||||
m.SetEncoding(libsnd.EncodingBinary)
|
m.SetEncoding(libsnd.EncodingBinary)
|
||||||
m.SetDateTime(time.Now())
|
m.SetDateTime(time.Now())
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user