Closed ricmoreira closed 6 years ago
Hi @ricmoreira
Check this link for information about this behaviour: http://www.krakend.io/docs/faq/#i-am-getting-a-500-status-when-the-backend-returns-anything-but-200-201-or-redirects.
Although now you can use the NoOpHTTPStatusHandler
to send the same response of the backend.
Thank you for the quick response. I've been through that documentation and I've understood that I've to inject my "own HTTPStatusHandler". In this case, as you said, the "NoOpHTTPStatusHandler" would solve my issue. But where do I put it?
You need to add your own custom BackendFactory
if you are not using the default Factories.
NewHTTPProxyDetailed will allow you to add your custom status handler.
Check this link for an example: https://github.com/devopsfaith/krakend/issues/102#issuecomment-373657911
This is what I've done now:
(...)
backendFactory := func(backendCfg *config.Backend) proxy.Proxy {
ns := proxy.NoOpHTTPStatusHandler
// the default request executor
re := proxy.DefaultHTTPRequestExecutor(proxy. NewHTTPClient)
// default entity formatter for the given backend
ef := proxy.NewEntityFormatter(backendCfg)
// the default response parser with the required config
rp := proxy.DefaultHTTPResponseParserFactory(proxy.HTTPResponseParserConfig{backendCfg.Decoder, ef})
// build and return the new backend proxy
return proxy.NewHTTPProxyDetailed(backendCfg, re, ns, rp)
}
// build the pipes on top of the custom backend factory
proxyFactory := proxy.NewDefaultFactory(backendFactory, logger)
routerFactory := krakendgin.NewFactory(krakendgin.Config{
Engine: gin.Default(),
ProxyFactory: proxyFactory,
(...)
I've managed to get the response body but now an error response (e.g. 400) is transformed to a 200 response.
hi, @ricmoreira
the renders are responsible for the behaviour you're describing (as you can see here: https://github.com/devopsfaith/krakend/blob/master/router/gin/render.go#L90) but you can inject your own render at the router level by registering it with gin.RegisterRender
and adding its name in the endpoint configuration (config.EndpointConfig.OutputEncoding
).
your implementation should copy the proxy.Response.Metadata.StatusCode
like in https://github.com/devopsfaith/krakend/blob/master/router/gin/render.go#L124
on the other hand, please notice you can use the CORS module already available in the master branch. the JOSE package is not ready yet, but we expect to finish it in a couple of weeks.
cheers!
Hello,
I have finally a working example:
package main
import (
"flag"
"io"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"gopkg.in/gin-contrib/cors.v1"
"api_gateway_admin/middleware"
"github.com/devopsfaith/krakend/config"
"github.com/devopsfaith/krakend/logging"
"github.com/devopsfaith/krakend/proxy"
krakendgin "github.com/devopsfaith/krakend/router/gin"
)
func main() {
port := flag.Int("p", 0, "Port of the service")
logLevel := flag.String("l", "ERROR", "Logging level")
debug := flag.Bool("d", false, "Enable the debug")
configFile := flag.String("c", "{{path to file}}/configuration.json", "Path to the configuration filename")
flag.Parse()
parser := config.NewParser()
serviceConfig, err := parser.Parse(*configFile)
if err != nil {
log.Fatal("ERROR:", err.Error())
}
// render that does not change response
noTransformRender := func(c *gin.Context, response *proxy.Response) {
if response == nil {
c.Status(http.StatusInternalServerError)
return
}
c.Status(response.Metadata.StatusCode)
for k, v := range response.Metadata.Headers {
c.Header(k, v[0])
}
io.Copy(c.Writer, response.Io)
}
// register the render at the router level
krakendgin.RegisterRender("NoTransformRender", noTransformRender)
// assign NoTransformRender to all endpoints loaded from config file
for _, v := range serviceConfig.Endpoints {
v.OutputEncoding = "NoTransformRender"
}
serviceConfig.Debug = serviceConfig.Debug || *debug
if *port != 0 {
serviceConfig.Port = *port
}
logger, err := logging.NewLogger(*logLevel, os.Stdout, "[KRAKEND]")
if err != nil {
log.Println("ERROR:", err.Error())
return
}
backendFactory := func(backendCfg *config.Backend) proxy.Proxy {
// status handler that does change status
ns := proxy.NoOpHTTPStatusHandler
// the default request executor
re := proxy.DefaultHTTPRequestExecutor(proxy.NewHTTPClient)
// response parser that copies Backend response body to proxy Response IO reader
rp := proxy.NoOpHTTPResponseParser
// build and return the new backend proxy
return proxy.NewHTTPProxyDetailed(backendCfg, re, ns, rp)
}
// build the pipes on top of the custom backend factory
proxyFactory := proxy.NewDefaultFactory(backendFactory, logger)
engine := gin.Default()
routerConfig := krakendgin.Config{
Engine: engine,
ProxyFactory: proxyFactory,
Logger: logger,
HandlerFactory: krakendgin.EndpointHandler,
Middlewares: []gin.HandlerFunc{
cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:4200", "http://127.0.0.1:4200", "http://localhost:8089", "http://localhost:8069", "http://localhost:8080", "http://localhost:8099"},
AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Accept",
"Accept-Encoding",
"Accept-Language",
"access-control-allow-origin",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"authorization",
"Cache-Control",
"Connection",
"Content-Type",
"Host",
"If-Modified-Since",
"Keep-Alive",
"Key",
"Origin",
"Pragma",
"User-Agent",
"X-Custom-Header"},
ExposeHeaders: []string{"Content-Length", "Content-Type"},
AllowCredentials: true,
MaxAge: 48 * time.Hour,
}),
middleware.JwtCheck(),
},
}
routerFactory := krakendgin.NewFactory(routerConfig)
routerFactory.New().Run(serviceConfig)
}
Thank you very much for your quick feedback. Congrats for your great project.
Thanks!
@ricmoreira that's amazing!
remember you can avoid some of that code by just defining the extra_config
for the CORS module like in this example: https://github.com/devopsfaith/krakend-cors#configuration-example
also, you can add "output_encoding": "NoTransformRender"
to each endpoint definition in the same config file, so you'll have more control.
enjoy playing with the KrakenD!!!
Thank you very much! Much better now.
I'll leave here my main and my config file for whoever needs an example.
package main
import (
"flag"
"io"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"api_gateway_admin/middleware"
"github.com/devopsfaith/krakend/config"
"github.com/devopsfaith/krakend/logging"
"github.com/devopsfaith/krakend/proxy"
krakendgin "github.com/devopsfaith/krakend/router/gin"
)
func main() {
port := flag.Int("p", 0, "Port of the service")
logLevel := flag.String("l", "ERROR", "Logging level")
debug := flag.Bool("d", false, "Enable the debug")
configFile := flag.String("c", "{{path to file}}/configuration.json", "Path to the configuration filename")
flag.Parse()
parser := config.NewParser()
serviceConfig, err := parser.Parse(*configFile)
if err != nil {
log.Fatal("ERROR:", err.Error())
}
// render that does not change response
noTransformRender := func(c *gin.Context, response *proxy.Response) {
if response == nil {
c.Status(http.StatusInternalServerError)
return
}
c.Status(response.Metadata.StatusCode)
for k, v := range response.Metadata.Headers {
c.Header(k, v[0])
}
io.Copy(c.Writer, response.Io)
}
// register the render at the router level
krakendgin.RegisterRender("NoTransformRender", noTransformRender)
serviceConfig.Debug = serviceConfig.Debug || *debug
if *port != 0 {
serviceConfig.Port = *port
}
logger, err := logging.NewLogger(*logLevel, os.Stdout, "[KRAKEND]")
if err != nil {
log.Println("ERROR:", err.Error())
return
}
backendFactory := func(backendCfg *config.Backend) proxy.Proxy {
// status handler that does change status
ns := proxy.NoOpHTTPStatusHandler
// the default request executor
re := proxy.DefaultHTTPRequestExecutor(proxy.NewHTTPClient)
// response parser that copies Backend response body to proxy Response IO reader
rp := proxy.NoOpHTTPResponseParser
// build and return the new backend proxy
return proxy.NewHTTPProxyDetailed(backendCfg, re, ns, rp)
}
// build the pipes on top of the custom backend factory
proxyFactory := proxy.NewDefaultFactory(backendFactory, logger)
engine := gin.Default()
routerConfig := krakendgin.Config{
Engine: engine,
ProxyFactory: proxyFactory,
Logger: logger,
HandlerFactory: krakendgin.EndpointHandler,
Middlewares: []gin.HandlerFunc{
middleware.JwtCheck(),
},
}
routerFactory := krakendgin.NewFactory(routerConfig)
routerFactory.New().Run(serviceConfig)
}
{
"version": 2,
"name": "ecommerce-service",
"port": 8080,
"cache_ttl": "1s",
"timeout": "10s",
"host": [
"http://localhost"
],
"extra_config": {
"github_com/devopsfaith/krakend-cors": {
"allow_origins": [ "http://localhost:4200", "http://127.0.0.1:4200", "http://localhost:8089", "http://localhost:8069", "http://localhost:8080", "http://localhost:8099" ],
"allow_methods": [ "PUT", "PATCH", "POST", "GET", "DELETE", "OPTIONS"],
"allow_heathers": [ "Accept-Encoding",
"Accept-Language",
"access-control-allow-origin",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"authorization",
"Cache-Control",
"Connection",
"Content-Type",
"Host",
"If-Modified-Since",
"Keep-Alive",
"Key",
"Origin",
"Pragma",
"User-Agent",
"X-Custom-Header"],
"max_age": "48h",
"allow_credentials": true,
"expose_headers": ["Content-Length", "Content-Type"]
}
},
"endpoints": [
{
"endpoint": "/api/v1/product",
"timeout": "800ms",
"method": "POST",
"backend": [
{
"url_pattern": "/api/v1/product",
"host": [
"http://localhost:8069"
]
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/api/v1/product",
"timeout": "800ms",
"method": "GET",
"querystring_params": [
"page",
"per_page"
],
"backend": [
{
"url_pattern": "/api/v1/product",
"host": [
"http://localhost:8069"
]
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/api/v1/saft/upload",
"timeout": "800ms",
"method": "POST",
"headers_to_pass": [
"content-type",
"Content-Type"
],
"backend": [
{
"url_pattern": "/api/v1/saft/upload",
"host": [
"http://localhost:8099"
]
}
],
"output_encoding": "NoTransformRender"
}
]
}
Hello, I'm trying to modify default krakend-ce backendFactory to make it works with NoOpHTTPStatusHandler. Original backendFactory code is (_backendfactory.go):
// NewBackendFactory creates a BackendFactory by stacking all the available middlewares:
// - oauth2 client credentials
// - martian
// - rate-limit
// - circuit breaker
// - metrics collector
// - opencensus collector
func NewBackendFactory(logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory {
requestExecutorFactory := func(cfg *config.Backend) client.HTTPRequestExecutor {
var clientFactory client.HTTPClientFactory
if _, ok := cfg.ExtraConfig[oauth2client.Namespace]; ok {
clientFactory = oauth2client.NewHTTPClient(cfg)
} else {
clientFactory = httpcache.NewHTTPClient(cfg)
}
return opencensus.HTTPRequestExecutor(clientFactory)
}
backendFactory := martian.NewConfiguredBackendFactory(logger, requestExecutorFactory)
backendFactory = juju.BackendFactory(backendFactory)
backendFactory = cb.BackendFactory(backendFactory)
backendFactory = metricCollector.BackendFactory("backend", backendFactory)
backendFactory = opencensus.BackendFactory(backendFactory)
return backendFactory
}
Is it possible to use NoOpHTTPStatusHandler together with all middlewares metioned in the NewBackendFactory function?
I'm a little confused about how krakend deals with middlewares listed in the krakend.json vs. middlewares "hardcoded" in the NewBackendFactory function. It is possible to use some middleware configured in the krakend.json even if it's not explicitly mentioned in the code of the NewBackendFactory function?
Thank you.
hi @dominiksimek
Is it possible to use NoOpHTTPStatusHandler together with all middlewares metioned in the NewBackendFactory function?
yes, it is. You can do it without touching the code by adding "output_encoding": "no-op"
at the endpoint config and "encoding": "no-op"
at the backend
I'm a little confused about how krakend deals with middlewares listed in the krakend.json vs. middlewares "hardcoded" in the NewBackendFactory function. It is possible to use some middleware configured in the krakend.json even if it's not explicitly mentioned in the code of the NewBackendFactory function?
The system loads all the factories required to support/enable the modules/middlewares included. Each factory looks for its configuration at some part of the config file. If it is not configured, the module/middleware is not added to the final pipe. That's why the config of a not included component is ignored.
cheers
Great, it works well.
However, I have an issue with endpoint used to sign JWT (github.com/devopsfaith/krakend-jose/signer). Signed token is not returned from Krakend when using Backend "encoding": "no-op" (it returns only empty JSON). On the other hand, response from Krakend contains valid json with signed token when using Backend "encoding": "json". But StatusCode from Krakend response can be only 200 or 400. In the case of unsuccessful user authentication (e.g. wrong password), my backend sends 401 with additional JSON in the response body. Is it possible to proxy such response from backend through Krakend?
Thank you
@dominiksimek in order to avoid polluting this issue, I'd suggest you to move the discussion to the slack
Hello.
I am using the example of ricomeira to return the original status and response of a request, which works well, but when doing a merge of three requests, it does not show any results
package main
import (
"flag"
"io"
"log"
"net/http"
"os"
limit "github.com/aviddiviner/gin-limit"
"github.com/gin-gonic/gin"
"github.com/devopsfaith/krakend/config"
"github.com/devopsfaith/krakend/logging"
"github.com/devopsfaith/krakend/proxy"
"github.com/devopsfaith/krakend/router"
krakendgin "github.com/devopsfaith/krakend/router/gin"
"github.com/devopsfaith/krakend/transport/http/client"
)
func main() {
port := flag.Int("p", 0, "Port of the service")
logLevel := flag.String("l", "ERROR", "Logging level")
debug := flag.Bool("d", false, "Enable the debug")
configFile := flag.String("c", "./krakend.json", "Path to the configuration filename")
flag.Parse()
parser := config.NewParser()
serviceConfig, err := parser.Parse(*configFile)
if err != nil {
log.Fatal("ERROR:", err.Error())
}
// render that does not change response
noTransformRender := func(c *gin.Context, response *proxy.Response) {
if response == nil {
c.Status(http.StatusInternalServerError)
return
}
c.Status(response.Metadata.StatusCode)
for k, v := range response.Metadata.Headers {
c.Header(k, v[0])
}
io.Copy(c.Writer, response.Io)
}
// register the render at the router level
krakendgin.RegisterRender("NoTransformRender", noTransformRender)
serviceConfig.Debug = serviceConfig.Debug || *debug
if *port != 0 {
serviceConfig.Port = *port
}
logger, err := logging.NewLogger(*logLevel, os.Stdout, "[KRAKEND]")
if err != nil {
log.Fatal("ERROR:", err.Error())
}
backendFactory := func(backendCfg *config.Backend) proxy.Proxy {
// status handler that does change status
ns := client.NoOpHTTPStatusHandler
// the default request executor
re := client.DefaultHTTPRequestExecutor(client.NewHTTPClient)
// response parser that copies Backend response body to proxy Response IO reader
rp := proxy.NoOpHTTPResponseParser
// build and return the new backend proxy
return proxy.NewHTTPProxyDetailed(backendCfg, re, ns, rp)
}
// build the pipes on top of the custom backend factory
proxyFactory := proxy.NewDefaultFactory(backendFactory, logger)
// store := cache.NewInMemoryStore(time.Minute)
mws := []gin.HandlerFunc{
limit.MaxAllowed(20),
}
// routerFactory := krakendgin.DefaultFactory(proxy.DefaultFactory(logger), logger)
routerFactory := krakendgin.NewFactory(krakendgin.Config{
Engine: gin.Default(),
ProxyFactory: proxyFactory,
Middlewares: mws,
Logger: logger,
HandlerFactory: krakendgin.EndpointHandler,
RunServer: router.RunServer,
})
routerFactory.New().Run(serviceConfig)
}
{
"version": 2,
"name": "kraken_test",
"port": 8000,
"cache_ttl": "3600s",
"timeout": "3000ms",
"extra_config": {
"github_com/devopsfaith/krakend-gologging": {
"level": "DEBUG",
"prefix": "[KRAKEND]",
"syslog": false,
"stdout": true
},
"github_com/devopsfaith/krakend-metrics": {
"collection_time": "60s",
"proxy_disabled": false,
"router_disabled": false,
"backend_disabled": false,
"endpoint_disabled": false,
"listen_address": ":8090"
},
"github_com/devopsfaith/krakend-cors": {
"allow_origins": [
"http://192.168.99.100:3000",
"http://localhost:8080"
],
"allow_methods": [
"POST",
"GET"
],
"allow_headers": [
"Origin",
"Authorization",
"Content-Type"
],
"expose_headers": [
"Content-Length"
],
"max_age": "12h"
}
},
"endpoints": [
{
"endpoint": "/abc",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/a",
"encoding": "json"
},
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/b",
"encoding": "json"
},
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/c",
"encoding": "json"
}
]
},
{
"endpoint": "/200",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/200",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/201",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/201",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/400",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/400",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/401",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/401",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/404",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/404",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
},
{
"endpoint": "/500",
"method": "GET",
"headers_to_pass": [
"Authorization",
"Content-Type"
],
"backend": [
{
"host": [
"http://127.0.0.1:8080"
],
"url_pattern": "/v1/test/500",
"encoding": "json",
"extra_config": {
"github.com/devopsfaith/krakend-ratelimit/juju/proxy": {
"maxRate": 1,
"capacity": 1
},
"github.com/devopsfaith/krakend-circuitbreaker/gobreaker": {
"interval": 60,
"timeout": 10,
"maxErrors": 1
}
}
}
],
"output_encoding": "NoTransformRender"
}
]
}
This is my test server where I generate the APIs : webnode
Could you please help me? Thank you.
@nadim500, notice the pipe of that example has two important customizations:
Response.Io
.Response.Io
into the output bufferThese two details are very important because they make the pipe behave like a no-op
pipe, so no merging is supported because merging requires the data from the backends to be already decoded and it ignores the Response.Io
.
Great, it works well.
However, I have an issue with endpoint used to sign JWT (github.com/devopsfaith/krakend-jose/signer). Signed token is not returned from Krakend when using Backend "encoding": "no-op" (it returns only empty JSON). On the other hand, response from Krakend contains valid json with signed token when using Backend "encoding": "json". But StatusCode from Krakend response can be only 200 or 400. In the case of unsuccessful user authentication (e.g. wrong password), my backend sends 401 with additional JSON in the response body. Is it possible to proxy such response from backend through Krakend?
Thank you
It's the same for me. I can't get response body from backend when my backend return invalid password but krakend returns response 400 with empty response body. Can anyone help me ?
This issue was marked as resolved a long time ago and now has been automatically locked as there has not been any recent activity after it. You can still open a new issue and reference this link.
Hello,
I'm finding difficult to get an working example for getting error responses according to backend response and not the default 500 error response.
I've implemented my Gateway according to your Gin example and, at the moment, it is like this:
Could you please help me out on what should I change here? Thank you.