package proxy import ( "context" "encoding/json" "net" "net/http" "strconv" "strings" "time" ) const proxyAPIVersion = "1.0.0" func startHTTPServer(p *Proxy) { if p == nil || p.config == nil || !p.config.HTTP.Enabled || p.httpServer != nil { return } mux := http.NewServeMux() registerMonitoringRoutes(mux, p) address := net.JoinHostPort(p.config.HTTP.Host, strconv.Itoa(int(p.config.HTTP.Port))) listener, errorValue := net.Listen("tcp", address) if errorValue != nil { return } server := &http.Server{ Handler: mux, } p.httpServer = server go func() { _ = server.Serve(listener) }() } func stopHTTPServer(p *Proxy) { if p == nil || p.httpServer == nil { return } server := p.httpServer p.httpServer = nil shutdownContext, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = server.Shutdown(shutdownContext) } func registerMonitoringRoutes(router *http.ServeMux, proxyValue *Proxy) { if router == nil || proxyValue == nil { return } router.HandleFunc("/1/summary", func(writer http.ResponseWriter, request *http.Request) { if !allowHTTPRequest(writer, request, proxyValue.HTTPConfig()) { return } summary := proxyValue.Summary() response := map[string]interface{}{ "version": proxyAPIVersion, "mode": proxyValue.Mode(), "hashrate": map[string]interface{}{ "total": summary.Hashrate, }, "miners": map[string]uint64{ "now": proxyValue.CurrentMiners(), "max": proxyValue.MaxMiners(), }, "workers": uint64(len(proxyValue.Workers())), "upstreams": func() map[string]interface{} { upstreams := proxyValue.Upstreams() ratio := 0.0 if upstreams.Total > 0 { ratio = float64(proxyValue.CurrentMiners()) / float64(upstreams.Total) } return map[string]interface{}{ "active": upstreams.Active, "sleep": upstreams.Sleep, "error": upstreams.Error, "total": upstreams.Total, "ratio": ratio, } }(), "results": map[string]interface{}{ "accepted": summary.Accepted, "rejected": summary.Rejected, "invalid": summary.Invalid, "expired": summary.Expired, "avg_time": summary.AvgTime, "latency": summary.AvgLatency, "hashes_total": summary.Hashes, "best": summary.TopDiff, }, } writeHTTPJSON(writer, response) }) router.HandleFunc("/1/workers", func(writer http.ResponseWriter, request *http.Request) { if !allowHTTPRequest(writer, request, proxyValue.HTTPConfig()) { return } records := proxyValue.Workers() rows := make([][]interface{}, 0, len(records)) for _, record := range records { rows = append(rows, []interface{}{ record.Name, record.LastIP, record.Connections, record.Accepted, record.Rejected, record.Invalid, record.Hashes, record.LastHashAt.Unix(), record.Hashrate(60), record.Hashrate(600), record.Hashrate(3600), record.Hashrate(43200), record.Hashrate(86400), }) } writeHTTPJSON(writer, map[string]interface{}{ "mode": proxyValue.WorkersMode(), "workers": rows, }) }) router.HandleFunc("/1/miners", func(writer http.ResponseWriter, request *http.Request) { if !allowHTTPRequest(writer, request, proxyValue.HTTPConfig()) { return } miners := proxyValue.Miners() rows := make([][]interface{}, 0, len(miners)) for _, miner := range miners { ip := "" if remote := miner.RemoteAddr(); remote != nil { ip = remote.String() } rows = append(rows, []interface{}{ miner.ID(), ip, miner.TX(), miner.RX(), miner.State(), miner.Diff(), miner.User(), "********", miner.RigID(), miner.Agent(), }) } writeHTTPJSON(writer, map[string]interface{}{ "format": []string{"id", "ip", "tx", "rx", "state", "diff", "user", "password", "rig_id", "agent"}, "miners": rows, }) }) } func allowHTTPRequest(writer http.ResponseWriter, request *http.Request, config HTTPConfig) bool { if request == nil { return false } if config.AccessToken != "" { header := request.Header.Get("Authorization") prefix := "Bearer " if !strings.HasPrefix(header, prefix) || strings.TrimSpace(strings.TrimPrefix(header, prefix)) != config.AccessToken { writer.Header().Set("WWW-Authenticate", "Bearer") http.Error(writer, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return false } } if config.Restricted && request.Method != http.MethodGet { http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return false } return true } func writeHTTPJSON(writer http.ResponseWriter, value interface{}) { writer.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(writer).Encode(value) }