forked from romapres2010/httpserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclientcall.go
More file actions
175 lines (154 loc) · 7.41 KB
/
Copy pathclientcall.go
File metadata and controls
175 lines (154 loc) · 7.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package client
import (
"bytes"
"context"
"crypto/tls"
"io/ioutil"
"net/http"
neturl "net/url"
"time"
myerror "github.com/romapres2010/httpserver/error"
httplog "github.com/romapres2010/httpserver/httpserver/httplog"
"github.com/romapres2010/httpserver/httpserver/httpservice"
mylog "github.com/romapres2010/httpserver/log"
)
// Header represent temporary HTTP header for save
type Header map[string]string
// Call represent а parameters for client requests
type Call struct {
URL string // URL
UserID string // UserID для аутентификации
UserPwd string // UserPwd для аутентификации
CallMethod string // HTTP метод для вызова
CallTimeout int // полный Timeout вызова
ContentType string // тип контента в ответе
InsecureSkipVerify bool // игнорировать проверку сертификатов
ReCallRepeat int // количество попыток вызова при недоступности сервиса - 0 не ограничено
ReCallWaitTimeout int // Timeout между вызовами при недоступности сервиса
HTTPLogger *httplog.Logger // сервис логирования HTTP
}
// Process - represent client common task in process outcoming HTTP request
func (c *Call) Process(ctx context.Context, cnt string, header Header, body []byte) (int, []byte, http.Header, uint64, error) {
var err error
var myerr error
var errM string
var req *http.Request
var resp *http.Response
var responseBuf []byte
// Получить уникальный номер HTTP запроса
reqID := httpservice.GetNextRequestID() // уникальный ID Request
{ // входные проверки
if ctx == nil {
myerr := myerror.New("6030", "Empty context")
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
}
if c.URL == "" {
myerr := myerror.New("6031", "Empty URL for client call")
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
}
} // входные проверки
// Создаем новый запрос c контекстом для возможности отмены
// В тело передаем буфер для передачи в составе запроса
if body != nil {
req, err = http.NewRequestWithContext(ctx, c.CallMethod, c.URL, bytes.NewReader(body))
} else {
req, err = http.NewRequestWithContext(ctx, c.CallMethod, c.URL, nil)
}
if err != nil {
myerr := myerror.WithCause("8012", "Failed to create new HTTP reques: reqID, Method, URL, len(body)", err, reqID, c.CallMethod, c.URL, len(body))
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
}
// запишем заголовок в запрос
if header != nil {
for key, h := range header {
req.Header.Add(key, h)
}
}
// добавим HTTP Basic Authentication
if c.UserID != "" && c.UserPwd != "" {
req.SetBasicAuth(c.UserID, c.UserPwd)
} else {
mylog.PrintfInfoMsg("UserID is null or UserPwd is null. Do call without HTTP Basic Authentication: reqID, Method, URL, len(body)", err, reqID, c.CallMethod, c.URL, len(body))
}
// Скопируем дефолтный транспорт
tr := http.DefaultTransport.(*http.Transport)
// переопределим проверку невалидных сертификатов при использовании SSL
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.InsecureSkipVerify}
// создадим HTTP клиента с переопределенным транспортом
client := &http.Client{
Transport: tr,
Timeout: time.Duration(c.CallTimeout * int(time.Second)), // полный таймаут ожидания
}
// Если сервис не доступен - то цикл с задержкой
tryCount := 1
for {
// проверим, не получен ли сигнал закрытия контекст - останавливаем обработку
select {
case <-ctx.Done():
myerr := myerror.WithCause("6666", "Context was closed: reqID, Method, URL, len(body)", ctx.Err(), err, reqID, c.CallMethod, c.URL, len(body))
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
default:
// логируем исходящий HTTP запрос
if c.HTTPLogger != nil {
_ = c.HTTPLogger.LogHTTPOutRequest(ctx, req, reqID)
}
// выполним запрос
resp, err = client.Do(req)
// логируем исходящий HTTP ответ
if c.HTTPLogger != nil {
_ = c.HTTPLogger.LogHTTPInResponse(ctx, resp, reqID)
}
// обработаем ошибки
if err != nil {
if httperr, ok := err.(*neturl.Error); ok {
// если прервано по Timeout или произошло закрытие контекста
if httperr.Timeout() {
myerr = myerror.WithCause("8013", "Failed to do HTTP request - timeout exceeded: reqID, Method, URL, len(body), CallTimeout", err, reqID, c.CallMethod, c.URL, len(body), c.CallTimeout)
} else {
myerr = myerror.WithCause("8014", "UNKNOWN neturl.Error - Failed to do HTTP request: reqID, Method, URL, len(body)", err, reqID, c.CallMethod, c.URL, len(body))
}
} else {
myerr = myerror.WithCause("8014", "UNKNOWN ERROR - Failed to do HTTP request: reqID, Method, URL, len(body)", err, reqID, c.CallMethod, c.URL, len(body))
}
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
}
// считаем тело ответа
if resp.Body != nil {
responseBuf, err = ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
myerr = myerror.WithCause("8001", "Failed to read HTTP body: reqID, Method, URL", err, reqID, c.CallMethod, c.URL)
mylog.PrintfErrorInfo(myerr)
return resp.StatusCode, nil, resp.Header, reqID, myerr
}
}
mylog.PrintfDebugMsg("Process HTTP call: reqID, Method, URL, len(reqBuf), resp.StatusCode, len(resp.Buf)", reqID, c.CallMethod, c.URL, len(body), resp.Status, len(responseBuf))
// частичный анализ статуса ответа
if resp.StatusCode == http.StatusNotFound {
// Если превышено количество попыток то на выход
if c.ReCallRepeat != 0 && tryCount >= c.ReCallRepeat {
myerr = myerror.New("8016", "URL was not found. Exceeded limit of attemts to call: reqID, Method, URL, ReCallRepeat", reqID, c.CallMethod, c.URL, c.ReCallRepeat)
mylog.PrintfErrorInfo(myerr)
return 0, nil, nil, reqID, myerr
}
// Если URL не доступен - продолжаем в цикле
mylog.PrintfInfoMsg("URL was not found, wait and try again: : reqID, Method, URL, ReCallWaitTimeout, tryCount", reqID, c.CallMethod, c.URL, c.ReCallWaitTimeout, tryCount)
// делаем задержку
time.Sleep(time.Duration(c.ReCallWaitTimeout * int(time.Second)))
tryCount++
break // выходим на начало цикла
} else if resp.StatusCode == http.StatusMethodNotAllowed {
myerr = myerror.New("8017", "URL Method Not Allowed: reqID, Method, URL, HTTP Status", reqID, c.CallMethod, c.URL, resp.Status)
mylog.PrintfInfoMsg(errM)
return http.StatusMethodNotAllowed, nil, resp.Header, reqID, myerr
}
// все успешно
return resp.StatusCode, responseBuf, resp.Header, reqID, nil
}
}
}