OnlineLibrary

Форк
0
147 строк · 2.8 Кб
1
package connection
2

3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
	"io"
8
	"net/http"
9
	"time"
10

11
	"OnlineLibrary/internal/config"
12
	"OnlineLibrary/internal/log"
13
)
14

15
var (
16
	ConnectionWasClosed = errors.New("connection was closed")
17
)
18

19
type Connection struct {
20
	url           string
21
	ctx           context.Context
22
	resp          *http.Response
23
	lastErr       error
24
	logger        *log.Logger
25
	timer         *time.Timer
26
	reads         int64
27
	contentLength int64
28
}
29

30
func NewConnection(url string, logger *log.Logger) (*Connection, error) {
31
	return NewConnectionWithContext(context.TODO(), url, logger)
32
}
33

34
func NewConnectionWithContext(ctx context.Context, url string, logger *log.Logger) (*Connection, error) {
35
	c := &Connection{
36
		url:    url,
37
		ctx:    ctx,
38
		logger: logger,
39
	}
40

41
	contentLength, err := c.createResponse(0)
42
	if err != nil {
43
		return nil, err
44
	}
45

46
	if contentLength <= 0 {
47
		return nil, fmt.Errorf("content length <= 0")
48
	}
49

50
	c.contentLength = contentLength
51
	return c, nil
52
}
53

54
func (c *Connection) createResponse(startPos int64) (int64, error) {
55
	ctx, cancelFunc := context.WithCancel(c.ctx)
56
	c.timer = time.AfterFunc(config.HTTPTimeout, cancelFunc)
57
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil)
58
	if err != nil {
59
		return 0, err
60
	}
61
	req.Header.Set("Range", fmt.Sprintf("bytes=%d-", startPos))
62

63
	var resp *http.Response
64
	for attempt := 0; attempt < 3; attempt++ {
65
		resp, err = http.DefaultClient.Do(req)
66
		if err == nil {
67
			break
68
		}
69
	}
70
	c.timer.Stop()
71

72
	if err != nil {
73
		return 0, err
74
	}
75

76
	if resp.StatusCode != http.StatusPartialContent {
77
		resp.Body.Close()
78
		return 0, fmt.Errorf("unexpected http status: %v", resp.Status)
79
	}
80

81
	if c.resp != nil {
82
		c.resp.Body.Close()
83
	}
84

85
	c.resp = resp
86
	return c.resp.ContentLength, nil
87
}
88

89
func (c *Connection) Read(p []byte) (int, error) {
90
	if c.resp == nil {
91
		return 0, ConnectionWasClosed
92
	}
93

94
	c.timer.Reset(config.HTTPTimeout)
95
	n, err := c.resp.Body.Read(p)
96
	c.timer.Stop()
97
	c.reads += int64(n)
98

99
	if err != nil && err != context.Canceled && c.reads < c.contentLength {
100
		c.logger.Warning("Connection recovery: %v", err)
101
		if _, e := c.createResponse(c.reads); e == nil {
102
			err = nil
103
		}
104
	}
105
	return n, err
106
}
107

108
func (c *Connection) Seek(offset int64, whence int) (int64, error) {
109
	if c.resp == nil {
110
		return 0, ConnectionWasClosed
111
	}
112

113
	var pos int64
114
	switch whence {
115
	case io.SeekStart:
116
		pos = offset
117
	case io.SeekCurrent:
118
		pos = c.reads + offset
119
	case io.SeekEnd:
120
		pos = c.contentLength + offset
121
	default:
122
		panic(fmt.Sprintf("Invalid whence: %v", whence))
123
	}
124

125
	if pos > c.contentLength {
126
		return 0, fmt.Errorf("offset greater than the end of the body")
127
	}
128

129
	if pos < 0 {
130
		pos = 0
131
	}
132

133
	if _, err := c.createResponse(pos); err != nil {
134
		return 0, err
135
	}
136
	c.reads = pos
137
	return c.reads, nil
138
}
139

140
func (c *Connection) Close() error {
141
	if c.resp == nil {
142
		return ConnectionWasClosed
143
	}
144
	err := c.resp.Body.Close()
145
	c.resp = nil
146
	return err
147
}
148

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.