Compare commits
5 Commits
ab326f6e64
...
4d8b56b0b2
Author | SHA1 | Date |
---|---|---|
Дмитрий | 4d8b56b0b2 | 2 years ago |
Дмитрий | cb8f5fead8 | 2 years ago |
Дмитрий | 88815db466 | 2 years ago |
Дмитрий | fb38eabeec | 2 years ago |
Дмитрий | 707510bae7 | 2 years ago |
10 changed files with 396 additions and 1 deletions
@ -1,2 +1,5 @@ |
|||||||
run: |
run: |
||||||
go run src/main.go
|
go run src/main.go -p 5053 -d "127.0.0.1:5059"
|
||||||
|
|
||||||
|
build: |
||||||
|
go build -o bin/simple-choose-ad src/main.go
|
||||||
|
@ -0,0 +1,8 @@ |
|||||||
|
# Микросервис для выбора рекламных предложений от партнеров |
||||||
|
|
||||||
|
Запуск |
||||||
|
```shell |
||||||
|
make build |
||||||
|
./bin/simple-choose-ad -p PORT -d "IP:PORT" |
||||||
|
``` |
||||||
|
где `PORT` это порт для входящих запросов, который слушает сервис, а `IP:PORT,IP2:PORT` список рекламных партнеров. |
@ -0,0 +1,72 @@ |
|||||||
|
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 |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
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) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
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 |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
package customtypes |
||||||
|
|
||||||
|
type PartnersAddress struct { |
||||||
|
Ip string |
||||||
|
Port int64 |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
/* |
||||||
|
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) |
||||||
|
} |
@ -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"` |
||||||
|
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