gopluginspanickrakend

invalid node type panic when implementing a plugin for KrakenD


im working on a plugin for no redirects. Im using krakend-ce 2.2.1 (with golang 1.19) im getting this panic:

gw_krakend_1  | [KRAKEND] 2023/03/15 - 21:09:06.675 ? DEBUG no_redirect_plugin: request received https://127.0.0.1:8443/ABC
gw_krakend_1  | [KRAKEND] 2023/03/15 - 21:09:06.689 ? DEBUG no_redirect_plugin: redirect detected https://127.0.0.1:8443/ABC
gw_krakend_1  | [KRAKEND] 2023/03/15 - 21:09:06.689 ? DEBUG Status code 302
gw_krakend_1  | 2023/03/15 21:09:06 http: panic serving [::1]:54778: invalid node type
gw_krakend_1  | goroutine 84 [running]:
gw_krakend_1  | net/http.(*conn).serve.func1()
gw_krakend_1  |         /usr/local/go/src/net/http/server.go:1854 +0xbf
gw_krakend_1  | panic({0x28cbb60, 0x34b5810})
gw_krakend_1  |         /usr/local/go/src/runtime/panic.go:890 +0x263
gw_krakend_1  | github.com/gin-gonic/gin.(*node).findCaseInsensitivePathRec(0x0?, {0xc0016ac2ec?, 0x0?}, {0xc0010fe800?, 0xc0016ac2ed?, 0xc000ced928?}, {0x0, 0x0, 0x0, 0x0}, ...)
gw_krakend_1  |         /go/pkg/mod/github.com/gin-gonic/gin@v1.8.2/tree.go:862 +0xa9d
gw_krakend_1  | github.com/gin-gonic/gin.(*node).findCaseInsensitivePath(0xc0016ac2ec?, {0xc0016ac2ec, 0x5}, 0x30?)
gw_krakend_1  |         /go/pkg/mod/github.com/gin-gonic/gin@v1.8.2/tree.go:669 +0x9c
gw_krakend_1  | github.com/gin-gonic/gin.redirectFixedPath(0xc000664300, 0xc0016ac2ec?, 0x60?)
gw_krakend_1  |         /go/pkg/mod/github.com/gin-gonic/gin@v1.8.2/gin.go:684 +0x5b
gw_krakend_1  | github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc000602b60, 0xc000664300)

seems similar to it https://github.com/gin-gonic/gin/issues/2959 but the version of gin was already upgraded on previous versions of krakend. It would be weird if they were really capital letters, without the plugin it works perfect. Im also triming the last / (for some reason its added in some point)

btw, im compiling the plugin with the same version of krakend.

package main

import (
    "context"
    "errors"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strings"
)

func main() {}

var ClientRegisterer = registerer("no_redirect_plugin")

type registerer string

type Logger interface {
    Debug(v ...interface{})
    Info(v ...interface{})
    Warning(v ...interface{})
    Error(v ...interface{})
    Critical(v ...interface{})
    Fatal(v ...interface{})
}

var logger Logger = nil

func (registerer) RegisterLogger(v interface{}) {
    l, ok := v.(Logger)
    if !ok {
        return
    }
    logger = l
    logger.Info(fmt.Sprintf("[PLUGIN: %s] Logger loaded", ClientRegisterer))
}

func (r registerer) RegisterClients(f func(
    name string,
    handler func(context.Context, map[string]interface{}) (http.Handler, error),
)) {
    f(string(r), r.registerClients)
}

func (r registerer) registerClients(_ context.Context, extra map[string]interface{}) (http.Handler, error) {
    name, ok := extra["name"].(string)
    if !ok {
        return nil, errors.New("wrong config")
    }

    if name != string(r) {
        return nil, fmt.Errorf("unknown register %s", name)
    }

    httpClient := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {

            // Trim the last "/" character from the URL if it exists
            urlStr := strings.TrimRight(req.URL.String(), "/")
            req.URL, _ = url.Parse(urlStr)

            logger.Debug("no_redirect_plugin: redirect detected", req.URL.String())
            return http.ErrUseLastResponse
        },
    }

    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

        // Trim the last "/" character from the URL if it exists
        urlStr := strings.TrimRight(req.URL.String(), "/")
        req.URL, _ = url.Parse(urlStr)

        logger.Debug("no_redirect_plugin: request received", req.URL.String())
        resp, err := httpClient.Do(req)
        if err != nil {
            logger.Debug("Error while proxying request", err.Error())
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        defer resp.Body.Close()

        for k, hs := range resp.Header {
            for _, h := range hs {
                w.Header().Add(k, h)
            }
        }

        w.WriteHeader(resp.StatusCode)
        logger.Debug("Status code", resp.StatusCode)
        if resp.Body == nil {
            return
        }

        _, err = io.Copy(w, resp.Body)
        if err != nil {
            logger.Debug("Error while proxying request 2", err.Error())
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

    }), nil
}

My endpoints are defined like this:

{
     "endpoint": "/",
     "input_headers":[
       "*"
     ],
     "input_query_strings":[
       "*"
     ],
     "method": "GET",
     "output_encoding": "no-op",
     "extra_config": {},
     "backend": [{
       "url_pattern": "",
       "encoding": "no-op",
       "sd": "static",
       "method": "GET",
       "extra_config": {
         "plugin/http-client": {
           "name": "no_redirect_plugin"
         }
       },
       "host": [
         "{{ env "HOST" }}"
       ],
       "disable_host_sanitize": false
   }]
},{
     "endpoint": "/{level1}",
     "input_headers":[
       "*"
     ],
     "input_query_strings":[
       "*"
     ],
     "method": "GET",
     "output_encoding": "no-op",
     "extra_config": {
     },
     "backend": [{
       "url_pattern": "/{level1}",
       "encoding": "no-op",
       "sd": "static",
       "method": "GET",
       "extra_config": {
         "plugin/http-client": {
           "name": "no_redirect_plugin"
         }
       },
       "host": [
         "{{ env "HOST" }}"
       ],
       "disable_host_sanitize": false
   }]
},{
     "endpoint": "/{level1}/{level2}",
     "input_headers":[
       "*"
     ],
     "input_query_strings":[
       "*"
     ],
     "method": "GET",
     "output_encoding": "no-op",
     "extra_config": {
     },
     "backend": [{
       "url_pattern": "/{level1}/{level2}",
       "encoding": "no-op",
       "sd": "static",
       "method": "GET",
       "extra_config": {
         "plugin/http-client": {
           "name": "no_redirect_plugin"
         }
       },
       "host": [
         "{{ env "HOST" }}"
       ],
       "disable_host_sanitize": false
   }]
}

EDIT: My browser is still showing /ABC/ instead of /ABC which may generate a posible colide between routes (like here: https://github.com/krakendio/krakend-ce/issues/386) anyways i dont know where the slash is being added (i thought i trimmed it for good... seems i didnt)

EDIT2: I found this https://www.krakend.io/docs/service-settings/router-options/ and with "disable_redirect_fixed_path": true and "disable_redirect_trailing_slash": true, it dont panic anymore... now i have another problem: infinite redirects (joking just 10) when my monolith tries to redirect to /a/ or any route with slash at the end, this worked before the plugin because KrakenD used to handle the redirects by its own...

i guess the principal issue here is the routing colide, when /{level1} and /{level1}/{level2} matches at the same time with /ABC/

Ideas?


Solution

  • Good, for solving this i tried to create a wildcard+no_redirect plugin with this https://www.krakend.io/docs/enterprise/endpoints/wildcard/ configuration but i failed even with copilot&gpt4, i dare you to implement this. So i solved it with another aproach: i put the gateway in front of my monolith and everytime the request failed i added the route... because we have no documented routes (*cries). For the files that needs the no redirect plugin it worked perfectly. An aweful solution but it worked till now, and it didn't take as long as I thought