Skip to content

Commit

Permalink
added middleware for implementing filter request
Browse files Browse the repository at this point in the history
  • Loading branch information
yookoala committed Feb 7, 2018
1 parent 8da8208 commit 0eaa8c7
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 0 deletions.
80 changes: 80 additions & 0 deletions session.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package gofast

import (
"fmt"
"net"
"net/http"
"path"
"path/filepath"
"regexp"
"strings"

"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/httpfs"
)

// SessionHandler handles the gofast *Reqeust with the provided given Client.
Expand Down Expand Up @@ -259,6 +264,81 @@ func MapEndpoint(endpointFile string) Middleware {
}
}

// MapFilterRequest changes the request role to RoleFilter and add the
// Data stream from the given file system, if file exists. Also
// set the required params to request.
//
// If the file do not exists or cannot be opened, the middleware
// will return empty response pipe and the error.
func MapFilterRequest(fs http.FileSystem) Middleware {
return func(inner SessionHandler) SessionHandler {
return func(client Client, req *Request) (*ResponsePipe, error) {

// force role to be RoleFilter
req.Role = RoleFilter

// define some required cgi parameters
// with the given http request
r := req.Raw
fastcgiScriptName := r.URL.Path

var fastcgiPathInfo string
pathinfoRe := regexp.MustCompile(`^(.+\.php)(/?.+)$`)
if matches := pathinfoRe.FindStringSubmatch(fastcgiScriptName); len(matches) > 0 {
fastcgiScriptName, fastcgiPathInfo = matches[1], matches[2]
}

req.Params["PATH_INFO"] = fastcgiPathInfo
req.Params["SCRIPT_NAME"] = fastcgiScriptName
req.Params["DOCUMENT_URI"] = r.URL.Path

// handle directory index
urlPath := r.URL.Path
if strings.HasSuffix(urlPath, "/") {
urlPath = path.Join(urlPath, "index.php")
}

// find the file
f, err := fs.Open(urlPath)
if err != nil {
err = fmt.Errorf("cannot open file: %s", err)
return nil, err
}

// map fcgi params for filtering
s, err := f.Stat()
if err != nil {
err = fmt.Errorf("cannot stat file: %s", err)
return nil, err
}
req.Params["FCGI_DATA_LAST_MOD"] = fmt.Sprintf("%d", s.ModTime().Unix())
req.Params["FCGI_DATA_LENGTH"] = fmt.Sprintf("%d", s.Size())

// use the file as FCGI_DATA in request
req.Data = f
return inner(client, req)
}
}
}

// NewFilterLocalFS is a shortcut to use NewFilterFS with
// a http.FileSystem created for the given local folder.
func NewFilterLocalFS(root string) Middleware {
fs := httpfs.New(vfs.OS(root))
return NewFilterFS(fs)
}

// NewFilterFS chains BasicParamsMap, MapHeader and MapFilterRequest
// to implement Middleware that prepares a fastcgi Filter session
// environment.
func NewFilterFS(fs http.FileSystem) Middleware {
return Chain(
BasicParamsMap,
MapHeader,
MapFilterRequest(fs),
)
}

// NewPHPFS chains BasicParamsMap, MapHeader and FileSystemRouter to implement
// Middleware that prepares an ordinary PHP hosting session environment.
func NewPHPFS(root string) Middleware {
Expand Down
178 changes: 178 additions & 0 deletions session_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package gofast_test

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"testing"
"time"

"github.com/yookoala/gofast"
)

type File struct {
*strings.Reader
FileInfo
}

func (f File) Stat() (os.FileInfo, error) {
return f.FileInfo, nil
}

func (f File) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}

func (f File) Close() error {
return nil
}

type FileEntry struct {

// stat result
FileInfo

// file content
content string
}

type FileInfo struct {
// base name of the file
name string

// length in bytes for regular files; system-dependent for others
size int64

// file mode bits
mode os.FileMode

// modification time
modTime time.Time
}

func (fi FileInfo) Name() string {
return fi.name
}

func (fi FileInfo) Size() int64 {
return fi.size
}

func (fi FileInfo) Mode() os.FileMode {
return fi.mode
}

func (fi FileInfo) ModTime() time.Time {
return fi.modTime
}

func (fi FileInfo) IsDir() bool {
return fi.mode.IsDir()
}

func (fi FileInfo) Sys() interface{} {
return nil
}

type VFS map[string]FileEntry

func (vfs VFS) Open(name string) (f http.File, err error) {
name = strings.Trim(name, "/")
fi, ok := vfs[name]
if !ok {
err = os.ErrNotExist
}
f = File{
FileInfo: fi.FileInfo,
Reader: strings.NewReader(fi.content),
}
return
}

func NopClient(id uint16) gofast.Client {
return nopClient(id)
}

type nopClient uint16

func (nopClient) Do(req *gofast.Request) (resp *gofast.ResponsePipe, err error) {
return
}

func (nc nopClient) AllocID() uint16 {
return uint16(nc)
}

func (nopClient) ReleaseID(uint16) {

}

func (nopClient) Close() error {
return nil
}

func TestMapFilterRequest(t *testing.T) {

dummyModTime := time.Now().Add(-10 * time.Second)

// dummy filesystem to test with
vfs := VFS{
"index.html": FileEntry{
FileInfo: FileInfo{
name: "index.html",
size: 11,
mode: 0644,
modTime: dummyModTime,
},
content: "hello world",
},
}

// dummy session handler
inner := func(client gofast.Client, req *gofast.Request) (resp *gofast.ResponsePipe, err error) {

if want, have := gofast.RoleFilter, req.Role; want != have {
t.Errorf("expected: %#v, got: %#v", want, have)
}

if req.Data == nil {
t.Error("filter request requries a data stream")
} else if content, err := ioutil.ReadAll(req.Data); err != nil {
t.Errorf("unexpected error: %s", err)
} else if want, have := "hello world", fmt.Sprintf("%s", content); want != have {
t.Errorf("expected: %#v, got: %#v", want, have)
}

if lastModStr, ok := req.Params["FCGI_DATA_LAST_MOD"]; !ok {
t.Error("filter request requries param FCGI_DATA_LAST_MOD")
} else if lastMod, err := strconv.ParseInt(lastModStr, 10, 32); err != nil {
t.Errorf("invalid parsing FCGI_DATA_LAST_MOD (%s)", err)
} else if want, have := dummyModTime.Unix(), lastMod; want != have {
t.Errorf("expected: %#v, got: %#v", want, have)
}

if _, ok := req.Params["FCGI_DATA_LENGTH"]; !ok {
t.Error("filter request requries param FCGI_DATA_LENGTH")
} else if _, err = strconv.ParseInt(req.Params["FCGI_DATA_LENGTH"], 10, 32); err != nil {
t.Errorf("invalid parsing FCGI_DATA_LENGTH (%s)", err)
}
return
}
sess := gofast.MapFilterRequest(vfs)(inner)

// make dummy request and examine in dummy session handler
r, err := http.NewRequest("GET", "http://foobar.com/index.html", nil)
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
c := NopClient(1)
_, err = sess(c, gofast.NewRequest(c, r))
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
}

0 comments on commit 0eaa8c7

Please sign in to comment.