{"record":{"id":"ea230da3418d2575","repo":"fatedier/frp","slug":"missing-s","errorCode":null,"errorMessage":"missing %s","messagePattern":"missing (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"server/http/controller_v2.go","lineNumber":300,"sourceCode":"// /api/v2/proxies/{name}/traffic\nfunc (c *Controller) APIV2ProxyTraffic(ctx *httppkg.Context) (any, error) {\n\tname, err := decodeV2PathParam(ctx, \"name\", \"proxy name\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)\n\tif proxyTrafficInfo == nil {\n\t\treturn nil, httppkg.NewError(http.StatusNotFound, \"no proxy info found\")\n\t}\n\n\treturn buildV2ProxyTrafficResp(name, proxyTrafficInfo, time.Now()), nil\n}\n\nfunc decodeV2PathParam(ctx *httppkg.Context, key string, label string) (string, error) {\n\traw := ctx.Param(key)\n\tif raw == \"\" {\n\t\treturn \"\", fmt.Errorf(\"missing %s\", label)\n\t}\n\tdecoded, err := url.PathUnescape(raw)\n\tif err != nil {\n\t\treturn \"\", httppkg.NewError(http.StatusBadRequest, fmt.Sprintf(\"invalid %s\", label))\n\t}\n\treturn decoded, nil\n}\n\nfunc getOrCreateV2User(items map[string]*model.V2UserResp, user string) *model.V2UserResp {\n\titem, ok := items[user]\n\tif !ok {\n\t\titem = &model.V2UserResp{User: user}\n\t\titems[user] = item\n\t}\n\treturn item\n}\n\nfunc parseV2PageParams(ctx *httppkg.Context) (int, int, error) {","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/http/controller_v2.go#L282-L318","documentation":"Produced by decodeV2PathParam when the requested path parameter (e.g. \"key\" for /api/v2/clients/{key}) is an empty string. The route matched but the variable segment is missing, so no URL-decoding is attempted. Because it is created with fmt.Errorf rather than httppkg.NewError, it surfaces as an internal-style error instead of a clean 400.","triggerScenarios":"Requesting /api/v2/clients/ (trailing slash, empty key) or /api/v2/clients//status; a route rewriter or reverse proxy stripping the path segment; calling the router with a mismatched route pattern that leaves the param unset.","commonSituations":"Manually building dashboard/API URLs and forgetting the key; gateway rewrite rules (nginx traefik) that trim the last path segment; scripts concatenating an empty variable into the URL.","solutions":["Include the client key in the path: GET /api/v2/clients/{actual-key}","Check reverse-proxy rewrite rules if the caller sends the key but the server sees it empty","In scripts, assert the key variable is non-empty before issuing the request"],"exampleFix":"# before\ncurl http://frps:7500/api/v2/clients/\n\n# after\ncurl http://frps:7500/api/v2/clients/abc123","handlingStrategy":"validation","validationCode":"key := os.Getenv(\"CLIENT_KEY\")\nif strings.TrimSpace(key) == \"\" {\n    log.Fatal(\"CLIENT_KEY must not be empty\")\n}\nurl := fmt.Sprintf(\"/api/v2/clients/%s\", url.PathEscape(key))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always URL-encode and non-empty-check path parameters before building v2 API URLs","Assert on the shape of the final URL (no empty segments) in integration tests","Watch reverse-proxy rewrite rules that can strip the last path segment"],"tags":["frps","api","routing","validation"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}