Files
yosoy/server.go
2024-01-27 21:03:09 -07:00

193 lines
5.7 KiB
Go

package main
import (
"encoding/json"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
const PING_DEFAULT_TIMEOUT = 10
const PING_DEFAULT_NETWORK = "tcp"
type response struct {
Host string `json:"host"`
Proto string `json:"proto"`
Method string `json:"method"`
Scheme string `json:"scheme"`
RequestURI string `json:"requestUri"`
URL string `json:"url"`
RemoteAddr string `json:"remoteAddr"`
Counter int `json:"counter"`
Headers map[string][]string `json:"headers"`
Hostname string `json:"hostname"`
EnvVariables []string `json:"envVariables,omitempty"`
Files map[string]string `json:"files,omitempty"`
}
type errorResponse struct {
Error string `json:"error"`
}
type successResponse struct {
Message string `json:"message"`
}
var counter = 0
var hostname = os.Getenv("HOSTNAME")
func preflight(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Expose-Headers", "*")
w.Header().Set("Access-Control-Max-Age", "600")
w.WriteHeader(http.StatusOK)
}
func handler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
showEnvs := os.Getenv("YOSOY_SHOW_ENVS")
showFiles := os.Getenv("YOSOY_SHOW_FILES")
response := &response{}
counter++
response.Counter = counter
remoteAddr := remoteAddrWithoutPort(req)
response.RemoteAddr = remoteAddr
response.RequestURI = req.RequestURI
response.Host = req.Host
response.Proto = req.Proto
response.Method = req.Method
response.Scheme = req.URL.Scheme
response.Headers = req.Header
response.URL = req.URL.String()
response.Hostname = hostname
if strings.ToLower(showEnvs) == "true" || strings.ToLower(showEnvs) == "yes" || strings.ToLower(showEnvs) == "on" || showEnvs == "1" {
response.EnvVariables = os.Environ()
}
if len(showFiles) > 0 {
response.Files = make(map[string]string)
files := strings.Split(showFiles, ",")
for _, file := range files {
bytes, err := os.ReadFile(file)
if err != nil {
log.Printf("Could not read file %v: %v\n", file, err)
continue
}
contents := string(bytes)
response.Files[file] = contents
}
}
w.Header().Add("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func ping(w http.ResponseWriter, req *http.Request) {
// get h, p, n, t parameters from query string
hostname := req.URL.Query().Get("h")
port := req.URL.Query().Get("p")
network := req.URL.Query().Get("n")
timeoutString := req.URL.Query().Get("t")
var timeout int64 = PING_DEFAULT_TIMEOUT
// return HTTP BadRequest when hostname is empty
if hostname == "" {
w.WriteHeader(http.StatusBadRequest)
w.Header().Add("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(&errorResponse{"hostname is empty"})
return
}
// return HTTP BadRequest when port is empty
if port == "" {
w.WriteHeader(http.StatusBadRequest)
w.Header().Add("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(&errorResponse{"port is empty"})
return
}
// check if timeoutString is a valid int64, return HTTP BadRequest when invalid, otherwise set timeout value
if timeoutString != "" {
timeoutInt, err := strconv.ParseInt(timeoutString, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Header().Add("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(&errorResponse{"timeout is invalid"})
return
}
timeout = timeoutInt
}
// if network is empty set default to tcp
if network == "" {
network = PING_DEFAULT_NETWORK
}
// ping the hostname and port
err := pingHost(hostname, port, network, timeout)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Header().Add("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(&errorResponse{err.Error()})
return
}
// return HTTP OK
w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(&successResponse{"ping succeeded"})
}
func pingHost(hostname, port, network string, timeout int64) error {
// create timeoutDuration variable of Duration type using timeout as the value in seconds
timeoutDuration := time.Duration(timeout) * time.Second
// open a socket to hostname and port
conn, err := net.DialTimeout(network, hostname+":"+port, timeoutDuration)
if err != nil {
return err
}
// close the socket
conn.Close()
return nil
}
func remoteAddrWithoutPort(req *http.Request) string {
remoteAddr := req.RemoteAddr
if index := strings.LastIndex(remoteAddr, ":"); index > 0 {
remoteAddr = remoteAddr[0:index]
}
return remoteAddr
}
func main() {
log.Printf("yosoy is up %v\n", hostname)
r := mux.NewRouter()
r.Handle("/favicon.ico", r.NotFoundHandler)
r.HandleFunc("/_/yosoy/ping", ping).Methods(http.MethodGet)
r.PathPrefix("/").HandlerFunc(preflight).Methods(http.MethodOptions)
r.PathPrefix("/").HandlerFunc(handler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete, http.MethodConnect, http.MethodHead, http.MethodTrace)
loggingRouter := handlers.CombinedLoggingHandler(os.Stdout, r)
proxyRouter := handlers.ProxyHeaders(loggingRouter)
recoveryRouter := handlers.RecoveryHandler()(proxyRouter)
http.ListenAndServe(":8080", recoveryRouter)
}