diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml index 1bd0b48..a89a383 100644 --- a/.github/workflows/runtests.yml +++ b/.github/workflows/runtests.yml @@ -39,7 +39,7 @@ jobs: # your code must be built into builds/ssp binary - name: Build SSP binary - run: echo Ain\'t no ssp yet + run: go build -o builds/ssp cmd/main.go #- name: Making it executable # run: chmod +x builds/ssp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..49b2564 --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +# port for main server +port := 5053 +moc_server_address := 127.0.0.1:5059,127.0.0.1:5058 + +bold := \033[1m +normal := \033[0m +good := \033[1m\033[0;32m + +help: + @echo "$(bold)Makefile commands$(normal)" + @echo "-----------------" + @echo "$(bold)make build$(normal) : will build the project" + @echo "$(bold)make tests$(normal) : run tests for the project" + @echo "$(bold)make run$(normal) : will run the project" + @echo "" + @echo "$(bold)OS commands$(normal)" + @echo "-----------" + @echo "start server at PORT with 'IP:PORT' list of partners:" + @echo "$(bold)./bin/simple-choose-ad -p PORT -d 'IP:PORT'$(normal)" + +run: + go run cmd/main.go -p $(port) -d "$(moc_server_address)" + +test-ip: + @echo + @go run cmd/main.go -p $(port) -d "$(moc_server_address),localhost:5059" || \ + { echo "\n[+] PASS wrong IP address test"; exit 0; } + +test-port: + @echo + @go run cmd/main.go -p $(port) -d "$(moc_server_address),127.0.0.1:as" || \ + { echo "\n[+] PASS wrong port test"; exit 0; } + +test-port-max: + @echo + @go run cmd/main.go -p $(port) -d "$(moc_server_address),127.0.0.1:65537" || \ + { echo "\n[+] PASS port too big test"; exit 0; } + +test-port-endpoint: + @echo + @go run cmd/main.go -p $(port) -d "127.0.0.1:9001/bid_request" || \ + { echo "\n[+] PASS endpoint with address test"; exit 0; } + +build: + go build -o bin/simple-choose-ad cmd/main.go + +start-moc-server: + @echo "[!] Starting up moc-server on $(moc_server_address) ..." + @go run internal/moc_server.go -l $(moc_server_address) & + + +stop-moc-server: + @echo "[!] Stopping moc-server ..." + @curl -s -o /dev/null "$(moc_server_address)/exit" & + +test-server: + @echo + @echo "Check response from moc-server " + @$(MAKE) start-moc-server + @cd "cmd/client_server/"; \ + go test -v + @$(MAKE) stop-moc-server + +tests: + # @$(MAKE) test-ip + # @$(MAKE) test-port + # @$(MAKE) test-port-max + # @$(MAKE) test-port-endpoint + @$(MAKE) test-server diff --git a/cmd/client_server/client.go b/cmd/client_server/client.go new file mode 100644 index 0000000..b4b78f6 --- /dev/null +++ b/cmd/client_server/client.go @@ -0,0 +1,110 @@ +package clientserver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "math" + "net/http" + customtypes "sample-choose-ad/cmd/custom_types" + req_types "sample-choose-ad/cmd/requests_types" + "sort" + "time" +) + +func sendRequest(url string, body *[]byte) (req_types.SuccesResponse, error) { + var pResp req_types.SuccesResponse + + c := &http.Client{ + Timeout: 200 * time.Millisecond, + } + + resp, err := c.Post(url, "application/json", bytes.NewReader(*body)) + + if err != nil { + eText := fmt.Sprintf("Error: partner %v not responding", url) + return pResp, errors.New(eText) + } + defer resp.Body.Close() + + if resp.StatusCode == 204 { + return pResp, errors.New("No content") + } + + b, _ := ioutil.ReadAll(resp.Body) + + if json.Unmarshal(b, &pResp) != nil { + log.Println(err) + } + + return pResp, nil +} + +// Create requset body based in incoming reqest `ir` and return +// `OutgoingRequest` as []byte from marshaled JSON +func constructPartnersRequestBody(ir *req_types.IncomingRequest) []byte { + var outReqBody req_types.OutgoingRequest + + var imps []req_types.Imp + + // WARN: uint and float multiplication may cause problems + for _, tile := range ir.Tiles { + imps = append(imps, req_types.Imp{ + Id: tile.Id, + Minwidth: tile.Width, + Minheight: uint(math.Floor(float64(tile.Width * uint(tile.Ratio))))}) + } + + outReqBody.Id = *ir.Id + outReqBody.Imp = imps + outReqBody.Context = ir.Context + t, _ := json.Marshal(outReqBody) + return t +} + +// map[imp.id]map[imp.id.price] +type PartnersResponses map[uint]map[float64]req_types.RespImp + +// Make request for each partner and returns +func makePartnersRequests(partners []customtypes.PartnersAddress, ir *req_types.IncomingRequest) { + p_body := constructPartnersRequestBody(ir) + + // Two data structures: + // partnersRespones for getting price with O(1) complexity + // []prices as slice of actual prices + partnersRespones := make(map[uint]map[float64]req_types.RespImp) + prices := make(map[uint][]float64) + + for _, p := range partners { + url := fmt.Sprintf("http://%v:%v/%v", p.Ip, p.Port, PARTNER_ENDPOINT) + + re, err := sendRequest(url, &p_body) + + if err != nil { + log.Println(err) + continue + } + // append only successful responses + for _, r := range re.Imp { + if partnersRespones[r.Id] == nil { + partnersRespones[r.Id] = make(map[float64]req_types.RespImp) + } + partnersRespones[r.Id][r.Price] = r + prices[r.Id] = append(prices[r.Id], r.Price) + } + + } + + if len(partnersRespones) == 0 { + log.Println("Error: no responses from partners.") + return + } + + // Sorting prices, now biggest price at index len-1 + for _, p := range prices { + sort.Float64s(p) + } +} diff --git a/cmd/client_server/handlers.go b/cmd/client_server/handlers.go new file mode 100644 index 0000000..7b6a074 --- /dev/null +++ b/cmd/client_server/handlers.go @@ -0,0 +1,133 @@ +package clientserver + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + customtypes "sample-choose-ad/cmd/custom_types" + req_types "sample-choose-ad/cmd/requests_types" + "sort" +) + +const PARTNER_ENDPOINT = "bid_request" + +// Parsing and checking incoming request. +func parseAndCheckIncomingRequest(w http.ResponseWriter, r *http.Request) (req_types.IncomingRequest, error) { + + var inpReqBody req_types.IncomingRequest + var err error + + //check request method. Only POST valid. + if r.Method == "GET" { + w.WriteHeader(http.StatusBadRequest) + return inpReqBody, errors.New("Wrong request method") + } + + // Check if body in incoming request is empty + body, _ := ioutil.ReadAll(r.Body) + + if json.Unmarshal(body, &inpReqBody) != nil { + log.Println("Unmarshaling problem", string(body)) + return inpReqBody, throwHTTPError("WRONG_SCHEMA", 400, &w) + } + + // Check if Id is empty + if inpReqBody.Id == nil { + return inpReqBody, throwHTTPError("EMPTY_FIELD", 400, &w) + } + + // Check if tiles is empty + if len(inpReqBody.Tiles) == 0 { + return inpReqBody, throwHTTPError("EMPTY_TILES", 400, &w) + } + + // ipv4 validation + if wrongIPAddresFormat(inpReqBody.Context.Ip) { + return inpReqBody, throwHTTPError("WRONG_SCHEMA", 400, &w) + } + + return inpReqBody, err +} + +// Request handler with wrapper (make request for each partner in `[]partners`). +func handleRequest(partners []customtypes.PartnersAddress) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // Parse incoming request and return an error, if it's empty + // or contains wrong/empty fields + incReq, err := parseAndCheckIncomingRequest(w, r) + if err != nil { + log.Println(err) + return + } + + p_body := constructPartnersRequestBody(&incReq) + + // Two data structures: + // partnersRespones for getting price with O(1) complexity + // []prices as slice of actual prices + partnersRespones := make(PartnersResponses) + prices := make(map[uint][]float64) + + for _, p := range partners { + url := fmt.Sprintf("http://%v:%v/%v", p.Ip, p.Port, PARTNER_ENDPOINT) + + re, err := sendRequest(url, &p_body) + + if err != nil { + log.Println(err) + continue + } + // append only successful responses + for _, r := range re.Imp { + if partnersRespones[r.Id] == nil { + partnersRespones[r.Id] = make(map[float64]req_types.RespImp) + } + partnersRespones[r.Id][r.Price] = r + prices[r.Id] = append(prices[r.Id], r.Price) + } + + } + + if len(partnersRespones) == 0 { + log.Println("Error: no responses from partners.") + return + } + + // Sorting prices, now biggest price at index len-1 + for _, p := range prices { + sort.Float64s(p) + } + + var bestOptions []req_types.RespImp + + // for each tile peak best price + for _, tile := range incReq.Tiles { + if len(prices[tile.Id]) == 0 { + log.Println("No imp for tile ", tile.Id) + continue + } + last := len(prices[tile.Id]) - 1 + biggestPrice := prices[tile.Id][last] + bestOptions = append(bestOptions, partnersRespones[tile.Id][biggestPrice]) + } + + response := req_types.SuccesResponse{ + Id: *incReq.Id, + Imp: bestOptions, + } + + respJSON, err := json.Marshal(response) + + if err != nil { + log.Println(err) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(respJSON) + } +} diff --git a/cmd/client_server/handlers_test.go b/cmd/client_server/handlers_test.go new file mode 100644 index 0000000..cf7657c --- /dev/null +++ b/cmd/client_server/handlers_test.go @@ -0,0 +1,86 @@ +package clientserver + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + customtypes "sample-choose-ad/cmd/custom_types" + req_types "sample-choose-ad/cmd/requests_types" + "testing" +) + +func TestGetRequestWithEmptyBody(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/placements/request", nil) + w := httptest.NewRecorder() + _, _ = req, w + a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5050}}) + a(w, req) + res := w.Result() + defer res.Body.Close() + + if res.StatusCode != http.StatusBadRequest { + t.Errorf("Expects code 400, got %v", res.StatusCode) + } + +} + +func TestPostRequestWithEmptyBody(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/placements/request", nil) + w := httptest.NewRecorder() + _, _ = req, w + a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5050}}) + a(w, req) + res := w.Result() + defer res.Body.Close() + + if res.StatusCode != http.StatusBadRequest { + t.Errorf("Expects code 400, got %v", res.StatusCode) + } +} + +func TestPostRequestWithRightBody(t *testing.T) { + body_json := `{ + "id": "123", + "tiles": [ + { + "id": 123, + "width": 122, + "ratio": 1.5 + } + ], + "context": { + "ip": "192.168.1.1", + "user_agent": "curl" + } +}` + + req := httptest.NewRequest(http.MethodPost, "/placements/request", bytes.NewBuffer([]byte(body_json))) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + _, _ = req, w + a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5059}}) + a(w, req) + res := w.Result() + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("expected error to be nil got %v", err) + } + + if res.StatusCode != http.StatusOK { + t.Errorf("Expects code 200, got %v", res.StatusCode) + } + + var d req_types.SuccesResponse + if json.Unmarshal(data, &d) != nil { + t.Log("Error parsing json response") + } + + if d.Imp[0].Title != "bestoption" { + t.Errorf("Wants title `bestoption`, got %v", d.Imp[0].Title) + } + +} diff --git a/cmd/client_server/server.go b/cmd/client_server/server.go new file mode 100644 index 0000000..1a304ae --- /dev/null +++ b/cmd/client_server/server.go @@ -0,0 +1,17 @@ +package clientserver + +import ( + "fmt" + "log" + "net/http" + customtypes "sample-choose-ad/cmd/custom_types" +) + +func StartServer(port string, partners []customtypes.PartnersAddress) { + + http.HandleFunc("/placements/request", handleRequest(partners)) + // http.HandleFunc("/placements/request", decorate(test2)) + + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) + +} diff --git a/cmd/client_server/validators.go b/cmd/client_server/validators.go new file mode 100644 index 0000000..093f7ef --- /dev/null +++ b/cmd/client_server/validators.go @@ -0,0 +1,67 @@ +package clientserver + +import ( + "errors" + "fmt" + "log" + "net/http" + "regexp" + "strconv" + "strings" +) + +const ( + MAX_PORT_NUM = 65535 + MIN_PORT_NUM = 1024 +) + +// Returns false if ipv4 `correct`. +func wrongIPAddresFormat(ipv4 string) bool { + re, err := regexp.Compile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`) + if err != nil { + log.Println(err) + } + return !re.Match([]byte(ipv4)) +} + +func throwHTTPError(err_text string, code int, w *http.ResponseWriter) error { + http.Error(*w, err_text, code) + eText := fmt.Sprintf("Error: %d %vr", code, err_text) + return errors.New(eText) +} + +// Wait string in format "10.10.10.10:8080", where `10.10.10.10` IPv4, +// and `8080` port. If ip or port has wrong format, returns error. +func ParsePartnersAddress(ipAndPort string) (string, int64, error) { + var err error + var ip string + var port int64 + + iap := strings.Split(ipAndPort, ":") + + if len(iap) != 2 { + err = errors.New(fmt.Sprintf("Wrong partners 'ip:port' format: %v", ipAndPort)) + return ip, port, err + } + + ip = iap[0] + if wrongIPAddresFormat(ip) { + err = errors.New(fmt.Sprintf("Wrong ip address format in partner ip: %v", ip)) + } + + port, e := strconv.ParseInt(iap[1], 10, 64) + if e != nil { + err = errors.New(fmt.Sprintf("Wrong port format in partner ip: %v", e)) + return ip, port, err + } + + if port > MAX_PORT_NUM { + err = errors.New(fmt.Sprintf("Wrong port in partner ip: grater than %v", MAX_PORT_NUM)) + } + + if port < MIN_PORT_NUM { + err = errors.New(fmt.Sprintf("Wrong port in partner ip: %v lower than %v", port, MIN_PORT_NUM)) + } + + return ip, port, err +} diff --git a/cmd/client_server/validators_test.go b/cmd/client_server/validators_test.go new file mode 100644 index 0000000..7c5743d --- /dev/null +++ b/cmd/client_server/validators_test.go @@ -0,0 +1,82 @@ +package clientserver + +import ( + "testing" +) + +// Wants: 10.10.10.10:5050 +// Gets : localhost:5050 +func TestIPAddresFormat_DomainName(t *testing.T) { + addres := "localhost:5050" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10: +func TestIPAddresFormat_OnlyIpAndColon(t *testing.T) { + addres := "10.10.10.10:" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10 +func TestIPAddresFormat_OnlyIp(t *testing.T) { + addres := "10.10.10.10" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10:65537 +func TestIPAddresFormat_IncorrectPortValue_TooBig(t *testing.T) { + addres := "10.10.10.10:65537" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10:1000 +func TestIPAddresFormat_IncorrectPortValue_TooSmall(t *testing.T) { + addres := "10.10.10.10:1000" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10:as +func TestIPAddresFormat_IncorrectPortValue_NotANumber(t *testing.T) { + addres := "10.10.10.10:as" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} + +// Wants: 10.10.10.10:5050 +// Gets : 10.10.10.10:5050/bid_request +func TestIPAddresFormat_AddressWithEndpoint(t *testing.T) { + addres := "10.10.10.10:5050/bid_request" + _, _, e := ParsePartnersAddress(addres) + if e == nil { + t.Error("Must be an error, when parsing ", addres) + } + t.Log(e) +} diff --git a/cmd/custom_types/partners_address.go b/cmd/custom_types/partners_address.go new file mode 100644 index 0000000..fac959f --- /dev/null +++ b/cmd/custom_types/partners_address.go @@ -0,0 +1,6 @@ +package customtypes + +type PartnersAddress struct { + Ip string + Port int64 +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ed9c539 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,59 @@ +/* +Usage: + + sample-choose-ad [flags] + +The flags are: + + -p + Listening port + -d + Adversment partners list in format ip_p1:port,ip_p2:port2...ip_p10:port +*/ +package main + +import ( + "flag" + "log" + clientserver "sample-choose-ad/cmd/client_server" + customtypes "sample-choose-ad/cmd/custom_types" + "strings" +) + +func main() { + log.Println("Info: Starting server") + + port := flag.String("p", "", "-p 5050") + addressesList := flag.String("d", "", "-d '10.10.10.10:5050,10.10.10.20:5050'") + flag.Parse() + + if *port == "" { + log.Fatalln("Error: Port number is require!") + } + + if *addressesList == "" { + log.Fatalln("Error: Partners list is require!") + } + + // Parse first 10 ip:port pairs into `[]partners` slise + var partners []customtypes.PartnersAddress + for i, p := range strings.Split(*addressesList, ",") { + + if i == 10 { + log.Println("Warning: Partners count must be less or equal 10!") + return + } + + ip, port, err := clientserver.ParsePartnersAddress(p) + + if err != nil { + log.Fatalln(err) + } + + partners = append(partners, customtypes.PartnersAddress{ + Ip: ip, + Port: port}) + } + + clientserver.StartServer(*port, partners) +} diff --git a/cmd/requests_types/requests_types.go b/cmd/requests_types/requests_types.go new file mode 100644 index 0000000..4574f27 --- /dev/null +++ b/cmd/requests_types/requests_types.go @@ -0,0 +1,31 @@ +package req_types + +type Tile struct { + Id uint `json:"id"` + Width uint `json:"width"` + Ratio float64 `json:"ratio"` +} + +type AdContext struct { + Ip string `json:"ip"` + UserAgent string `json:"user_agent"` +} + +type IncomingRequest struct { + Id *string `json:"id"` + Tiles []Tile `json:"tiles"` + Context AdContext `json:"context"` +} + +// Based in Tile +type Imp struct { + Id uint `json:"id"` // same as related `Tile.Id` + Minwidth uint `json:"minwidth"` // `Tile.Width` + Minheight uint `json:"minheight"` // math.Floor(Tile.Width * Tile.Ratio) +} + +type OutgoingRequest struct { + Id string `json:"id"` + Imp []Imp `json:"imp"` + Context AdContext `json:"context"` +} diff --git a/cmd/requests_types/response_type.go b/cmd/requests_types/response_type.go new file mode 100644 index 0000000..f105fcf --- /dev/null +++ b/cmd/requests_types/response_type.go @@ -0,0 +1,16 @@ +package req_types + +type RespImp struct { + Id uint `json:"id"` + Width uint `json:"width"` + Height uint `json:"height"` + Title string `json:"title"` + Url string `json:"url"` + Price float64 `json:"price,string"` +} + +// Response from ad partners +type SuccesResponse struct { + Id string `json:"id"` + Imp []RespImp `json:"imp"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4dda111 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module sample-choose-ad + +go 1.18 diff --git a/internal/curl_requests/main.sh b/internal/curl_requests/main.sh new file mode 100755 index 0000000..a29447c --- /dev/null +++ b/internal/curl_requests/main.sh @@ -0,0 +1,13 @@ +#!/bin/zsh +P=$(pwd) +curl -X POST http://127.0.0.1:5053/placements/request \ + -H "Content-Type: application/json" \ + -d @simple.json + +curl -X POST http://127.0.0.1:5053/placements/request \ + -H "Content-Type: application/json" \ + -d @skip.json + +curl -X POST http://127.0.0.1:5053/placements/request \ + -H "Content-Type: application/json" \ + -d @no_resp_for_imp.json diff --git a/internal/curl_requests/no_resp_for_imp.json b/internal/curl_requests/no_resp_for_imp.json new file mode 100644 index 0000000..de26b54 --- /dev/null +++ b/internal/curl_requests/no_resp_for_imp.json @@ -0,0 +1,29 @@ +{ + "id": "test", + "tiles": [ + { + "id": 554, + "width": 100, + "ratio": 1.5 + }, + { + "id": 555, + "width": 300, + "ratio": 1.5 + }, + { + "id": 556, + "width": 300, + "ratio": 2.5 + }, + { + "id": 400, + "width": 100, + "ratio": 2.5 + } + ], + "context": { + "ip": "192.168.1.1", + "user_agent": "curl" + } +} diff --git a/internal/curl_requests/simple.json b/internal/curl_requests/simple.json new file mode 100644 index 0000000..ef2c64f --- /dev/null +++ b/internal/curl_requests/simple.json @@ -0,0 +1,19 @@ +{ + "id": "123", + "tiles": [ + { + "id": 123, + "width": 100, + "ratio": 1.5 + }, + { + "id": 124, + "width": 300, + "ratio": 1.5 + } + ], + "context": { + "ip": "192.168.1.1", + "user_agent": "curl" + } +} diff --git a/internal/curl_requests/skip.json b/internal/curl_requests/skip.json new file mode 100644 index 0000000..19af0e3 --- /dev/null +++ b/internal/curl_requests/skip.json @@ -0,0 +1,19 @@ +{ + "id": "skip", + "tiles": [ + { + "id": 123, + "width": 100, + "ratio": 1.5 + }, + { + "id": 124, + "width": 300, + "ratio": 1.5 + } + ], + "context": { + "ip": "192.168.1.1", + "user_agent": "curl" + } +} diff --git a/internal/json/valid_response.json b/internal/json/valid_response.json new file mode 100644 index 0000000..b8433a7 --- /dev/null +++ b/internal/json/valid_response.json @@ -0,0 +1,29 @@ +{ + "id": "123", + "imp": [ + { + "id": 123, + "width": 144, + "height": 122, + "title": "example1", + "url": "example.com", + "price": 123.5 + }, + { + "id": 123, + "width": 155, + "height": 133, + "title": "bestoption", + "url": "bestoption.com", + "price": 143.8 + }, + { + "id": 123, + "width": 155, + "height": 133, + "title": "notabestoption", + "url": "notabestoption.com", + "price": 100.8 + } + ] +} diff --git a/internal/moc_server.go b/internal/moc_server.go new file mode 100644 index 0000000..4063436 --- /dev/null +++ b/internal/moc_server.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "log" + "net/http" + "os" +) + +func main() { + file, err := os.ReadFile("internal/json/valid_response.json") + if err != nil { + log.Fatalln(err) + } + + addr := flag.String("l", "", "-l 127.0.0.1:5059") + flag.Parse() + if *addr == "" { + log.Fatalln("Error: listening address is required!") + } + + http.HandleFunc("/bid_request", func(w http.ResponseWriter, r *http.Request) { + // b, _ := ioutil.ReadAll(r.Body) + // log.Println(string(b)) + w.Header().Add("Content-Type", "application/json") + w.Write(file) + }) + + // endpoint: /exit + // Terminate server with code 0. + http.HandleFunc("/exit", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + os.Exit(0) + }) + + log.Fatal(http.ListenAndServe(*addr, nil)) +}