{"record":{"id":"8ab6428682f63529","repo":"GopeedLab/gopeed","slug":"method-not-allowed","errorCode":null,"errorMessage":"method not allowed","messagePattern":"method not allowed","errorType":"http","errorClass":null,"httpStatus":405,"severity":"error","filePath":"internal/blob/registry.go","lineNumber":311,"sourceCode":"\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tr.listener = listener\n\tr.baseURL = \"http://\" + listener.Addr().String() + urlPathPrefix\n\tserver := &http.Server{Handler: r}\n\tr.server = server\n\tgo func() {\n\t\tif err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\t_ = r.Close()\n\t\t}\n\t}()\n\treturn r.baseURL, nil\n}\n\nfunc (r *Registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif req.Method != http.MethodGet {\n\t\tw.Header().Set(\"Allow\", \"GET\")\n\t\thttp.Error(w, \"method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\tid := parseRequest(req)\n\tif id == \"\" {\n\t\thttp.NotFound(w, req)\n\t\treturn\n\t}\n\tsrc, err := r.getByID(id)\n\tif err != nil {\n\t\thttp.NotFound(w, req)\n\t\treturn\n\t}\n\tmeta, open, session := src.acquireOpen()\n\tif open == nil {\n\t\thttp.NotFound(w, req)\n\t\treturn\n\t}\n\tif session != nil {","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/blob/registry.go#L293-L329","documentation":"The registry serves blobs from a loopback HTTP server (paths under /__blob/) and its handler is read-only: only GET is accepted. ServeHTTP rejects every other method with 405 and an 'Allow: GET' response header before it even parses the blob ID, so the rejection is independent of whether the blob exists. Any HEAD, POST, PUT, DELETE, or OPTIONS request to a blob URL gets this response.","triggerScenarios":"Issuing any non-GET request to a URL returned by Registry.CreateBlob/CreateOpener, e.g. http.Head(blobURL) for an existence probe, or a generic HTTP client whose default method is POST. The method check at registry.go:309 runs first, so even HEAD on a valid, live blob URL returns 405.","commonSituations":"Health/existence checks written with http.Head; download frameworks that probe servers with OPTIONS or HEAD before GETting; axios/fetch calls that default to POST; wrapping code that forwards all methods to the registry handler unchanged.","solutions":["Change the request to GET — it is the only method the endpoint serves","From Go, skip HTTP probing entirely: use Registry.IsURL(url) or Registry.Metadata(url) to check a blob","If you mount the registry behind your own mux, route only GET to /__blob/ and answer other methods yourself","When debugging, read the 'Allow: GET' response header to confirm the supported method set"],"exampleFix":"// before\nresp, err := http.Head(blobURL) // 405 method not allowed\n\n// after\nresp, err := http.Get(blobURL)\n// existence checks from Go code:\nif reg.IsURL(blobURL) { /* source registered */ }","handlingStrategy":"validation","validationCode":"// blob URLs are GET-only: gate the method before issuing the request\nif method != http.MethodGet {\n\treturn fmt.Errorf(\"blob endpoint is GET-only, got %s\", method)\n}\nreq, err := http.NewRequest(http.MethodGet, blobURL, nil)\nif err != nil {\n\treturn err\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.Get(blobURL)\nif err != nil {\n\treturn err\n}\ndefer resp.Body.Close()\nif resp.StatusCode == http.StatusMethodNotAllowed { // 405\n\t// wrong method; only GET is served (resp.Header.Get(\"Allow\") == \"GET\")\n\treturn fmt.Errorf(\"blob endpoint rejected %s; use GET\", method)\n}","preventionTips":["Treat blob URLs as read-only GET resources","Use Registry.IsURL / Registry.Metadata instead of HTTP HEAD probes","When a 405 appears, check the Allow header before assuming the blob is missing"],"tags":["http","http-405","blob-registry","method"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}