dragonfly/cdn/fileserver/fileserver.go

112 lines
2.6 KiB
Go

/*
* Copyright 2020 The Dragonfly Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fileserver
import (
"context"
"fmt"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/pkg/errors"
logger "d7y.io/dragonfly/v2/internal/dflog"
)
const (
maxRetryAttempts = 5
)
// d7yDir is modified from http.Dir, for http.Dir does not treat syscall.EMFILE as we want.
type d7yDir string
func mapDirOpenError(originalErr error, name string) error {
if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
return originalErr
}
parts := strings.Split(name, string(filepath.Separator))
for i := range parts {
if parts[i] == "" {
continue
}
fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
if err != nil {
return originalErr
}
if !fi.IsDir() {
pathError, ok := originalErr.(*fs.PathError)
if ok && pathError.Err == syscall.EMFILE {
return syscall.EMFILE
}
return fs.ErrNotExist
}
}
return originalErr
}
func (d d7yDir) Open(name string) (http.File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
dir := string(d)
if dir == "" {
dir = "."
}
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
f, err := os.Open(fullName)
if err != nil {
mappedErr := mapDirOpenError(err, fullName)
// retry when syscall.EMFILE
for i := 0; mappedErr == syscall.EMFILE && i < maxRetryAttempts; i++ {
time.Sleep(100 * time.Millisecond)
f, err = os.Open(fullName)
if err == nil {
return f, nil
}
mappedErr = mapDirOpenError(err, fullName)
}
return nil, mappedErr
}
return f, nil
}
type Server struct {
*http.Server
}
func New(port int, prefix, uploadPath string) *Server {
return &Server{
&http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: http.StripPrefix(prefix, http.FileServer(d7yDir(uploadPath))),
},
}
}
func (server *Server) Shutdown(ctx context.Context) error {
defer logger.Infof("====stopped file server====")
return server.Server.Shutdown(ctx)
}