netclient.go 4.2 KB

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