diff options
Diffstat (limited to 'arbiter')
-rw-r--r-- | arbiter/Dockerfile | 6 | ||||
-rw-r--r-- | arbiter/arbiter.go | 165 | ||||
-rwxr-xr-x | arbiter/tests.sh | 3 |
3 files changed, 150 insertions, 24 deletions
diff --git a/arbiter/Dockerfile b/arbiter/Dockerfile index fe3fb31..849b345 100644 --- a/arbiter/Dockerfile +++ b/arbiter/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.13 as builder +FROM alpine:3.16 as builder ENV GOPROXY=https://goproxy.io RUN apk update && apk upgrade RUN apk add go git @@ -8,13 +8,13 @@ RUN cd /arbiter && go mod download COPY *.go /arbiter/ RUN cd /arbiter && go build -FROM alpine:3.15 as certbuilder +FROM alpine:3.16 as certbuilder RUN apk add openssl WORKDIR /certs RUN openssl req -nodes -new -x509 -subj="/C=US/ST=Denial/L=springfield/O=Dis/CN=localhost" -keyout server.key -out server.cert # FROM gcr.io/distroless/static-debian10 -FROM alpine:3.13 +FROM alpine:3.16 COPY --from=certbuilder /certs /certs COPY --from=builder /arbiter/arbiter /arbiter/ ENTRYPOINT ["/arbiter/arbiter"] diff --git a/arbiter/arbiter.go b/arbiter/arbiter.go index 2806842..9cb3ad0 100644 --- a/arbiter/arbiter.go +++ b/arbiter/arbiter.go @@ -3,12 +3,16 @@ package main import ( "context" "crypto/tls" + "encoding/json" "errors" "flag" "fmt" + "io/ioutil" "net/http" + "net/url" "os" "os/signal" + "sync" "time" "github.com/go-redis/redis/v8" @@ -28,8 +32,26 @@ var ( const ( SERVER_DEPLOYMENT_TYPE = "SERVER_DEPLOYMENT_TYPE" + coingeckoAPIURLv3 = "https://api.coingecko.com/api/v3" ) +type HttpHandlerFunc func(http.ResponseWriter, *http.Request) + +type HttpHandler struct { + name string + function HttpHandlerFunc +} + +type priceChanStruct struct { + name string + price float64 +} + +type errorChanStruct struct { + hasError bool + err error +} + // OWASP: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html func addSecureHeaders(w *http.ResponseWriter) { (*w).Header().Set("Cache-Control", "no-store") @@ -40,49 +62,152 @@ func addSecureHeaders(w *http.ResponseWriter) { (*w).Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") } +//binance +func getPriceFromBinance(name, unit string, + wg *sync.WaitGroup, + priceChan chan<- priceChanStruct, + errChan chan<- errorChanStruct) { + +} + +//kucoin +func getPriceFromKu(name, uni string, + wg *sync.WaitGroup, + priceChan chan<- priceChanStruct, + errChan chan<- errorChanStruct) { + +} + +func getPriceFromCoinGecko( + name, unit string, + wg *sync.WaitGroup, + priceChan chan<- priceChanStruct, + errChan chan<- errorChanStruct) { + defer wg.Done() + + params := "/simple/price?ids=" + url.QueryEscape(name) + "&" + + "vs_currencies=" + url.QueryEscape(unit) + path := coingeckoAPIURLv3 + params + fmt.Println(path) + resp, err := http.Get(path) + if err != nil { + priceChan <- priceChanStruct{name: name, price: 0.} + errChan <- errorChanStruct{hasError: true, err: err} + log.Error().Err(err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + priceChan <- priceChanStruct{name: name, price: 0.} + errChan <- errorChanStruct{hasError: true, err: err} + log.Error().Err(err) + } + + jsonBody := make(map[string]interface{}) + err = json.Unmarshal(body, &jsonBody) + if err != nil { + priceChan <- priceChanStruct{name: name, price: 0.} + errChan <- errorChanStruct{hasError: true, err: err} + log.Error().Err(err) + } + + price := jsonBody[name].(map[string]interface{})[unit].(float64) + + log.Info().Msg(string(body)) + + priceChan <- priceChanStruct{name: name, price: price} + errChan <- errorChanStruct{hasError: false, err: nil} +} + func arbHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") if r.Method != "GET" { http.Error(w, "Method is not supported.", http.StatusNotFound) } addSecureHeaders(&w) - //binance - //kucoin + + var name string + var unit string + params := r.URL.Query() + for key, value := range params { + switch key { + case "name": + name = value[0] + case "unit": + unit = value[0] + default: + log.Error().Err(errors.New("Got unexpected parameter.")) + } + } + + priceChan := make(chan priceChanStruct, 1) + errChan := make(chan errorChanStruct, 1) + var wg sync.WaitGroup + wg.Add(1) + getPriceFromCoinGecko(name, unit, &wg, priceChan, errChan) + wg.Wait() + + select { + case err := <-errChan: + if err.hasError != false { + log.Error().Err(err.err) + } + default: + log.Error().Err(errors.New("We shouldnt be here")) + } + + var price priceChanStruct + select { + case priceCh := <-priceChan: + price = priceCh + default: + log.Fatal().Err(errors.New("We shouldnt be here")) + } + + json.NewEncoder(w).Encode(map[string]interface{}{ + "name": price.name, + "price": price.price, + "unit": unit, + "err": "", + "isSuccessful": true, + }) } -func startServer(gracefulWait time.Duration) { +func setupLogging() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix +} + +func startServer(gracefulWait time.Duration, + handlers []HttpHandler, + serverDeploymentType string, port string) { r := mux.NewRouter() cfg := &tls.Config{ MinVersion: tls.VersionTLS13, - // CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, - // PreferServerCipherSuites: true, - // CipherSuites: []uint16{ - // tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - // tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, - // tls.TLS_RSA_WITH_AES_256_CBC_SHA, - // }, } srv := &http.Server{ - Addr: "0.0.0.0:" + *flagPort, + Addr: "0.0.0.0:" + port, WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, Handler: r, TLSConfig: cfg, } - r.HandleFunc("/crypto/arb", arbHandler) + + for i := 0; i < len(handlers); i++ { + r.HandleFunc(handlers[i].name, handlers[i].function) + } go func() { var certPath, keyPath string - if os.Getenv(SERVER_DEPLOYMENT_TYPE) == "deployment" { + if os.Getenv(serverDeploymentType) == "deployment" { certPath = "/certs/fullchain1.pem" keyPath = "/certs/privkey1.pem" - } else if os.Getenv(SERVER_DEPLOYMENT_TYPE) == "test" { + } else if os.Getenv(serverDeploymentType) == "test" { certPath = "/certs/server.cert" keyPath = "/certs/server.key" } else { - log.Fatal().Err(errors.New(fmt.Sprintf("unknown deployment kind: %s", SERVER_DEPLOYMENT_TYPE))) + log.Fatal().Err(errors.New(fmt.Sprintf("unknown deployment kind: %s", serverDeploymentType))) } if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil { log.Fatal().Err(err) @@ -99,10 +224,6 @@ func startServer(gracefulWait time.Duration) { log.Info().Msg("gracefully shut down the server") } -func setupLogging() { - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix -} - func main() { var gracefulWait time.Duration flag.DurationVar(&gracefulWait, "gracefulwait", time.Second*15, "the duration to wait during the graceful shutdown") @@ -115,5 +236,7 @@ func main() { defer rdb.Close() setupLogging() - startServer(gracefulWait) + var handlerFuncs = []HttpHandler{{name: "/arb", function: arbHandler}} + + startServer(gracefulWait, handlerFuncs, SERVER_DEPLOYMENT_TYPE, *flagPort) } diff --git a/arbiter/tests.sh b/arbiter/tests.sh new file mode 100755 index 0000000..1fe9792 --- /dev/null +++ b/arbiter/tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +curl -X GET "https://localhost:8009/arb?name=ethereum&unit=usd" |