Skip to content

Instantly share code, notes, and snippets.

@umarquez
Created August 7, 2020 23:58
Show Gist options
  • Save umarquez/5f9757562e46c008a049479fbdfd896b to your computer and use it in GitHub Desktop.
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
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 {}
}
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