Skip to content

Commit

Permalink
Merge 15d7ac1 into 1cf43da
Browse files Browse the repository at this point in the history
  • Loading branch information
b00lduck committed Jul 27, 2016
2 parents 1cf43da + 15d7ac1 commit b09cbdd
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 23 deletions.
2 changes: 1 addition & 1 deletion cache/cache_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (tcs *CacheStrategy) HashWithParameters(method string, url string, requestH
return hex.EncodeToString(hasher.Sum(nil))
}

func (tcs *CacheStrategy) IsCachable(method string, url string, statusCode int, requestHeader http.Header, responseHeader http.Header) bool {
func (tcs *CacheStrategy) IsCacheable(method string, url string, statusCode int, requestHeader http.Header, responseHeader http.Header) bool {
// TODO: it is expensive to create a request object only for passing to the cachecontrol library
req := &http.Request{Method: method, Header: requestHeader}
reasons, _, err := cacheobject.UsingRequestResponse(req, statusCode, responseHeader, true)
Expand Down
2 changes: 1 addition & 1 deletion cache/cache_strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func Test_CacheStrategy_IsCachable(t *testing.T) {
}

for _, t := range tests {
cacheable := t.strategy.IsCachable(t.method, "", t.statusCode, t.requestHeader, t.responseHeader)
cacheable := t.strategy.IsCacheable(t.method, "", t.statusCode, t.requestHeader, t.responseHeader)
message := fmt.Sprintf("%v = isCacheable(%q, %q, %v, %v, %v)", cacheable, t.method, "", t.statusCode, t.requestHeader, t.responseHeader)
if t.isCacheable {
a.True(cacheable, message)
Expand Down
2 changes: 1 addition & 1 deletion composition/cache_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (loader *CachingContentLoader) Load(fd *FetchDefinition) (Content, error) {
logging.Cacheinfo(fd.URL, false)
c, err := loader.load(fd)
if err == nil {
if fd.IsCachable(c.HttpStatusCode(), c.HttpHeader()) {
if fd.IsCacheable(c.HttpStatusCode(), c.HttpHeader()) {
if c.Reader() != nil {
var streamBytes []byte
streamBytes, err = ioutil.ReadAll(c.Reader())
Expand Down
19 changes: 19 additions & 0 deletions composition/discovered_fetch_definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package composition

import (
"github.com/tarent/lib-servicediscovery/servicediscovery"
)

// Fluent-interface decorator for the FetchDefinition that activates the ServiceDiscovery
func (d *FetchDefinition) DiscoveredBy(dnsServer string) *FetchDefinition {

serviceDiscovery, err := servicediscovery.NewConsulServiceDiscovery(dnsServer)
if err != nil {
panic(err)
}

d.ServiceDiscovery = serviceDiscovery
d.ServiceDiscoveryActive = true

return d
}
27 changes: 27 additions & 0 deletions composition/discovered_fetch_definition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package composition

import (
"testing"
"github.com/stretchr/testify/assert"
)

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

testSubject := FetchDefinition{}

testSubject.DiscoveredBy("127.0.0.1:53")

a.NotNil(testSubject.ServiceDiscovery)
a.True(testSubject.ServiceDiscoveryActive)
}

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

testSubject := FetchDefinition{}

a.Panics(func() {
testSubject.DiscoveredBy("a")
})
}
35 changes: 18 additions & 17 deletions composition/fetch_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"strings"
"time"
"github.com/tarent/lib-servicediscovery/servicediscovery"
)

// ForwardRequestHeaders are those headers,
Expand Down Expand Up @@ -55,19 +56,18 @@ const (

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

// Creates a fetch definition
Expand Down Expand Up @@ -105,13 +105,13 @@ func NewFetchDefinitionWithResponseProcessor(url string, rp ResponseProcessor) *
}

// NewFetchDefinitionFromRequest creates a fetch definition
// from the request path, but replaces the sheme, host and port with the provided base url
// from the request path, but replaces the scheme, host and port with the provided base url
func NewFetchDefinitionFromRequest(baseUrl string, r *http.Request) *FetchDefinition {
return NewFetchDefinitionWithResponseProcessorFromRequest(baseUrl, r, nil)
}

// NewFetchDefinitionFromRequest creates a fetch definition
// from the request path, but replaces the sheme, host and port with the provided base url
// from the request path, but replaces the scheme, host and port with the provided base url
// If a ResponseProcessor-Implementation is given it can be used to change the response before composition
// Only those headers, defined in ForwardRequestHeaders are copied to the FetchDefinition.
func NewFetchDefinitionWithResponseProcessorFromRequest(baseUrl string, r *http.Request, rp ResponseProcessor) *FetchDefinition {
Expand Down Expand Up @@ -150,9 +150,9 @@ func (def *FetchDefinition) Hash() string {
return def.URL
}

func (def *FetchDefinition) IsCachable(responseStatus int, responseHeaders http.Header) bool {
func (def *FetchDefinition) IsCacheable(responseStatus int, responseHeaders http.Header) bool {
if def.CacheStrategy != nil {
return def.CacheStrategy.IsCachable(def.Method, def.URL, responseStatus, def.Header, responseHeaders)
return def.CacheStrategy.IsCacheable(def.Method, def.URL, responseStatus, def.Header, responseHeaders)
}
return false
}
Expand Down Expand Up @@ -185,3 +185,4 @@ func NewDefaultErrorHandler() *DefaultErrorHandler {
func (der *DefaultErrorHandler) Handle(err error, status int, w http.ResponseWriter, r *http.Request) {
http.Error(w, "Error: "+err.Error(), status)
}

5 changes: 5 additions & 0 deletions composition/html_content_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ func getInclude(z *html.Tokenizer, attrs []html.Attribute) (*FetchDefinition, st
placeholder = placeholder[1:]
}

attr, found := getAttr(attrs, "discoveredBy")
if found {
fd.DiscoveredBy(attr.Val)
}

return fd, fmt.Sprintf("§[> %s]§", placeholder), nil
}

Expand Down
44 changes: 42 additions & 2 deletions composition/http_content_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (
"net/http"
"strings"
"time"

"errors"
"github.com/tarent/lib-compose/logging"
"github.com/tarent/lib-servicediscovery/servicediscovery"
"net/url"
"net"
)

var redirectAttemptedError = errors.New("do not follow redirects")
Expand Down Expand Up @@ -42,7 +43,16 @@ func (loader *HttpContentLoader) Load(fd *FetchDefinition) (Content, error) {
client.CheckRedirect = noRedirectFunc
}

request, err := http.NewRequest(fd.Method, fd.URL, fd.Body)
fetchUrl := fd.URL
if fd.ServiceDiscoveryActive {
discoveredUrl, err := loader.discoverServiceInUrl(fetchUrl, fd.ServiceDiscovery)
if err != nil {
return c, err
}
fetchUrl = discoveredUrl
}

request, err := http.NewRequest(fd.Method, fetchUrl, fd.Body)
if err != nil {
return c, err
}
Expand Down Expand Up @@ -108,3 +118,33 @@ func (loader *HttpContentLoader) Load(fd *FetchDefinition) (Content, error) {
c.reader = resp.Body
return c, nil
}

func (loader *HttpContentLoader) discoverServiceInUrl(rawUrl string, serviceDiscovery servicediscovery.ServiceDiscovery) (string, error) {

parsedUrl, err := url.Parse(rawUrl)
if err != nil {
return "", err
}

host, origPort, err := net.SplitHostPort(parsedUrl.Host)
if err != nil {
if !strings.Contains(err.Error(), "missing port") {
return "", err
}
host = parsedUrl.Host
}

if net.ParseIP(host) == nil {
if origPort != "" {
return "", fmt.Errorf("Service name with port given, this is not allowed. The port will be resolved by service discovery!")
}
ip, port, err := serviceDiscovery.DiscoverService(parsedUrl.Host)
if err != nil {
return "", err
}
parsedUrl.Host = net.JoinHostPort(ip, port)
}

return parsedUrl.String(), nil

}
76 changes: 76 additions & 0 deletions composition/http_content_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strings"
"testing"
"time"
"github.com/tarent/lib-servicediscovery/servicediscovery"
"fmt"
)

func Test_HttpContentLoader_Load(t *testing.T) {
Expand Down Expand Up @@ -244,6 +246,80 @@ func Test_HttpContentLoader_DoNotFollowRedirects(t *testing.T) {
}
}

func Test_HttpContentLoader_DiscoverServiceInUrl(t *testing.T) {

a := assert.New(t)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

// given
loader := &HttpContentLoader{}

mockServiceDiscovery := servicediscovery.NewMockServiceDiscovery(ctrl)
mockServiceDiscovery.EXPECT().DiscoverService("serviceName").Return("10.0.0.1", "42", nil)

// when
url, _ := loader.discoverServiceInUrl("http://serviceName/test.jpg", mockServiceDiscovery)

// then
a.Equal(url, "http://10.0.0.1:42/test.jpg")
}



func Test_HttpContentLoader_DiscoverServiceInUrlRawIp(t *testing.T) {

a := assert.New(t)

cases := [][]string {
{"http://127.0.0.1:80/test.jpg", "http://127.0.0.1:80/test.jpg"},
{"http://127.0.0.1/test.jpg", "http://127.0.0.1/test.jpg"},
}

for _, v := range cases {

fmt.Println(v)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

// given
loader := &HttpContentLoader{}

mockServiceDiscovery := servicediscovery.NewMockServiceDiscovery(ctrl)

// when
url, _ := loader.discoverServiceInUrl(v[0], mockServiceDiscovery)

// then
a.Equal(url, v[1])
}

}

func Test_HttpContentLoader_DiscoverServiceInUrlWithPortError(t *testing.T) {

a := assert.New(t)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

// given
loader := &HttpContentLoader{}

mockServiceDiscovery := servicediscovery.NewMockServiceDiscovery(ctrl)

// when
url, err := loader.discoverServiceInUrl("http://serviceName:80/test.jpg", mockServiceDiscovery)

// then
a.Equal(url, "")
a.EqualError(err, "Service name with port given, this is not allowed. The port will be resolved by service discovery!")

}


func testServer(content string, timeout time.Duration) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
Expand Down
2 changes: 1 addition & 1 deletion composition/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type FetchResultSupplier interface {

type CacheStrategy interface {
Hash(method string, url string, requestHeader http.Header) string
IsCachable(method string, url string, statusCode int, requestHeader http.Header, responseHeader http.Header) bool
IsCacheable(method string, url string, statusCode int, requestHeader http.Header, responseHeader http.Header) bool
}

// Vontent is the abstration over includable data.
Expand Down

0 comments on commit b09cbdd

Please sign in to comment.