netclient.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package netclient
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "strings"
  9. "sync"
  10. "time"
  11. "wartank/pkg/components/scene_net/netstat"
  12. "wartank/pkg/types"
  13. )
  14. /*
  15. Объект сетевого соединения
  16. */
  17. // Ответ после запроса
  18. type response struct {
  19. lstString []string
  20. err error
  21. }
  22. // NetClient -- объект сетевого соединения
  23. type NetClient struct {
  24. botNet types.ИБотСеть
  25. conn *http.Client
  26. stat *netstat.NetStat
  27. chRes chan *response // Канал ответа от http-клиента
  28. block sync.Mutex
  29. }
  30. // NewNetClient -- возвращает сетевого клиента
  31. func NewNetClient(botNet types.ИБотСеть) *NetClient {
  32. сам := &NetClient{
  33. botNet: botNet,
  34. conn: botNet.Коннект(),
  35. stat: netstat.NewNetStat(botNet),
  36. chRes: make(chan *response, 2),
  37. }
  38. return сам
  39. }
  40. // Get -- выполняет безопасный GET-запрос в сеть
  41. func (сам *NetClient) Get(strLink string) (lstString []string, err error) {
  42. сам.block.Lock()
  43. defer сам.block.Unlock()
  44. // if strLink == "https://wartank.ru/production/Mine" {
  45. log.Printf("NetClient.Get(): link=%v\n", strLink)
  46. // }
  47. ctxCancel, fnCancel := context.WithTimeout(сам.botNet.Кнт(), time.Second*10)
  48. defer fnCancel()
  49. defer func() { // Возможный перехват паники
  50. if _panic := recover(); _panic != nil {
  51. err = fmt.Errorf("NetClient.Get().defer(): перехвачена паника для URL(%v), panic=%v", strLink, _panic)
  52. lstString = nil
  53. time.Sleep(time.Millisecond * 250) // Чтобы не насиловать не работающую сеть
  54. }
  55. }()
  56. go сам.get(strLink)
  57. select {
  58. case <-ctxCancel.Done(): // Таймаут по ожиданию
  59. err = fmt.Errorf("NetClient.get(): таймаут ожидания ответа")
  60. сам.botNet.Отмена()
  61. return nil, err
  62. case resp := <-сам.chRes: // Получен ответ
  63. if resp.err != nil {
  64. return nil, resp.err
  65. }
  66. return resp.lstString, nil
  67. }
  68. }
  69. // Внутренний вызов для сокрытия под общей блокировкой
  70. func (сам *NetClient) get(strLink string) {
  71. resp := &response{}
  72. defer func() {
  73. if resp.err != nil {
  74. сам.stat.IncErr()
  75. }
  76. }()
  77. req, err := http.NewRequest("GET", strLink, nil)
  78. if err != nil {
  79. resp.err = fmt.Errorf("NetClient.get(): при создании запроса, err=\n\t%w", err)
  80. return
  81. }
  82. req.Header.Set("User-Agent", "Mozilla Firefox 94.1")
  83. httpResp, err := сам.conn.Do(req)
  84. if err != nil {
  85. resp.err = fmt.Errorf("NetClient.get(): при выполнении GET-запроса, err=\n\t%w", err)
  86. return
  87. }
  88. defer сам.closeGetBody(strLink, httpResp, resp)
  89. if httpResp.StatusCode != http.StatusOK {
  90. resp.err = fmt.Errorf("NetClient.get(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status)
  91. return
  92. }
  93. binData, err := io.ReadAll(httpResp.Body)
  94. if err != nil {
  95. resp.err = fmt.Errorf("NetClient.get(): при чтении тела ответа, err=\n\t%w", err)
  96. return
  97. }
  98. if len(binData) == 0 {
  99. resp.err = fmt.Errorf("NetClient.get(): пустое тело ответа, err=\n\t%w", err)
  100. return
  101. }
  102. lenData := len(binData) + len(strLink)
  103. сам.stat.AddByte(lenData)
  104. lstString := strings.Split(string(binData), "\n")
  105. if len(lstString) == 0 {
  106. resp.err = fmt.Errorf("NetClient.get(): lstString is empty")
  107. return
  108. }
  109. resp.lstString = lstString
  110. }
  111. // Вызывается по завершению вызова, закрывает тело запроса
  112. func (сам *NetClient) closeGetBody(strLink string, httpResp *http.Response, resp *response) {
  113. defer func() {
  114. if _panic := recover(); _panic != nil {
  115. // log._rintf("NetClient.closeGetBody(): strLink='%v', panic=%v\n", strLink, _panic)
  116. сам.botNet.Отмена()
  117. }
  118. }()
  119. err := httpResp.Body.Close()
  120. if err != nil {
  121. _err := fmt.Errorf("NetClient.closeGetBody(): ошибка при закрытии запроса URL(%q), err=\n\t%w", strLink, err)
  122. if resp.err != nil { // Есть и ошибка в закрытии тела запроса и внутренняя
  123. resp.err = fmt.Errorf("NetClient.closeGetBody(): двойная ошибка при закрытии запроса URL(%q), err=\n\t%w\n\tinternal err=\n\t%w", strLink, _err, resp.err)
  124. }
  125. resp.lstString = nil
  126. сам.stat.IncErr()
  127. }
  128. сам.chRes <- resp
  129. }