Skip to content

Commit

Permalink
Merge pull request #23 from InVisionApp/add-statics
Browse files Browse the repository at this point in the history
Adding static file handlers
  • Loading branch information
dselans authored Mar 14, 2017
2 parents 34336c1 + 67fa4b1 commit e4ee692
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 1 deletion.
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,41 @@ routes.Handle("/", middlewareHandler.Handle([]rye.Handler{

```

## Serving Static Files

Rye has the ability to add serving static files in the chain. Two handlers
have been provided: `StaticFilesystem` and `StaticFile`. These middlewares
should always be used at the end of the chain. Their configuration is
simply based on an absolute path on the server and possibly a skipped
path prefix.

The use case here could be a powerful one. Rye allows you to serve a filesystem
just as a whole or a single file. Used together you could facilitate an application
which does both -> fulfilling the capability to provide a single page application.
For example, if you had a webpack application which served static resources and
artifacts, you would use the `StaticFilesystem` to serve those. Then you'd use
`StaticFile` to serve the single page which refers to the single-page application
through `index.html`.

A full sample is provided in the `static-examples` folder. Here's a snippet from
the example using Gorilla:

```go
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("NewStaticFile: Could not get working directory.")
}

routes.PathPrefix("/dist/").Handler(middlewareHandler.Handle([]rye.Handler{
rye.MiddlewareRouteLogger(),
rye.NewStaticFilesystem(pwd+"/dist/", "/dist/"),
}))

routes.PathPrefix("/ui/").Handler(middlewareHandler.Handle([]rye.Handler{
rye.MiddlewareRouteLogger(),
rye.NewStaticFile(pwd + "/dist/index.html"),
}))
```

### Middleware list

Expand All @@ -193,7 +227,10 @@ routes.Handle("/", middlewareHandler.Handle([]rye.Handler{
| [CIDR](middleware_cidr.go) | Provide request IP whitelisting |
| [CORS](middleware_cors.go) | Provide CORS functionality for routes |
| [JWT](middleware_jwt.go) | Provide JWT validation |
| [Route Logger](middleware_routelogger.go) | Provide basic logging for a specific route |
| [Route Logger](middleware_routelogger.go) | Provide basic logging for a specific route |
| [Static File](middleware_static_file.go) | Provides serving a single file |
| [Static Filesystem](middleware_static_filesystem.go) | Provides serving a single file |


### A Note on the JWT Middleware

Expand Down
43 changes: 43 additions & 0 deletions middleware_static_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package rye

import (
"net/http"
)

type staticFile struct {
path string
}

/*
NewStaticFile creates a new handler to serve a file from a path on the local filesystem.
The path should be an absolute path -> i.e., it's up to the program using Rye to
correctly determine what path it should be serving from. An example is available
in the `static_example.go` file which shows setting up a path relative to
the go executable.
The purpose of this handler is to serve a specific file for any requests through the
route handler. For instance, in the example below, any requests made to `/ui` will
always be routed to /dist/index.html. This is important for single page applications
which happen to use client-side routers. Therefore, you might have a webpack application
with it's entrypoint `/dist/index.html`. That file may point at your `bundle.js`.
Every request into the app will need to always be routed to `/dist/index.html`
Example use case:
routes.PathPrefix("/ui/").Handler(middlewareHandler.Handle([]rye.Handler{
rye.MiddlewareRouteLogger(),
rye.NewStaticFile(pwd + "/dist/index.html"),
}))
*/
func NewStaticFile(path string) func(rw http.ResponseWriter, req *http.Request) *Response {
s := &staticFile{
path: path,
}
return s.handle
}

func (s *staticFile) handle(rw http.ResponseWriter, req *http.Request) *Response {
http.ServeFile(rw, req, s.path)
return nil
}
67 changes: 67 additions & 0 deletions middleware_static_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package rye

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Static File Middleware", func() {

var (
request *http.Request
response *httptest.ResponseRecorder

path string
testPath string
)

BeforeEach(func() {
response = httptest.NewRecorder()
request = &http.Request{}
testPath, _ = os.Getwd()
})

Describe("handle", func() {
Context("when a valid file is referenced", func() {
It("should return a response", func() {
path = "/static-examples/dist/index.html"
url, _ := url.Parse("/thisstuff")
request.URL = url
resp := NewStaticFile(testPath+path)(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(200))

body, err := ioutil.ReadAll(response.Body)
Expect(err).To(BeNil())
Expect(body).To(ContainSubstring("Index.html"))
})

It("should return a Moved Permanently response", func() {
path = "/static-examples/dist/index.html"
url, _ := url.Parse("/thisstuff")
request.URL = url
resp := NewStaticFile("")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(301))
})

It("should return a File Not Found response", func() {
path = "/static-examples/dist/index.html"
url, _ := url.Parse("/thisstuff")
request.URL = url
resp := NewStaticFile(path)(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(404))
})
})
})
})
44 changes: 44 additions & 0 deletions middleware_static_filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package rye

import (
"net/http"
)

type staticFilesystem struct {
path string
stripPrefix string
}

/*
NewStaticFilesystem creates a new handler to serve a filesystem from a path
on the local filesystem. The path should be an absolute path -> i.e., it's
up to the program using Rye to correctly determine what path it should be
serving from. An example is available in the `static_example.go` file which
shows setting up a path relative to the go executable.
The primary benefit of this is to serve an entire set of files. You can
pre-pend typical Rye middlewares to the chain. The static filesystem
middleware should always be last in a chain, however. The `stripPrefix` allows
you to ignore the prefix on requests so that the proper files will be matched.
Example use case:
routes.PathPrefix("/dist/").Handler(middlewareHandler.Handle([]rye.Handler{
rye.MiddlewareRouteLogger(),
rye.NewStaticFilesystem(pwd+"/dist/", "/dist/"),
}))
*/
func NewStaticFilesystem(path string, stripPrefix string) func(rw http.ResponseWriter, req *http.Request) *Response {
s := &staticFilesystem{
path: path,
stripPrefix: stripPrefix,
}
return s.handle
}

func (s *staticFilesystem) handle(rw http.ResponseWriter, req *http.Request) *Response {
x := http.StripPrefix(s.stripPrefix, http.FileServer(http.Dir(s.path)))
x.ServeHTTP(rw, req)
return nil
}
103 changes: 103 additions & 0 deletions middleware_static_filesystem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package rye

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Static File Middleware", func() {

var (
request *http.Request
response *httptest.ResponseRecorder

path string
testPath string
)

BeforeEach(func() {
response = httptest.NewRecorder()
request = &http.Request{}
testPath, _ = os.Getwd()
})

Describe("handle", func() {
Context("when a valid file is referenced", func() {
It("should return a response", func() {
path = "/static-examples/dist/"

request, _ := http.NewRequest("GET", "/dist/test.html", nil)

resp := NewStaticFilesystem(testPath+path, "/dist/")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(200))

body, err := ioutil.ReadAll(response.Body)
Expect(err).To(BeNil())
Expect(body).To(ContainSubstring("Test.html"))
})

It("should return Index.html when request is just path", func() {
path = "/static-examples/dist/"

request, _ := http.NewRequest("GET", "/dist/", nil)

resp := NewStaticFilesystem(testPath+path, "/dist/")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(200))

body, err := ioutil.ReadAll(response.Body)
Expect(err).To(BeNil())
Expect(body).To(ContainSubstring("Index.html"))
})

It("should return Index.html when strip prefix is empty", func() {
path = "/static-examples/dist/"

request, _ := http.NewRequest("GET", "/", nil)

resp := NewStaticFilesystem(testPath+path, "")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(200))

body, err := ioutil.ReadAll(response.Body)
Expect(err).To(BeNil())
Expect(body).To(ContainSubstring("Index.html"))
})

It("should return Index.html when strip prefix is empty", func() {
path = "/static-examples/dist/"

request, _ := http.NewRequest("GET", "/ASDads.HTML", nil)

resp := NewStaticFilesystem(testPath+path, "")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(404))
})

It("should return test.css on subpath", func() {
path = "/static-examples/dist/"

request, _ := http.NewRequest("GET", "/styles/test.css", nil)

resp := NewStaticFilesystem(testPath+path, "")(response, request)
Expect(resp).To(BeNil())
Expect(response).ToNot(BeNil())
Expect(response.Code).To(Equal(200))

body, err := ioutil.ReadAll(response.Body)
Expect(err).To(BeNil())
Expect(body).To(ContainSubstring("test.css"))
})
})
})
})
11 changes: 11 additions & 0 deletions static-examples/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<title>Index.html</title>
<link rel="stylesheet" type="text/css" href="/dist/styles/index.css">
</head>
<body>
<h1>
Index.html
</h1>
</body>
</html>
5 changes: 5 additions & 0 deletions static-examples/dist/styles/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
h1 {
color: #0000AC;
margin-left: 120px;
margin-top: 120px;
}
6 changes: 6 additions & 0 deletions static-examples/dist/styles/test.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* test.css */
h1 {
color: #ff0000;
margin-left: 20px;
margin-top: 20px;
}
11 changes: 11 additions & 0 deletions static-examples/dist/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<title>Test.html</title>
<link rel="stylesheet" type="text/css" href="styles/test.css">
</head>
<body>
<h1>
Test.html
</h1>
</body>
</html>
Loading

0 comments on commit e4ee692

Please sign in to comment.