You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
7.8 KiB

package lib
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"git.edtech.vm.prod-6.cloud.el/fabric/models"
"github.com/labstack/gommon/color"
)
const clientHttpTimeout = 60 * time.Second
var reCrLf = regexp.MustCompile(`[\r\n]+`)
// Curl всегде возвращает результат в интерфейс + ошибка (полезно для внешних запросов с неизвестной структурой)
// сериализуем в объект, при передаче ссылки на переменную типа
func Curl(ctx context.Context, method, urlc, bodyJSON string, response interface{}, headers map[string]string, cookies []*http.Cookie) (result interface{}, err error) {
r, _, err := curl_engine(ctx, method, urlc, bodyJSON, response, headers, cookies)
return r, err
}
func CurlCookies(ctx context.Context, method, urlc, bodyJSON string, response interface{}, headers map[string]string, cookies []*http.Cookie) (result interface{}, resp_cookies []*http.Cookie, err error) {
return curl_engine(ctx, method, urlc, bodyJSON, response, headers, cookies)
}
func curl_engine(ctx context.Context, method, urlc, bodyJSON string, response interface{}, headers map[string]string, cookies []*http.Cookie) (result interface{}, resp_cookies []*http.Cookie, err error) {
var mapValues map[string]string
var req *http.Request
var skipTLSVerify = true
client := &http.Client{
Timeout: clientHttpTimeout,
}
dialer := net.Dialer{
Timeout: clientHttpTimeout,
}
//nolint:gosec
tlsConfig := tls.Config{
InsecureSkipVerify: skipTLSVerify, // ignore expired SSL certificates
}
transCfg := &http.Transport{
DialContext: dialer.DialContext,
TLSHandshakeTimeout: clientHttpTimeout / 5,
TLSClientConfig: &tlsConfig,
}
client.Transport = transCfg
if method == "" {
method = "POST"
}
method = strings.Trim(method, " ")
values := url.Values{}
actionType := ""
// если в гете мы передали еще и json (его добавляем в строку запроса)
// только если в запросе не указаны передаваемые параметры
clearUrl := strings.Contains(urlc, "?")
bodyJSON = reCrLf.ReplaceAllString(bodyJSON, "")
if method == "JSONTOGET" && bodyJSON != "" && clearUrl {
actionType = "JSONTOGET"
}
if method == "JSONTOPOST" && bodyJSON != "" {
actionType = "JSONTOPOST"
}
switch actionType {
case "JSONTOGET": // преобразуем параметры в json в строку запроса
err = json.Unmarshal([]byte(bodyJSON), &mapValues)
if err != nil {
return nil, nil, fmt.Errorf("error Unmarshal in Curl, bodyJSON: %s, err: %s", bodyJSON, err)
}
for k, v := range mapValues {
values.Set(k, v)
}
uri, _ := url.Parse(urlc)
uri.RawQuery = values.Encode()
urlc = uri.String()
req, err = http.NewRequest("GET", urlc, strings.NewReader(bodyJSON))
case "JSONTOPOST": // преобразуем параметры в json в тело запроса
err = json.Unmarshal([]byte(bodyJSON), &mapValues)
if err != nil {
return nil, nil, fmt.Errorf("error Unmarshal in Curl, bodyJSON: %s, err: %s", bodyJSON, err)
}
for k, v := range mapValues {
values.Set(k, v)
}
req, err = http.NewRequest("POST", urlc, strings.NewReader(values.Encode()))
req.PostForm = values
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
default:
req, err = http.NewRequest(method, urlc, strings.NewReader(bodyJSON))
}
// дополняем переданными заголовками
httpClientHeaders(ctx, req, headers)
// дополянем куками назначенными для данного запроса
if cookies != nil {
for _, v := range cookies {
req.AddCookie(v)
}
}
resp, err := client.Do(req)
if err != nil {
//fmt.Println("Error request: method:", method, ", url:", urlc, ", bodyJSON:", bodyJSON, "err:", err)
return "", nil, err
} else {
defer resp.Body.Close()
}
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}
responseString := string(responseData)
// возвращаем объект ответа, если передано - в какой объект класть результат
// НА ОШИБКУ НЕ ПРОВЕРЯТЬ!!!!!!
if response != nil {
json.Unmarshal([]byte(responseString), &response)
}
// всегда отдаем в интерфейсе результат (полезно, когда внешние запросы или сериализация на клиенте)
//json.Unmarshal([]byte(responseString), &result)
if resp.StatusCode != 200 {
err = fmt.Errorf("request is not success. request: %s, status: %s, method: %s, req: %+v, response: %s", urlc, resp.Status, method, req, responseString)
}
return responseString, resp.Cookies(), err
}
func AddressProxy(addressProxy, interval string) (port string, err error) {
var res interface{}
fail := color.Red("[Fail]")
urlProxy := ""
// если автоматическая настройка портов
if addressProxy != "" && interval != "" {
if addressProxy[len(addressProxy)-1:] != "/" {
addressProxy = addressProxy + "/"
}
var portDataAPI models.Response
// запрашиваем порт у указанного прокси-сервера
urlProxy = addressProxy + "port?interval=" + interval
res, err = Curl(context.Background(), "GET", urlProxy, "", &portDataAPI, map[string]string{}, nil)
if err != nil {
return "", err
}
port = fmt.Sprint(portDataAPI.Data)
}
if port == "" {
err = fmt.Errorf("Port APP-service is null. Servive not running. (urlProxy: %s, response: %+v)", urlProxy, res)
fmt.Print(fail, err, "\n")
}
return port, err
}
func ClearSlash(url string) (result string) {
if len(url) == 0 {
return ""
}
// удаляем слеш сзади
lastSleshF := url[len(url)-1:]
if lastSleshF == "/" {
url = url[:len(url)-1]
}
// удаляем слеш спереди
lastSleshS := url[0:1]
if lastSleshS == "/" {
url = url[1:len(url)]
}
return url
}
func PortResolver(port string) (status bool) {
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
return false
}
ln.Close()
return true
}
// ProxyPort свободный порт от прокси с проверкой доступности на локальной машине
// если занято - ретраим согласно заданным параметрам
func ProxyPort(addressProxy, interval string, maxCountRetries int, timeRetries time.Duration) (port string, err error) {
port, err = Retrier(maxCountRetries, timeRetries, true, func() (string, error) {
port, err = AddressProxy(addressProxy, interval)
if err != nil {
return "", err
}
status := PortResolver(port)
if status {
return port, nil
}
return "", fmt.Errorf("listen tcp :%s. address already in use", port)
})
return port, err
}
func ReadUserIP(r *http.Request) string {
IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" {
IPAddress = r.Header.Get("X-Forwarded-For")
}
if IPAddress == "" {
IPAddress = r.RemoteAddr
}
return IPAddress
}
// httpClientHeaders устанавливает заголовки реквеста из контекста и headers
func httpClientHeaders(ctx context.Context, req *http.Request, headers map[string]string) {
if req == nil {
return
}
for ctxField, headerField := range models.ProxiedHeaders {
if value := getFieldCtx(ctx, ctxField); value != "" {
req.Header.Add(headerField, value)
}
}
if len(headers) > 0 {
for k, v := range headers {
req.Header.Add(k, v)
}
}
}
func getFieldCtx(ctx context.Context, name string) string {
if ctx == nil {
return ""
}
nameKey := "logger." + name
a := ctx.Value(nameKey)
if a == nil {
return ""
}
requestID, ok := a.(string)
if !ok {
return ""
}
return requestID
}