19 changed files with 826 additions and 1 deletions
@ -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
|
@ -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) |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
|
||||
} |
@ -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)) |
||||
|
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -0,0 +1,6 @@
|
||||
package customtypes |
||||
|
||||
type PartnersAddress struct { |
||||
Ip string |
||||
Port int64 |
||||
} |
@ -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) |
||||
} |
@ -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"` |
||||
} |
@ -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"` |
||||
} |
@ -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 |
@ -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" |
||||
} |
||||
} |
@ -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" |
||||
} |
||||
} |
@ -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" |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
] |
||||
} |
@ -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)) |
||||
} |
Loading…
Reference in new issue