Skip to content

Commit

Permalink
Merge pull request #137 from devopsfaith/0.5-dev
Browse files Browse the repository at this point in the history
Integrate the 0.5 dev branch into master
  • Loading branch information
taik0 authored Jun 4, 2018
2 parents 8c69c03 + 4df38b4 commit a911912
Show file tree
Hide file tree
Showing 65 changed files with 3,430 additions and 293 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ server.rsa.key
*.json
*.yml
*.toml
coverage.out
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ deps:
@echo ""

test:
go fmt ./...
go test -v -cover ./...
go test -cover -race ./...

benchmark:
@echo "Proxy middleware stack"
Expand Down
109 changes: 101 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,94 @@ type ServiceConfig struct {
Port int `mapstructure:"port"`
// version code of the configuration
Version int `mapstructure:"version"`
// OutputEncoding defines the default encoding strategy to use for the endpoint responses
OutputEncoding string `mapstructure:"output_encoding"`
// Extra configuration for customized behaviour
ExtraConfig ExtraConfig `mapstructure:"extra_config"`

ReadTimeout time.Duration `mapstructure:"read_timeout"`
WriteTimeout time.Duration `mapstructure:"write_timeout"`
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration `mapstructure:"read_timeout"`
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout time.Duration `mapstructure:"write_timeout"`
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, ReadHeaderTimeout is used.
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body.
ReadHeaderTimeout time.Duration `mapstructure:"read_header_timeout"`

MaxIdleConnsPerHost int `mapstructure:"max_idle_connections"`
// DisableKeepAlives, if true, prevents re-use of TCP connections
// between different HTTP requests.
DisableKeepAlives bool `mapstructure:"disable_keep_alives"`
// DisableCompression, if true, prevents the Transport from
// requesting compression with an "Accept-Encoding: gzip"
// request header when the Request contains no existing
// Accept-Encoding value. If the Transport requests gzip on
// its own and gets a gzipped response, it's transparently
// decoded in the Response.Body. However, if the user
// explicitly requested gzip it is not automatically
// uncompressed.
DisableCompression bool `mapstructure:"disable_compression"`
// MaxIdleConns controls the maximum number of idle (keep-alive)
// connections across all hosts. Zero means no limit.
MaxIdleConns int `mapstructure:"max_idle_connections"`
// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
// (keep-alive) connections to keep per-host. If zero,
// DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"`
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration `mapstructure:"idle_connection_timeout"`
// ResponseHeaderTimeout, if non-zero, specifies the amount of
// time to wait for a server's response headers after fully
// writing the request (including its body, if any). This
// time does not include the time to read the response body.
ResponseHeaderTimeout time.Duration `mapstructure:"response_header_timeout"`
// ExpectContinueTimeout, if non-zero, specifies the amount of
// time to wait for a server's first response headers after fully
// writing the request headers if the request has an
// "Expect: 100-continue" header. Zero means no timeout and
// causes the body to be sent immediately, without
// waiting for the server to approve.
// This time does not include the time to send the request header.
ExpectContinueTimeout time.Duration `mapstructure:"expect_continue_timeout"`
// DialerTimeout is the maximum amount of time a dial will wait for
// a connect to complete. If Deadline is also set, it may fail
// earlier.
//
// The default is no timeout.
//
// When using TCP and dialing a host name with multiple IP
// addresses, the timeout may be divided between them.
//
// With or without a timeout, the operating system may impose
// its own earlier timeout. For instance, TCP timeouts are
// often around 3 minutes.
DialerTimeout time.Duration `mapstructure:"dialer_timeout"`
// DialerFallbackDelay specifies the length of time to wait before
// spawning a fallback connection, when DualStack is enabled.
// If zero, a default delay of 300ms is used.
DialerFallbackDelay time.Duration `mapstructure:"dialer_fallback_delay"`
// DialerKeepAlive specifies the keep-alive period for an active
// network connection.
// If zero, keep-alives are not enabled. Network protocols
// that do not support keep-alives ignore this field.
DialerKeepAlive time.Duration `mapstructure:"dialer_keep_alive"`

// DisableStrictREST flags if the REST enforcement is disabled
DisableStrictREST bool `mapstructure:"disable_rest"`
Expand Down Expand Up @@ -85,6 +164,8 @@ type EndpointConfig struct {
ExtraConfig ExtraConfig `mapstructure:"extra_config"`
// HeadersToPass defines the list of headers to pass to the backends
HeadersToPass []string `mapstructure:"headers_to_pass"`
// OutputEncoding defines the encoding strategy to use for the endpoint responses
OutputEncoding string `mapstructure:"output_encoding"`
}

// Backend defines how krakend should connect to the backend service (the API resource to consume)
Expand Down Expand Up @@ -151,10 +232,11 @@ const defaultNamespace = "github.com/devopsfaith/krakend/config"
var ConfigGetters = map[string]ConfigGetter{defaultNamespace: DefaultConfigGetter}

var (
simpleURLKeysPattern = regexp.MustCompile(`\{([a-zA-Z\-_0-9]+)\}`)
debugPattern = "^[^/]|/__debug(/.*)?$"
errInvalidHost = errors.New("invalid host")
defaultPort = 8080
simpleURLKeysPattern = regexp.MustCompile(`\{([a-zA-Z\-_0-9]+)\}`)
debugPattern = "^[^/]|/__debug(/.*)?$"
errInvalidHost = errors.New("invalid host")
errInvalidNoOpEncoding = errors.New("can not use NoOp encoding with more than one backends connected to the same endpoint")
defaultPort = 8080
)

// Init initializes the configuration struct and its defined endpoints and backends.
Expand Down Expand Up @@ -194,6 +276,10 @@ func (s *ServiceConfig) Init() error {

s.initEndpointDefaults(i)

if e.OutputEncoding == encoding.NOOP && len(e.Backend) > 1 {
return errInvalidNoOpEncoding
}

for j, b := range e.Backend {

s.initBackendDefaults(i, j)
Expand Down Expand Up @@ -241,6 +327,13 @@ func (s *ServiceConfig) initEndpointDefaults(e int) {
if endpoint.ConcurrentCalls == 0 {
endpoint.ConcurrentCalls = 1
}
if endpoint.OutputEncoding == "" {
if s.OutputEncoding != "" {
endpoint.OutputEncoding = s.OutputEncoding
} else {
endpoint.OutputEncoding = encoding.JSON
}
}
}

func (s *ServiceConfig) initBackendDefaults(e, b int) {
Expand Down
37 changes: 32 additions & 5 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ func TestConfig_init(t *testing.T) {
URLPattern: "/__debug/supu",
}
supuEndpoint := EndpointConfig{
Endpoint: "/supu",
Method: "post",
Timeout: 1500 * time.Millisecond,
CacheTTL: 6 * time.Hour,
Backend: []*Backend{&supuBackend},
Endpoint: "/supu",
Method: "post",
Timeout: 1500 * time.Millisecond,
CacheTTL: 6 * time.Hour,
Backend: []*Backend{&supuBackend},
OutputEncoding: "some_render",
}

githubBackend := Backend{
Expand Down Expand Up @@ -208,6 +209,32 @@ func TestConfig_initKONoBackends(t *testing.T) {
}
}

func TestConfig_initKOMultipleBackendsForNoopEncoder(t *testing.T) {
subject := ServiceConfig{
Version: ConfigVersion,
Host: []string{"http://127.0.0.1:8080"},
Endpoints: []*EndpointConfig{
{
Endpoint: "/supu",
Method: "post",
OutputEncoding: "no-op",
Backend: []*Backend{
{
Encoding: "no-op",
},
{
Encoding: "no-op",
},
},
},
},
}

if err := subject.Init(); err != errInvalidNoOpEncoding {
t.Error("Expecting an error at the configuration init!", err)
}
}

func TestConfig_initKOInvalidHost(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down
74 changes: 48 additions & 26 deletions config/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,36 +44,56 @@ func (p parser) Parse(configFile string) (ServiceConfig, error) {
}

type parseableServiceConfig struct {
Endpoints []*parseableEndpointConfig `json:"endpoints"`
Timeout string `json:"timeout"`
CacheTTL string `json:"cache_ttl"`
Host []string `json:"host"`
Port int `json:"port"`
Version int `json:"version"`
ExtraConfig *ExtraConfig `json:"extra_config,omitempty"`
ReadTimeout string `json:"read_timeout"`
WriteTimeout string `json:"write_timeout"`
IdleTimeout string `json:"idle_timeout"`
ReadHeaderTimeout string `json:"read_header_timeout"`
MaxIdleConnsPerHost int `json:"max_idle_connections"`
Debug bool
Plugin *Plugin
Endpoints []*parseableEndpointConfig `json:"endpoints"`
Timeout string `json:"timeout"`
CacheTTL string `json:"cache_ttl"`
Host []string `json:"host"`
Port int `json:"port"`
Version int `json:"version"`
ExtraConfig *ExtraConfig `json:"extra_config,omitempty"`
ReadTimeout string `json:"read_timeout"`
WriteTimeout string `json:"write_timeout"`
IdleTimeout string `json:"idle_timeout"`
ReadHeaderTimeout string `json:"read_header_timeout"`
DisableKeepAlives bool `json:"disable_keep_alives"`
DisableCompression bool `json:"disable_compression"`
MaxIdleConns int `json:"max_idle_connections"`
MaxIdleConnsPerHost int `json:"max_idle_connections_per_host"`
IdleConnTimeout string `json:"idle_connection_timeout"`
ResponseHeaderTimeout string `json:"response_header_timeout"`
ExpectContinueTimeout string `json:"expect_continue_timeout"`
OutputEncoding string `json:"output_encoding"`
DialerTimeout string `json:"dialer_timeout"`
DialerFallbackDelay string `json:"dialer_fallback_delay"`
DialerKeepAlive string `json:"dialer_keep_alive"`
Debug bool
Plugin *Plugin
}

func (p *parseableServiceConfig) normalize() ServiceConfig {
cfg := ServiceConfig{
Timeout: parseDuration(p.Timeout),
CacheTTL: parseDuration(p.CacheTTL),
Host: p.Host,
Port: p.Port,
Version: p.Version,
Debug: p.Debug,
ReadTimeout: parseDuration(p.ReadTimeout),
WriteTimeout: parseDuration(p.WriteTimeout),
IdleTimeout: parseDuration(p.IdleTimeout),
ReadHeaderTimeout: parseDuration(p.ReadHeaderTimeout),
MaxIdleConnsPerHost: p.MaxIdleConnsPerHost,
Plugin: p.Plugin,
Timeout: parseDuration(p.Timeout),
CacheTTL: parseDuration(p.CacheTTL),
Host: p.Host,
Port: p.Port,
Version: p.Version,
Debug: p.Debug,
ReadTimeout: parseDuration(p.ReadTimeout),
WriteTimeout: parseDuration(p.WriteTimeout),
IdleTimeout: parseDuration(p.IdleTimeout),
ReadHeaderTimeout: parseDuration(p.ReadHeaderTimeout),
DisableKeepAlives: p.DisableKeepAlives,
DisableCompression: p.DisableCompression,
MaxIdleConns: p.MaxIdleConns,
MaxIdleConnsPerHost: p.MaxIdleConnsPerHost,
IdleConnTimeout: parseDuration(p.IdleConnTimeout),
ResponseHeaderTimeout: parseDuration(p.ResponseHeaderTimeout),
ExpectContinueTimeout: parseDuration(p.ExpectContinueTimeout),
DialerTimeout: parseDuration(p.DialerTimeout),
DialerFallbackDelay: parseDuration(p.DialerFallbackDelay),
DialerKeepAlive: parseDuration(p.DialerKeepAlive),
OutputEncoding: p.OutputEncoding,
Plugin: p.Plugin,
}
if p.ExtraConfig != nil {
cfg.ExtraConfig = *p.ExtraConfig
Expand All @@ -96,6 +116,7 @@ type parseableEndpointConfig struct {
QueryString []string `json:"querystring_params"`
ExtraConfig *ExtraConfig `json:"extra_config,omitempty"`
HeadersToPass []string `json:"headers_to_pass"`
OutputEncoding string `json:"output_encoding"`
}

func (p *parseableEndpointConfig) normalize() *EndpointConfig {
Expand All @@ -107,6 +128,7 @@ func (p *parseableEndpointConfig) normalize() *EndpointConfig {
CacheTTL: time.Duration(p.CacheTTL) * time.Second,
QueryString: p.QueryString,
HeadersToPass: p.HeadersToPass,
OutputEncoding: p.OutputEncoding,
}
if p.ExtraConfig != nil {
e.ExtraConfig = *p.ExtraConfig
Expand Down
32 changes: 14 additions & 18 deletions encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,22 @@ import "io"
type Decoder func(io.Reader, *map[string]interface{}) error

// A DecoderFactory is a function that returns CollectionDecoder or an EntityDecoder
type DecoderFactory func(bool) Decoder
type DecoderFactory func(bool) func(io.Reader, *map[string]interface{}) error

var decoders = map[string]DecoderFactory{
JSON: NewJSONDecoder,
STRING: NewStringDecoder,
// Deprecated: Register is deprecated
func Register(name string, dec func(bool) func(io.Reader, *map[string]interface{}) error) error {
return decoders.Register(name, dec)
}

// Register registers the decoder factory with the given name
func Register(name string, dec DecoderFactory) error {
decoders[name] = dec
return nil
}

// Get returns (from the register) the decoder factory by name. If there is no factory with the received name
// it returns the JSON decoder factory
// Deprecated: Get is deprecated
func Get(name string) DecoderFactory {
for _, n := range []string{name, JSON} {
if dec, ok := decoders[n]; ok {
return dec
}
}
return NewJSONDecoder
return decoders.Get(name)
}

// NOOP is the key for the NoOp encoding
const NOOP = "no-op"

// NoOpDecoder implements the Decoder interface
func NoOpDecoder(_ io.Reader, _ *map[string]interface{}) error { return nil }

func noOpDecoderFactory(_ bool) func(io.Reader, *map[string]interface{}) error { return NoOpDecoder }
Loading

0 comments on commit a911912

Please sign in to comment.