| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- package netclient
- import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "sync"
- "time"
- "wartank/pkg/components/scene_net/netstat"
- "wartank/pkg/types"
- )
- /*
- Объект сетевого соединения
- */
- // Ответ после запроса
- type response struct {
- lstString []string
- err error
- }
- // NetClient -- объект сетевого соединения
- type NetClient struct {
- botNet types.ИБотСеть
- conn *http.Client
- stat *netstat.NetStat
- chRes chan *response // Канал ответа от http-клиента
- block sync.Mutex
- }
- // NewNetClient -- возвращает сетевого клиента
- func NewNetClient(botNet types.ИБотСеть) *NetClient {
- сам := &NetClient{
- botNet: botNet,
- conn: botNet.Коннект(),
- stat: netstat.NewNetStat(botNet),
- chRes: make(chan *response, 2),
- }
- return сам
- }
- // Get -- выполняет безопасный GET-запрос в сеть
- func (сам *NetClient) Get(strLink string) (lstString []string, err error) {
- сам.block.Lock()
- defer сам.block.Unlock()
- ctxCancel, fnCancel := context.WithTimeout(сам.botNet.Кнт(), time.Second*10)
- defer fnCancel()
- defer func() { // Возможный перехват паники
- if _panic := recover(); _panic != nil {
- err = fmt.Errorf("NetClient.Get().defer(): перехвачена паника для URL(%v), panic=%v", strLink, _panic)
- lstString = nil
- time.Sleep(time.Millisecond * 250) // Чтобы не насиловать не работающую сеть
- }
- }()
- go сам.get(strLink)
- select {
- case <-ctxCancel.Done(): // Таймаут по ожиданию
- err = fmt.Errorf("NetClient.get(): таймаут ожидания ответа")
- сам.botNet.Отмена()
- return nil, err
- case resp := <-сам.chRes: // Получен ответ
- if resp.err != nil {
- return nil, resp.err
- }
- return resp.lstString, nil
- }
- }
- // Внутренний вызов для сокрытия под общей блокировкой
- func (сам *NetClient) get(strLink string) {
- resp := &response{}
- defer func() {
- if resp.err != nil {
- сам.stat.IncErr()
- }
- }()
- req, err := http.NewRequest("GET", strLink, nil)
- if err != nil {
- resp.err = fmt.Errorf("NetClient.get(): при создании запроса, err=\n\t%w", err)
- return
- }
- req.Header.Set("User-Agent", "Mozilla Firefox 94.1")
- httpResp, err := сам.conn.Do(req)
- if err != nil {
- resp.err = fmt.Errorf("NetClient.get(): при выполнении GET-запроса, err=\n\t%w", err)
- return
- }
- defer сам.closeGetBody(strLink, httpResp, resp)
- if httpResp.StatusCode != http.StatusOK {
- resp.err = fmt.Errorf("NetClient.get(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status)
- return
- }
- binData, err := io.ReadAll(httpResp.Body)
- if err != nil {
- resp.err = fmt.Errorf("NetClient.get(): при чтении тела ответа, err=\n\t%w", err)
- return
- }
- if len(binData) == 0 {
- resp.err = fmt.Errorf("NetClient.get(): пустое тело ответа, err=\n\t%w", err)
- return
- }
- lenData := len(binData) + len(strLink)
- сам.stat.AddByte(lenData)
- lstString := strings.Split(string(binData), "\n")
- if len(lstString) == 0 {
- resp.err = fmt.Errorf("NetClient.get(): lstString is empty")
- return
- }
- resp.lstString = lstString
- }
- // Вызывается по завершению вызова, закрывает тело запроса
- func (сам *NetClient) closeGetBody(strLink string, httpResp *http.Response, resp *response) {
- defer func() {
- if _panic := recover(); _panic != nil {
- // log._rintf("NetClient.closeGetBody(): strLink='%v', panic=%v\n", strLink, _panic)
- сам.botNet.Отмена()
- }
- }()
- err := httpResp.Body.Close()
- if err != nil {
- _err := fmt.Errorf("NetClient.closeGetBody(): ошибка при закрытии запроса URL(%q), err=\n\t%w", strLink, err)
- if resp.err != nil { // Есть и ошибка в закрытии тела запроса и внутренняя
- resp.err = fmt.Errorf("NetClient.closeGetBody(): двойная ошибка при закрытии запроса URL(%q), err=\n\t%w\n\tinternal err=\n\t%w", strLink, _err, resp.err)
- }
- resp.lstString = nil
- сам.stat.IncErr()
- }
- сам.chRes <- resp
- }
|