Skip to content

Commit

Permalink
Major refactor, added fileopener and caching
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Dec 27, 2016
1 parent add9eeb commit 6ae1225
Show file tree
Hide file tree
Showing 12 changed files with 518 additions and 338 deletions.
47 changes: 18 additions & 29 deletions README.md
Expand Up @@ -58,24 +58,20 @@ Extracts URIs from
- `<use href="..." xlink:href="...">`

## Usage
You can use `NewLookup` (or `NewParser`) to parse a file as-is. Use `NewRecursiveLookup` (or `NewRecursiveParser`) to parse the file and also read and parse the content of all referenced URIs.

### Middleware
``` go
lookup := push.NewLookup("example.com", "/") // host and base URI

http.HandleFunc("/", push.Middleware(lookup, nil, func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc("/", push.Middleware("example.com/", fileOpener, cache, func(w http.ResponseWriter, r *http.Request) {
// ...
}))
```

Pass `nil` for `fileOpener` and `cache` to disable recursive parsing and URI caching respectively.

### ResponseWriter
Wrap an existing `http.ResponseWriter` so that it pushes resources automatically:
``` go
lookup := push.NewLookup("example.com", "/") // host and base URI

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if pushWriter, err := push.ResponseWriter(w, r, lookup, nil); err == nil {
if pushWriter, err := push.ResponseWriter(w, r, "example.com/", fileOpener, cache); err == nil {
defer pushWriter.Close() // Close returns an error...
w = pushWriter
}
Expand All @@ -84,27 +80,25 @@ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
})
```

Pass `nil` for `fileOpener` and `cache` to disable recursive parsing and URI caching respectively.

### Reader
Wrap a reader and obtain the URIs from a channel:
``` go
var uriChan chan string

func openIndex() io.Reader {
r, _ := os.Open("index.html")

parser, err := push.NewRecursiveParser("example.com", "/", push.FileOpenerFunc(func(uri string) (io.Reader, string, error) {
uriHandler := push.URIHandlerFunc(func(uri string) error {
// is called concurrently when using a recursive parser
return nil
})
fileOpener := push.FileOpenerFunc(func(uri string) (io.Reader, string, error) {
// open file for uri
return r, mimetype, nil
}), "/index.html")
if err != nil {
panic(err)
}
})
parser := push.NewParser("example.com/", fileOpener, uriHandler)

return p.Reader(r, "localhost", "/index.html", push.URIHandlerFunc(func(uri string) error {
// is called asynchronously when using a recursive parser
fmt.Println(uri)
return nil
}))
return p.Reader(parser, r, "text/html", "/index.html")
}
```

Expand All @@ -113,31 +107,26 @@ List the resource URIs found:
``` go
r, _ := os.Open("index.html")

parser, err := push.NewParser("example.com", "/", "/index.html")
if err != nil {
panic(err)
}

uris, err := push.List(parser, r, "text/html")
uris, err := push.List("example.com/", fileOpener, r, "text/html", "/index.html")
if err != nil {
panic(err)
}
```

### Push
### Low-level usage
`Push` pushes resources to `pusher`. It is the underlying functionality of `ResponseWriter`.
``` go
httpPusher, ok := w.(http.Pusher)
if ok {
pusher := NewPusher(httpPusher, &http.PushOptions{"", http.Header{}})

parser, err := push.NewParser("example.com", "/", "index.html")
parser, err := push.NewParser("example.com/", nil, pusher)
if err != nil {
panic(err)
}

tr := io.TeeReader(r, w)
err := parser.Parse(r, "text/html", pusher)
err := parser.Parse(r, "text/html", "/index.html")
}
```

Expand Down
47 changes: 47 additions & 0 deletions cache.go
@@ -0,0 +1,47 @@
package push

import "sync"

// Cache is an interface that allows Middleware and ResponseWriter to cache the results of the list of resources to improve performance.
type Cache interface {
Get(string) ([]string, bool)
Add(string, string)
Del(string)
}

////////////////

type DefaultCache struct {
uris map[string][]string
mutex sync.RWMutex
}

func NewDefaultCache() *DefaultCache {
return &DefaultCache{make(map[string][]string), sync.RWMutex{}}
}

func (c *DefaultCache) Get(uri string) ([]string, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()

resources, ok := c.uris[uri]
return resources, ok
}

func (c *DefaultCache) Add(uri string, resource string) {
c.mutex.Lock()
defer c.mutex.Unlock()

if _, ok := c.uris[uri]; !ok {
c.uris[uri] = []string{resource}
return
}
c.uris[uri] = append(c.uris[uri], resource)
}

func (c *DefaultCache) Del(uri string) {
c.mutex.Lock()
defer c.mutex.Unlock()

delete(c.uris, uri)
}
Binary file modified example/cpu.pprof
Binary file not shown.
39 changes: 23 additions & 16 deletions example/main.go
Expand Up @@ -3,6 +3,7 @@ package main
import (
"log"
"net/http"
"path"
"time"

"github.com/pkg/profile"
Expand All @@ -12,24 +13,30 @@ import (
func main() {
defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()

lookup := push.NewLookup("localhost", "/")
fileOpener := push.NewDefaultFileOpener("www")
cache := push.NewDefaultCache()

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Handle("/", push.Middleware("example.com/", fileOpener, cache, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(50 * time.Millisecond)

if pushWriter, err := push.ResponseWriter(w, r, lookup, nil); err == nil {
defer func() {
if err := pushWriter.Close(); err != nil {
log.Println(err, r.RequestURI)
}
}()
w = pushWriter
} else if err != push.ErrRecursivePush && err != push.ErrNoPusher {
log.Println(err, r.RequestURI)
}

http.ServeFile(w, r, "www"+r.URL.Path)
})
http.ServeFile(w, r, path.Join("www", r.URL.Path))
})))

// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// time.Sleep(50 * time.Millisecond)

// if pushWriter, err := push.ResponseWriter(w, r, "example.com/", fileOpener, cache); err == nil {
// defer func() {
// if err := pushWriter.Close(); err != nil {
// log.Println(err, r.RequestURI)
// }
// }()
// w = pushWriter
// } else if err != push.ErrNoParser && err != push.ErrRecursivePush {
// log.Println(err, r.RequestURI)
// }

// http.ServeFile(w, r, path.Join("www", r.URL.Path))
// })

go func() {
log.Fatal(http.ListenAndServe(":80", nil))
Expand Down
36 changes: 36 additions & 0 deletions fileopener.go
@@ -0,0 +1,36 @@
package push

import (
"io"
"os"
"path"
)

// FileOpener is an interface that allows the parser to load embedded resources recursively.
type FileOpener interface {
Open(string) (io.Reader, string, error)
}

type FileOpenerFunc func(string) (io.Reader, string, error)

func (f FileOpenerFunc) Open(uri string) (io.Reader, string, error) {
return f(uri)
}

////////////////

type DefaultFileOpener struct {
basePath string
}

func NewDefaultFileOpener(basePath string) *DefaultFileOpener {
return &DefaultFileOpener{basePath}
}

func (o *DefaultFileOpener) Open(uri string) (io.Reader, string, error) {
r, err := os.Open(path.Join(o.basePath, uri))
if err != nil {
return nil, "", err
}
return r, ExtToMimetype[path.Ext(uri)], nil
}
70 changes: 70 additions & 0 deletions handler.go
@@ -0,0 +1,70 @@
package push

import (
"errors"
"net/http"
"sync"
)

// ErrNoPusher is returned when the ResponseWriter does not implement the Pusher interface.
var ErrNoPusher = errors.New("ResponseWriter is not a Pusher")

// URIHandler is a callback definition that is called when a resource URI is found.
type URIHandler interface {
URI(string) error
}

type URIHandlerFunc func(string) error

func (f URIHandlerFunc) URI(uri string) error {
return f(uri)
}

////////////////

// PushHandler is a URIHandler that pushes resources to the client.
type PushHandler struct {
pusher http.Pusher
opts *http.PushOptions
}

func NewPushHandler(pusher http.Pusher, opts *http.PushOptions) *PushHandler {
if opts == nil {
opts = &http.PushOptions{"", http.Header{}}
}
opts.Header.Set("X-Pushed", "1")
return &PushHandler{pusher, opts}
}

func NewPushHandlerFromResponseWriter(w http.ResponseWriter) (*PushHandler, error) {
pusher, ok := w.(http.Pusher)
if !ok {
return nil, ErrNoPusher
}
opts := &http.PushOptions{"", http.Header{}}
opts.Header.Set("X-Pushed", "1")
return &PushHandler{pusher, opts}, nil
}

func (p *PushHandler) URI(uri string) error {
return p.pusher.Push(uri, p.opts)
}

////////////////

// ListHandler is a URIHandler that collects all resource URIs in a list.
type ListHandler struct {
URIs []string
mutex sync.Mutex
}

func NewListHandler() *ListHandler {
return &ListHandler{[]string{}, sync.Mutex{}}
}

func (h *ListHandler) URI(uri string) error {
h.mutex.Lock()
h.URIs = append(h.URIs, uri)
h.mutex.Unlock()
return nil
}

0 comments on commit 6ae1225

Please sign in to comment.