Compare commits
	
		
			5 Commits 
		
	
	
		
			1b052a8622
			...
			064155f5d5
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 064155f5d5 | 3 years ago | 
|  | 127818008c | 3 years ago | 
|  | f97b9b6691 | 3 years ago | 
|  | a82fc7009b | 3 years ago | 
|  | 5032d675b7 | 3 years ago | 
				 10 changed files with 65 additions and 415 deletions
			
			
		| @ -1,17 +1,39 @@ | ||||
| # port for main server
 | ||||
| port := 5053
 | ||||
| moc_server_address := 127.0.0.1:5059
 | ||||
| 
 | ||||
| run: | ||||
| 	go run src/main.go -p 5053 -d "127.0.0.1:5059"
 | ||||
| 	go run cmd/main.go -p $(port) -d "$(moc_server_address)"
 | ||||
| 
 | ||||
| test-ip: | ||||
| 	go run src/main.go -p 5053 -d "127.0.0.1:5059,localhost:5059"
 | ||||
| 	go run cmd/main.go -p $(port) -d "$(moc_server_address),localhost:5059"
 | ||||
| 
 | ||||
| test-port: | ||||
| 	go run src/main.go -p 5053 -d "127.0.0.1:5059,127.0.0.1:as"
 | ||||
| 	go run cmd/main.go -p $(port) -d "$(moc_server_address),127.0.0.1:as"
 | ||||
| 
 | ||||
| test-port-max: | ||||
| 	go run src/main.go -p 5053 -d "127.0.0.1:5059,127.0.0.1:65537"
 | ||||
| 	go run cmd/main.go -p $(port) -d "$(moc_server_address),127.0.0.1:65537"
 | ||||
| 
 | ||||
| test-port-endpoint: | ||||
| 	go run src/main.go -p 5053 -d "127.0.0.1:9001/bid_request"
 | ||||
| 	go run cmd/main.go -p $(port) -d "127.0.0.1:9001/bid_request"
 | ||||
| 
 | ||||
| build: | ||||
| 	go build -o bin/simple-choose-ad src/main.go
 | ||||
| 	go build -o bin/simple-choose-ad cmd/main.go
 | ||||
| 
 | ||||
| start-moc-server: | ||||
| 	@echo "[!] Starting moc server on $(moc_server_address) ..."
 | ||||
| 	@go run internal/moc_server.go -l $(moc_server_address) &
 | ||||
| 
 | ||||
| 
 | ||||
| stop-moc-server: | ||||
| 	@echo "[!] Killing moc server"
 | ||||
| 	@curl -s -o /dev/null "$(moc_server_address)/exit" &
 | ||||
| 
 | ||||
| test-server: | ||||
| 	@echo "Testing server..."
 | ||||
| 	@$(MAKE) start-moc-server
 | ||||
| 	@cd "cmd/client_server/"; \
 | ||||
| 	go test
 | ||||
| 	@$(MAKE) stop-moc-server
 | ||||
| 
 | ||||
| tests: test-server | ||||
|  | ||||
| @ -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)) | ||||
| } | ||||
| @ -1,75 +0,0 @@ | ||||
| package clientserver | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	req_types "sample-choose-ad/src/requests_types" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func sendRequest(url string, body *io.Reader) (req_types.SuccesResponse, error) { | ||||
| 	var pResp req_types.SuccesResponse | ||||
| 
 | ||||
| 	c := &http.Client{ | ||||
| 		Timeout: 200 * time.Millisecond, | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := c.Post(url, "application/json", *body) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		eText := fmt.Sprintf("%v\n not responding", url) | ||||
| 		return pResp, errors.New(eText) | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode == 204 { | ||||
| 		return pResp, errors.New("No content") | ||||
| 	} | ||||
| 
 | ||||
| 	b, _ := ioutil.ReadAll(resp.Body) | ||||
| 
 | ||||
| 	err = json.Unmarshal(b, &pResp) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 	} | ||||
| 	return pResp, nil | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|    key string | ||||
| 
 | ||||
|    map[price]{Imp} | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| func sendRequest2(url string, body *io.Reader) ([]req_types.RespImp, error) { | ||||
| 	var pResp req_types.SuccesResponse | ||||
| 
 | ||||
| 	c := &http.Client{ | ||||
| 		Timeout: 200 * time.Millisecond, | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := c.Post(url, "application/json", *body) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode == 204 { | ||||
| 		return pResp.Imp, errors.New("No content") | ||||
| 	} | ||||
| 
 | ||||
| 	b, _ := ioutil.ReadAll(resp.Body) | ||||
| 
 | ||||
| 	err = json.Unmarshal(b, &pResp) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return pResp.Imp, nil | ||||
| } | ||||
| @ -1,154 +0,0 @@ | ||||
| package clientserver | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"math" | ||||
| 	"net/http" | ||||
| 	customtypes "sample-choose-ad/src/custom_types" | ||||
| 	req_types "sample-choose-ad/src/requests_types" | ||||
| 	"sort" | ||||
| ) | ||||
| 
 | ||||
| const PARTNER_ENDPOINT = "bid_request" | ||||
| 
 | ||||
| // Create requset body based in incoming reqest `ir` and return
 | ||||
| // `OutgoingRequest` as bytes.Reader from marshaled JSON
 | ||||
| func constructPartnersRequestBody(ir *req_types.IncomingRequest) io.Reader { | ||||
| 	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 bytes.NewReader(t) | ||||
| } | ||||
| 
 | ||||
| // 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" { | ||||
| 		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 { | ||||
| 		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
 | ||||
| 		// var partnersRespones map[float64]req_types.RespImp
 | ||||
| 		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 | ||||
| 			} | ||||
| 			// adding 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 { | ||||
| 			last := len(prices[tile.Id]) - 1 | ||||
| 			biggestPrice := prices[tile.Id][last] | ||||
| 			_ = biggestPrice | ||||
| 			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) | ||||
| 	} | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| package clientserver | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	customtypes "sample-choose-ad/src/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)) | ||||
| 
 | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| package clientserver | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| const MAX_PORT_NUM = 65535 | ||||
| 
 | ||||
| // 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 %v", 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 | ||||
| 	iap := strings.Split(ipAndPort, ":") | ||||
| 
 | ||||
| 	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, 32) | ||||
| 	if e != nil { | ||||
| 		err = errors.New(fmt.Sprintf("Wrong port format in partner ip: %v", e)) | ||||
| 	} | ||||
| 
 | ||||
| 	if port > MAX_PORT_NUM { | ||||
| 		err = errors.New(fmt.Sprintf("Wrong port in partner ip: grater than %v", MAX_PORT_NUM)) | ||||
| 	} | ||||
| 
 | ||||
| 	return ip, port, err | ||||
| } | ||||
| @ -1,6 +0,0 @@ | ||||
| package customtypes | ||||
| 
 | ||||
| type PartnersAddress struct { | ||||
| 	Ip   string | ||||
| 	Port int64 | ||||
| } | ||||
| @ -1,59 +0,0 @@ | ||||
| /* | ||||
| 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/src/client_server" | ||||
| 	customtypes "sample-choose-ad/src/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) | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| 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"` | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package req_types | ||||
| 
 | ||||
| type RespImp struct { | ||||
| 	Id     uint    `json:"id"` | ||||
| 	Width  uint    `json:"width"` | ||||
| 	Height uint    `json:"height"` | ||||
| 	Tile   string  `json:"tile"` | ||||
| 	Url    string  `json:"url"` | ||||
| 	Price  float64 `json:"price"` | ||||
| } | ||||
| 
 | ||||
| // Response from ad partners
 | ||||
| type SuccesResponse struct { | ||||
| 	Id  string    `json:"id"` | ||||
| 	Imp []RespImp `json:"imp"` | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue