Created
August 7, 2020 23:58
-
-
Save umarquez/5f9757562e46c008a049479fbdfd896b to your computer and use it in GitHub Desktop.
#1mC0D3 - Monitoreo de sitios web con #Golang Go y expresiones regulares, en 1 minuto. https://umarquez.c0d3.mx/1mC0D3/p4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"log" | |
"net/http" | |
"time" | |
) | |
/* | |
* ============================================================================ | |
* #1mC0D3 - Monitoreo de sitios web con #Golang Go y expresiones regulares, en | |
* 1 minuto. | |
* ============================================================================ | |
*/ | |
var ( | |
// lista de monitores | |
lstMonitors map[string]*WebMonitor | |
// Intervalos de tiempo entre pruebas | |
testInterval = 5 * time.Minute | |
retryInterval = time.Minute | |
) | |
// TestSites ejecuta el EvalIfTimeExpires en cada monitor y programa la | |
// siguiente ronda | |
func TestSites() { | |
for _, mon := range lstMonitors { | |
mon.EvalIfTimeExpires() | |
} | |
time.AfterFunc(retryInterval, TestSites) | |
} | |
func init() { | |
lstMonitors = make(map[string]*WebMonitor) | |
// Vamos a comprobar google.com buscando esa misma cadena en el contenido | |
lstMonitors["google.com"] = NewWebMonitor( | |
"google.com", | |
"https://google.com", | |
"google.com", | |
) | |
// Esperemos que falle pues es poco probable que encuentre coincidencias | |
lstMonitors["yahoo.com"] = NewWebMonitor( | |
"yahoo.com", | |
"https://yahoo.com", | |
"yah-oo.com", | |
) // Falla intencional | |
// Endpoint de consulta | |
http.HandleFunc( | |
"/sites", | |
func(writer http.ResponseWriter, request *http.Request) { | |
writer.Header().Set( | |
"Content-Type", | |
"application/json; sharset=utf-8", | |
) | |
err := json.NewEncoder(writer).Encode(lstMonitors) | |
if err != nil { | |
http.Error(writer, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
}) | |
} | |
func main() { | |
// Ejecutamos la primera vez para tener los valores iniciales | |
TestSites() | |
// Y programamos la siguiente ejecución | |
time.AfterFunc(retryInterval, TestSites) | |
// Mantenemos el servicio web en ejecución | |
go func() { | |
for true { | |
log.Print("INFO - iniciando servidor web") | |
err := http.ListenAndServe(":8080", nil) | |
if err != nil { | |
log.Printf("ERROR - http.ListenAndServe: %v", err) | |
} | |
} | |
}() | |
// Mantenemos el programa en ejecución | |
select {} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"io/ioutil" | |
"log" | |
"net/http" | |
"regexp" | |
"time" | |
) | |
type EventType string | |
// Tipos de evento | |
const ( | |
// EvtError Tipo de evento ERROR | |
EvtError EventType = "ERROR" | |
//EvtStatus Tipo de evento STATUS | |
EvtStatus = "STATUS" | |
) | |
// Event Es el resultado de una prueba aun sitio web en determinada fecha, este | |
// puede ser un ERROR en caso de fallo o un STATUS si es exitosa | |
type Event struct { | |
Type EventType `json:"type"` | |
DateTime time.Time `json:"date_time"` | |
Status string `json:"status"` | |
} | |
// WebMonitor es un monitor para un solo sitio, este contiene el historial de | |
// eventos y otros valores como el estado y el último tiempo de respuesta | |
type WebMonitor struct { | |
Name string `json:"name"` | |
Url string `json:"url"` | |
Pattern string `json:"pattern"` | |
ResponseTime int64 `json:"response_time"` | |
Status bool `json:"status"` | |
LastResponse time.Time `json:"last_response"` | |
NextScan time.Time `json:"next_scan"` | |
History []Event `json:"history"` | |
CreatedAt time.Time `json:"created_at"` | |
} | |
// NewWebMonitor inicializa un nuevo monitor utilizanfo el nombre, patrón y la | |
// URL a consultar | |
func NewWebMonitor(name, url, pattern string) *WebMonitor { | |
return &WebMonitor{ | |
Name: name, | |
Url: url, | |
Pattern: pattern, | |
CreatedAt: time.Now(), | |
} | |
} | |
// AppendEvent Agrega un evento al historial del monitor | |
func (mon *WebMonitor) AppendEvent(t EventType, msj string) { | |
evt := Event{ | |
Type: t, | |
DateTime: time.Now(), | |
Status: msj, | |
} | |
mon.History = append(mon.History, evt) | |
} | |
// EvalStatus Realiza una prueba y almacena el resultado en el historial del | |
// monitor, además de actualizar los datos este | |
func (mon *WebMonitor) EvalStatus() { | |
startTime := time.Now() | |
mon.Status = false | |
// Consultando la URL del sitio | |
res, err := http.Get(mon.Url) | |
if err != nil { | |
mon.AppendEvent(EvtError, err.Error()) | |
return | |
} | |
respTime := time.Since(startTime).Milliseconds() | |
// Evaluando respuesta | |
if res.StatusCode < 200 || res.StatusCode >= 300 { | |
mon.AppendEvent(EvtError, res.Status) | |
return | |
} | |
defer func() { | |
err := res.Body.Close() | |
if err != nil { | |
log.Printf("ERROR - resp.Body.Close: %v", err) | |
} | |
}() | |
// Leyendo el contenido de la URL | |
content, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
mon.AppendEvent(EvtError, err.Error()) | |
return | |
} | |
// Buscando coincidencias | |
match, err := regexp.Match(mon.Pattern, content) | |
if err != nil { | |
mon.AppendEvent(EvtError, err.Error()) | |
return | |
} | |
// Si no se encuentran coincidencias almacena un evento ERROR y no se | |
// actualizan los datos | |
if !match { | |
mon.AppendEvent(EvtError, "no se encontraron coincidencias") | |
return | |
} | |
// Si no hay fallo, se actualizan los datos | |
mon.AppendEvent(EvtStatus, "sitio disponible") | |
mon.ResponseTime = respTime | |
mon.LastResponse = time.Now() | |
mon.Status = true | |
} | |
// EvalIfTimeExpires evalua si la fecha actual es mayor o igual a la de la | |
// siguiente prueba y la ejecuta si fuera necesario, además de actualizar la | |
// fecha/hora de la siguiente prueba a realizar con los valores | |
// correspondientes si la actual fallara o fuera satisfactoria. | |
func (mon *WebMonitor) EvalIfTimeExpires() { | |
if time.Now().After(mon.NextScan) { | |
mon.NextScan = time.Now().Add(retryInterval) | |
mon.EvalStatus() | |
if !mon.Status { | |
log.Printf( | |
"ALERTA - Sitio '%v' no disponible: %v", | |
mon.Name, | |
mon.History[len(mon.History)-1].Status, | |
) | |
return | |
} | |
log.Printf( | |
"INFO - Sitio '%v' disponible, tiempo de respuesta: %vms", | |
mon.Name, | |
mon.ResponseTime, | |
) | |
mon.NextScan = time.Now().Add(testInterval) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment