{"record":{"id":"09e22b6487627178","repo":"kataras/iris","slug":"precondition-failed","errorCode":null,"errorMessage":"precondition failed","messagePattern":"precondition failed","errorType":"error_code","errorClass":null,"httpStatus":412,"severity":"error","filePath":"context/context.go","lineNumber":3458,"sourceCode":"func (ctx *Context) SetLastModified(modtime time.Time) {\n\tif !IsZeroTime(modtime) {\n\t\tctx.Header(LastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()?\n\t}\n}\n\n// ErrPreconditionFailed may be returned from `Context` methods\n// that has to perform one or more client side preconditions before the actual check, e.g. `CheckIfModifiedSince`.\n// Usage:\n// ok, err := context.CheckIfModifiedSince(modTime)\n//\n//\tif err != nil {\n//\t   if errors.Is(err, context.ErrPreconditionFailed) {\n//\t        [handle missing client conditions,such as not valid request method...]\n//\t    }else {\n//\t        [the error is probably a time parse error...]\n//\t   }\n//\t}\nvar ErrPreconditionFailed = errors.New(\"precondition failed\")\n\n// CheckIfModifiedSince checks if the response is modified since the \"modtime\".\n// Note that it has nothing to do with server-side caching.\n// It does those checks by checking if the \"If-Modified-Since\" request header\n// sent by client or a previous server response header\n// (e.g with WriteWithExpiration or HandleDir or Favicon etc.)\n// is a valid one and it's before the \"modtime\".\n//\n// A check for !modtime && err == nil is necessary to make sure that\n// it's not modified since, because it may return false but without even\n// had the chance to check the client-side (request) header due to some errors,\n// like the HTTP Method is not \"GET\" or \"HEAD\" or if the \"modtime\" is zero\n// or if parsing time from the header failed. See `ErrPreconditionFailed` too.\n//\n// It's mostly used internally, e.g. `context#WriteWithExpiration`.\nfunc (ctx *Context) CheckIfModifiedSince(modtime time.Time) (bool, error) {\n\tif method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {\n\t\treturn false, fmt.Errorf(\"method: %w\", ErrPreconditionFailed)","sourceCodeStart":3440,"sourceCodeEnd":3476,"githubUrl":"https://github.com/kataras/iris/blob/7bedaf55a0b64bbb2248a5845a2c60d81a30996a/context/context.go#L3440-L3476","documentation":"ErrPreconditionFailed is a sentinel error returned by Context.CheckPreconditions (and its helpers such as CheckIfModifiedSince / CheckIfNoneMatch) when a client precondition header does not match the server state, so the request should be answered with 412 Precondition Failed rather than the normal handler body. The library throws it to separate precondition failures from other errors like time-parsing failures of the If-Modified-Since header.","triggerScenarios":"ctx.CheckIfModifiedSince(modtime, ...) when the If-Modified-Since header equals or is after modtime with ShouldReplyPreconditionFailed semantics; ctx.CheckIfNoneMatch(etag) when If-(None-)Match preconditions fail under a modifying method; ctx.CheckPreconditions(modtime, etag) returning this error so the handler must write 412.","commonSituations":"Implementing conditional GET caching with ETag/Last-Modified; clients (proxies, curl -H 'If-Modified-Since: ...') sending stale or mismatched conditions; HTTP method not GET/HEAD for If-Modified-Since; developers mistaking this for a time.Parse failure and not checking errors.Is(err, context.ErrPreconditionFailed) first.","solutions":["Check the error with errors.Is(err, context.ErrPreconditionFailed) and reply ctx.WriteNotModified() or ctx.StatusCode(http.StatusPreconditionFailed) accordingly.","Handle non-sentinel errors separately as time-parse failures of the precondition header (log/ignore and continue serving the resource).","Only call CheckIfModifiedSince for GET/HEAD requests, as the method requires.","Make sure the modtime/ETag you pass matches what previous responses advertised (WriteWithExpiration, HandleDir, etc.)."],"exampleFix":"// before\nif err := ctx.CheckPreconditions(modtime, etag); err != nil {\n    return err\n}\n\n// after\nif err := ctx.CheckPreconditions(modtime, etag); err != nil {\n    if errors.Is(err, context.ErrPreconditionFailed) {\n        ctx.StatusCode(http.StatusPreconditionFailed)\n        return nil\n    }\n    return err // time parse error or other\n}","handlingStrategy":"try-catch","validationCode":"// Only run preconditions on methods that allow them:\nif ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {\n    _ = ctx.CheckIfModifiedSince(modtime, context.ShouldReplyPreconditionFailed)\n}","typeGuard":"func isPreconditionFailed(err error) bool {\n    return errors.Is(err, context.ErrPreconditionFailed)\n}","tryCatchPattern":"if err := ctx.CheckPreconditions(modtime, etag); err != nil {\n    if errors.Is(err, context.ErrPreconditionFailed) {\n        ctx.StatusCode(http.StatusPreconditionFailed)\n        return nil\n    }\n    // otherwise it's a header time-parse error: ignore and serve normally\n    return nil\n}","preventionTips":["Check errors.Is(err, context.ErrPreconditionFailed) before other error handling, as the docs show.","Only call CheckIfModifiedSince for GET/HEAD requests.","Keep the modtime/ETag consistent across responses (expiration headers, HandleDir) so conditions match.","Never treat precondition failures as server errors; map them to 412/304 as appropriate."],"tags":["http","conditional-request","caching","etag","iris"],"backgroundTag":"precondition-failed-412","analyzedSha":"7bedaf55a0b64bbb2248a5845a2c60d81a30996a","analyzedAt":"2026-08-30T20:38:16.250Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}