OnlineLibrary
147 строк · 2.8 Кб
1package connection
2
3import (
4"context"
5"errors"
6"fmt"
7"io"
8"net/http"
9"time"
10
11"OnlineLibrary/internal/config"
12"OnlineLibrary/internal/log"
13)
14
15var (
16ConnectionWasClosed = errors.New("connection was closed")
17)
18
19type Connection struct {
20url string
21ctx context.Context
22resp *http.Response
23lastErr error
24logger *log.Logger
25timer *time.Timer
26reads int64
27contentLength int64
28}
29
30func NewConnection(url string, logger *log.Logger) (*Connection, error) {
31return NewConnectionWithContext(context.TODO(), url, logger)
32}
33
34func NewConnectionWithContext(ctx context.Context, url string, logger *log.Logger) (*Connection, error) {
35c := &Connection{
36url: url,
37ctx: ctx,
38logger: logger,
39}
40
41contentLength, err := c.createResponse(0)
42if err != nil {
43return nil, err
44}
45
46if contentLength <= 0 {
47return nil, fmt.Errorf("content length <= 0")
48}
49
50c.contentLength = contentLength
51return c, nil
52}
53
54func (c *Connection) createResponse(startPos int64) (int64, error) {
55ctx, cancelFunc := context.WithCancel(c.ctx)
56c.timer = time.AfterFunc(config.HTTPTimeout, cancelFunc)
57req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil)
58if err != nil {
59return 0, err
60}
61req.Header.Set("Range", fmt.Sprintf("bytes=%d-", startPos))
62
63var resp *http.Response
64for attempt := 0; attempt < 3; attempt++ {
65resp, err = http.DefaultClient.Do(req)
66if err == nil {
67break
68}
69}
70c.timer.Stop()
71
72if err != nil {
73return 0, err
74}
75
76if resp.StatusCode != http.StatusPartialContent {
77resp.Body.Close()
78return 0, fmt.Errorf("unexpected http status: %v", resp.Status)
79}
80
81if c.resp != nil {
82c.resp.Body.Close()
83}
84
85c.resp = resp
86return c.resp.ContentLength, nil
87}
88
89func (c *Connection) Read(p []byte) (int, error) {
90if c.resp == nil {
91return 0, ConnectionWasClosed
92}
93
94c.timer.Reset(config.HTTPTimeout)
95n, err := c.resp.Body.Read(p)
96c.timer.Stop()
97c.reads += int64(n)
98
99if err != nil && err != context.Canceled && c.reads < c.contentLength {
100c.logger.Warning("Connection recovery: %v", err)
101if _, e := c.createResponse(c.reads); e == nil {
102err = nil
103}
104}
105return n, err
106}
107
108func (c *Connection) Seek(offset int64, whence int) (int64, error) {
109if c.resp == nil {
110return 0, ConnectionWasClosed
111}
112
113var pos int64
114switch whence {
115case io.SeekStart:
116pos = offset
117case io.SeekCurrent:
118pos = c.reads + offset
119case io.SeekEnd:
120pos = c.contentLength + offset
121default:
122panic(fmt.Sprintf("Invalid whence: %v", whence))
123}
124
125if pos > c.contentLength {
126return 0, fmt.Errorf("offset greater than the end of the body")
127}
128
129if pos < 0 {
130pos = 0
131}
132
133if _, err := c.createResponse(pos); err != nil {
134return 0, err
135}
136c.reads = pos
137return c.reads, nil
138}
139
140func (c *Connection) Close() error {
141if c.resp == nil {
142return ConnectionWasClosed
143}
144err := c.resp.Body.Close()
145c.resp = nil
146return err
147}
148