package httpfile import ( "encoding/json" "fmt" "log" "net/http" "os" "path/filepath" "strings" "ftp-server/config" ) // Server represents the HTTP file server type Server struct { config *config.Config } // NewServer creates a new HTTP file server func NewServer(cfg *config.Config) *Server { return &Server{config: cfg} } // Start starts the HTTP file server func (s *Server) Start() error { if !s.config.HTTPFile.Enable { return nil } rootDir := s.config.HTTPFile.RootDir if err := os.MkdirAll(rootDir, 0755); err != nil { return fmt.Errorf("failed to create root directory: %v", err) } mux := http.NewServeMux() // Serve HTML page mux.HandleFunc("/", s.handleFileBrowser) // API endpoints mux.HandleFunc("/api/list", s.apiListDir) mux.HandleFunc("/api/download", s.apiDownload) addr := fmt.Sprintf("%s:%d", s.config.HTTPFile.Host, s.config.HTTPFile.Port) log.Printf("HTTP file server listening on http://%s", addr) return http.ListenAndServe(addr, mux) } func (s *Server) handleFileBrowser(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(fileBrowserHTML)) } func (s *Server) apiListDir(w http.ResponseWriter, r *http.Request) { dir := r.URL.Query().Get("path") if dir == "" { dir = "/" } realPath := s.toRealPath(dir) entries, err := os.ReadDir(realPath) if err != nil { http.Error(w, `{"error":"Cannot read directory"}`, http.StatusInternalServerError) return } var files []map[string]interface{} for _, entry := range entries { info, _ := entry.Info() modTime := info.ModTime().Format("2006-01-02 15:04:05") size := info.Size() file := map[string]interface{}{ "name": entry.Name(), "modTime": modTime, "size": formatSize(size), "isDir": entry.IsDir(), } if !entry.IsDir() { file["ext"] = strings.ToLower(filepath.Ext(entry.Name())) } files = append(files, file) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "files": files, }) } func (s *Server) apiDownload(w http.ResponseWriter, r *http.Request) { file := r.URL.Query().Get("file") if file == "" { http.Error(w, "Missing file parameter", http.StatusBadRequest) return } realPath := s.toRealPath(file) if info, err := os.Stat(realPath); err != nil || info.IsDir() { http.Error(w, "File not found", http.StatusNotFound) return } http.ServeFile(w, r, realPath) log.Printf("HTTP download: %s from %s", file, r.RemoteAddr) } func (s *Server) toRealPath(httpPath string) string { rootDir := s.config.HTTPFile.RootDir cleanPath := strings.TrimPrefix(httpPath, "/") return filepath.Join(rootDir, cleanPath) } func formatSize(size int64) string { switch { case size < 1024: return fmt.Sprintf("%d B", size) case size < 1024*1024: return fmt.Sprintf("%.1f KB", float64(size)/1024) case size < 1024*1024*1024: return fmt.Sprintf("%.1f MB", float64(size)/(1024*1024)) default: return fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024)) } } // fileBrowserHTML is the embedded HTML page const fileBrowserHTML = ` HTTP 文件服务器

📁 HTTP 文件服务器

在线浏览、下载文件

名称大小修改时间操作
`