/
middleware.go
177 lines (164 loc) · 4.85 KB
/
middleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Funciones auxiliares para GIN-GONIC
package ginhelper
import (
"bytes"
"errors"
"io"
"net/http"
"net/url"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/horus-es/go-util/v2/postgres"
)
// Middleware para inicializar estado a no implementado
func MiddlewareNotImplemented() gin.HandlerFunc {
return func(c *gin.Context) {
c.Status(http.StatusNotImplemented)
c.Next()
}
}
// Middleware de registro de actividad en modo produccion
func productionLogger(c *gin.Context) {
t := time.Now()
c.Next()
latency := time.Since(t).Milliseconds()
statusCode := c.Writer.Status()
statusText := http.StatusText(statusCode)
method := c.Request.Method
path := c.Request.URL.String()
path, _ = url.PathUnescape(path)
path = strings.ReplaceAll(path, " ", "+")
const fmt = "%d %s - %s %s - %dms"
const max = 1000
if statusCode >= 200 && statusCode <= 299 {
if latency < max {
// Todo OK
ghLog.Infof(fmt, statusCode, statusText, method, path, latency)
} else {
// Respuesta lenta
ghLog.Warnf(fmt, statusCode, statusText, method, path, latency)
}
return
}
if statusCode >= 300 && statusCode <= 499 {
// Error de solicitud o redirección
ghLog.Warnf(fmt, statusCode, statusText, method, path, latency)
} else {
// Error de servidor
ghLog.Errorf(fmt, statusCode, statusText, method, path, latency)
}
}
// Middleware de registro de actividad en modo depuración
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func debugLogger(c *gin.Context) {
// Registramos metodo y ruta
method := c.Request.Method
path := c.Request.URL.String()
path, _ = url.PathUnescape(path)
path = strings.ReplaceAll(path, " ", "+")
ghLog.Infof("%s %s", method, path)
if c.Request.ContentLength != 0 {
// Registramos solicitud duplicando reader
buffer, _ := io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(buffer))
if len(buffer) < 3000 {
ghLog.Infof(string(buffer))
} else {
ghLog.Infof(string(buffer[:3000]) + "···")
}
}
// Duplicamos writer
blw := &bodyLogWriter{body: new(bytes.Buffer), ResponseWriter: c.Writer}
c.Writer = blw
// Siguiente en cadena
t := time.Now()
c.Next()
latency := time.Since(t).Milliseconds()
// Registramos status
statusCode := c.Writer.Status()
statusText := http.StatusText(statusCode)
ghLog.Infof("HTTP %d %s - %dms", statusCode, statusText, latency)
// Registramos respuesta
ct := c.Writer.Header().Get("Content-Type")
if strings.HasPrefix(ct, "application/json") && blw.body.Len() > 0 {
ghLog.Infof(blw.body.String())
} else {
if ct != "" {
ghLog.Infof("Content-Type: %s", ct)
}
ghLog.Infof("Content-Length: %d", blw.body.Len())
}
ghLog.Infof("==================================================")
}
// Devuelve el logger de depuración o de producción
func MiddlewareLogger(debug bool) gin.HandlerFunc {
if debug {
return debugLogger
} else {
return productionLogger
}
}
// Middleware de gestión de transacciones
func MiddlewareTransaction() gin.HandlerFunc {
return func(c *gin.Context) {
postgres.StartTX()
defer postgres.RollbackTX()
c.Next()
statusCode := c.Writer.Status()
if statusCode >= 200 && statusCode <= 299 {
postgres.CommitTX()
}
}
}
// Middleware de recuperación de errores
func MiddlewarePanic() gin.HandlerFunc {
return func(c *gin.Context) {
defer recuperaDiferido(c)
c.Next()
}
}
// Auxiliar de MiddlewarePanic
func recuperaDiferido(c *gin.Context) {
causa := recover()
if causa == nil {
// No panic
return
}
// ¿Es debido a un error de red?
networkError := isNetworkError(causa.(error))
if networkError {
// Si hay error de red, no podemos responder nada ...
ghLog.Errorf("Error de red: %v", causa)
} else {
// TODO: ¿añadir 404 para claves no halladas?
// ¿Es debido a una violación de restricción SQL o custom?
errorSQL, msg := postgres.GetErrorSQL(causa.(error))
switch errorSQL {
case postgres.INTEGRITY_CONSTRAINT_VIOLATION:
// Si es una violación de restriccion SQL, se supone que la culpa es del cliente
// TODO: ¿Cambiar mensaje según tipo de violación?
c.PureJSON(http.StatusBadRequest, BadRequestResponse("Valor duplicado", causa))
case postgres.PL_PGSQL_RAISE_EXCEPTION:
// Si es una excepcion levantada en un procedimiento, se supone que la culpa es del cliente
c.PureJSON(http.StatusBadRequest, BadRequestResponse(msg, causa))
default:
// Otros errores seguramente sean de programación o de sistema
ghLog.Errorf("Error interno: %v", causa)
c.PureJSON(http.StatusInternalServerError, gin.H{"error": "Error interno"})
}
}
c.Abort()
}
// Determina si un error es debido a un corte de red (broken pipe / connection reset by peer)
func isNetworkError(err error) bool {
return errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET)
}