package netclient import ( "context" "fmt" "io" "log" // "log" "net/http" "strings" "sync" "time" "wartank/pkg/components/sectionnet/netstat" "wartank/pkg/types" ) /* Объект сетевого соединения */ // Ответ после запроса type response struct { lstString []string err error } // NetClient -- объект сетевого соединения type NetClient struct { server types.IServer conn *http.Client stat *netstat.NetStat ctx context.Context chRes chan *response // Канал ответа от http-клиента fnCancel func() // Отмена контекста приложения block sync.Mutex } // NewNetClient -- возвращает сетевого клиента func NewNetClient(server types.IServer, bot types.IServBot) *NetClient { sf := &NetClient{ server: server, conn: bot.BotNet().Conn(), stat: netstat.NewNetStat(server), chRes: make(chan *response, 2), ctx: context.Background(), fnCancel: server.CancelApp, } return sf } // Теневая функция на блокировку func (sf *NetClient) Get(strLink string) (lstString []string, err error) { sf.block.Lock() defer sf.block.Unlock() ctxCancel, fnCancel := context.WithTimeout(sf.ctx, time.Second*7) defer func() { fnCancel() if _panic := recover(); _panic != nil { err = fmt.Errorf("NetClient.get(): перехвачена паника для URL(%v), panic=%v", strLink, _panic) lstString = nil time.Sleep(time.Millisecond * 250) // Чтобы не насиловать не работающую сеть } }() go sf.get(strLink) select { case <-ctxCancel.Done(): // Таймаут по ожиданию err = fmt.Errorf("NetClient.get(): таймаут ожидания ответа") sf.fnCancel() return nil, err case resp := <-sf.chRes: // Получен ответ if resp.err != nil { return nil, resp.err } return resp.lstString, nil } } // Внутренний вызов для сокрытия под общей блокировкой func (sf *NetClient) get(strLink string) { req, err := http.NewRequest("GET", strLink, nil) if err != nil { log.Fatalln(err) } req.Header.Set("User-Agent", "Mozilla Firefox 86.0") httpResp, err := sf.conn.Do(req) resp := &response{} if err != nil { resp.err = fmt.Errorf("NetClient.get().fnGet(): при выполнении GET-запроса, err=\n\t%w", err) sf.stat.AddByte(-1) // Фиксируем ошибку return } defer sf.endGet(strLink, httpResp, resp) if httpResp.StatusCode != http.StatusOK { resp.err = fmt.Errorf("NetClient.get().fnGet(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status) sf.stat.AddByte(-1) // Фиксируем ошибку return } binData, err := io.ReadAll(httpResp.Body) if err != nil { resp.err = fmt.Errorf("NetClient.get().fnGet(): при чтении тела ответа, err=\n\t%w", err) sf.stat.AddByte(-1) // Фиксируем ошибку return } if len(binData) == 0 { resp.err = fmt.Errorf("NetClient.get().fnGet(): пустое тело ответа, err=\n\t%w", err) sf.stat.AddByte(-1) // Фиксируем ошибку return } lenData := len(binData) + len(strLink) sf.stat.AddByte(lenData) lstString := strings.Split(string(binData), "\n") if len(lstString) == 0 { resp.err = fmt.Errorf("NetClient.get().fnGet(): lstString is empty") sf.stat.AddByte(-1) // Фиксируем ошибку return } resp.lstString = lstString } // Вызывается по завершению вызова func (sf *NetClient) endGet(strLink string, httpResp *http.Response, resp *response) { defer func() { if _panic := recover(); _panic != nil { // log._rintf("NetClient.sndGet(): strLink='%v', panic=%v\n", strLink, _panic) sf.fnCancel() } }() err := httpResp.Body.Close() if err != nil { switch resp.err == nil { case true: resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%w", strLink, err) case false: resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%v\n\toldErr=\n\t%w", strLink, err, resp.err) } resp.lstString = nil sf.stat.AddByte(-1) // Отметим ошибку закрытия клиента } sf.chRes <- resp }