Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/net/serve.go
1560 views
1
package net
2
3
import (
4
"compress/gzip"
5
"context"
6
"crypto/tls"
7
"fmt"
8
"io"
9
"mime"
10
"mime/multipart"
11
"net/http"
12
"path/filepath"
13
"strconv"
14
"strings"
15
"sync"
16
"time"
17
18
"github.com/alist-org/alist/v3/internal/conf"
19
"github.com/alist-org/alist/v3/internal/model"
20
"github.com/alist-org/alist/v3/pkg/http_range"
21
"github.com/alist-org/alist/v3/pkg/utils"
22
"github.com/pkg/errors"
23
log "github.com/sirupsen/logrus"
24
)
25
26
//this file is inspired by GO_SDK net.http.ServeContent
27
28
//type RangeReadCloser struct {
29
// GetReaderForRange RangeReaderFunc
30
//}
31
32
// ServeHTTP replies to the request using the content in the
33
// provided RangeReadCloser. The main benefit of ServeHTTP over io.Copy
34
// is that it handles Range requests properly, sets the MIME type, and
35
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
36
// and If-Range requests.
37
//
38
// If the response's Content-Type header is not set, ServeHTTP
39
// first tries to deduce the type from name's file extension and,
40
// if that fails, falls back to reading the first block of the content
41
// and passing it to DetectContentType.
42
// The name is otherwise unused; in particular it can be empty and is
43
// never sent in the response.
44
//
45
// If modtime is not the zero time or Unix epoch, ServeHTTP
46
// includes it in a Last-Modified header in the response. If the
47
// request includes an If-Modified-Since header, ServeHTTP uses
48
// modtime to decide whether the content needs to be sent at all.
49
//
50
// The content's RangeReadCloser method must work: ServeHTTP gives a range,
51
// caller will give the reader for that Range.
52
//
53
// If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
54
// ServeHTTP uses it to handle requests using If-Match, If-None-Match, or If-Range.
55
func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time.Time, size int64, RangeReadCloser model.RangeReadCloserIF) error {
56
defer RangeReadCloser.Close()
57
setLastModified(w, modTime)
58
done, rangeReq := checkPreconditions(w, r, modTime)
59
if done {
60
return nil
61
}
62
63
if size < 0 {
64
// since too many functions need file size to work,
65
// will not implement the support of unknown file size here
66
http.Error(w, "negative content size not supported", http.StatusInternalServerError)
67
return nil
68
}
69
70
code := http.StatusOK
71
72
// If Content-Type isn't set, use the file's extension to find it, but
73
// if the Content-Type is unset explicitly, do not sniff the type.
74
contentTypes, haveType := w.Header()["Content-Type"]
75
var contentType string
76
if !haveType {
77
contentType = mime.TypeByExtension(filepath.Ext(name))
78
if contentType == "" {
79
// most modern application can handle the default contentType
80
contentType = "application/octet-stream"
81
}
82
w.Header().Set("Content-Type", contentType)
83
} else if len(contentTypes) > 0 {
84
contentType = contentTypes[0]
85
}
86
87
// handle Content-Range header.
88
sendSize := size
89
var sendContent io.ReadCloser
90
ranges, err := http_range.ParseRange(rangeReq, size)
91
switch {
92
case err == nil:
93
case errors.Is(err, http_range.ErrNoOverlap):
94
if size == 0 {
95
// Some clients add a Range header to all requests to
96
// limit the size of the response. If the file is empty,
97
// ignore the range header and respond with a 200 rather
98
// than a 416.
99
ranges = nil
100
break
101
}
102
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
103
fallthrough
104
default:
105
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
106
return nil
107
}
108
109
if sumRangesSize(ranges) > size {
110
// The total number of bytes in all the ranges is larger than the size of the file
111
// or unknown file size, ignore the range request.
112
ranges = nil
113
}
114
115
// 使用请求的Context
116
// 不然从sendContent读不到数据,即使请求断开CopyBuffer也会一直堵塞
117
ctx := context.WithValue(r.Context(), "request_header", r.Header)
118
switch {
119
case len(ranges) == 0:
120
reader, err := RangeReadCloser.RangeRead(ctx, http_range.Range{Length: -1})
121
if err != nil {
122
code = http.StatusRequestedRangeNotSatisfiable
123
if err == ErrExceedMaxConcurrency {
124
code = http.StatusTooManyRequests
125
}
126
http.Error(w, err.Error(), code)
127
return nil
128
}
129
sendContent = reader
130
case len(ranges) == 1:
131
// RFC 7233, Section 4.1:
132
// "If a single part is being transferred, the server
133
// generating the 206 response MUST generate a
134
// Content-Range header field, describing what range
135
// of the selected representation is enclosed, and a
136
// payload consisting of the range.
137
// ...
138
// A server MUST NOT generate a multipart response to
139
// a request for a single range, since a client that
140
// does not request multiple parts might not support
141
// multipart responses."
142
ra := ranges[0]
143
sendContent, err = RangeReadCloser.RangeRead(ctx, ra)
144
if err != nil {
145
code = http.StatusRequestedRangeNotSatisfiable
146
if err == ErrExceedMaxConcurrency {
147
code = http.StatusTooManyRequests
148
}
149
http.Error(w, err.Error(), code)
150
return nil
151
}
152
sendSize = ra.Length
153
code = http.StatusPartialContent
154
w.Header().Set("Content-Range", ra.ContentRange(size))
155
case len(ranges) > 1:
156
sendSize, err = rangesMIMESize(ranges, contentType, size)
157
if err != nil {
158
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
159
}
160
code = http.StatusPartialContent
161
162
pr, pw := io.Pipe()
163
mw := multipart.NewWriter(pw)
164
w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
165
sendContent = pr
166
defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
167
go func() {
168
for _, ra := range ranges {
169
part, err := mw.CreatePart(ra.MimeHeader(contentType, size))
170
if err != nil {
171
pw.CloseWithError(err)
172
return
173
}
174
reader, err := RangeReadCloser.RangeRead(ctx, ra)
175
if err != nil {
176
pw.CloseWithError(err)
177
return
178
}
179
if _, err := utils.CopyWithBufferN(part, reader, ra.Length); err != nil {
180
pw.CloseWithError(err)
181
return
182
}
183
}
184
185
mw.Close()
186
pw.Close()
187
}()
188
}
189
190
w.Header().Set("Accept-Ranges", "bytes")
191
if w.Header().Get("Content-Encoding") == "" {
192
w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
193
}
194
195
w.WriteHeader(code)
196
197
if r.Method != "HEAD" {
198
written, err := utils.CopyWithBufferN(w, sendContent, sendSize)
199
if err != nil {
200
log.Warnf("ServeHttp error. err: %s ", err)
201
if written != sendSize {
202
log.Warnf("Maybe size incorrect or reader not giving correct/full data, or connection closed before finish. written bytes: %d ,sendSize:%d, ", written, sendSize)
203
}
204
code = http.StatusInternalServerError
205
if err == ErrExceedMaxConcurrency {
206
code = http.StatusTooManyRequests
207
}
208
w.WriteHeader(code)
209
return err
210
}
211
}
212
return nil
213
}
214
func ProcessHeader(origin, override http.Header) http.Header {
215
result := http.Header{}
216
// client header
217
for h, val := range origin {
218
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
219
continue
220
}
221
result[h] = val
222
}
223
// needed header
224
for h, val := range override {
225
result[h] = val
226
}
227
return result
228
}
229
230
// RequestHttp deal with Header properly then send the request
231
func RequestHttp(ctx context.Context, httpMethod string, headerOverride http.Header, URL string) (*http.Response, error) {
232
req, err := http.NewRequestWithContext(ctx, httpMethod, URL, nil)
233
if err != nil {
234
return nil, err
235
}
236
req.Header = headerOverride
237
res, err := HttpClient().Do(req)
238
if err != nil {
239
return nil, err
240
}
241
// TODO clean header with blocklist or passlist
242
res.Header.Del("set-cookie")
243
var reader io.Reader
244
if res.StatusCode >= 400 {
245
// 根据 Content-Encoding 判断 Body 是否压缩
246
switch res.Header.Get("Content-Encoding") {
247
case "gzip":
248
// 使用gzip.NewReader解压缩
249
reader, _ = gzip.NewReader(res.Body)
250
defer reader.(*gzip.Reader).Close()
251
default:
252
// 没有Content-Encoding,直接读取
253
reader = res.Body
254
}
255
all, _ := io.ReadAll(reader)
256
_ = res.Body.Close()
257
msg := string(all)
258
log.Debugln(msg)
259
return res, fmt.Errorf("http request [%s] failure,status: %d response:%s", URL, res.StatusCode, msg)
260
}
261
return res, nil
262
}
263
264
var once sync.Once
265
var httpClient *http.Client
266
267
func HttpClient() *http.Client {
268
once.Do(func() {
269
httpClient = NewHttpClient()
270
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
271
if len(via) >= 10 {
272
return errors.New("stopped after 10 redirects")
273
}
274
req.Header.Del("Referer")
275
return nil
276
}
277
})
278
return httpClient
279
}
280
281
func NewHttpClient() *http.Client {
282
return &http.Client{
283
Timeout: time.Hour * 48,
284
Transport: &http.Transport{
285
Proxy: http.ProxyFromEnvironment,
286
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify},
287
},
288
}
289
}
290
291