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