Compare commits

..

No commits in common. '4d8b56b0b239177442a0fdec39e16b6b83c64d07' and 'ab326f6e649418e6fec721f3a83ebf07148f2e08' have entirely different histories.

  1. 5
      Makefile
  2. 8
      readme.md
  3. 72
      src/client_server/client.go
  4. 145
      src/client_server/handlers.go
  5. 14
      src/client_server/server.go
  6. 38
      src/client_server/validators.go
  7. 6
      src/custom_types/partners_address.go
  8. 62
      src/main.go
  9. 31
      src/requests_types/requests_types.go
  10. 16
      src/requests_types/response_type.go

5
Makefile

@ -1,5 +1,2 @@
run:
go run src/main.go -p 5053 -d "127.0.0.1:5059"
build:
go build -o bin/simple-choose-ad src/main.go
go run src/main.go

8
readme.md

@ -1,8 +0,0 @@
# Микросервис для выбора рекламных предложений от партнеров
Запуск
```shell
make build
./bin/simple-choose-ad -p PORT -d "IP:PORT"
```
где `PORT` это порт для входящих запросов, который слушает сервис, а `IP:PORT,IP2:PORT` список рекламных партнеров.

72
src/client_server/client.go

@ -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
}

145
src/client_server/handlers.go

@ -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)
}
}

14
src/client_server/server.go

@ -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)
}

38
src/client_server/validators.go

@ -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
}

6
src/custom_types/partners_address.go

@ -1,6 +0,0 @@
package customtypes
type PartnersAddress struct {
Ip string
Port int64
}

62
src/main.go

@ -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)
}

31
src/requests_types/requests_types.go

@ -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"`
}

16
src/requests_types/response_type.go

@ -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…
Cancel
Save