Skip to content

Commit

Permalink
Merge ba8ca96 into 41f9387
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Mancke committed Jun 20, 2016
2 parents 41f9387 + ba8ca96 commit e5d5d24
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 78 deletions.
12 changes: 9 additions & 3 deletions composition/content_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"

"github.com/tarent/lib-compose/logging"
"strings"
)

// IsFetchable returns, whether the fetch definition refers to a fetchable resource
Expand All @@ -31,7 +32,8 @@ type ContentFetcher struct {
json map[string]interface{}
mutex sync.Mutex
}
contentLoader ContentLoader
httpContentLoader ContentLoader
fileContentLoader ContentLoader
}

// NewContentFetcher creates a ContentFetcher with an HtmlContentParser as default.
Expand All @@ -40,7 +42,8 @@ type ContentFetcher struct {
func NewContentFetcher(defaultMetaJSON map[string]interface{}) *ContentFetcher {
f := &ContentFetcher{}
f.r.results = make([]*FetchResult, 0, 0)
f.contentLoader = NewHttpContentLoader()
f.httpContentLoader = NewHttpContentLoader()
f.fileContentLoader = NewFileContentLoader()
f.meta.json = defaultMetaJSON
if f.meta.json == nil {
f.meta.json = make(map[string]interface{})
Expand Down Expand Up @@ -110,7 +113,10 @@ func (fetcher *ContentFetcher) AddFetchJob(d *FetchDefinition) {
}

func (fetcher *ContentFetcher) fetch(fd *FetchDefinition) (Content, error) {
return fetcher.contentLoader.Load(fd)
if strings.HasPrefix(fd.URL, FileURLPrefix) {
return fetcher.fileContentLoader.Load(fd)
}
return fetcher.httpContentLoader.Load(fd)
}

// isAlreadySheduled checks, if there is already a job for a FetchDefinition, or it is already fetched.
Expand Down
2 changes: 1 addition & 1 deletion composition/content_fetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func Test_ContentFetcher_FetchingWithDependency(t *testing.T) {
bazzFd := getFetchDefinitionMock(ctrl, loader, "/bazz", []*FetchDefinition{barFd}, time.Millisecond, map[string]interface{}{})

fetcher := NewContentFetcher(nil)
fetcher.contentLoader = loader
fetcher.httpContentLoader = loader

fetcher.AddFetchJob(fooFd)
fetcher.AddFetchJob(bazzFd)
Expand Down
77 changes: 39 additions & 38 deletions composition/fetch_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,51 +46,53 @@ var ForwardResponseHeaders = []string{
"Set-Cookie",
"WWW-Authenticate"}

const(
DEFAULTTIMEOUT time.Duration = 10 * time.Second
const (
DefaultTimeout time.Duration = 10 * time.Second
FileURLPrefix = "file://"
)

// FetchDefinition is a descriptor for fetching Content from an endpoint.
type FetchDefinition struct {
URL string
Timeout time.Duration
Required bool
Header http.Header
Method string
Body io.Reader
RespProc ResponseProcessor
ErrHandler ErrorHandler
URL string
Timeout time.Duration
Required bool
Header http.Header
Method string
Body io.Reader
RespProc ResponseProcessor
ErrHandler ErrorHandler
//ServeResponseHeaders bool
//IsPrimary bool
//FallbackURL string
}

// Creates a fetch definition
func NewFetchDefinition(url string) *FetchDefinition {
return NewFetchDefinitionWithResponseProcessor(url, nil)
}

func NewFetchDefinitionWithErrorHandler(url string, errHandler ErrorHandler) *FetchDefinition {
if (errHandler == nil) {
errHandler = NewDefaultErrorHandler()
}
return &FetchDefinition{
URL: url,
Timeout: DEFAULTTIMEOUT,
Required: true,
Method: "GET",
ErrHandler: errHandler,
}
if errHandler == nil {
errHandler = NewDefaultErrorHandler()
}
return &FetchDefinition{
URL: url,
Timeout: DefaultTimeout,
Required: true,
Method: "GET",
ErrHandler: errHandler,
}
}

// If a ResponseProcessor-Implementation is given it can be used to change the response before composition
func NewFetchDefinitionWithResponseProcessor(url string, rp ResponseProcessor) *FetchDefinition {
return &FetchDefinition{
URL: url,
Timeout: DEFAULTTIMEOUT,
Required: true,
Method: "GET",
RespProc: rp,
ErrHandler: NewDefaultErrorHandler(),
URL: url,
Timeout: DefaultTimeout,
Required: true,
Method: "GET",
RespProc: rp,
ErrHandler: NewDefaultErrorHandler(),
}
}

Expand Down Expand Up @@ -118,14 +120,14 @@ func NewFetchDefinitionWithResponseProcessorFromRequest(baseUrl string, r *http.
}

return &FetchDefinition{
URL: baseUrl + fullPath,
Timeout: DEFAULTTIMEOUT,
Header: copyHeaders(r.Header, nil, ForwardRequestHeaders),
Method: r.Method,
Body: r.Body,
Required: true,
RespProc: rp,
ErrHandler: NewDefaultErrorHandler(),
URL: baseUrl + fullPath,
Timeout: DefaultTimeout,
Header: copyHeaders(r.Header, nil, ForwardRequestHeaders),
Method: r.Method,
Body: r.Body,
Required: true,
RespProc: rp,
ErrHandler: NewDefaultErrorHandler(),
}
}

Expand Down Expand Up @@ -161,11 +163,10 @@ type DefaultErrorHandler struct {
}

func NewDefaultErrorHandler() *DefaultErrorHandler {
deh := new(DefaultErrorHandler)
return deh
deh := new(DefaultErrorHandler)
return deh
}

func (der *DefaultErrorHandler) Handle(err error, w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Gateway: " + err.Error(), 502)
http.Error(w, "Bad Gateway: "+err.Error(), 502)
}

51 changes: 51 additions & 0 deletions composition/file_content_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package composition

import (
"errors"
"fmt"
"github.com/tarent/lib-compose/logging"
"os"
"strings"
"time"
)

var ResponseProcessorsNotApplicable = errors.New("request processors are not apliable on file content")

type FileContentLoader struct {
parser ContentParser
}

func NewFileContentLoader() *FileContentLoader {
return &FileContentLoader{
parser: &HtmlContentParser{},
}
}

func (loader *FileContentLoader) Load(fd *FetchDefinition) (Content, error) {
if fd.RespProc != nil {
return nil, ResponseProcessorsNotApplicable
}
filename := strings.TrimPrefix(fd.URL, FileURLPrefix)
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("error opening file %v: %v", fd.URL, err)
}

c := NewMemoryContent()
c.url = fd.URL
c.httpStatusCode = 200

if strings.HasSuffix(filename, ".html") {
parsingStart := time.Now()
err := loader.parser.Parse(c, f)
logging.Logger.
WithField("full_url", c.URL()).
WithField("duration", time.Since(parsingStart)).
Debug("content parsing")
f.Close()
return c, err
}

c.reader = f
return c, nil
}
81 changes: 81 additions & 0 deletions composition/file_content_loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package composition

import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"testing"
"time"
)

func init() {
rand.Seed(time.Now().UTC().UnixNano())
}

func Test_FileContentLoader_LoadHTML(t *testing.T) {
a := assert.New(t)

fileName := filepath.Join(os.TempDir(), randString(10)+".html")

err := ioutil.WriteFile(fileName, []byte("<html><head>some head content</head></html>"), 0660)
a.NoError(err)

loader := NewFileContentLoader()
c, err := loader.Load(NewFetchDefinition(FileURLPrefix + fileName))
a.NoError(err)
a.NotNil(c)
a.Nil(c.Reader())
a.Equal(FileURLPrefix+fileName, c.URL())
eqFragment(t, "some head content", c.Head())
a.Equal(0, len(c.Body()))
}

func Test_FileContentLoader_LoadStream(t *testing.T) {
a := assert.New(t)

fileName := filepath.Join(os.TempDir(), randString(10)+".css")

err := ioutil.WriteFile(fileName, []byte("some non html content"), 0660)
a.NoError(err)

loader := NewFileContentLoader()
c, err := loader.Load(NewFetchDefinition(FileURLPrefix + fileName))
a.NoError(err)
a.NotNil(c)
body, err := ioutil.ReadAll(c.Reader())
a.NoError(err)
a.Equal("some non html content", string(body))
}

func Test_FileContentLoader_LoadError(t *testing.T) {
a := assert.New(t)

loader := NewFileContentLoader()
_, err := loader.Load(NewFetchDefinition("/tmp/some/non/existing/path"))
a.Error(err)
}

func Test_FileContentLoader_RequestProcessor(t *testing.T) {
a := assert.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

fd := NewFetchDefinition("/tmp/some/non/existing/path")
fd.RespProc = NewMockResponseProcessor(ctrl)

_, err := NewFileContentLoader().Load(fd)
a.Equal(ResponseProcessorsNotApplicable, err)
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func randString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
36 changes: 0 additions & 36 deletions composition/http_content_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,6 @@ import (
"time"
)

func XTest_HttpContentLoader_Load(t *testing.T) {
a := assert.New(t)

server := testServer("", time.Millisecond*0)
defer server.Close()

loader := &HttpContentLoader{}
c, err := loader.Load(NewFetchDefinition(server.URL))
a.NoError(err)
a.NotNil(c)
a.Nil(c.Reader())

a.Equal(server.URL, c.URL())
eqFragment(t, integratedTestHtmlExpectedHead, c.Head())
a.Equal(2, len(c.Body()))

eqFragment(t, integratedTestHtmlExpectedHeadline, c.Body()["headline"])
eqFragment(t, integratedTestHtmlExpectedContent, c.Body()["content"])
a.Equal(integratedTestHtmlExpectedMeta, c.Meta())
eqFragment(t, integratedTestHtmlExpectedTail, c.Tail())
cMemoryConent := c.(*MemoryContent)
a.Equal(2, len(cMemoryConent.RequiredContent()))
a.Equal(&FetchDefinition{
URL: "example.com/foo",
Timeout: time.Millisecond * 42000,
Required: true,
}, cMemoryConent.requiredContent["example.com/foo"])

a.Equal(&FetchDefinition{
URL: "example.com/optional",
Timeout: time.Millisecond * 100,
Required: false,
}, cMemoryConent.requiredContent["example.com/optional"])

}

func Test_HttpContentLoader_Load(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down

0 comments on commit e5d5d24

Please sign in to comment.