From 9bf98eed984a3d3021a8ac5af05bd68844db0f8b Mon Sep 17 00:00:00 2001
From: jianli-97
Date: Thu, 11 Apr 2024 14:03:37 +0800
Subject: [PATCH] Support directory http service
---
pkg/httpserver/dirserver.go | 266 ++++++++++++++++++++
pkg/httpserver/{server.go => fileserver.go} | 0
2 files changed, 266 insertions(+)
create mode 100644 pkg/httpserver/dirserver.go
rename pkg/httpserver/{server.go => fileserver.go} (100%)
diff --git a/pkg/httpserver/dirserver.go b/pkg/httpserver/dirserver.go
new file mode 100644
index 0000000..0ed518d
--- /dev/null
+++ b/pkg/httpserver/dirserver.go
@@ -0,0 +1,266 @@
+/*
+Copyright 2023 KylinSoft Co., Ltd.
+
+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 httpserver
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/sirupsen/logrus"
+)
+
+// HttpDirService encapsulates the properties of the HTTP dir service
+type HttpDirService struct {
+ Port string
+ DirPath string
+ server *http.Server
+ running bool
+ fileCache map[string][]byte
+ dirCache map[string][]string
+ mutex sync.RWMutex
+}
+
+// NewDirService creates a new instance of dir service
+func NewDirService(port string, dirPath string) *HttpDirService {
+ return &HttpDirService{
+ Port: port,
+ DirPath: dirPath,
+ running: false,
+ fileCache: make(map[string][]byte),
+ dirCache: make(map[string][]string),
+ }
+}
+
+// AddFileToCache add file content to the file cache
+func (fs *HttpDirService) addFileToCache(filePath string) ([]byte, error) {
+ // Check if the file exists in the cache
+ fileContent, ok := fs.fileCache[filePath]
+ if ok {
+ return fileContent, nil
+ }
+
+ // If the file is not in cache, read its contents
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ // Cache the file content
+ fs.fileCache[filePath] = content
+
+ return content, nil
+}
+
+// RemoveFileFromCache removes file content from the file cache
+func (fs *HttpDirService) removeFileFromCache(filePath string) {
+ fs.mutex.Lock()
+ defer fs.mutex.Unlock()
+
+ delete(fs.fileCache, filePath)
+}
+
+func (fs *HttpDirService) addDirToCache(path string) ([]string, error) {
+ // Check if the directory exists in the cache
+ dirContents, ok := fs.dirCache[path]
+ if ok {
+ return dirContents, nil
+ }
+
+ // If not in cache, read the directory contents
+ files, err := os.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+
+ // Cache the directory contents
+ var fileList []string
+ for _, file := range files {
+ fileList = append(fileList, file.Name())
+ }
+ fs.dirCache[path] = fileList
+
+ return fileList, nil
+}
+
+func (fs *HttpDirService) removeDirFromCache(dirPath string) {
+ fs.mutex.Lock()
+ defer fs.mutex.Unlock()
+
+ delete(fs.dirCache, dirPath)
+}
+
+func (fs *HttpDirService) Start() error {
+ // Check if the server is already running
+ if fs.running {
+ return errors.New("server is already running")
+ }
+
+ smux := http.NewServeMux()
+ // Set up HTTP route
+ smux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ err := fs.handleDirRequest(w, r)
+ if err != nil {
+ logrus.Errorf("Error handling dir request: %v", err)
+
+ var statusCode int
+ var errorMessage string
+
+ if os.IsNotExist(err) {
+ statusCode = http.StatusNotFound
+ errorMessage = "File Not Found"
+ } else {
+ statusCode = http.StatusInternalServerError
+ errorMessage = "Internal Server Error"
+ }
+
+ http.Error(w, errorMessage, statusCode)
+ return
+ }
+ })
+
+ fs.server = &http.Server{
+ Addr: ":" + fs.Port,
+ Handler: smux,
+ }
+
+ go func() {
+ logrus.Infof("HTTP server is listening on port %s...\n", fs.Port)
+ fs.running = true
+ if fs.server != nil {
+ if err := fs.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ logrus.Errorf("ListenAndServe(): %v", err)
+ fs.running = false
+ return
+ }
+ } else {
+ logrus.Error("Server is nil. Cannot start.")
+ }
+ }()
+
+ return nil
+}
+
+// handleDirRequest handles dir requests
+func (fs *HttpDirService) handleDirRequest(w http.ResponseWriter, r *http.Request) error {
+ // Get the requested dir path
+ rpath := r.URL.Path
+
+ if !strings.HasPrefix(rpath, fs.DirPath) {
+ // Generate HTML page for 403 Forbidden error
+ w.Header().Set("Content-Type", "text/html")
+ w.WriteHeader(http.StatusForbidden)
+ fmt.Fprintf(w, "403 Forbidden")
+ fmt.Fprintf(w, "Forbidden
")
+ fmt.Fprintf(w, "You don't have permission to access this resource.
")
+ fmt.Fprintf(w, "Additionally, a 403 Forbidden error was encountered while trying to use an ErrorDocument to handle the request.
")
+ fmt.Fprintf(w, "")
+ return nil
+ }
+
+ fs.mutex.RLock()
+ defer fs.mutex.RUnlock()
+
+ // Check if the requested path is a directory
+ rpathInfo, err := os.Stat(rpath)
+ if err != nil {
+ return err
+ }
+
+ if !rpathInfo.IsDir() {
+ // File request
+ fileContent, err := fs.addFileToCache(rpath)
+ if err != nil {
+ return err
+ }
+
+ // Set the content type of the file
+ contentType := http.DetectContentType(fileContent)
+ w.Header().Set("Content-Type", contentType)
+
+ // Write file content directly into the response
+ _, err = w.Write(fileContent)
+ if err != nil {
+ errMsg := "unable to write file to response: " + err.Error()
+ return errors.New(errMsg)
+ }
+
+ return nil
+ }
+
+ // // Directory request
+ if !strings.HasSuffix(rpath, "/") {
+ // Redirect to the directory path with a trailing slash
+ http.Redirect(w, r, rpath+"/", http.StatusMovedPermanently)
+ return nil
+ }
+
+ dirContents, err := fs.addDirToCache(rpath)
+ if err != nil {
+ return err
+ }
+
+ // Generate HTML page with directory contents
+ w.Header().Set("Content-Type", "text/html")
+ fmt.Fprintf(w, "Directory Listing")
+ fmt.Fprintf(w, "Directory Listing for %s
", rpath)
+ fmt.Fprintf(w, "")
+ for _, file := range dirContents {
+ fmt.Fprintf(w, "- %s
", file, file)
+ }
+ fmt.Fprintf(w, "
")
+ fmt.Fprintf(w, "")
+
+ return nil
+}
+
+// Stop method stops the dir service
+func (fs *HttpDirService) Stop() error {
+ if !fs.running || fs.server == nil {
+ logrus.Warn("Server is not running.")
+ return nil
+ }
+
+ fs.mutex.Lock()
+ defer fs.mutex.Unlock()
+
+ logrus.Info("Stopping http server...")
+ if err := fs.server.Close(); err != nil {
+ logrus.Errorf("Error closing server: %v", err)
+ return errors.New("error closing server: " + err.Error())
+ }
+
+ // Clear the file cache
+ if fs.fileCache != nil {
+ for fileName := range fs.fileCache {
+ fs.removeFileFromCache(fileName)
+ }
+ }
+
+ // Clear the dir cache
+ if fs.dirCache != nil {
+ for dirPath := range fs.dirCache {
+ fs.removeDirFromCache(dirPath)
+ }
+ }
+
+ fs.running = false
+ return nil
+}
diff --git a/pkg/httpserver/server.go b/pkg/httpserver/fileserver.go
similarity index 100%
rename from pkg/httpserver/server.go
rename to pkg/httpserver/fileserver.go
--
Gitee