From 807faf956645c5af79f8c83d4ca6b1abe9bad84d Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Sun, 2 Jun 2024 16:06:27 +0530 Subject: [PATCH 01/38] Added Static File Handler --- examples/serving-static-files/configs/.env | 2 + examples/serving-static-files/main.go | 10 ++ examples/serving-static-files/main_test.go | 87 ++++++++++++++++++ .../serving-static-files/public/hardhat.jpeg | Bin 0 -> 14779 bytes .../public/industrial.jpeg | Bin 0 -> 11228 bytes .../serving-static-files/public/skross.png | Bin 0 -> 76239 bytes pkg/gofr/gofr.go | 46 ++++++++- pkg/gofr/http/router.go | 5 + 8 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 examples/serving-static-files/configs/.env create mode 100644 examples/serving-static-files/main.go create mode 100644 examples/serving-static-files/main_test.go create mode 100644 examples/serving-static-files/public/hardhat.jpeg create mode 100644 examples/serving-static-files/public/industrial.jpeg create mode 100644 examples/serving-static-files/public/skross.png diff --git a/examples/serving-static-files/configs/.env b/examples/serving-static-files/configs/.env new file mode 100644 index 000000000..c61feb858 --- /dev/null +++ b/examples/serving-static-files/configs/.env @@ -0,0 +1,2 @@ +APP_NAME="serving-static-files" +HTTP_PORT=9000 \ No newline at end of file diff --git a/examples/serving-static-files/main.go b/examples/serving-static-files/main.go new file mode 100644 index 000000000..8bd1b6e91 --- /dev/null +++ b/examples/serving-static-files/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "gofr.dev/pkg/gofr" +) + +func main() { + app := gofr.New() + app.Run() +} diff --git a/examples/serving-static-files/main_test.go b/examples/serving-static-files/main_test.go new file mode 100644 index 000000000..638000d7b --- /dev/null +++ b/examples/serving-static-files/main_test.go @@ -0,0 +1,87 @@ +package main + +import ( + "bytes" + "io" + "net/http" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestFileServer(t *testing.T) { + const host = "http://localhost:9000" + go main() + time.Sleep(time.Second * 3) + tests := []struct { + desc string + method string + path string + body []byte + statusCode int + expectedBody string + expectedBodyLength int + expectedResponseHeaderType string + }{ + { + desc: "check static files", + method: http.MethodGet, + path: "/public/", + statusCode: http.StatusOK, + expectedBody: "
\nhardhat.jpeg\nindustrial.jpeg\nskross.png\n
\n", + }, + { + desc: "check file content hardhat.jpeg", + method: http.MethodGet, + path: "/public/hardhat.jpeg", + statusCode: http.StatusOK, + expectedBodyLength: 14779, + expectedResponseHeaderType: "image/jpeg", + }, + { + desc: "check file content industrial.jpeg", + method: http.MethodGet, + path: "/public/industrial.jpeg", + statusCode: http.StatusOK, + expectedBodyLength: 11228, + expectedResponseHeaderType: "image/jpeg", + }, + { + desc: "check file content skross.png", + method: http.MethodGet, + path: "/public/skross.png", + statusCode: http.StatusOK, + expectedBodyLength: 76239, + expectedResponseHeaderType: "image/png", + }, + { + desc: "check public endpoint", + method: http.MethodGet, + path: "/public", + statusCode: http.StatusNotFound, + }, + } + + for it, tc := range tests { + request, _ := http.NewRequest(tc.method, host+tc.path, bytes.NewBuffer(tc.body)) + request.Header.Set("Content-Type", "application/json") + client := http.Client{} + resp, err := client.Do(request) + bodyBytes, _ := io.ReadAll(resp.Body) + body := string(bodyBytes) + assert.Nil(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) + assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) + if tc.expectedBody != "" { + assert.Equal(t, tc.expectedBody, body, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) + } + if tc.expectedBodyLength != 0 { + contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) + assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) + } + if tc.expectedResponseHeaderType != "" { + assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) + } + } +} diff --git a/examples/serving-static-files/public/hardhat.jpeg b/examples/serving-static-files/public/hardhat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f1d3175e83cfb835b0a7d7bdaf87e176c104931d GIT binary patch literal 14779 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 literal 0 HcmV?d00001 diff --git a/examples/serving-static-files/public/industrial.jpeg b/examples/serving-static-files/public/industrial.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..16e151d3eb6111c0862abadbbcdd11dc7cacbf2f GIT binary patch literal 11228 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 Uj6iNym9?^PSx+Ox0XUiXKj{qcng9R* literal 0 HcmV?d00001 diff --git a/examples/serving-static-files/public/skross.png b/examples/serving-static-files/public/skross.png new file mode 100644 index 0000000000000000000000000000000000000000..eb237370145ee0f290069c5a9baf65f9a69197f7 GIT binary patch literal 76239 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 literal 0 HcmV?d00001 diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 236e5e403..0cd302c58 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "runtime" "strconv" "strings" "sync" @@ -76,7 +77,7 @@ func New() *App { app.metricServer = newMetricServer(port) // HTTP Server - port, err = strconv.Atoi(app.Config.Get("HTTP_PORT")) + port, err = strconv.Atoi(app.Config.GetOrDefault("HTTP_PORT", "6333")) if err != nil || port <= 0 { port = defaultHTTPPort } @@ -93,9 +94,52 @@ func New() *App { app.subscriptionManager = newSubscriptionManager(app.container) + // static fileserver + currentWd, _ := os.Getwd() + checkDirectory := fmt.Sprintf("%s%spublic", currentWd, getFilePathSplitter()) + + if _, err := os.Stat(checkDirectory); !os.IsNotExist(err) { + app.AddStaticFiles("public", checkDirectory) + } + return app } +func getFilePathSplitter() string { + fileSplitter := "" + + switch runtime.GOOS { + case "windows": + fileSplitter = "\\" + case "linux": + case "darwin": + case "android": + case "ios": + fileSplitter = "/" + } + return fileSplitter +} + +func (a *App) AddStaticFiles(endpoint, filePath string) { + a.httpRegistered = true + dupFilePath := "" + fileSplitter := getFilePathSplitter() + if filePath[:2] == "./" { + dupFilePath, _ = os.Getwd() + dupFilePath += fileSplitter + filePath + fileSplitter + } else { + dupFilePath = filePath + } + if endpoint[0] != '/' { + endpoint = "/" + endpoint + } + if _, err := os.Stat(dupFilePath); err == nil { + a.httpServer.router.AddStaticFiles(endpoint, dupFilePath) + } else { + a.container.Logger.Errorf("Couldn't register %s static endpoint", endpoint) + } +} + // NewCMD creates a command-line application. func NewCMD() *App { app := &App{} diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index b60700be1..73ed386b4 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -38,6 +38,11 @@ func (rou *Router) Add(method, pattern string, handler http.Handler) { rou.Router.NewRoute().Methods(method).Path(pattern).Handler(h) } +func (rou *Router) AddStaticFiles(endpoint, directory string) { + fileServer := http.FileServer(http.Dir(directory)) + rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, fileServer)) +} + // UseMiddleware registers middlewares to the router. func (rou *Router) UseMiddleware(mws ...Middleware) { middlewares := make([]mux.MiddlewareFunc, 0, len(mws)) From b449cb43fc1c24b2b993177010c536d170644dae Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Mon, 3 Jun 2024 00:50:03 +0530 Subject: [PATCH 02/38] changes to filepath and gofr file --- pkg/gofr/gofr.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 0cd302c58..f0a967a5c 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" "os" - "runtime" + "path/filepath" "strconv" "strings" "sync" @@ -77,7 +77,7 @@ func New() *App { app.metricServer = newMetricServer(port) // HTTP Server - port, err = strconv.Atoi(app.Config.GetOrDefault("HTTP_PORT", "6333")) + port, err = strconv.Atoi(app.Config.Get("HTTP_PORT")) if err != nil || port <= 0 { port = defaultHTTPPort } @@ -96,7 +96,7 @@ func New() *App { // static fileserver currentWd, _ := os.Getwd() - checkDirectory := fmt.Sprintf("%s%spublic", currentWd, getFilePathSplitter()) + checkDirectory := filepath.Join(currentWd, "public") if _, err := os.Stat(checkDirectory); !os.IsNotExist(err) { app.AddStaticFiles("public", checkDirectory) @@ -105,28 +105,12 @@ func New() *App { return app } -func getFilePathSplitter() string { - fileSplitter := "" - - switch runtime.GOOS { - case "windows": - fileSplitter = "\\" - case "linux": - case "darwin": - case "android": - case "ios": - fileSplitter = "/" - } - return fileSplitter -} - func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true dupFilePath := "" - fileSplitter := getFilePathSplitter() if filePath[:2] == "./" { dupFilePath, _ = os.Getwd() - dupFilePath += fileSplitter + filePath + fileSplitter + dupFilePath = filepath.Join(dupFilePath, filePath) } else { dupFilePath = filePath } From 7c136e46380b083f2c54d8caeea93940574ad2eb Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Tue, 4 Jun 2024 01:21:24 +0530 Subject: [PATCH 03/38] Added documentation and removed the example with small edits --- .../serving-static-files/page.md | 76 +++++++++++++++ examples/serving-static-files/configs/.env | 2 - examples/serving-static-files/main.go | 10 -- examples/serving-static-files/main_test.go | 87 ------------------ .../serving-static-files/public/hardhat.jpeg | Bin 14779 -> 0 bytes .../public/industrial.jpeg | Bin 11228 -> 0 bytes .../serving-static-files/public/skross.png | Bin 76239 -> 0 bytes pkg/gofr/gofr.go | 14 +-- pkg/gofr/http/router.go | 2 + 9 files changed, 85 insertions(+), 106 deletions(-) create mode 100644 docs/advanced-guide/serving-static-files/page.md delete mode 100644 examples/serving-static-files/configs/.env delete mode 100644 examples/serving-static-files/main.go delete mode 100644 examples/serving-static-files/main_test.go delete mode 100644 examples/serving-static-files/public/hardhat.jpeg delete mode 100644 examples/serving-static-files/public/industrial.jpeg delete mode 100644 examples/serving-static-files/public/skross.png diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md new file mode 100644 index 000000000..76270495b --- /dev/null +++ b/docs/advanced-guide/serving-static-files/page.md @@ -0,0 +1,76 @@ +# Serving Static Files using GoFr + +Often times we require to serve static content be it a default profile image or a static website. We want to have a mechanism to serve those content without having a hassel of implementing it from scratch. + +GoFr provides a default mechanism where if a public folder is available in the directory of the application, it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. + +Example Project folder utilizing public endpoint: + +``` +project_folder +| +|---config +| .env +|---public +| .jpeg +| .png +| .jpeg +| main.go +| main_test.go +``` + +main.go code: + +```go +package main + +import "gofr.dev/pkg/gofr" + +func main(){ + app := gofr.New() + app.Run() +} + +``` + +Additionally if we want to serve additional static endpoints, we have a open function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. + +Providing an example below along with File System Example: + +``` +project_folder +| +|---config +| .env +|---public +| .jpeg +| .png +| .jpeg +|---static +| |---css +| | main.css +| |---js +| | main.js +| | index.html +| main.go +| main_test.go +``` + + +main.go file: + +```go + +package main + +import "gofr.dev/pkg/gofr" + +func main(){ + app := gofr.New() + app.AddStaticFiles("static","./static") + app.Run() +} + +``` + +In the above example, both endpoints `/public` and `/static` are available for the app to render the static content. \ No newline at end of file diff --git a/examples/serving-static-files/configs/.env b/examples/serving-static-files/configs/.env deleted file mode 100644 index c61feb858..000000000 --- a/examples/serving-static-files/configs/.env +++ /dev/null @@ -1,2 +0,0 @@ -APP_NAME="serving-static-files" -HTTP_PORT=9000 \ No newline at end of file diff --git a/examples/serving-static-files/main.go b/examples/serving-static-files/main.go deleted file mode 100644 index 8bd1b6e91..000000000 --- a/examples/serving-static-files/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "gofr.dev/pkg/gofr" -) - -func main() { - app := gofr.New() - app.Run() -} diff --git a/examples/serving-static-files/main_test.go b/examples/serving-static-files/main_test.go deleted file mode 100644 index 638000d7b..000000000 --- a/examples/serving-static-files/main_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "bytes" - "io" - "net/http" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestFileServer(t *testing.T) { - const host = "http://localhost:9000" - go main() - time.Sleep(time.Second * 3) - tests := []struct { - desc string - method string - path string - body []byte - statusCode int - expectedBody string - expectedBodyLength int - expectedResponseHeaderType string - }{ - { - desc: "check static files", - method: http.MethodGet, - path: "/public/", - statusCode: http.StatusOK, - expectedBody: "
\nhardhat.jpeg\nindustrial.jpeg\nskross.png\n
\n", - }, - { - desc: "check file content hardhat.jpeg", - method: http.MethodGet, - path: "/public/hardhat.jpeg", - statusCode: http.StatusOK, - expectedBodyLength: 14779, - expectedResponseHeaderType: "image/jpeg", - }, - { - desc: "check file content industrial.jpeg", - method: http.MethodGet, - path: "/public/industrial.jpeg", - statusCode: http.StatusOK, - expectedBodyLength: 11228, - expectedResponseHeaderType: "image/jpeg", - }, - { - desc: "check file content skross.png", - method: http.MethodGet, - path: "/public/skross.png", - statusCode: http.StatusOK, - expectedBodyLength: 76239, - expectedResponseHeaderType: "image/png", - }, - { - desc: "check public endpoint", - method: http.MethodGet, - path: "/public", - statusCode: http.StatusNotFound, - }, - } - - for it, tc := range tests { - request, _ := http.NewRequest(tc.method, host+tc.path, bytes.NewBuffer(tc.body)) - request.Header.Set("Content-Type", "application/json") - client := http.Client{} - resp, err := client.Do(request) - bodyBytes, _ := io.ReadAll(resp.Body) - body := string(bodyBytes) - assert.Nil(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) - assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) - if tc.expectedBody != "" { - assert.Equal(t, tc.expectedBody, body, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) - } - if tc.expectedBodyLength != 0 { - contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) - assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) - } - if tc.expectedResponseHeaderType != "" { - assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) - } - } -} diff --git a/examples/serving-static-files/public/hardhat.jpeg b/examples/serving-static-files/public/hardhat.jpeg deleted file mode 100644 index f1d3175e83cfb835b0a7d7bdaf87e176c104931d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14779 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 diff --git a/examples/serving-static-files/public/industrial.jpeg b/examples/serving-static-files/public/industrial.jpeg deleted file mode 100644 index 16e151d3eb6111c0862abadbbcdd11dc7cacbf2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11228 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 Uj6iNym9?^PSx+Ox0XUiXKj{qcng9R* diff --git a/examples/serving-static-files/public/skross.png b/examples/serving-static-files/public/skross.png deleted file mode 100644 index eb237370145ee0f290069c5a9baf65f9a69197f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76239 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 diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index f0a967a5c..536037009 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -108,20 +108,20 @@ func New() *App { func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true dupFilePath := "" - if filePath[:2] == "./" { + if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() dupFilePath = filepath.Join(dupFilePath, filePath) } else { dupFilePath = filePath } - if endpoint[0] != '/' { - endpoint = "/" + endpoint - } - if _, err := os.Stat(dupFilePath); err == nil { - a.httpServer.router.AddStaticFiles(endpoint, dupFilePath) - } else { + + endpoint = "/" + strings.TrimPrefix(endpoint, "/") + + if _, err := os.Stat(dupFilePath); err != nil { a.container.Logger.Errorf("Couldn't register %s static endpoint", endpoint) + return } + a.httpServer.router.AddStaticFiles(endpoint, dupFilePath) } // NewCMD creates a command-line application. diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 73ed386b4..9aa4b2e76 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -15,6 +15,8 @@ type Router struct { mux.Router } +type Config func(r *Router) + type Middleware func(handler http.Handler) http.Handler // NewRouter creates a new Router instance. From 25c7cc214099bc52a608b57da53c9881df3bbfe9 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Tue, 4 Jun 2024 15:26:44 +0530 Subject: [PATCH 04/38] Made changes to Doc file and removed config line --- docs/advanced-guide/serving-static-files/page.md | 4 ++-- pkg/gofr/http/router.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index 76270495b..42920e35e 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -1,6 +1,6 @@ # Serving Static Files using GoFr -Often times we require to serve static content be it a default profile image or a static website. We want to have a mechanism to serve those content without having a hassel of implementing it from scratch. +Often, we require to serve static content be it a default profile image or a static website. We want to have a mechanism to serve those content without having a hassel of implementing it from scratch. GoFr provides a default mechanism where if a public folder is available in the directory of the application, it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. @@ -33,7 +33,7 @@ func main(){ ``` -Additionally if we want to serve additional static endpoints, we have a open function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. +Additionally,if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. Providing an example below along with File System Example: diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 9aa4b2e76..73ed386b4 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -15,8 +15,6 @@ type Router struct { mux.Router } -type Config func(r *Router) - type Middleware func(handler http.Handler) http.Handler // NewRouter creates a new Router instance. From 6e5a7be5162fac3f4850a05bbe10c840040f6266 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Tue, 4 Jun 2024 17:13:48 +0530 Subject: [PATCH 05/38] typo fix --- docs/advanced-guide/serving-static-files/page.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index 42920e35e..c91ba12d1 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -33,7 +33,7 @@ func main(){ ``` -Additionally,if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. +Additionally, if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. Providing an example below along with File System Example: From fbbb2c689b2df2fba4ff1e38dac7f6d2b1447c0d Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 6 Jun 2024 19:23:47 +0530 Subject: [PATCH 06/38] Added test for static handling --- pkg/gofr/gofr_test.go | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 0a38c871c..f3445bf15 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -1,6 +1,7 @@ package gofr import ( + "bytes" "context" "encoding/json" "fmt" @@ -504,3 +505,114 @@ func Test_AddCronJob_Success(t *testing.T) { assert.Truef(t, pass, "unable to add cron job to cron table") } + +func TestStaticHandler(t *testing.T) { + // Application Generation + app := New() + + //Generate a public directory endpoint + directory := "./public" + + if _, err := os.Stat(directory); err != nil { + err := os.Mkdir("./public", 0755) + if err != nil { + t.Errorf("Couldn't create a public directory, error: %s", err) + } + } + + app.AddStaticFiles("/public", directory) + + // Generate some files for testing + svgFileContent1 := []byte("") + svgFileContent2 := []byte("") + file1, errFile := os.Create("./public/shopping.svg") + if errFile != nil { + t.Error("Couldn't create shopping.svg file") + } + file2, errFile := os.Create("./public/meeting.svg") + if errFile != nil { + t.Error("Couldn't create meeting.svg file") + } + svgFile1ContentSize, _ := file1.Write(svgFileContent1) + svgFile2ContentSize, _ := file2.Write(svgFileContent2) + + defer func() { + errFolder := os.RemoveAll("./public") + if errFolder != nil { + t.Error("Couldn't remove public folder") + } + }() + + // Run the Application and compare the fileType and fileSize + app.httpRegistered = true + app.httpServer.port = 8002 + + go app.Run() + time.Sleep(1 * time.Second) + + host := "http://localhost:8002" + + tests := []struct { + desc string + method string + path string + body []byte + statusCode int + expectedBody string + expectedBodyLength int + expectedResponseHeaderType string + }{ + { + desc: "check static files", + method: http.MethodGet, + path: "/public/", + statusCode: http.StatusOK, + expectedBody: "meeting.svg\n", + }, + { + desc: "check file content hardhat.jpeg", + method: http.MethodGet, + path: "/public/meeting.svg", + statusCode: http.StatusOK, + expectedBodyLength: svgFile2ContentSize, + expectedResponseHeaderType: "image/svg+xml", + }, + { + desc: "check file content industrial.jpeg", + method: http.MethodGet, + path: "/public/shopping.svg", + statusCode: http.StatusOK, + expectedBodyLength: svgFile1ContentSize, + expectedResponseHeaderType: "image/svg+xml", + }, + { + desc: "check public endpoint", + method: http.MethodGet, + path: "/public", + statusCode: http.StatusNotFound, + }, + } + + for it, tc := range tests { + request, _ := http.NewRequest(tc.method, host+tc.path, bytes.NewBuffer(tc.body)) + request.Header.Set("Content-Type", "application/json") + client := http.Client{} + resp, err := client.Do(request) + bodyBytes, _ := io.ReadAll(resp.Body) + defer resp.Body.Close() + body := string(bodyBytes) + assert.Nil(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) + assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) + if tc.expectedBody != "" { + assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) + } + if tc.expectedBodyLength != 0 { + contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) + assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) + } + if tc.expectedResponseHeaderType != "" { + assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) + } + } + +} From 761e814adc2d2721912a8df54f523d32e8de1c10 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 6 Jun 2024 19:30:42 +0530 Subject: [PATCH 07/38] minor changes --- .gitignore | 3 +++ pkg/gofr/gofr_test.go | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ab2b93a56..6b4ea8e9c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ vendor/ # IDE Cache .vscode .DS_Store + +# Public Folder +pkg/gofr/public diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index f3445bf15..62e6b4991 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -507,9 +507,6 @@ func Test_AddCronJob_Success(t *testing.T) { } func TestStaticHandler(t *testing.T) { - // Application Generation - app := New() - //Generate a public directory endpoint directory := "./public" @@ -519,8 +516,8 @@ func TestStaticHandler(t *testing.T) { t.Errorf("Couldn't create a public directory, error: %s", err) } } - - app.AddStaticFiles("/public", directory) + // Application Generation + app := New() // Generate some files for testing svgFileContent1 := []byte("") From b7605e6c063835233dbf9509f79eee5285399a19 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 6 Jun 2024 19:38:36 +0530 Subject: [PATCH 08/38] Changes --- pkg/gofr/gofr_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 62e6b4991..bd56610cc 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -533,12 +533,12 @@ func TestStaticHandler(t *testing.T) { svgFile1ContentSize, _ := file1.Write(svgFileContent1) svgFile2ContentSize, _ := file2.Write(svgFileContent2) - defer func() { + t.Cleanup(func() { errFolder := os.RemoveAll("./public") if errFolder != nil { t.Error("Couldn't remove public folder") } - }() + }) // Run the Application and compare the fileType and fileSize app.httpRegistered = true @@ -598,7 +598,7 @@ func TestStaticHandler(t *testing.T) { bodyBytes, _ := io.ReadAll(resp.Body) defer resp.Body.Close() body := string(bodyBytes) - assert.Nil(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) + assert.NoError(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) if tc.expectedBody != "" { assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) From b7f7cbf3b73a6f0a7ca9dece16a73d8475c17e60 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Fri, 7 Jun 2024 18:07:59 +0530 Subject: [PATCH 09/38] made changes --- .gitignore | 3 - .../serving-static-files/page.md | 6 +- pkg/gofr/gofr_test.go | 66 ++++++++----------- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 6b4ea8e9c..ab2b93a56 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,3 @@ vendor/ # IDE Cache .vscode .DS_Store - -# Public Folder -pkg/gofr/public diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index c91ba12d1..e5ce1227a 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -1,12 +1,12 @@ # Serving Static Files using GoFr -Often, we require to serve static content be it a default profile image or a static website. We want to have a mechanism to serve those content without having a hassel of implementing it from scratch. +Often, we are required to serve static content be it a default profile image or a static website. We want to have a mechanism to serve that content without the hassle of implementing it from scratch. GoFr provides a default mechanism where if a public folder is available in the directory of the application, it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. Example Project folder utilizing public endpoint: -``` +```dotenv project_folder | |---config @@ -37,7 +37,7 @@ Additionally, if we want to serve more static endpoints, we have a dedicated fun Providing an example below along with File System Example: -``` +```dotenv project_folder | |---config diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index bd56610cc..ddac0dcab 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -507,7 +507,7 @@ func Test_AddCronJob_Success(t *testing.T) { } func TestStaticHandler(t *testing.T) { - //Generate a public directory endpoint + // Generate a public directory endpoint directory := "./public" if _, err := os.Stat(directory); err != nil { @@ -520,34 +520,31 @@ func TestStaticHandler(t *testing.T) { app := New() // Generate some files for testing - svgFileContent1 := []byte("") - svgFileContent2 := []byte("") - file1, errFile := os.Create("./public/shopping.svg") - if errFile != nil { - t.Error("Couldn't create shopping.svg file") - } - file2, errFile := os.Create("./public/meeting.svg") + htmlContent := []byte("Test Static File

Testing Static File

") + + file1, errFile := os.Create("./public/index.html") + if errFile != nil { - t.Error("Couldn't create meeting.svg file") + t.Error("Couldn't create index.html file") } - svgFile1ContentSize, _ := file1.Write(svgFileContent1) - svgFile2ContentSize, _ := file2.Write(svgFileContent2) + + htmlContentSize, _ := file1.Write(htmlContent) t.Cleanup(func() { - errFolder := os.RemoveAll("./public") - if errFolder != nil { - t.Error("Couldn't remove public folder") + err := os.RemoveAll("./public") + if err != nil { + t.Log("Couldn't remove public folder") } }) // Run the Application and compare the fileType and fileSize app.httpRegistered = true - app.httpServer.port = 8002 + app.httpServer.port = 8022 go app.Run() time.Sleep(1 * time.Second) - host := "http://localhost:8002" + host := "http://localhost:8022" tests := []struct { desc string @@ -560,27 +557,13 @@ func TestStaticHandler(t *testing.T) { expectedResponseHeaderType string }{ { - desc: "check static files", - method: http.MethodGet, - path: "/public/", - statusCode: http.StatusOK, - expectedBody: "meeting.svg\n", - }, - { - desc: "check file content hardhat.jpeg", - method: http.MethodGet, - path: "/public/meeting.svg", - statusCode: http.StatusOK, - expectedBodyLength: svgFile2ContentSize, - expectedResponseHeaderType: "image/svg+xml", - }, - { - desc: "check file content industrial.jpeg", + desc: "check file content index.html", method: http.MethodGet, - path: "/public/shopping.svg", + path: "/public/index.html", statusCode: http.StatusOK, - expectedBodyLength: svgFile1ContentSize, - expectedResponseHeaderType: "image/svg+xml", + expectedBodyLength: htmlContentSize, + expectedResponseHeaderType: "text/html; charset=utf-8", + expectedBody: string(htmlContent), }, { desc: "check public endpoint", @@ -592,24 +575,33 @@ func TestStaticHandler(t *testing.T) { for it, tc := range tests { request, _ := http.NewRequest(tc.method, host+tc.path, bytes.NewBuffer(tc.body)) + request.Header.Set("Content-Type", "application/json") + client := http.Client{} + resp, err := client.Do(request) + bodyBytes, _ := io.ReadAll(resp.Body) - defer resp.Body.Close() + body := string(bodyBytes) + assert.NoError(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) + if tc.expectedBody != "" { assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) } + if tc.expectedBodyLength != 0 { contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) } + if tc.expectedResponseHeaderType != "" { assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) } - } + resp.Body.Close() + } } From a2b06f9d3009af5c90b24d34e70701ea4d90e945 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Fri, 7 Jun 2024 18:41:55 +0530 Subject: [PATCH 10/38] code quality checks --- pkg/gofr/gofr.go | 3 +++ pkg/gofr/gofr_test.go | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 75a58507a..43ec1ae13 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -108,7 +108,9 @@ func New() *App { func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true + dupFilePath := "" + if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() dupFilePath = filepath.Join(dupFilePath, filePath) @@ -122,6 +124,7 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { a.container.Logger.Errorf("Couldn't register %s static endpoint", endpoint) return } + a.httpServer.router.AddStaticFiles(endpoint, dupFilePath) } diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index ddac0dcab..e4d35db58 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -1,7 +1,6 @@ package gofr import ( - "bytes" "context" "encoding/json" "fmt" @@ -574,7 +573,7 @@ func TestStaticHandler(t *testing.T) { } for it, tc := range tests { - request, _ := http.NewRequest(tc.method, host+tc.path, bytes.NewBuffer(tc.body)) + request, _ := http.NewRequest(tc.method, host+tc.path, http.NoBody) request.Header.Set("Content-Type", "application/json") @@ -599,7 +598,14 @@ func TestStaticHandler(t *testing.T) { } if tc.expectedResponseHeaderType != "" { - assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) + assert.Equal( + t, + tc.expectedResponseHeaderType, + resp.Header.Get("Content-Type"), + "TEST [%d], Failed at Expected Content-Type.\n%s", + it, + tc.desc, + ) } resp.Body.Close() From 1d34bccff186db92bc6b63df577b30dacc404707 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Fri, 7 Jun 2024 18:47:38 +0530 Subject: [PATCH 11/38] rework code quality checks --- pkg/gofr/gofr_test.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index e4d35db58..29abd0c06 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -549,7 +549,6 @@ func TestStaticHandler(t *testing.T) { desc string method string path string - body []byte statusCode int expectedBody string expectedBodyLength int @@ -573,7 +572,7 @@ func TestStaticHandler(t *testing.T) { } for it, tc := range tests { - request, _ := http.NewRequest(tc.method, host+tc.path, http.NoBody) + request, _ := http.NewRequestWithContext(context.Background(), tc.method, host+tc.path, http.NoBody) request.Header.Set("Content-Type", "application/json") @@ -591,21 +590,17 @@ func TestStaticHandler(t *testing.T) { if tc.expectedBody != "" { assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) } - if tc.expectedBodyLength != 0 { contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) } - if tc.expectedResponseHeaderType != "" { - assert.Equal( - t, + assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), "TEST [%d], Failed at Expected Content-Type.\n%s", it, - tc.desc, - ) + tc.desc) } resp.Body.Close() From 0115e03212bcd5b468d7fd8bac0bb4b3d1d5a991 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Fri, 7 Jun 2024 19:19:33 +0530 Subject: [PATCH 12/38] code quality clearance --- pkg/gofr/gofr_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 29abd0c06..5263a096e 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -590,10 +590,12 @@ func TestStaticHandler(t *testing.T) { if tc.expectedBody != "" { assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) } + if tc.expectedBodyLength != 0 { contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) } + if tc.expectedResponseHeaderType != "" { assert.Equal(t, tc.expectedResponseHeaderType, From aa5288f359fdb0208e55ac4aefd14d0b3f66e1f6 Mon Sep 17 00:00:00 2001 From: umang01-hash Date: Mon, 10 Jun 2024 11:43:42 +0530 Subject: [PATCH 13/38] resolve review comments --- .../serving-static-files/page.md | 3 +- pkg/gofr/gofr.go | 8 +- pkg/gofr/gofr_test.go | 90 ++++++++++++------- 3 files changed, 63 insertions(+), 38 deletions(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index e5ce1227a..feb31b9f2 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -1,6 +1,7 @@ # Serving Static Files using GoFr -Often, we are required to serve static content be it a default profile image or a static website. We want to have a mechanism to serve that content without the hassle of implementing it from scratch. +Often, we are required to serve static content such as a default profile image, a favicon, or a background image for our +web application. We want to have a mechanism to serve that static content without the hassle of implementing it from scratch. GoFr provides a default mechanism where if a public folder is available in the directory of the application, it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 43ec1ae13..efed8ea90 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -54,6 +54,8 @@ type App struct { subscriptionManager SubscriptionManager } +const publicDir = "public" + // RegisterService adds a gRPC service to the GoFr application. func (a *App) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { a.container.Logger.Infof("registering GRPC Server: %s", desc.ServiceName) @@ -97,10 +99,10 @@ func New() *App { // static fileserver currentWd, _ := os.Getwd() - checkDirectory := filepath.Join(currentWd, "public") + checkDirectory := filepath.Join(currentWd, publicDir) - if _, err := os.Stat(checkDirectory); !os.IsNotExist(err) { - app.AddStaticFiles("public", checkDirectory) + if _, err := os.Stat(checkDirectory); err == nil { + app.AddStaticFiles(publicDir, checkDirectory) } return app diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 5263a096e..71a5ca20f 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -506,35 +506,16 @@ func Test_AddCronJob_Success(t *testing.T) { } func TestStaticHandler(t *testing.T) { - // Generate a public directory endpoint - directory := "./public" - - if _, err := os.Stat(directory); err != nil { - err := os.Mkdir("./public", 0755) - if err != nil { - t.Errorf("Couldn't create a public directory, error: %s", err) - } - } - // Application Generation - app := New() - // Generate some files for testing htmlContent := []byte("Test Static File

Testing Static File

") - file1, errFile := os.Create("./public/index.html") - - if errFile != nil { - t.Error("Couldn't create index.html file") + err := createPublicDirectory(t, htmlContent) + if err != nil { + return } - htmlContentSize, _ := file1.Write(htmlContent) - - t.Cleanup(func() { - err := os.RemoveAll("./public") - if err != nil { - t.Log("Couldn't remove public folder") - } - }) + // Application Generation + app := New() // Run the Application and compare the fileType and fileSize app.httpRegistered = true @@ -557,30 +538,39 @@ func TestStaticHandler(t *testing.T) { { desc: "check file content index.html", method: http.MethodGet, - path: "/public/index.html", + path: "/" + publicDir + "/index.html", statusCode: http.StatusOK, - expectedBodyLength: htmlContentSize, + expectedBodyLength: len(htmlContent), expectedResponseHeaderType: "text/html; charset=utf-8", expectedBody: string(htmlContent), }, { desc: "check public endpoint", method: http.MethodGet, - path: "/public", + path: "/" + publicDir, statusCode: http.StatusNotFound, }, } for it, tc := range tests { - request, _ := http.NewRequestWithContext(context.Background(), tc.method, host+tc.path, http.NoBody) + request, err := http.NewRequestWithContext(context.Background(), tc.method, host+tc.path, http.NoBody) + if err != nil { + t.Fatalf("TEST[%d], Failed to create request. %s", it, err) + } request.Header.Set("Content-Type", "application/json") client := http.Client{} resp, err := client.Do(request) + if err != nil { + t.Fatalf("TEST[%d], Request failed. %s", it, err) + } - bodyBytes, _ := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("TEST[%d], Failed to read response body. %s", it, err) + } body := string(bodyBytes) @@ -592,19 +582,51 @@ func TestStaticHandler(t *testing.T) { } if tc.expectedBodyLength != 0 { - contentLength, _ := strconv.Atoi(resp.Header.Get("Content-Length")) - assert.Equal(t, tc.expectedBodyLength, contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) + contentLength := resp.Header.Get("Content-Length") + assert.Equal(t, strconv.Itoa(tc.expectedBodyLength), contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) } if tc.expectedResponseHeaderType != "" { assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), - "TEST [%d], Failed at Expected Content-Type.\n%s", - it, - tc.desc) + "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) } resp.Body.Close() } } + +func createPublicDirectory(t *testing.T, htmlContent []byte) error { + directory := "./" + publicDir + if _, err := os.Stat(directory); err != nil { + if err := os.Mkdir("./"+publicDir, 0755); err != nil { + t.Errorf("Couldn't create a "+publicDir+" directory, error: %s", err) + return err + } + } + + file1, errFile := os.Create(directory + "/index.html") + + if errFile != nil { + t.Error("Couldn't create index.html file") + return errFile + } + + _, err := file1.Write(htmlContent) + if err != nil { + t.Error("Couldn't write to index.html file") + return err + } + + file1.Close() + + t.Cleanup(func() { + err := os.RemoveAll(directory) + if err != nil { + t.Log("Couldn't remove " + publicDir + " folder") + } + }) + + return nil +} From 4c52caf5afd77d9a47da316d5bd7887a02266a50 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Mon, 10 Jun 2024 16:36:41 +0530 Subject: [PATCH 14/38] refactor tests --- pkg/gofr/gofr_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 71a5ca20f..cf8cf79c9 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -598,6 +598,8 @@ func TestStaticHandler(t *testing.T) { } func createPublicDirectory(t *testing.T, htmlContent []byte) error { + t.Helper() + directory := "./" + publicDir if _, err := os.Stat(directory); err != nil { if err := os.Mkdir("./"+publicDir, 0755); err != nil { @@ -609,14 +611,12 @@ func createPublicDirectory(t *testing.T, htmlContent []byte) error { file1, errFile := os.Create(directory + "/index.html") if errFile != nil { - t.Error("Couldn't create index.html file") - return errFile + t.Fatal("Couldn't create index.html file") } _, err := file1.Write(htmlContent) if err != nil { - t.Error("Couldn't write to index.html file") - return err + t.Fatal("Couldn't write to index.html file") } file1.Close() From e75d5b561d1bf11543ff83873115a9039d1deca6 Mon Sep 17 00:00:00 2001 From: srijan-27 Date: Tue, 11 Jun 2024 12:33:24 +0530 Subject: [PATCH 15/38] fix docs, tests, logs --- .../serving-static-files/page.md | 22 +++++----- pkg/gofr/gofr.go | 6 +-- pkg/gofr/gofr_test.go | 42 ++++++++----------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index feb31b9f2..22b3c1ce9 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -3,14 +3,15 @@ Often, we are required to serve static content such as a default profile image, a favicon, or a background image for our web application. We want to have a mechanism to serve that static content without the hassle of implementing it from scratch. -GoFr provides a default mechanism where if a public folder is available in the directory of the application, it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. +GoFr provides a default mechanism where if a public folder is available in the directory of the application, +it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. -Example Project folder utilizing public endpoint: +Example project structure: ```dotenv project_folder | -|---config +|---configs | .env |---public | .jpeg @@ -31,17 +32,17 @@ func main(){ app := gofr.New() app.Run() } - ``` -Additionally, if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. +Additionally, if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` +which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. -Providing an example below along with File System Example: +Example project structure: ```dotenv project_folder | -|---config +|---configs | .env |---public | .jpeg @@ -57,21 +58,18 @@ project_folder | main_test.go ``` - main.go file: ```go - package main import "gofr.dev/pkg/gofr" func main(){ app := gofr.New() - app.AddStaticFiles("static","./static") + app.AddStaticFiles("static", "./static") app.Run() } - ``` -In the above example, both endpoints `/public` and `/static` are available for the app to render the static content. \ No newline at end of file +In the above example, both endpoints `/public` and `/static` are available for the app to render the static content. diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index efed8ea90..0235529ea 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -101,7 +101,7 @@ func New() *App { currentWd, _ := os.Getwd() checkDirectory := filepath.Join(currentWd, publicDir) - if _, err := os.Stat(checkDirectory); err == nil { + if _, err = os.Stat(checkDirectory); err == nil { app.AddStaticFiles(publicDir, checkDirectory) } @@ -111,7 +111,7 @@ func New() *App { func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true - dupFilePath := "" + var dupFilePath string if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() @@ -123,7 +123,7 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { endpoint = "/" + strings.TrimPrefix(endpoint, "/") if _, err := os.Stat(dupFilePath); err != nil { - a.container.Logger.Errorf("Couldn't register %s static endpoint", endpoint) + a.container.Logger.Errorf("error in registering '%s' static endpoint, error: %v", endpoint, err) return } diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index cf8cf79c9..d0df577d8 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -506,18 +506,13 @@ func Test_AddCronJob_Success(t *testing.T) { } func TestStaticHandler(t *testing.T) { - // Generate some files for testing + // Generating some files for testing htmlContent := []byte("Test Static File

Testing Static File

") - err := createPublicDirectory(t, htmlContent) - if err != nil { - return - } + createPublicDirectory(t, htmlContent) - // Application Generation app := New() - // Run the Application and compare the fileType and fileSize app.httpRegistered = true app.httpServer.port = 8022 @@ -552,10 +547,10 @@ func TestStaticHandler(t *testing.T) { }, } - for it, tc := range tests { + for i, tc := range tests { request, err := http.NewRequestWithContext(context.Background(), tc.method, host+tc.path, http.NoBody) if err != nil { - t.Fatalf("TEST[%d], Failed to create request. %s", it, err) + t.Fatalf("TEST[%d], Failed to create request, error: %s", i, err) } request.Header.Set("Content-Type", "application/json") @@ -564,62 +559,61 @@ func TestStaticHandler(t *testing.T) { resp, err := client.Do(request) if err != nil { - t.Fatalf("TEST[%d], Request failed. %s", it, err) + t.Fatalf("TEST[%d], Request failed, error: %s", i, err) } bodyBytes, err := io.ReadAll(resp.Body) if err != nil { - t.Fatalf("TEST[%d], Failed to read response body. %s", it, err) + t.Fatalf("TEST[%d], Failed to read response body, error: %s", i, err) } body := string(bodyBytes) - assert.NoError(t, err, "TEST[%d], Failed.\n%s", it, tc.desc) - assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", it, tc.desc) + assert.NoError(t, err, "TEST[%d], Failed.\n%s", i, tc.desc) + assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed with Status Body.\n%s", i, tc.desc) if tc.expectedBody != "" { - assert.Contains(t, body, tc.expectedBody, "TEST [%d], Failed with Expected Body. \n%s", it, tc.desc) + assert.Contains(t, body, tc.expectedBody, "TEST[%d], Failed with Expected Body.\n%s", i, tc.desc) } if tc.expectedBodyLength != 0 { contentLength := resp.Header.Get("Content-Length") - assert.Equal(t, strconv.Itoa(tc.expectedBodyLength), contentLength, "TEST [%d], Failed at Content-Length.\n %s", it, tc.desc) + assert.Equal(t, strconv.Itoa(tc.expectedBodyLength), contentLength, "TEST[%d], Failed at Content-Length.\n%s", i, tc.desc) } if tc.expectedResponseHeaderType != "" { assert.Equal(t, tc.expectedResponseHeaderType, resp.Header.Get("Content-Type"), - "TEST [%d], Failed at Expected Content-Type.\n%s", it, tc.desc) + "TEST[%d], Failed at Expected Content-Type.\n%s", i, tc.desc) } resp.Body.Close() } } -func createPublicDirectory(t *testing.T, htmlContent []byte) error { +func createPublicDirectory(t *testing.T, htmlContent []byte) { t.Helper() directory := "./" + publicDir if _, err := os.Stat(directory); err != nil { - if err := os.Mkdir("./"+publicDir, 0755); err != nil { - t.Errorf("Couldn't create a "+publicDir+" directory, error: %s", err) - return err + if err = os.Mkdir("./"+publicDir, 0755); err != nil { + t.Fatalf("Couldn't create a "+publicDir+" directory, error: %s", err) } } - file1, errFile := os.Create(directory + "/index.html") + file, errFile := os.Create(directory + "/index.html") if errFile != nil { t.Fatal("Couldn't create index.html file") } - _, err := file1.Write(htmlContent) + _, err := file.Write(htmlContent) if err != nil { t.Fatal("Couldn't write to index.html file") } - file1.Close() + file.Close() t.Cleanup(func() { err := os.RemoveAll(directory) @@ -627,6 +621,4 @@ func createPublicDirectory(t *testing.T, htmlContent []byte) error { t.Log("Couldn't remove " + publicDir + " folder") } }) - - return nil } From ca1122b92f1fc68c25b93c5728104ebd991c3434 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Wed, 12 Jun 2024 19:02:43 +0530 Subject: [PATCH 16/38] add config to static file handling --- docs/references/configs/page.md | 20 +++++++ pkg/gofr/gofr.go | 18 ++++++- pkg/gofr/http/router.go | 96 +++++++++++++++++++++++++++++++-- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/docs/references/configs/page.md b/docs/references/configs/page.md index 3cb736912..df0b64abf 100644 --- a/docs/references/configs/page.md +++ b/docs/references/configs/page.md @@ -78,6 +78,26 @@ This document lists all the configuration options supported by the Gofr framewor - Name: CMD_LOGS_FILE - Description: File to save the logs in case of a CMD application +--- + +- Name: STATIC_DIRECTORY_LISTING +- Description: To enable directory listing of files in folder (default: true) + +--- + +- Name: STATIC_HIDEDOTFILES +- Description: To stop rendering hidden files (default: true) + +--- + +- Name: STATIC_EXCLUDE_EXTENSIONS +- Description: To stop rendering files with the given extensions (e.g., png, jpeg, etc.) + +--- + +- Name: STATIC_EXCLUDE_FILES +- Description: To stop certain files from rendering (added openapi.json by default) + {% endtable %} ## Datasource Configs diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 4ab811b1f..9146a802c 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -52,7 +52,7 @@ type App struct { subscriptionManager SubscriptionManager } -const publicDir = "public" +const publicDir = "static" // RegisterService adds a gRPC service to the GoFr application. func (a *App) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { @@ -106,11 +106,23 @@ func New() *App { return app } +func updateConfigInformation(config *gofrHTTP.StaticFileConfig, envConfig config.Config) { + config.DirectoryListing, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_DIRECTORY_LISTING", "true")) + config.HideDotFiles, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_HIDEDOTFILES", "true")) + config.ExcludeExtensions = strings.Split(envConfig.Get("STATIC_EXCLUDE_EXTENSIONS"), ",") + config.ExcludeFiles = strings.Split(envConfig.Get("STATIC_EXCLUDE_FILES"), ",") + config.ExcludeFiles = append(config.ExcludeFiles, "openapi.json") +} + func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true var dupFilePath string + defaultConfig := a.httpServer.router.GetDefaultStaticFilesConfig() + + updateConfigInformation(&defaultConfig, a.Config) + if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() dupFilePath = filepath.Join(dupFilePath, filePath) @@ -125,7 +137,9 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { return } - a.httpServer.router.AddStaticFiles(endpoint, dupFilePath) + defaultConfig.FileDirectory = dupFilePath + + a.httpServer.router.AddStaticFiles(endpoint, defaultConfig) } // NewCMD creates a command-line application. diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index c51b99f9b..90bd83b28 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -2,6 +2,9 @@ package http import ( "net/http" + "os" + "path/filepath" + "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -14,6 +17,14 @@ type Router struct { RegisteredRoutes *[]string } +type StaticFileConfig struct { + DirectoryListing bool + HideDotFiles bool + ExcludeExtensions []string + ExcludeFiles []string + FileDirectory string +} + type Middleware func(handler http.Handler) http.Handler // NewRouter creates a new Router instance. @@ -36,9 +47,88 @@ func (rou *Router) Add(method, pattern string, handler http.Handler) { rou.Router.NewRoute().Methods(method).Path(pattern).Handler(h) } -func (rou *Router) AddStaticFiles(endpoint, directory string) { - fileServer := http.FileServer(http.Dir(directory)) - rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, fileServer)) +func (router *Router) GetDefaultStaticFilesConfig() StaticFileConfig { + config := StaticFileConfig{ + DirectoryListing: true, + HideDotFiles: true, + } + return config +} + +// Static File Handling +func (rou *Router) AddStaticFiles(endpoint string, config StaticFileConfig) { + fileServer := http.FileServer(http.Dir(config.FileDirectory)) + rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticHandler(fileServer, config))) +} + +// Check all the static handling configs +func staticHandler(fileServer http.Handler, config StaticFileConfig) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + url := r.URL.Path + + const forbiddenBody string = "403 forbidden" + + if config.DirectoryListing { + if strings.HasSuffix(url, "/") { + http.NotFound(w, r) + return + } + } + + filePath := strings.Split(url, "/") + + fileName := filePath[len(filePath)-1] + + if config.HideDotFiles { + + if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + if strings.HasPrefix(fileName, ".") { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + return + } + } + + if len(config.ExcludeExtensions) != 0 { + + if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + for _, ext := range config.ExcludeExtensions { + if strings.HasSuffix(fileName, ext) { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + return + } + } + } + + if len(config.ExcludeFiles) != 0 { + if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + for _, file := range config.ExcludeFiles { + if file == fileName { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + return + } + } + } + + fileServer.ServeHTTP(w, r) + }) } // UseMiddleware registers middlewares to the router. From 58ccbc0790f82bda3b9a109160930066b3798422 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Wed, 12 Jun 2024 19:19:02 +0530 Subject: [PATCH 17/38] update of test --- pkg/gofr/gofr_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 181cf08d7..73b031f0c 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -541,9 +541,9 @@ func TestStaticHandler(t *testing.T) { expectedResponseHeaderType string }{ { - desc: "check file content index.html", + desc: "check file content indexNew.html", method: http.MethodGet, - path: "/" + publicDir + "/index.html", + path: "/" + publicDir + "/indexNew.html", statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), expectedResponseHeaderType: "text/html; charset=utf-8", @@ -612,15 +612,15 @@ func createPublicDirectory(t *testing.T, htmlContent []byte) { } } - file, errFile := os.Create(directory + "/index.html") + file, errFile := os.Create(directory + "/indexNew.html") if errFile != nil { - t.Fatal("Couldn't create index.html file") + t.Fatal("Couldn't create indexNew.html file") } _, err := file.Write(htmlContent) if err != nil { - t.Fatal("Couldn't write to index.html file") + t.Fatal("Couldn't write to indexNew.html file") } file.Close() From 105543f28db58f525bd3baf03032005cabb3c1d4 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Wed, 12 Jun 2024 19:55:39 +0530 Subject: [PATCH 18/38] fixing for index file handling --- pkg/gofr/gofr_test.go | 10 +++++----- pkg/gofr/http/router.go | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 73b031f0c..181cf08d7 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -541,9 +541,9 @@ func TestStaticHandler(t *testing.T) { expectedResponseHeaderType string }{ { - desc: "check file content indexNew.html", + desc: "check file content index.html", method: http.MethodGet, - path: "/" + publicDir + "/indexNew.html", + path: "/" + publicDir + "/index.html", statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), expectedResponseHeaderType: "text/html; charset=utf-8", @@ -612,15 +612,15 @@ func createPublicDirectory(t *testing.T, htmlContent []byte) { } } - file, errFile := os.Create(directory + "/indexNew.html") + file, errFile := os.Create(directory + "/index.html") if errFile != nil { - t.Fatal("Couldn't create indexNew.html file") + t.Fatal("Couldn't create index.html file") } _, err := file.Write(htmlContent) if err != nil { - t.Fatal("Couldn't write to indexNew.html file") + t.Fatal("Couldn't write to index.html file") } file.Close() diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 90bd83b28..8afd01214 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -69,7 +69,7 @@ func staticHandler(fileServer http.Handler, config StaticFileConfig) http.Handle const forbiddenBody string = "403 forbidden" if config.DirectoryListing { - if strings.HasSuffix(url, "/") { + if _, err := os.Stat(filepath.Join(config.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { http.NotFound(w, r) return } @@ -94,14 +94,15 @@ func staticHandler(fileServer http.Handler, config StaticFileConfig) http.Handle } } - if len(config.ExcludeExtensions) != 0 { + if len(config.ExcludeExtensions) > 1 { if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { http.NotFound(w, r) return } - for _, ext := range config.ExcludeExtensions { + extensions := config.ExcludeExtensions[1:] + for _, ext := range extensions { if strings.HasSuffix(fileName, ext) { w.WriteHeader(http.StatusForbidden) w.Header().Set("Content-Type", "text/plain;charset=utf-8") @@ -111,13 +112,14 @@ func staticHandler(fileServer http.Handler, config StaticFileConfig) http.Handle } } - if len(config.ExcludeFiles) != 0 { + if len(config.ExcludeFiles) > 1 { if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { http.NotFound(w, r) return } - for _, file := range config.ExcludeFiles { + excludedFiles := config.ExcludeFiles[1:] + for _, file := range excludedFiles { if file == fileName { w.WriteHeader(http.StatusForbidden) w.Header().Set("Content-Type", "text/plain;charset=utf-8") From 438bfa3dbdad9d90eb33f4898cd1da0504e68df2 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Wed, 12 Jun 2024 20:43:06 +0530 Subject: [PATCH 19/38] updates to documentation and code quality fixes --- .../serving-static-files/page.md | 12 +- pkg/gofr/http/router.go | 133 ++++++++++-------- 2 files changed, 81 insertions(+), 64 deletions(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index 22b3c1ce9..b169ec6f1 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -3,8 +3,8 @@ Often, we are required to serve static content such as a default profile image, a favicon, or a background image for our web application. We want to have a mechanism to serve that static content without the hassle of implementing it from scratch. -GoFr provides a default mechanism where if a public folder is available in the directory of the application, -it automatically provides an endpoint with `/public/`, here filename refers to the file we want to get static content to be served. +GoFr provides a default mechanism where if a static folder is available in the directory of the application, +it automatically provides an endpoint with `/static/`, here filename refers to the file we want to get static content to be served. Example project structure: @@ -13,7 +13,7 @@ project_folder | |---configs | .env -|---public +|---static | .jpeg | .png | .jpeg @@ -44,11 +44,11 @@ project_folder | |---configs | .env -|---public +|---static | .jpeg | .png | .jpeg -|---static +|---public | |---css | | main.css | |---js @@ -67,7 +67,7 @@ import "gofr.dev/pkg/gofr" func main(){ app := gofr.New() - app.AddStaticFiles("static", "./static") + app.AddStaticFiles("public", "./public") app.Run() } ``` diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 8afd01214..0f525b491 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -47,92 +47,109 @@ func (rou *Router) Add(method, pattern string, handler http.Handler) { rou.Router.NewRoute().Methods(method).Path(pattern).Handler(h) } -func (router *Router) GetDefaultStaticFilesConfig() StaticFileConfig { - config := StaticFileConfig{ +func (rou *Router) GetDefaultStaticFilesConfig() StaticFileConfig { + staticConfig := StaticFileConfig{ DirectoryListing: true, HideDotFiles: true, } - return config + return staticConfig } -// Static File Handling -func (rou *Router) AddStaticFiles(endpoint string, config StaticFileConfig) { - fileServer := http.FileServer(http.Dir(config.FileDirectory)) - rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticHandler(fileServer, config))) +// Static File Handling. +func (rou *Router) AddStaticFiles(endpoint string, staticConfig StaticFileConfig) { + fileServer := http.FileServer(http.Dir(staticConfig.FileDirectory)) + rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticHandler(fileServer, staticConfig))) } -// Check all the static handling configs -func staticHandler(fileServer http.Handler, config StaticFileConfig) http.Handler { +// Check all the static handling configs. +func staticHandler(fileServer http.Handler, staticConfig StaticFileConfig) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.Path const forbiddenBody string = "403 forbidden" - if config.DirectoryListing { - if _, err := os.Stat(filepath.Join(config.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { - http.NotFound(w, r) - return - } + if staticConfig.DirectoryListing { + checkDirectoryListing(w, r, staticConfig, url) } filePath := strings.Split(url, "/") fileName := filePath[len(filePath)-1] - if config.HideDotFiles { - - if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } - - if strings.HasPrefix(fileName, ".") { - w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") - w.Write([]byte(forbiddenBody)) - return - } + if staticConfig.HideDotFiles { + checkDotFiles(w, r, staticConfig, fileName, url, forbiddenBody) } - if len(config.ExcludeExtensions) > 1 { - - if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } - - extensions := config.ExcludeExtensions[1:] - for _, ext := range extensions { - if strings.HasSuffix(fileName, ext) { - w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") - w.Write([]byte(forbiddenBody)) - return - } - } + if len(staticConfig.ExcludeExtensions) > 1 { + checkExcludedExtensions(w, r, staticConfig, fileName, url, forbiddenBody) } - if len(config.ExcludeFiles) > 1 { - if _, err := os.Stat(filepath.Join(config.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } - - excludedFiles := config.ExcludeFiles[1:] - for _, file := range excludedFiles { - if file == fileName { - w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") - w.Write([]byte(forbiddenBody)) - return - } - } + if len(staticConfig.ExcludeFiles) > 1 { + checkExcludedFiles(w, r, staticConfig, fileName, url, forbiddenBody) } fileServer.ServeHTTP(w, r) }) } +func checkDirectoryListing(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, url string) { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { + http.NotFound(w, r) + return + } +} + +func checkDotFiles(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + if strings.HasPrefix(fileName, ".") { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + + return + } +} + +func checkExcludedExtensions(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + extensions := staticConfig.ExcludeExtensions[1:] + for _, ext := range extensions { + if strings.HasSuffix(fileName, ext) { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + + return + } + } +} + +func checkExcludedFiles(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { + http.NotFound(w, r) + return + } + + excludedFiles := staticConfig.ExcludeFiles[1:] + for _, file := range excludedFiles { + if file == fileName { + w.WriteHeader(http.StatusForbidden) + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.Write([]byte(forbiddenBody)) + + return + } + } +} + // UseMiddleware registers middlewares to the router. func (rou *Router) UseMiddleware(mws ...Middleware) { middlewares := make([]mux.MiddlewareFunc, 0, len(mws)) From 0a5fb78a83e3b6d9fb45884dd751a8bf18141726 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 13 Jun 2024 20:14:29 +0530 Subject: [PATCH 20/38] changes and fixes --- .../serving-static-files/page.md | 14 ++--- pkg/gofr/gofr.go | 16 +++-- pkg/gofr/helpers.go | 23 +++++++ pkg/gofr/helpers_test.go | 19 ++++++ pkg/gofr/http/router.go | 60 +++++++------------ 5 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 pkg/gofr/helpers.go create mode 100644 pkg/gofr/helpers_test.go diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index b169ec6f1..2edfc91e0 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -3,7 +3,7 @@ Often, we are required to serve static content such as a default profile image, a favicon, or a background image for our web application. We want to have a mechanism to serve that static content without the hassle of implementing it from scratch. -GoFr provides a default mechanism where if a static folder is available in the directory of the application, +GoFr provides a default mechanism where if a `static` folder is available in the directory of the application, it automatically provides an endpoint with `/static/`, here filename refers to the file we want to get static content to be served. Example project structure: @@ -14,9 +14,9 @@ project_folder |---configs | .env |---static -| .jpeg -| .png -| .jpeg +| img1.jpeg +| img2.png +| img3.jpeg | main.go | main_test.go ``` @@ -45,9 +45,9 @@ project_folder |---configs | .env |---static -| .jpeg -| .png -| .jpeg +| img1.jpeg +| img2.png +| img3.jpeg |---public | |---css | | main.css diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 9146a802c..766615f44 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -106,18 +106,18 @@ func New() *App { return app } -func updateConfigInformation(config *gofrHTTP.StaticFileConfig, envConfig config.Config) { - config.DirectoryListing, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_DIRECTORY_LISTING", "true")) - config.HideDotFiles, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_HIDEDOTFILES", "true")) - config.ExcludeExtensions = strings.Split(envConfig.Get("STATIC_EXCLUDE_EXTENSIONS"), ",") - config.ExcludeFiles = strings.Split(envConfig.Get("STATIC_EXCLUDE_FILES"), ",") - config.ExcludeFiles = append(config.ExcludeFiles, "openapi.json") +func updateConfigInformation(staticConfig *gofrHTTP.StaticFileConfig, envConfig config.Config) { + staticConfig.DirectoryListing, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_DIRECTORY_LISTING", "true")) + staticConfig.HideDotFiles, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_HIDEDOTFILES", "true")) + staticConfig.ExcludeExtensions = SplitEnv(envConfig.Get("STATIC_EXCLUDE_EXTENSIONS"), ",") + staticConfig.ExcludeFiles = SplitEnv(envConfig.Get("STATIC_EXCLUDE_FILES"), ",") + staticConfig.ExcludeFiles = append(staticConfig.ExcludeFiles, "openapi.json") } func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true - var dupFilePath string + dupFilePath := filePath defaultConfig := a.httpServer.router.GetDefaultStaticFilesConfig() @@ -126,8 +126,6 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() dupFilePath = filepath.Join(dupFilePath, filePath) - } else { - dupFilePath = filePath } endpoint = "/" + strings.TrimPrefix(endpoint, "/") diff --git a/pkg/gofr/helpers.go b/pkg/gofr/helpers.go new file mode 100644 index 000000000..5620841d8 --- /dev/null +++ b/pkg/gofr/helpers.go @@ -0,0 +1,23 @@ +package gofr + +import ( + "strings" +) + +func SplitEnv(envString, splitString string) []string { + if envString == "" { + return []string{} + } + + splitArray := strings.Split(envString, splitString) + + tempArray := []string{} + + for _, ele := range splitArray { + if ele == "" { + continue + } + tempArray = append(tempArray, ele) + } + return tempArray +} diff --git a/pkg/gofr/helpers_test.go b/pkg/gofr/helpers_test.go new file mode 100644 index 000000000..ed2164177 --- /dev/null +++ b/pkg/gofr/helpers_test.go @@ -0,0 +1,19 @@ +package gofr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_StringSplitEnv_EmptyString(t *testing.T) { + res := SplitEnv("", ",") + + assert.Equal(t, 0, len(res), "Test Failed at SplitEnv for Empty String") +} + +func Test_StringSplitEnv_NonEmptyString(t *testing.T) { + res := SplitEnv("test1,test2", ",") + + assert.EqualValues(t, []string{"test1", "test2"}, res, "Test Failed at SplitEnv for Non Empty String") +} diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 0f525b491..87110e6c4 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -52,24 +52,25 @@ func (rou *Router) GetDefaultStaticFilesConfig() StaticFileConfig { DirectoryListing: true, HideDotFiles: true, } + return staticConfig } // Static File Handling. func (rou *Router) AddStaticFiles(endpoint string, staticConfig StaticFileConfig) { fileServer := http.FileServer(http.Dir(staticConfig.FileDirectory)) - rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticHandler(fileServer, staticConfig))) + rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticConfig.staticHandler(fileServer))) } // Check all the static handling configs. -func staticHandler(fileServer http.Handler, staticConfig StaticFileConfig) http.Handler { +const forbiddenBody string = "403 forbidden" + +func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.Path - const forbiddenBody string = "403 forbidden" - if staticConfig.DirectoryListing { - checkDirectoryListing(w, r, staticConfig, url) + staticConfig.checkDirectoryListing(w, r, url) } filePath := strings.Split(url, "/") @@ -77,54 +78,44 @@ func staticHandler(fileServer http.Handler, staticConfig StaticFileConfig) http. fileName := filePath[len(filePath)-1] if staticConfig.HideDotFiles { - checkDotFiles(w, r, staticConfig, fileName, url, forbiddenBody) + staticConfig.checkDotFiles(w, fileName, url) } - if len(staticConfig.ExcludeExtensions) > 1 { - checkExcludedExtensions(w, r, staticConfig, fileName, url, forbiddenBody) + if len(staticConfig.ExcludeExtensions) > 0 { + staticConfig.checkExcludedExtensions(w, fileName, url) } - if len(staticConfig.ExcludeFiles) > 1 { - checkExcludedFiles(w, r, staticConfig, fileName, url, forbiddenBody) + if len(staticConfig.ExcludeFiles) > 0 { + staticConfig.checkExcludedFiles(w, fileName, url) } fileServer.ServeHTTP(w, r) }) } -func checkDirectoryListing(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, url string) { +func (staticConfig StaticFileConfig) checkDirectoryListing(w http.ResponseWriter, r *http.Request, url string) { if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { http.NotFound(w, r) return } } -func checkDotFiles(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } - - if strings.HasPrefix(fileName, ".") { +func (staticConfig StaticFileConfig) checkDotFiles(w http.ResponseWriter, fileName, url string) { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err == nil && strings.HasPrefix(fileName, ".") { w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.Write([]byte(forbiddenBody)) return } + } -func checkExcludedExtensions(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } +func (staticConfig StaticFileConfig) checkExcludedExtensions(w http.ResponseWriter, fileName, url string) { + _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)) - extensions := staticConfig.ExcludeExtensions[1:] - for _, ext := range extensions { - if strings.HasSuffix(fileName, ext) { + for _, ext := range staticConfig.ExcludeExtensions { + if strings.HasSuffix(fileName, ext) && err == nil { w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.Write([]byte(forbiddenBody)) return @@ -132,17 +123,12 @@ func checkExcludedExtensions(w http.ResponseWriter, r *http.Request, staticConfi } } -func checkExcludedFiles(w http.ResponseWriter, r *http.Request, staticConfig StaticFileConfig, fileName string, url string, forbiddenBody string) { - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err != nil { - http.NotFound(w, r) - return - } +func (staticConfig StaticFileConfig) checkExcludedFiles(w http.ResponseWriter, fileName, url string) { + _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)) - excludedFiles := staticConfig.ExcludeFiles[1:] - for _, file := range excludedFiles { - if file == fileName { + for _, file := range staticConfig.ExcludeFiles { + if file == fileName && err == nil { w.WriteHeader(http.StatusForbidden) - w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.Write([]byte(forbiddenBody)) return From 824b1bced8b8994b022fc1712ea21c7ace617c85 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 13 Jun 2024 20:15:47 +0530 Subject: [PATCH 21/38] small typo in documentation --- docs/advanced-guide/serving-static-files/page.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-guide/serving-static-files/page.md b/docs/advanced-guide/serving-static-files/page.md index 2edfc91e0..e1d856bc5 100644 --- a/docs/advanced-guide/serving-static-files/page.md +++ b/docs/advanced-guide/serving-static-files/page.md @@ -35,7 +35,7 @@ func main(){ ``` Additionally, if we want to serve more static endpoints, we have a dedicated function called `AddStaticFiles()` -which takes 2 parameters endpoint and the filepath of the static folder which we want to serve. +which takes 2 parameters `endpoint` and the `filepath` of the static folder which we want to serve. Example project structure: From 8d8af7ad901130eedc4ce53ed664d269b8fcf2b3 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Thu, 13 Jun 2024 20:25:14 +0530 Subject: [PATCH 22/38] checking for public endpoint --- pkg/gofr/gofr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 766615f44..7c65b4e32 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -52,7 +52,7 @@ type App struct { subscriptionManager SubscriptionManager } -const publicDir = "static" +const publicDir = "public" // RegisterService adds a gRPC service to the GoFr application. func (a *App) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { From 492dbab8ac9bd6d2ab1988a9b74b7a8c20002c52 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Mon, 17 Jun 2024 11:04:57 +0530 Subject: [PATCH 23/38] fix linters --- pkg/gofr/helpers.go | 2 ++ pkg/gofr/http/router.go | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/gofr/helpers.go b/pkg/gofr/helpers.go index 5620841d8..1c2c3daf6 100644 --- a/pkg/gofr/helpers.go +++ b/pkg/gofr/helpers.go @@ -17,7 +17,9 @@ func SplitEnv(envString, splitString string) []string { if ele == "" { continue } + tempArray = append(tempArray, ele) } + return tempArray } diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 87110e6c4..a71680acf 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -103,11 +103,11 @@ func (staticConfig StaticFileConfig) checkDirectoryListing(w http.ResponseWriter func (staticConfig StaticFileConfig) checkDotFiles(w http.ResponseWriter, fileName, url string) { if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err == nil && strings.HasPrefix(fileName, ".") { w.WriteHeader(http.StatusForbidden) - w.Write([]byte(forbiddenBody)) + + _, _ = w.Write([]byte(forbiddenBody)) return } - } func (staticConfig StaticFileConfig) checkExcludedExtensions(w http.ResponseWriter, fileName, url string) { @@ -116,7 +116,8 @@ func (staticConfig StaticFileConfig) checkExcludedExtensions(w http.ResponseWrit for _, ext := range staticConfig.ExcludeExtensions { if strings.HasSuffix(fileName, ext) && err == nil { w.WriteHeader(http.StatusForbidden) - w.Write([]byte(forbiddenBody)) + + _, _ = w.Write([]byte(forbiddenBody)) return } @@ -129,7 +130,8 @@ func (staticConfig StaticFileConfig) checkExcludedFiles(w http.ResponseWriter, f for _, file := range staticConfig.ExcludeFiles { if file == fileName && err == nil { w.WriteHeader(http.StatusForbidden) - w.Write([]byte(forbiddenBody)) + + _, _ = w.Write([]byte(forbiddenBody)) return } From 8f0dd2c928ad367be393b6bb644329bd7e4a201e Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Mon, 24 Jun 2024 15:26:01 +0530 Subject: [PATCH 24/38] changes as requested --- docs/references/configs/page.md | 4 ---- pkg/gofr/constants.go | 6 ++++++ pkg/gofr/gofr.go | 3 --- pkg/gofr/gofr_test.go | 12 ++++++------ pkg/gofr/http/router.go | 4 +--- 5 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 pkg/gofr/constants.go diff --git a/docs/references/configs/page.md b/docs/references/configs/page.md index df0b64abf..eebcfd00b 100644 --- a/docs/references/configs/page.md +++ b/docs/references/configs/page.md @@ -83,10 +83,6 @@ This document lists all the configuration options supported by the Gofr framewor - Name: STATIC_DIRECTORY_LISTING - Description: To enable directory listing of files in folder (default: true) ---- - -- Name: STATIC_HIDEDOTFILES -- Description: To stop rendering hidden files (default: true) --- diff --git a/pkg/gofr/constants.go b/pkg/gofr/constants.go new file mode 100644 index 000000000..905bd394c --- /dev/null +++ b/pkg/gofr/constants.go @@ -0,0 +1,6 @@ +package gofr + +const ( + publicDir = "static" + indexHTML = "indexTest.html" +) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 7c65b4e32..c216cf6dd 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -52,8 +52,6 @@ type App struct { subscriptionManager SubscriptionManager } -const publicDir = "public" - // RegisterService adds a gRPC service to the GoFr application. func (a *App) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { a.container.Logger.Infof("registering GRPC Server: %s", desc.ServiceName) @@ -108,7 +106,6 @@ func New() *App { func updateConfigInformation(staticConfig *gofrHTTP.StaticFileConfig, envConfig config.Config) { staticConfig.DirectoryListing, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_DIRECTORY_LISTING", "true")) - staticConfig.HideDotFiles, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_HIDEDOTFILES", "true")) staticConfig.ExcludeExtensions = SplitEnv(envConfig.Get("STATIC_EXCLUDE_EXTENSIONS"), ",") staticConfig.ExcludeFiles = SplitEnv(envConfig.Get("STATIC_EXCLUDE_FILES"), ",") staticConfig.ExcludeFiles = append(staticConfig.ExcludeFiles, "openapi.json") diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index cb017f766..04f0e0b6b 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -542,7 +542,7 @@ func TestStaticHandler(t *testing.T) { { desc: "check file content index.html", method: http.MethodGet, - path: "/" + publicDir + "/index.html", + path: "/" + publicDir + "/" + indexHTML, statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), expectedResponseHeaderType: "text/html; charset=utf-8", @@ -611,23 +611,23 @@ func createPublicDirectory(t *testing.T, htmlContent []byte) { } } - file, errFile := os.Create(directory + "/index.html") + file, errFile := os.Create(filepath.Join(directory, indexHTML)) if errFile != nil { - t.Fatal("Couldn't create index.html file") + t.Fatalf("Couldn't create %s file", indexHTML) } _, err := file.Write(htmlContent) if err != nil { - t.Fatal("Couldn't write to index.html file") + t.Fatalf("Couldn't write to %s file", indexHTML) } file.Close() t.Cleanup(func() { - err := os.RemoveAll(directory) + err := os.Remove(filepath.Join(directory, indexHTML)) if err != nil { - t.Log("Couldn't remove " + publicDir + " folder") + t.Logf("Couldn't remove %s file", indexHTML) } }) } diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index a71680acf..11daf708e 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -77,9 +77,7 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http fileName := filePath[len(filePath)-1] - if staticConfig.HideDotFiles { - staticConfig.checkDotFiles(w, fileName, url) - } + staticConfig.checkDotFiles(w, fileName, url) if len(staticConfig.ExcludeExtensions) > 0 { staticConfig.checkExcludedExtensions(w, fileName, url) From 5aa610522eda5af2a1618b3dc7104914fddfecc1 Mon Sep 17 00:00:00 2001 From: KedarisettiSreeVamsi Date: Mon, 24 Jun 2024 16:53:20 +0530 Subject: [PATCH 25/38] conflict check --- docs/references/configs/page.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/references/configs/page.md b/docs/references/configs/page.md index eebcfd00b..1cf4921cf 100644 --- a/docs/references/configs/page.md +++ b/docs/references/configs/page.md @@ -83,7 +83,6 @@ This document lists all the configuration options supported by the Gofr framewor - Name: STATIC_DIRECTORY_LISTING - Description: To enable directory listing of files in folder (default: true) - --- - Name: STATIC_EXCLUDE_EXTENSIONS From e6bbd59f428445cb44e13cc54387f4408453835a Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 12:03:04 +0530 Subject: [PATCH 26/38] fix reveiw comments --- pkg/gofr/constants.go | 6 ---- pkg/gofr/gofr.go | 63 ++++++++++++++++++---------------------- pkg/gofr/gofr_test.go | 12 ++++---- pkg/gofr/helpers.go | 25 ---------------- pkg/gofr/helpers_test.go | 19 ------------ pkg/gofr/http/router.go | 32 +++++--------------- 6 files changed, 42 insertions(+), 115 deletions(-) delete mode 100644 pkg/gofr/constants.go delete mode 100644 pkg/gofr/helpers.go delete mode 100644 pkg/gofr/helpers_test.go diff --git a/pkg/gofr/constants.go b/pkg/gofr/constants.go deleted file mode 100644 index 905bd394c..000000000 --- a/pkg/gofr/constants.go +++ /dev/null @@ -1,6 +0,0 @@ -package gofr - -const ( - publicDir = "static" - indexHTML = "indexTest.html" -) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 2b292f2ee..0c6cd7498 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -31,6 +31,8 @@ import ( "gofr.dev/pkg/gofr/service" ) +const defaultPublicStaticDir = "static" + // App is the main application in the GoFr framework. type App struct { // Config can be used by applications to fetch custom configurations from environment or file. @@ -95,48 +97,15 @@ func New() *App { // static fileserver currentWd, _ := os.Getwd() - checkDirectory := filepath.Join(currentWd, publicDir) + checkDirectory := filepath.Join(currentWd, defaultPublicStaticDir) if _, err = os.Stat(checkDirectory); err == nil { - app.AddStaticFiles(publicDir, checkDirectory) + app.AddStaticFiles(defaultPublicStaticDir, checkDirectory) } return app } -func updateConfigInformation(staticConfig *gofrHTTP.StaticFileConfig, envConfig config.Config) { - staticConfig.DirectoryListing, _ = strconv.ParseBool(envConfig.GetOrDefault("STATIC_DIRECTORY_LISTING", "true")) - staticConfig.ExcludeExtensions = SplitEnv(envConfig.Get("STATIC_EXCLUDE_EXTENSIONS"), ",") - staticConfig.ExcludeFiles = SplitEnv(envConfig.Get("STATIC_EXCLUDE_FILES"), ",") - staticConfig.ExcludeFiles = append(staticConfig.ExcludeFiles, "openapi.json") -} - -func (a *App) AddStaticFiles(endpoint, filePath string) { - a.httpRegistered = true - - dupFilePath := filePath - - defaultConfig := a.httpServer.router.GetDefaultStaticFilesConfig() - - updateConfigInformation(&defaultConfig, a.Config) - - if strings.HasPrefix(filePath, "./") { - dupFilePath, _ = os.Getwd() - dupFilePath = filepath.Join(dupFilePath, filePath) - } - - endpoint = "/" + strings.TrimPrefix(endpoint, "/") - - if _, err := os.Stat(dupFilePath); err != nil { - a.container.Logger.Errorf("error in registering '%s' static endpoint, error: %v", endpoint, err) - return - } - - defaultConfig.FileDirectory = dupFilePath - - a.httpServer.router.AddStaticFiles(endpoint, defaultConfig) -} - // NewCMD creates a command-line application. func NewCMD() *App { app := &App{} @@ -481,3 +450,27 @@ func contains(elems []string, v string) bool { return false } + +func (a *App) AddStaticFiles(endpoint, filePath string) { + a.httpRegistered = true + + dupFilePath := filePath + + defaultConfig := a.httpServer.router.GetDefaultStaticFilesConfig() + + if strings.HasPrefix(filePath, "./") { + dupFilePath, _ = os.Getwd() + dupFilePath = filepath.Join(dupFilePath, filePath) + } + + endpoint = "/" + strings.TrimPrefix(endpoint, "/") + + if _, err := os.Stat(dupFilePath); err != nil { + a.container.Logger.Errorf("error in registering '%s' static endpoint, error: %v", endpoint, err) + return + } + + defaultConfig.FileDirectory = dupFilePath + + a.httpServer.router.AddStaticFiles(endpoint, defaultConfig) +} diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 4b338fab1..b5aca3950 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -562,6 +562,8 @@ func Test_AddCronJob_Success(t *testing.T) { assert.Truef(t, pass, "unable to add cron job to cron table") } +const indexHTML = "indexTest.html" + func TestStaticHandler(t *testing.T) { // Generating some files for testing htmlContent := []byte("Test Static File

Testing Static File

") @@ -590,7 +592,7 @@ func TestStaticHandler(t *testing.T) { { desc: "check file content index.html", method: http.MethodGet, - path: "/" + publicDir + "/" + indexHTML, + path: "/" + defaultPublicStaticDir + "/" + indexHTML, statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), expectedResponseHeaderType: "text/html; charset=utf-8", @@ -599,7 +601,7 @@ func TestStaticHandler(t *testing.T) { { desc: "check public endpoint", method: http.MethodGet, - path: "/" + publicDir, + path: "/" + defaultPublicStaticDir, statusCode: http.StatusNotFound, }, } @@ -652,10 +654,10 @@ func TestStaticHandler(t *testing.T) { func createPublicDirectory(t *testing.T, htmlContent []byte) { t.Helper() - directory := "./" + publicDir + directory := "./" + defaultPublicStaticDir if _, err := os.Stat(directory); err != nil { - if err = os.Mkdir("./"+publicDir, 0755); err != nil { - t.Fatalf("Couldn't create a "+publicDir+" directory, error: %s", err) + if err = os.Mkdir("./"+defaultPublicStaticDir, 0755); err != nil { + t.Fatalf("Couldn't create a "+defaultPublicStaticDir+" directory, error: %s", err) } } diff --git a/pkg/gofr/helpers.go b/pkg/gofr/helpers.go deleted file mode 100644 index 1c2c3daf6..000000000 --- a/pkg/gofr/helpers.go +++ /dev/null @@ -1,25 +0,0 @@ -package gofr - -import ( - "strings" -) - -func SplitEnv(envString, splitString string) []string { - if envString == "" { - return []string{} - } - - splitArray := strings.Split(envString, splitString) - - tempArray := []string{} - - for _, ele := range splitArray { - if ele == "" { - continue - } - - tempArray = append(tempArray, ele) - } - - return tempArray -} diff --git a/pkg/gofr/helpers_test.go b/pkg/gofr/helpers_test.go deleted file mode 100644 index ed2164177..000000000 --- a/pkg/gofr/helpers_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package gofr - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_StringSplitEnv_EmptyString(t *testing.T) { - res := SplitEnv("", ",") - - assert.Equal(t, 0, len(res), "Test Failed at SplitEnv for Empty String") -} - -func Test_StringSplitEnv_NonEmptyString(t *testing.T) { - res := SplitEnv("test1,test2", ",") - - assert.EqualValues(t, []string{"test1", "test2"}, res, "Test Failed at SplitEnv for Non Empty String") -} diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 11daf708e..551d44e7f 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -18,11 +18,10 @@ type Router struct { } type StaticFileConfig struct { - DirectoryListing bool - HideDotFiles bool - ExcludeExtensions []string - ExcludeFiles []string - FileDirectory string + DirectoryListing bool + HideDotFiles bool + excludeFiles []string + FileDirectory string } type Middleware func(handler http.Handler) http.Handler @@ -51,6 +50,7 @@ func (rou *Router) GetDefaultStaticFilesConfig() StaticFileConfig { staticConfig := StaticFileConfig{ DirectoryListing: true, HideDotFiles: true, + excludeFiles: []string{"openapi.json"}, } return staticConfig @@ -79,11 +79,7 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http staticConfig.checkDotFiles(w, fileName, url) - if len(staticConfig.ExcludeExtensions) > 0 { - staticConfig.checkExcludedExtensions(w, fileName, url) - } - - if len(staticConfig.ExcludeFiles) > 0 { + if len(staticConfig.excludeFiles) > 0 { staticConfig.checkExcludedFiles(w, fileName, url) } @@ -108,24 +104,10 @@ func (staticConfig StaticFileConfig) checkDotFiles(w http.ResponseWriter, fileNa } } -func (staticConfig StaticFileConfig) checkExcludedExtensions(w http.ResponseWriter, fileName, url string) { - _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)) - - for _, ext := range staticConfig.ExcludeExtensions { - if strings.HasSuffix(fileName, ext) && err == nil { - w.WriteHeader(http.StatusForbidden) - - _, _ = w.Write([]byte(forbiddenBody)) - - return - } - } -} - func (staticConfig StaticFileConfig) checkExcludedFiles(w http.ResponseWriter, fileName, url string) { _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)) - for _, file := range staticConfig.ExcludeFiles { + for _, file := range staticConfig.excludeFiles { if file == fileName && err == nil { w.WriteHeader(http.StatusForbidden) From 9b9a6ba0a477ef0f8521fce6bf1ced95f72683d0 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 12:44:02 +0530 Subject: [PATCH 27/38] fix issues in rendering file --- examples/http-server/public/abc.json | 128 +++++++++++++++++++++++++++ pkg/gofr/http/router.go | 47 +++------- pkg/gofr/swagger_test.go | 4 +- 3 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 examples/http-server/public/abc.json diff --git a/examples/http-server/public/abc.json b/examples/http-server/public/abc.json new file mode 100644 index 000000000..d856e7ff1 --- /dev/null +++ b/examples/http-server/public/abc.json @@ -0,0 +1,128 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Http-Server API", + "description": "Example Http-Server with multiple endpoints. This swagger represents the requests and responses for various endpoints included in the [http-server example of gofr](https://github.com/gofr-dev/gofr/tree/development/examples/http-server).", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:9000" + } + ], + "paths": { + "/hello": { + "get": { + "summary": "Get a greeting message", + "parameters": [ + { + "in": "query", + "name": "name", + "schema": { + "type": "string" + }, + "description": "Name to include in the greeting message" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/error": { + "get": { + "summary": "Simulate an error response", + "responses": { + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "/redis": { + "get": { + "summary": "Simulate a response from Redis", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/mysql": { + "get": { + "summary": "Simulate a response from MySQL", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "/trace": { + "get": { + "summary": "Simulate the tracing feature for a request", + "responses": { + "200": { + "description": "Successful response" + } + } + } + } + } +} \ No newline at end of file diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 551d44e7f..def76d52d 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -20,7 +20,6 @@ type Router struct { type StaticFileConfig struct { DirectoryListing bool HideDotFiles bool - excludeFiles []string FileDirectory string } @@ -50,7 +49,6 @@ func (rou *Router) GetDefaultStaticFilesConfig() StaticFileConfig { staticConfig := StaticFileConfig{ DirectoryListing: true, HideDotFiles: true, - excludeFiles: []string{"openapi.json"}, } return staticConfig @@ -69,53 +67,34 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.Path - if staticConfig.DirectoryListing { - staticConfig.checkDirectoryListing(w, r, url) + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { + http.NotFound(w, r) + + return } filePath := strings.Split(url, "/") fileName := filePath[len(filePath)-1] - staticConfig.checkDotFiles(w, fileName, url) - - if len(staticConfig.excludeFiles) > 0 { - staticConfig.checkExcludedFiles(w, fileName, url) - } - - fileServer.ServeHTTP(w, r) - }) -} - -func (staticConfig StaticFileConfig) checkDirectoryListing(w http.ResponseWriter, r *http.Request, url string) { - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { - http.NotFound(w, r) - return - } -} - -func (staticConfig StaticFileConfig) checkDotFiles(w http.ResponseWriter, fileName, url string) { - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err == nil && strings.HasPrefix(fileName, ".") { - w.WriteHeader(http.StatusForbidden) - - _, _ = w.Write([]byte(forbiddenBody)) + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err == nil && strings.HasPrefix(fileName, ".") { + w.WriteHeader(http.StatusForbidden) - return - } -} + _, _ = w.Write([]byte(forbiddenBody)) -func (staticConfig StaticFileConfig) checkExcludedFiles(w http.ResponseWriter, fileName, url string) { - _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)) + return + } - for _, file := range staticConfig.excludeFiles { - if file == fileName && err == nil { + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); "openapi.json" == fileName && err == nil { w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte(forbiddenBody)) return } - } + + fileServer.ServeHTTP(w, r) + }) } // UseMiddleware registers middlewares to the router. diff --git a/pkg/gofr/swagger_test.go b/pkg/gofr/swagger_test.go index 46f1eff04..724940ab3 100644 --- a/pkg/gofr/swagger_test.go +++ b/pkg/gofr/swagger_test.go @@ -79,7 +79,7 @@ func TestSwaggerHandler(t *testing.T) { }{ {"fetch index.html", "", "text/html"}, {"fetch favicon image", "favicon-16x16.png", "image/png"}, - {"fetch js files", "swagger-ui.js", "text/javascript"}, + {"fetch js files", "swagger-ui.js", "application/javascript"}, } for _, tc := range tests { @@ -98,7 +98,7 @@ func TestSwaggerHandler(t *testing.T) { } if strings.Split(fileResponse.ContentType, ";")[0] != tc.contentType { - t.Errorf("Expected content type 'application/json', got '%s'", fileResponse.ContentType) + t.Errorf("Expected content type '%s', got '%s'", tc.contentType, fileResponse.ContentType) } } } From 39597eeed23d8ca9dbdef9343b81c5427a7c02c1 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 13:12:22 +0530 Subject: [PATCH 28/38] remove unwanted exported methods --- pkg/gofr/gofr.go | 7 +++++-- pkg/gofr/http/router.go | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 0c6cd7498..262379594 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -454,9 +454,12 @@ func contains(elems []string, v string) bool { func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true - dupFilePath := filePath + defaultConfig := gofrHTTP.StaticFileConfig{ + DirectoryListing: true, + HideDotFiles: true, + } - defaultConfig := a.httpServer.router.GetDefaultStaticFilesConfig() + dupFilePath := filePath if strings.HasPrefix(filePath, "./") { dupFilePath, _ = os.Getwd() diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index def76d52d..5a05ab17d 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -45,15 +45,6 @@ func (rou *Router) Add(method, pattern string, handler http.Handler) { rou.Router.NewRoute().Methods(method).Path(pattern).Handler(h) } -func (rou *Router) GetDefaultStaticFilesConfig() StaticFileConfig { - staticConfig := StaticFileConfig{ - DirectoryListing: true, - HideDotFiles: true, - } - - return staticConfig -} - // Static File Handling. func (rou *Router) AddStaticFiles(endpoint string, staticConfig StaticFileConfig) { fileServer := http.FileServer(http.Dir(staticConfig.FileDirectory)) From d92b6f1059b37b52b6d1c575c3f8eead1b026894 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 13:17:00 +0530 Subject: [PATCH 29/38] refactor implementatins --- pkg/gofr/gofr.go | 11 +++++------ pkg/gofr/http/router.go | 4 +++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 262379594..9b17a4e47 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -459,21 +459,20 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { HideDotFiles: true, } - dupFilePath := filePath - + // update file path based on current directory if it starts with ./ if strings.HasPrefix(filePath, "./") { - dupFilePath, _ = os.Getwd() - dupFilePath = filepath.Join(dupFilePath, filePath) + currentWorkingDir, _ := os.Getwd() + filePath = filepath.Join(currentWorkingDir, filePath) } endpoint = "/" + strings.TrimPrefix(endpoint, "/") - if _, err := os.Stat(dupFilePath); err != nil { + if _, err := os.Stat(filePath); err != nil { a.container.Logger.Errorf("error in registering '%s' static endpoint, error: %v", endpoint, err) return } - defaultConfig.FileDirectory = dupFilePath + defaultConfig.FileDirectory = filePath a.httpServer.router.AddStaticFiles(endpoint, defaultConfig) } diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 5a05ab17d..6b2070b24 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -76,7 +76,9 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http return } - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); "openapi.json" == fileName && err == nil { + const defaultSwaggerFileName = "openapi.json" + + if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); fileName == defaultSwaggerFileName && err == nil { w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte(forbiddenBody)) From b25c4b6417d297d4156627687b99ea5043e33076 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 13:40:20 +0530 Subject: [PATCH 30/38] unexport static file config function --- pkg/gofr/gofr.go | 9 +------ pkg/gofr/http/router.go | 59 ++++++++++++++++------------------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 9b17a4e47..90066cf6b 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -454,11 +454,6 @@ func contains(elems []string, v string) bool { func (a *App) AddStaticFiles(endpoint, filePath string) { a.httpRegistered = true - defaultConfig := gofrHTTP.StaticFileConfig{ - DirectoryListing: true, - HideDotFiles: true, - } - // update file path based on current directory if it starts with ./ if strings.HasPrefix(filePath, "./") { currentWorkingDir, _ := os.Getwd() @@ -472,7 +467,5 @@ func (a *App) AddStaticFiles(endpoint, filePath string) { return } - defaultConfig.FileDirectory = filePath - - a.httpServer.router.AddStaticFiles(endpoint, defaultConfig) + a.httpServer.router.AddStaticFiles(endpoint, filePath) } diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 6b2070b24..6d2bba229 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -6,9 +6,8 @@ import ( "path/filepath" "strings" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "github.com/gorilla/mux" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Router is responsible for routing HTTP request. @@ -17,12 +16,6 @@ type Router struct { RegisteredRoutes *[]string } -type StaticFileConfig struct { - DirectoryListing bool - HideDotFiles bool - FileDirectory string -} - type Middleware func(handler http.Handler) http.Handler // NewRouter creates a new Router instance. @@ -45,20 +38,32 @@ func (rou *Router) Add(method, pattern string, handler http.Handler) { rou.Router.NewRoute().Methods(method).Path(pattern).Handler(h) } -// Static File Handling. -func (rou *Router) AddStaticFiles(endpoint string, staticConfig StaticFileConfig) { - fileServer := http.FileServer(http.Dir(staticConfig.FileDirectory)) - rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, staticConfig.staticHandler(fileServer))) +// UseMiddleware registers middlewares to the router. +func (rou *Router) UseMiddleware(mws ...Middleware) { + middlewares := make([]mux.MiddlewareFunc, 0, len(mws)) + for _, m := range mws { + middlewares = append(middlewares, mux.MiddlewareFunc(m)) + } + + rou.Use(middlewares...) } -// Check all the static handling configs. -const forbiddenBody string = "403 forbidden" +type staticFileConfig struct { + directoryName string +} + +func (rou *Router) AddStaticFiles(endpoint, fileName string) { + cfg := staticFileConfig{directoryName: fileName} + + fileServer := http.FileServer(http.Dir(cfg.directoryName)) + rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, cfg.staticHandler(fileServer))) +} -func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http.Handler { +func (staticConfig staticFileConfig) staticHandler(fileServer http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.Path - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, "index.html")); err != nil && strings.HasSuffix(url, "/") { + if _, err := os.Stat(filepath.Join(staticConfig.directoryName, "index.html")); err != nil && strings.HasSuffix(url, "/") { http.NotFound(w, r) return @@ -68,20 +73,12 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http fileName := filePath[len(filePath)-1] - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); err == nil && strings.HasPrefix(fileName, ".") { - w.WriteHeader(http.StatusForbidden) - - _, _ = w.Write([]byte(forbiddenBody)) - - return - } - const defaultSwaggerFileName = "openapi.json" - if _, err := os.Stat(filepath.Join(staticConfig.FileDirectory, url)); fileName == defaultSwaggerFileName && err == nil { + if _, err := os.Stat(filepath.Join(staticConfig.directoryName, url)); fileName == defaultSwaggerFileName && err == nil { w.WriteHeader(http.StatusForbidden) - _, _ = w.Write([]byte(forbiddenBody)) + _, _ = w.Write([]byte("403 forbidden")) return } @@ -89,13 +86,3 @@ func (staticConfig StaticFileConfig) staticHandler(fileServer http.Handler) http fileServer.ServeHTTP(w, r) }) } - -// UseMiddleware registers middlewares to the router. -func (rou *Router) UseMiddleware(mws ...Middleware) { - middlewares := make([]mux.MiddlewareFunc, 0, len(mws)) - for _, m := range mws { - middlewares = append(middlewares, mux.MiddlewareFunc(m)) - } - - rou.Use(middlewares...) -} From 886e3efd18d749501f4171ead98dd3b4c2122cdc Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Tue, 25 Jun 2024 13:41:10 +0530 Subject: [PATCH 31/38] remove unwanted files --- examples/http-server/public/abc.json | 128 --------------------------- 1 file changed, 128 deletions(-) delete mode 100644 examples/http-server/public/abc.json diff --git a/examples/http-server/public/abc.json b/examples/http-server/public/abc.json deleted file mode 100644 index d856e7ff1..000000000 --- a/examples/http-server/public/abc.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Http-Server API", - "description": "Example Http-Server with multiple endpoints. This swagger represents the requests and responses for various endpoints included in the [http-server example of gofr](https://github.com/gofr-dev/gofr/tree/development/examples/http-server).", - "version": "1.0.0" - }, - "servers": [ - { - "url": "http://localhost:9000" - } - ], - "paths": { - "/hello": { - "get": { - "summary": "Get a greeting message", - "parameters": [ - { - "in": "query", - "name": "name", - "schema": { - "type": "string" - }, - "description": "Name to include in the greeting message" - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "/error": { - "get": { - "summary": "Simulate an error response", - "responses": { - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - }, - "/redis": { - "get": { - "summary": "Simulate a response from Redis", - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "/mysql": { - "get": { - "summary": "Simulate a response from MySQL", - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "/trace": { - "get": { - "summary": "Simulate the tracing feature for a request", - "responses": { - "200": { - "description": "Successful response" - } - } - } - } - } -} \ No newline at end of file From 8054eeccc172c04f25ce02225b0aad4fc2e52ee8 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 13:02:12 +0530 Subject: [PATCH 32/38] add test for invalid file and custom route --- pkg/gofr/gofr_test.go | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index b5aca3950..221640ff5 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -568,10 +568,13 @@ func TestStaticHandler(t *testing.T) { // Generating some files for testing htmlContent := []byte("Test Static File

Testing Static File

") - createPublicDirectory(t, htmlContent) + createPublicDirectory(t, defaultPublicStaticDir, htmlContent) + createPublicDirectory(t, "testdir", htmlContent) app := New() + app.AddStaticFiles("gofr", "./testdir") + app.httpRegistered = true app.httpServer.port = 8022 @@ -604,6 +607,21 @@ func TestStaticHandler(t *testing.T) { path: "/" + defaultPublicStaticDir, statusCode: http.StatusNotFound, }, + { + desc: "check file content index.html in custom dir", + method: http.MethodGet, + path: "/" + "gofr" + "/" + indexHTML, + statusCode: http.StatusOK, + expectedBodyLength: len(htmlContent), + expectedResponseHeaderType: "text/html; charset=utf-8", + expectedBody: string(htmlContent), + }, + { + desc: "check public endpoint in custom dir", + method: http.MethodGet, + path: "/" + "gofr", + statusCode: http.StatusNotFound, + }, } for i, tc := range tests { @@ -651,7 +669,24 @@ func TestStaticHandler(t *testing.T) { } } -func createPublicDirectory(t *testing.T, htmlContent []byte) { +func TestStaticHandlerInvalidFilePath(t *testing.T) { + // Generating some files for testing + htmlContent := []byte("Test Static File

Testing Static File

") + + createPublicDirectory(t, "!@#$%^&", htmlContent) + + logs := testutil.StderrOutputForFunc(func() { + app := New() + + app.AddStaticFiles("gofr", ".//,.!@#$%^&") + }) + + assert.Contains(t, logs, "no such file or directory") + assert.Contains(t, logs, "error in registering '/gofr' static endpoint") + +} + +func createPublicDirectory(t *testing.T, defaultPublicStaticDir string, htmlContent []byte) { t.Helper() directory := "./" + defaultPublicStaticDir From f725bc4283bae677e5f925cdf9d85298056982e7 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 13:06:41 +0530 Subject: [PATCH 33/38] update invalid filepath test --- pkg/gofr/gofr_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 221640ff5..3f762e741 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -671,10 +671,6 @@ func TestStaticHandler(t *testing.T) { func TestStaticHandlerInvalidFilePath(t *testing.T) { // Generating some files for testing - htmlContent := []byte("Test Static File

Testing Static File

") - - createPublicDirectory(t, "!@#$%^&", htmlContent) - logs := testutil.StderrOutputForFunc(func() { app := New() From d8be25c04591704ac7a2fd37174602d95b92576a Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 13:54:44 +0530 Subject: [PATCH 34/38] add test and remove unwanted code --- pkg/gofr/gofr_test.go | 18 +++++----- pkg/gofr/http/router.go | 10 ++---- pkg/gofr/http/router_test.go | 67 ++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 3f762e741..49aba652f 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -562,15 +562,19 @@ func Test_AddCronJob_Success(t *testing.T) { assert.Truef(t, pass, "unable to add cron job to cron table") } -const indexHTML = "indexTest.html" - func TestStaticHandler(t *testing.T) { + const indexHTML = "indexTest.html" + // Generating some files for testing htmlContent := []byte("Test Static File

Testing Static File

") createPublicDirectory(t, defaultPublicStaticDir, htmlContent) + defer os.Remove("static/indexTest.html") + createPublicDirectory(t, "testdir", htmlContent) + defer os.RemoveAll("testdir") + app := New() app.AddStaticFiles("gofr", "./testdir") @@ -684,10 +688,11 @@ func TestStaticHandlerInvalidFilePath(t *testing.T) { func createPublicDirectory(t *testing.T, defaultPublicStaticDir string, htmlContent []byte) { t.Helper() + const indexHTML = "indexTest.html" directory := "./" + defaultPublicStaticDir if _, err := os.Stat(directory); err != nil { - if err = os.Mkdir("./"+defaultPublicStaticDir, 0755); err != nil { + if err = os.Mkdir("./"+defaultPublicStaticDir, os.ModePerm); err != nil { t.Fatalf("Couldn't create a "+defaultPublicStaticDir+" directory, error: %s", err) } } @@ -704,11 +709,4 @@ func createPublicDirectory(t *testing.T, defaultPublicStaticDir string, htmlCont } file.Close() - - t.Cleanup(func() { - err := os.Remove(filepath.Join(directory, indexHTML)) - if err != nil { - t.Logf("Couldn't remove %s file", indexHTML) - } - }) } diff --git a/pkg/gofr/http/router.go b/pkg/gofr/http/router.go index 6d2bba229..9b8c8ca5d 100644 --- a/pkg/gofr/http/router.go +++ b/pkg/gofr/http/router.go @@ -52,8 +52,8 @@ type staticFileConfig struct { directoryName string } -func (rou *Router) AddStaticFiles(endpoint, fileName string) { - cfg := staticFileConfig{directoryName: fileName} +func (rou *Router) AddStaticFiles(endpoint, dirName string) { + cfg := staticFileConfig{directoryName: dirName} fileServer := http.FileServer(http.Dir(cfg.directoryName)) rou.Router.NewRoute().PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint, cfg.staticHandler(fileServer))) @@ -63,12 +63,6 @@ func (staticConfig staticFileConfig) staticHandler(fileServer http.Handler) http return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.Path - if _, err := os.Stat(filepath.Join(staticConfig.directoryName, "index.html")); err != nil && strings.HasSuffix(url, "/") { - http.NotFound(w, r) - - return - } - filePath := strings.Split(url, "/") fileName := filePath[len(filePath)-1] diff --git a/pkg/gofr/http/router_test.go b/pkg/gofr/http/router_test.go index 9c599f21e..36f35f28f 100644 --- a/pkg/gofr/http/router_test.go +++ b/pkg/gofr/http/router_test.go @@ -3,7 +3,10 @@ package http import ( "net/http" "net/http/httptest" + "os" + "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" @@ -66,3 +69,67 @@ func TestRouterWithMiddleware(t *testing.T) { testHeaderValue := rec.Header().Get("X-Test-Middleware") assert.Equal(t, "applied", testHeaderValue, "Test_UseMiddleware Failed! header value mismatch.") } + +func TestRouter_AddStaticFiles(t *testing.T) { + cfg := map[string]string{"HTTP_PORT": "8000", "LOG_LEVEL": "INFO"} + _ = container.NewContainer(config.NewMockConfig(cfg)) + + createTestFileAndDirectory(t, "testDir") + defer os.RemoveAll("testDir") + + time.Sleep(1 * time.Second) + + currentWorkingDir, _ := os.Getwd() + + // Create a new router instance using the mock container + router := NewRouter() + router.AddStaticFiles("/gofr", currentWorkingDir+"/testDir") + + // Send a request to the test handler + req := httptest.NewRequest("GET", "/gofr/indexTest.html", http.NoBody) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + // Verify the response + assert.Equal(t, http.StatusOK, rec.Code) + + // Send a request to the test handler + req = httptest.NewRequest("GET", "/gofr/openapi.json", http.NoBody) + rec = httptest.NewRecorder() + router.ServeHTTP(rec, req) + + // Verify the response + assert.Equal(t, http.StatusForbidden, rec.Code) +} + +func createTestFileAndDirectory(t *testing.T, dirName string) { + t.Helper() + htmlContent := []byte("Test Static File

Testing Static File

") + + const indexHTML = "indexTest.html" + + directory := "./" + dirName + if err := os.Mkdir("./"+dirName, os.ModePerm); err != nil { + t.Fatalf("Couldn't create a "+dirName+" directory, error: %s", err) + } + + file, err := os.Create(filepath.Join(directory, indexHTML)) + if err != nil { + t.Fatalf("Couldn't create %s file", indexHTML) + } + + _, err = file.Write(htmlContent) + if err != nil { + t.Fatalf("Couldn't write to %s file", indexHTML) + } + + file.Close() + + file, err = os.Create(filepath.Join(directory, "openapi.json")) + if err != nil { + t.Fatalf("Couldn't create %s file", indexHTML) + } + + file.Close() + +} From a45db0f04f832a879db49395535a6d32f353ad37 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 13:58:27 +0530 Subject: [PATCH 35/38] resolve linters --- pkg/gofr/gofr_test.go | 39 +++++++++++++----------------------- pkg/gofr/http/router_test.go | 4 +++- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 49aba652f..b2423f86e 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -569,6 +569,7 @@ func TestStaticHandler(t *testing.T) { htmlContent := []byte("Test Static File

Testing Static File

") createPublicDirectory(t, defaultPublicStaticDir, htmlContent) + defer os.Remove("static/indexTest.html") createPublicDirectory(t, "testdir", htmlContent) @@ -577,7 +578,7 @@ func TestStaticHandler(t *testing.T) { app := New() - app.AddStaticFiles("gofr", "./testdir") + app.AddStaticFiles("gofrTest", "./testdir") app.httpRegistered = true app.httpServer.port = 8022 @@ -597,33 +598,21 @@ func TestStaticHandler(t *testing.T) { expectedResponseHeaderType string }{ { - desc: "check file content index.html", - method: http.MethodGet, - path: "/" + defaultPublicStaticDir + "/" + indexHTML, - statusCode: http.StatusOK, - expectedBodyLength: len(htmlContent), - expectedResponseHeaderType: "text/html; charset=utf-8", - expectedBody: string(htmlContent), + desc: "check file content index.html", method: http.MethodGet, path: "/" + defaultPublicStaticDir + "/" + indexHTML, + statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), + expectedResponseHeaderType: "text/html; charset=utf-8", expectedBody: string(htmlContent), }, { - desc: "check public endpoint", - method: http.MethodGet, - path: "/" + defaultPublicStaticDir, - statusCode: http.StatusNotFound, + desc: "check public endpoint", method: http.MethodGet, + path: "/" + defaultPublicStaticDir, statusCode: http.StatusNotFound, }, { - desc: "check file content index.html in custom dir", - method: http.MethodGet, - path: "/" + "gofr" + "/" + indexHTML, - statusCode: http.StatusOK, - expectedBodyLength: len(htmlContent), - expectedResponseHeaderType: "text/html; charset=utf-8", - expectedBody: string(htmlContent), + desc: "check file content index.html in custom dir", method: http.MethodGet, path: "/" + "gofrTest" + "/" + indexHTML, + statusCode: http.StatusOK, expectedBodyLength: len(htmlContent), + expectedResponseHeaderType: "text/html; charset=utf-8", expectedBody: string(htmlContent), }, { - desc: "check public endpoint in custom dir", - method: http.MethodGet, - path: "/" + "gofr", + desc: "check public endpoint in custom dir", method: http.MethodGet, path: "/" + "gofrTest", statusCode: http.StatusNotFound, }, } @@ -678,16 +667,16 @@ func TestStaticHandlerInvalidFilePath(t *testing.T) { logs := testutil.StderrOutputForFunc(func() { app := New() - app.AddStaticFiles("gofr", ".//,.!@#$%^&") + app.AddStaticFiles("gofrTest", ".//,.!@#$%^&") }) assert.Contains(t, logs, "no such file or directory") - assert.Contains(t, logs, "error in registering '/gofr' static endpoint") - + assert.Contains(t, logs, "error in registering '/gofrTest' static endpoint") } func createPublicDirectory(t *testing.T, defaultPublicStaticDir string, htmlContent []byte) { t.Helper() + const indexHTML = "indexTest.html" directory := "./" + defaultPublicStaticDir diff --git a/pkg/gofr/http/router_test.go b/pkg/gofr/http/router_test.go index 36f35f28f..afc6dba7f 100644 --- a/pkg/gofr/http/router_test.go +++ b/pkg/gofr/http/router_test.go @@ -75,6 +75,7 @@ func TestRouter_AddStaticFiles(t *testing.T) { _ = container.NewContainer(config.NewMockConfig(cfg)) createTestFileAndDirectory(t, "testDir") + defer os.RemoveAll("testDir") time.Sleep(1 * time.Second) @@ -104,11 +105,13 @@ func TestRouter_AddStaticFiles(t *testing.T) { func createTestFileAndDirectory(t *testing.T, dirName string) { t.Helper() + htmlContent := []byte("Test Static File

Testing Static File

") const indexHTML = "indexTest.html" directory := "./" + dirName + if err := os.Mkdir("./"+dirName, os.ModePerm); err != nil { t.Fatalf("Couldn't create a "+dirName+" directory, error: %s", err) } @@ -131,5 +134,4 @@ func createTestFileAndDirectory(t *testing.T, dirName string) { } file.Close() - } From 683952f7462c974db6f6efba886faab8c55f5f9b Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 14:00:35 +0530 Subject: [PATCH 36/38] fix test --- pkg/gofr/swagger_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/gofr/swagger_test.go b/pkg/gofr/swagger_test.go index 724940ab3..5617bccbf 100644 --- a/pkg/gofr/swagger_test.go +++ b/pkg/gofr/swagger_test.go @@ -79,7 +79,8 @@ func TestSwaggerHandler(t *testing.T) { }{ {"fetch index.html", "", "text/html"}, {"fetch favicon image", "favicon-16x16.png", "image/png"}, - {"fetch js files", "swagger-ui.js", "application/javascript"}, + {"fetch js files", "swagger-ui.js", "text" + + "/javascript"}, } for _, tc := range tests { From f2d186e725526e40aa82ec2f7a2da7de72aa98a8 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 14:01:23 +0530 Subject: [PATCH 37/38] keep inline --- pkg/gofr/swagger_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/gofr/swagger_test.go b/pkg/gofr/swagger_test.go index 5617bccbf..ec4d2dfcc 100644 --- a/pkg/gofr/swagger_test.go +++ b/pkg/gofr/swagger_test.go @@ -79,8 +79,7 @@ func TestSwaggerHandler(t *testing.T) { }{ {"fetch index.html", "", "text/html"}, {"fetch favicon image", "favicon-16x16.png", "image/png"}, - {"fetch js files", "swagger-ui.js", "text" + - "/javascript"}, + {"fetch js files", "swagger-ui.js", "text/javascript"}, } for _, tc := range tests { From fc4ffe3eba2e25b692177fc8c725b9ccac6bfa82 Mon Sep 17 00:00:00 2001 From: mehrotra234 Date: Wed, 26 Jun 2024 17:20:07 +0530 Subject: [PATCH 38/38] fix reveiw comments --- pkg/gofr/gofr_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index b2423f86e..1953839a0 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -686,13 +686,13 @@ func createPublicDirectory(t *testing.T, defaultPublicStaticDir string, htmlCont } } - file, errFile := os.Create(filepath.Join(directory, indexHTML)) + file, err := os.Create(filepath.Join(directory, indexHTML)) - if errFile != nil { + if err != nil { t.Fatalf("Couldn't create %s file", indexHTML) } - _, err := file.Write(htmlContent) + _, err = file.Write(htmlContent) if err != nil { t.Fatalf("Couldn't write to %s file", indexHTML) }