Compare commits
No commits in common. '4d8b56b0b239177442a0fdec39e16b6b83c64d07' and 'ab326f6e649418e6fec721f3a83ebf07148f2e08' have entirely different histories.
4d8b56b0b2
...
ab326f6e64
10 changed files with 1 additions and 396 deletions
@ -1,5 +1,2 @@ |
|||||||
run: |
run: |
||||||
go run src/main.go -p 5053 -d "127.0.0.1:5059"
|
go run src/main.go
|
||||||
|
|
||||||
build: |
|
||||||
go build -o bin/simple-choose-ad src/main.go
|
|
||||||
|
@ -1,8 +0,0 @@ |
|||||||
# Микросервис для выбора рекламных предложений от партнеров |
|
||||||
|
|
||||||
Запуск |
|
||||||
```shell |
|
||||||
make build |
|
||||||
./bin/simple-choose-ad -p PORT -d "IP:PORT" |
|
||||||
``` |
|
||||||
где `PORT` это порт для входящих запросов, который слушает сервис, а `IP:PORT,IP2:PORT` список рекламных партнеров. |
|
@ -1,72 +0,0 @@ |
|||||||
package clientserver |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"errors" |
|
||||||
"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) |
|
||||||
} |
|
||||||
|
|
||||||
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,145 +0,0 @@ |
|||||||
package clientserver |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"log" |
|
||||||
"math" |
|
||||||
"net/http" |
|
||||||
customtypes "sample-choose-ad/src/custom_types" |
|
||||||
req_types "sample-choose-ad/src/requests_types" |
|
||||||
"sort" |
|
||||||
) |
|
||||||
|
|
||||||
// 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) { |
|
||||||
body, _ := ioutil.ReadAll(r.Body) |
|
||||||
|
|
||||||
var inpReqBody req_types.IncomingRequest |
|
||||||
var err error |
|
||||||
|
|
||||||
if json.Unmarshal(body, &inpReqBody) != nil { |
|
||||||
throwHTTPError("WRONG_SCHEMA", 400, &w) |
|
||||||
return inpReqBody, err |
|
||||||
} |
|
||||||
|
|
||||||
// Check if Id is empty
|
|
||||||
if inpReqBody.Id == nil { |
|
||||||
throwHTTPError("EMPTY_FIELD", 400, &w) |
|
||||||
return inpReqBody, err |
|
||||||
} |
|
||||||
|
|
||||||
// Check if tiles is empty
|
|
||||||
if len(inpReqBody.Tiles) == 0 { |
|
||||||
throwHTTPError("EMPTY_TILES", 400, &w) |
|
||||||
return inpReqBody, err |
|
||||||
} |
|
||||||
|
|
||||||
// ipv4 validation
|
|
||||||
if wrongIPAddresFormat(inpReqBody.Context.Ip) { |
|
||||||
throwHTTPError("WRONG_SCHEMA", 400, &w) |
|
||||||
return inpReqBody, err |
|
||||||
} |
|
||||||
|
|
||||||
return inpReqBody, err |
|
||||||
} |
|
||||||
|
|
||||||
// Request handler with closure (make request for each partner in `[]partners`).
|
|
||||||
func handleRequest(partners []customtypes.PartnersAddress) http.HandlerFunc { |
|
||||||
return func(w http.ResponseWriter, r *http.Request) { |
|
||||||
|
|
||||||
incReq, err := parseAndCheckIncomingRequest(w, r) |
|
||||||
if err != nil { |
|
||||||
log.Println(err) |
|
||||||
} |
|
||||||
|
|
||||||
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", p.Ip, p.Port) |
|
||||||
|
|
||||||
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,14 +0,0 @@ |
|||||||
package clientserver |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"net/http" |
|
||||||
customtypes "sample-choose-ad/src/custom_types" |
|
||||||
) |
|
||||||
|
|
||||||
func StartServer(port string, partners []customtypes.PartnersAddress) { |
|
||||||
|
|
||||||
http.HandleFunc("/placements/request", handleRequest(partners)) |
|
||||||
http.ListenAndServe(fmt.Sprintf(":%v", port), nil) |
|
||||||
|
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
package clientserver |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"net/http" |
|
||||||
"regexp" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// 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) { |
|
||||||
http.Error(*w, err_text, code) |
|
||||||
log.Printf("Error: %d %v\n", code, err_text) |
|
||||||
} |
|
||||||
|
|
||||||
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, _ := strconv.ParseInt(iap[1], 10, 32) |
|
||||||
return ip, port, err |
|
||||||
} |
|
@ -1,6 +0,0 @@ |
|||||||
package customtypes |
|
||||||
|
|
||||||
type PartnersAddress struct { |
|
||||||
Ip string |
|
||||||
Port int64 |
|
||||||
} |
|
@ -1,62 +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.Println(err) |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
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