[{"data":1,"prerenderedAt":1704},["ShallowReactive",2],{"navigation_docs":3,"-core-middleware":172,"-core-middleware-surround":1699},[4,26,67,98,124,140,161],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting Started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Core","i-lucide-box","\u002Fcore","2.core",[32,37,42,47,52,57,62],{"title":33,"path":34,"stem":35,"icon":36},"Routing","\u002Fcore\u002Frouting","2.core\u002F1.routing","i-lucide-route",{"title":38,"path":39,"stem":40,"icon":41},"Handlers","\u002Fcore\u002Fhandlers","2.core\u002F2.handlers","i-lucide-braces",{"title":43,"path":44,"stem":45,"icon":46},"Request Binding","\u002Fcore\u002Frequest-binding","2.core\u002F3.request-binding","i-lucide-tags",{"title":48,"path":49,"stem":50,"icon":51},"Validation","\u002Fcore\u002Fvalidation","2.core\u002F4.validation","i-lucide-check-check",{"title":53,"path":54,"stem":55,"icon":56},"Context","\u002Fcore\u002Fcontext","2.core\u002F5.context","i-lucide-file-input",{"title":58,"path":59,"stem":60,"icon":61},"Errors","\u002Fcore\u002Ferrors","2.core\u002F6.errors","i-lucide-triangle-alert",{"title":63,"path":64,"stem":65,"icon":66},"Middleware","\u002Fcore\u002Fmiddleware","2.core\u002F7.middleware","i-lucide-layers",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":25},"Auth","i-lucide-shield-check","\u002Fauth","3.auth",[73,78,83,88,93],{"title":74,"path":75,"stem":76,"icon":77},"Overview","\u002Fauth\u002Foverview","3.auth\u002F1.overview","i-lucide-shield",{"title":79,"path":80,"stem":81,"icon":82},"Auth Rules","\u002Fauth\u002Fauth-rules","3.auth\u002F2.auth-rules","i-lucide-lock",{"title":84,"path":85,"stem":86,"icon":87},"JWT Providers","\u002Fauth\u002Fjwt-providers","3.auth\u002F3.jwt-providers","i-lucide-key",{"title":89,"path":90,"stem":91,"icon":92},"Roles & Permissions","\u002Fauth\u002Froles-and-permissions","3.auth\u002F4.roles-and-permissions","i-lucide-users",{"title":94,"path":95,"stem":96,"icon":97},"Audit Logging","\u002Fauth\u002Faudit-logging","3.auth\u002F5.audit-logging","i-lucide-scroll-text",{"title":99,"icon":100,"path":101,"stem":102,"children":103,"page":25},"GoTrue","i-lucide-key-round","\u002Fgotrue","4.gotrue",[104,109,114,119],{"title":105,"path":106,"stem":107,"icon":108},"Proxy","\u002Fgotrue\u002Fproxy","4.gotrue\u002F1.proxy","i-lucide-shuffle",{"title":110,"path":111,"stem":112,"icon":113},"Endpoints","\u002Fgotrue\u002Fendpoints","4.gotrue\u002F2.endpoints","i-lucide-list",{"title":115,"path":116,"stem":117,"icon":118},"Client IP & Rate Limits","\u002Fgotrue\u002Fclient-ip-and-rate-limits","4.gotrue\u002F3.client-ip-and-rate-limits","i-lucide-network",{"title":120,"path":121,"stem":122,"icon":123},"Deployment","\u002Fgotrue\u002Fdeployment","4.gotrue\u002F4.deployment","i-lucide-server",{"title":125,"icon":126,"path":127,"stem":128,"children":129,"page":25},"API Docs","i-lucide-file-text","\u002Fapi-docs","5.api-docs",[130,135],{"title":131,"path":132,"stem":133,"icon":134},"OpenAPI","\u002Fapi-docs\u002Fopenapi","5.api-docs\u002F1.openapi","i-lucide-file-json",{"title":136,"path":137,"stem":138,"icon":139},"Scalar UI","\u002Fapi-docs\u002Fscalar","5.api-docs\u002F2.scalar","i-lucide-book-open-text",{"title":141,"icon":142,"path":143,"stem":144,"children":145,"page":25},"Guides","i-lucide-compass","\u002Fguides","6.guides",[146,151,156],{"title":147,"path":148,"stem":149,"icon":150},"Testing","\u002Fguides\u002Ftesting","6.guides\u002F1.testing","i-lucide-flask-conical",{"title":152,"path":153,"stem":154,"icon":155},"Performance","\u002Fguides\u002Fperformance","6.guides\u002F2.performance","i-lucide-gauge",{"title":157,"path":158,"stem":159,"icon":160},"Releasing","\u002Fguides\u002Freleasing","6.guides\u002F3.releasing","i-lucide-tag",{"title":162,"icon":163,"path":164,"stem":165,"children":166,"page":25},"Reference","i-lucide-code","\u002Freference","7.reference",[167],{"title":168,"path":169,"stem":170,"icon":171},"API Reference","\u002Freference\u002Fapi-reference","7.reference\u002F1.api-reference","i-lucide-list-tree",{"id":173,"title":63,"body":174,"description":1692,"extension":1693,"links":1694,"meta":1695,"navigation":1696,"path":64,"seo":1697,"stem":65,"__hash__":1698},"docs\u002F2.core\u002F7.middleware.md",{"type":175,"value":176,"toc":1682},"minimark",[177,186,231,236,242,298,305,427,440,444,447,455,458,468,499,503,687,711,715,838,849,853,856,946,949,1087,1105,1115,1119,1122,1199,1203,1432,1438,1644,1659,1663,1678],[178,179,180,181,185],"p",{},"Middleware is the standard shape, so anything written for ",[182,183,184],"code",{},"net\u002Fhttp"," works unchanged:",[187,188,193],"pre",{"className":189,"code":190,"language":191,"meta":192,"style":192},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","type Middleware func(http.Handler) http.Handler\n","go","",[182,194,195],{"__ignoreMap":192},[196,197,200,204,208,211,214,217,220,223,226,228],"span",{"class":198,"line":199},"line",1,[196,201,203],{"class":202},"sMK4o","type",[196,205,207],{"class":206},"sBMFI"," Middleware",[196,209,210],{"class":202}," func(",[196,212,213],{"class":206},"http",[196,215,216],{"class":202},".",[196,218,219],{"class":206},"Handler",[196,221,222],{"class":202},")",[196,224,225],{"class":206}," http",[196,227,216],{"class":202},[196,229,230],{"class":206},"Handler\n",[232,233,235],"h2",{"id":234},"start-from-the-defaults","Start from the defaults",[178,237,238,241],{},[182,239,240],{},"ezz.New()"," installs three, in this order:",[243,244,245,257],"table",{},[246,247,248],"thead",{},[249,250,251,254],"tr",{},[252,253,63],"th",{},[252,255,256],{},"What it does",[258,259,260,274,288],"tbody",{},[249,261,262,268],{},[263,264,265],"td",{},[182,266,267],{},"Recovery(logger)",[263,269,270,271],{},"Catches panics, logs them, replies ",[182,272,273],{},"500",[249,275,276,281],{},[263,277,278],{},[182,279,280],{},"RequestID()",[263,282,283,284,287],{},"Reuses an incoming ",[182,285,286],{},"X-Request-ID"," or generates one, and echoes it on the response",[249,289,290,295],{},[263,291,292],{},[182,293,294],{},"Logging(logger)",[263,296,297],{},"Logs method, path, status, duration, remote address, and user agent",[178,299,300,301,304],{},"Add more with ",[182,302,303],{},"Use",":",[187,306,308],{"className":189,"code":307,"language":191,"meta":192,"style":192},"import \"github.com\u002Fsulv-io\u002Fezz\u002Fmiddleware\"\n\napp := ezz.New().\n    Use(\n        ezz.CORS(ezz.DefaultCORSConfig()),\n        middleware.Gzip(),\n        middleware.IPRateLimit(100),\n    )\n",[182,309,310,325,332,354,363,388,402,421],{"__ignoreMap":192},[196,311,312,316,319,322],{"class":198,"line":199},[196,313,315],{"class":314},"s7zQu","import",[196,317,318],{"class":202}," \"",[196,320,321],{"class":206},"github.com\u002Fsulv-io\u002Fezz\u002Fmiddleware",[196,323,324],{"class":202},"\"\n",[196,326,328],{"class":198,"line":327},2,[196,329,331],{"emptyLinePlaceholder":330},true,"\n",[196,333,335,339,342,345,347,351],{"class":198,"line":334},3,[196,336,338],{"class":337},"sTEyZ","app ",[196,340,341],{"class":202},":=",[196,343,344],{"class":337}," ezz",[196,346,216],{"class":202},[196,348,350],{"class":349},"s2Zo4","New",[196,352,353],{"class":202},"().\n",[196,355,357,360],{"class":198,"line":356},4,[196,358,359],{"class":349},"    Use",[196,361,362],{"class":202},"(\n",[196,364,366,369,371,374,377,380,382,385],{"class":198,"line":365},5,[196,367,368],{"class":337},"        ezz",[196,370,216],{"class":202},[196,372,373],{"class":349},"CORS",[196,375,376],{"class":202},"(",[196,378,379],{"class":337},"ezz",[196,381,216],{"class":202},[196,383,384],{"class":349},"DefaultCORSConfig",[196,386,387],{"class":202},"()),\n",[196,389,391,394,396,399],{"class":198,"line":390},6,[196,392,393],{"class":337},"        middleware",[196,395,216],{"class":202},[196,397,398],{"class":349},"Gzip",[196,400,401],{"class":202},"(),\n",[196,403,405,407,409,412,414,418],{"class":198,"line":404},7,[196,406,393],{"class":337},[196,408,216],{"class":202},[196,410,411],{"class":349},"IPRateLimit",[196,413,376],{"class":202},[196,415,417],{"class":416},"sbssI","100",[196,419,420],{"class":202},"),\n",[196,422,424],{"class":198,"line":423},8,[196,425,426],{"class":202},"    )\n",[428,429,430,433,434,437,438,216],"warning",{},[182,431,432],{},"Logger(l)"," rebuilds the default three with the new logger and discards anything added before it. Call ",[182,435,436],{},"Logger"," first, then ",[182,439,303],{},[232,441,443],{"id":442},"understand-the-order","Understand the order",[178,445,446],{},"The chain is compiled when a route is registered, not per request. Middleware runs outside-in, in the order it was added, and app-level middleware wraps group-level middleware:",[187,448,453],{"className":449,"code":451,"language":452},[450],"language-text","auth check\n  └─ Recovery\n      └─ RequestID\n          └─ Logging\n              └─ your app middleware, in Use order\n                  └─ group middleware, in Use order\n                      └─ handler (binding, validation, your function)\n","text",[182,454,451],{"__ignoreMap":192},[178,456,457],{},"Two consequences are worth knowing before you debug something surprising.",[428,459,460,464,465,467],{},[461,462,463],"strong",{},"Middleware must be registered before routes."," Each route captures the chain that exists at registration time, so ",[182,466,303],{}," after a route is registered has no effect on it. Configure the app fully, then register routes.",[469,470,471,474,475,478,479,482,483,486,487,490,491,493,494,498],"caution",{},[461,472,473],{},"Auth rejections run before the middleware chain."," A ",[182,476,477],{},"401"," or ",[182,480,481],{},"403"," produced by a route's auth rule is written before ",[182,484,485],{},"Recovery",", ",[182,488,489],{},"Logging",", or any middleware you added, so those responses are not logged and carry no middleware-added headers. That includes CORS headers, so a browser calling a protected route without a token sees a CORS failure rather than a clean ",[182,492,477],{},". If that matters, handle authentication in middleware you control (see ",[495,496,497],"a",{"href":85},"JWT providers",") or terminate CORS at your proxy.",[232,500,502],{"id":501},"add-cors","Add CORS",[187,504,506],{"className":189,"code":505,"language":191,"meta":192,"style":192},"app.Use(ezz.CORS(ezz.CORSConfig{\n    AllowedOrigins:   []string{\"https:\u002F\u002Fapp.example.com\", \"https:\u002F\u002Fadmin.example.com\"},\n    AllowedMethods:   []string{\"GET\", \"POST\", \"PATCH\", \"DELETE\"},\n    AllowedHeaders:   []string{\"Content-Type\", \"Authorization\"},\n    AllowCredentials: true,\n    MaxAge:           3600,\n}))\n",[182,507,508,537,576,625,656,670,682],{"__ignoreMap":192},[196,509,510,513,515,517,519,521,523,525,527,529,531,534],{"class":198,"line":199},[196,511,512],{"class":337},"app",[196,514,216],{"class":202},[196,516,303],{"class":349},[196,518,376],{"class":202},[196,520,379],{"class":337},[196,522,216],{"class":202},[196,524,373],{"class":349},[196,526,376],{"class":202},[196,528,379],{"class":206},[196,530,216],{"class":202},[196,532,533],{"class":206},"CORSConfig",[196,535,536],{"class":202},"{\n",[196,538,539,542,544,547,551,554,557,561,563,566,568,571,573],{"class":198,"line":327},[196,540,541],{"class":337},"    AllowedOrigins",[196,543,304],{"class":202},[196,545,546],{"class":202},"   []",[196,548,550],{"class":549},"spNyl","string",[196,552,553],{"class":202},"{",[196,555,556],{"class":202},"\"",[196,558,560],{"class":559},"sfazB","https:\u002F\u002Fapp.example.com",[196,562,556],{"class":202},[196,564,565],{"class":202},",",[196,567,318],{"class":202},[196,569,570],{"class":559},"https:\u002F\u002Fadmin.example.com",[196,572,556],{"class":202},[196,574,575],{"class":202},"},\n",[196,577,578,581,583,585,587,589,591,594,596,598,600,603,605,607,609,612,614,616,618,621,623],{"class":198,"line":334},[196,579,580],{"class":337},"    AllowedMethods",[196,582,304],{"class":202},[196,584,546],{"class":202},[196,586,550],{"class":549},[196,588,553],{"class":202},[196,590,556],{"class":202},[196,592,593],{"class":559},"GET",[196,595,556],{"class":202},[196,597,565],{"class":202},[196,599,318],{"class":202},[196,601,602],{"class":559},"POST",[196,604,556],{"class":202},[196,606,565],{"class":202},[196,608,318],{"class":202},[196,610,611],{"class":559},"PATCH",[196,613,556],{"class":202},[196,615,565],{"class":202},[196,617,318],{"class":202},[196,619,620],{"class":559},"DELETE",[196,622,556],{"class":202},[196,624,575],{"class":202},[196,626,627,630,632,634,636,638,640,643,645,647,649,652,654],{"class":198,"line":356},[196,628,629],{"class":337},"    AllowedHeaders",[196,631,304],{"class":202},[196,633,546],{"class":202},[196,635,550],{"class":549},[196,637,553],{"class":202},[196,639,556],{"class":202},[196,641,642],{"class":559},"Content-Type",[196,644,556],{"class":202},[196,646,565],{"class":202},[196,648,318],{"class":202},[196,650,651],{"class":559},"Authorization",[196,653,556],{"class":202},[196,655,575],{"class":202},[196,657,658,661,663,667],{"class":198,"line":365},[196,659,660],{"class":337},"    AllowCredentials",[196,662,304],{"class":202},[196,664,666],{"class":665},"sfNiH"," true",[196,668,669],{"class":202},",\n",[196,671,672,675,677,680],{"class":198,"line":390},[196,673,674],{"class":337},"    MaxAge",[196,676,304],{"class":202},[196,678,679],{"class":416},"           3600",[196,681,669],{"class":202},[196,683,684],{"class":198,"line":404},[196,685,686],{"class":202},"}))\n",[178,688,689,692,693,695,696,698,699,702,703,706,707,710],{},[182,690,691],{},"DefaultCORSConfig()"," allows every origin, the common methods, and ",[182,694,642],{}," plus ",[182,697,651],{},", with a one-hour preflight cache. Origins are looked up in a map built once at construction, and an ",[182,700,701],{},"OPTIONS"," request is answered with ",[182,704,705],{},"200"," without reaching your handler. An empty ",[182,708,709],{},"AllowedOrigins"," means \"allow all\", so set it explicitly in production.",[232,712,714],{"id":713},"compress-responses","Compress responses",[187,716,718],{"className":189,"code":717,"language":191,"meta":192,"style":192},"app.Use(middleware.Gzip())\n\n\u002F\u002F or tune it\napp.Use(middleware.Gzip(middleware.CompressionConfig{\n    Level:     6,\n    MinLength: 2048,\n    MIMETypes: map[string]bool{\"application\u002Fjson\": true},\n}))\n",[182,719,720,740,744,750,777,789,801,834],{"__ignoreMap":192},[196,721,722,724,726,728,730,733,735,737],{"class":198,"line":199},[196,723,512],{"class":337},[196,725,216],{"class":202},[196,727,303],{"class":349},[196,729,376],{"class":202},[196,731,732],{"class":337},"middleware",[196,734,216],{"class":202},[196,736,398],{"class":349},[196,738,739],{"class":202},"())\n",[196,741,742],{"class":198,"line":327},[196,743,331],{"emptyLinePlaceholder":330},[196,745,746],{"class":198,"line":334},[196,747,749],{"class":748},"sHwdD","\u002F\u002F or tune it\n",[196,751,752,754,756,758,760,762,764,766,768,770,772,775],{"class":198,"line":356},[196,753,512],{"class":337},[196,755,216],{"class":202},[196,757,303],{"class":349},[196,759,376],{"class":202},[196,761,732],{"class":337},[196,763,216],{"class":202},[196,765,398],{"class":349},[196,767,376],{"class":202},[196,769,732],{"class":206},[196,771,216],{"class":202},[196,773,774],{"class":206},"CompressionConfig",[196,776,536],{"class":202},[196,778,779,782,784,787],{"class":198,"line":365},[196,780,781],{"class":337},"    Level",[196,783,304],{"class":202},[196,785,786],{"class":416},"     6",[196,788,669],{"class":202},[196,790,791,794,796,799],{"class":198,"line":390},[196,792,793],{"class":337},"    MinLength",[196,795,304],{"class":202},[196,797,798],{"class":416}," 2048",[196,800,669],{"class":202},[196,802,803,806,808,811,813,816,819,821,823,826,828,830,832],{"class":198,"line":404},[196,804,805],{"class":337},"    MIMETypes",[196,807,304],{"class":202},[196,809,810],{"class":202}," map[",[196,812,550],{"class":549},[196,814,815],{"class":202},"]",[196,817,818],{"class":549},"bool",[196,820,553],{"class":202},[196,822,556],{"class":202},[196,824,825],{"class":559},"application\u002Fjson",[196,827,556],{"class":202},[196,829,304],{"class":202},[196,831,666],{"class":665},[196,833,575],{"class":202},[196,835,836],{"class":198,"line":423},[196,837,686],{"class":202},[178,839,840,841,844,845,848],{},"Defaults are level 6, a 1 KB minimum, and JSON, JavaScript, XML, CSS, HTML, and plain text. Responses are only compressed when the client sends ",[182,842,843],{},"Accept-Encoding: gzip",". ",[182,846,847],{},"middleware.DecompressionMiddleware()"," handles the reverse case, transparently decoding gzipped request bodies.",[232,850,852],{"id":851},"rate-limit-requests","Rate limit requests",[178,854,855],{},"The in-memory limiters are token buckets kept in a map keyed by whatever you choose, with a background sweep for idle keys.",[187,857,859],{"className":189,"code":858,"language":191,"meta":192,"style":192},"app.Use(middleware.IPRateLimit(100))                      \u002F\u002F per client IP, per minute\napp.Use(middleware.APIKeyRateLimitMiddleware(1000))       \u002F\u002F per X-API-Key, else Authorization, else IP\napp.Use(middleware.UserRateLimitMiddleware(60, userID))   \u002F\u002F per value from your own function\n",[182,860,861,887,914],{"__ignoreMap":192},[196,862,863,865,867,869,871,873,875,877,879,881,884],{"class":198,"line":199},[196,864,512],{"class":337},[196,866,216],{"class":202},[196,868,303],{"class":349},[196,870,376],{"class":202},[196,872,732],{"class":337},[196,874,216],{"class":202},[196,876,411],{"class":349},[196,878,376],{"class":202},[196,880,417],{"class":416},[196,882,883],{"class":202},"))",[196,885,886],{"class":748},"                      \u002F\u002F per client IP, per minute\n",[196,888,889,891,893,895,897,899,901,904,906,909,911],{"class":198,"line":327},[196,890,512],{"class":337},[196,892,216],{"class":202},[196,894,303],{"class":349},[196,896,376],{"class":202},[196,898,732],{"class":337},[196,900,216],{"class":202},[196,902,903],{"class":349},"APIKeyRateLimitMiddleware",[196,905,376],{"class":202},[196,907,908],{"class":416},"1000",[196,910,883],{"class":202},[196,912,913],{"class":748},"       \u002F\u002F per X-API-Key, else Authorization, else IP\n",[196,915,916,918,920,922,924,926,928,931,933,936,938,941,943],{"class":198,"line":334},[196,917,512],{"class":337},[196,919,216],{"class":202},[196,921,303],{"class":349},[196,923,376],{"class":202},[196,925,732],{"class":337},[196,927,216],{"class":202},[196,929,930],{"class":349},"UserRateLimitMiddleware",[196,932,376],{"class":202},[196,934,935],{"class":416},"60",[196,937,565],{"class":202},[196,939,940],{"class":337}," userID",[196,942,883],{"class":202},[196,944,945],{"class":748},"   \u002F\u002F per value from your own function\n",[178,947,948],{},"For full control, build a limiter and a key function:",[187,950,952],{"className":189,"code":951,"language":191,"meta":192,"style":192},"limiter := middleware.NewMemoryLimiter(100, time.Second, 5*time.Minute)\n\napp.Use(middleware.RateLimitMiddleware(limiter, func(r *http.Request) string {\n    return r.Header.Get(\"X-Tenant-ID\")\n}))\n",[182,953,954,1002,1006,1054,1083],{"__ignoreMap":192},[196,955,956,959,961,964,966,969,971,973,975,978,980,983,985,988,991,994,996,999],{"class":198,"line":199},[196,957,958],{"class":337},"limiter ",[196,960,341],{"class":202},[196,962,963],{"class":337}," middleware",[196,965,216],{"class":202},[196,967,968],{"class":349},"NewMemoryLimiter",[196,970,376],{"class":202},[196,972,417],{"class":416},[196,974,565],{"class":202},[196,976,977],{"class":337}," time",[196,979,216],{"class":202},[196,981,982],{"class":337},"Second",[196,984,565],{"class":202},[196,986,987],{"class":416}," 5",[196,989,990],{"class":202},"*",[196,992,993],{"class":337},"time",[196,995,216],{"class":202},[196,997,998],{"class":337},"Minute",[196,1000,1001],{"class":202},")\n",[196,1003,1004],{"class":198,"line":327},[196,1005,331],{"emptyLinePlaceholder":330},[196,1007,1008,1010,1012,1014,1016,1018,1020,1023,1025,1028,1030,1032,1036,1039,1041,1043,1046,1048,1051],{"class":198,"line":334},[196,1009,512],{"class":337},[196,1011,216],{"class":202},[196,1013,303],{"class":349},[196,1015,376],{"class":202},[196,1017,732],{"class":337},[196,1019,216],{"class":202},[196,1021,1022],{"class":349},"RateLimitMiddleware",[196,1024,376],{"class":202},[196,1026,1027],{"class":337},"limiter",[196,1029,565],{"class":202},[196,1031,210],{"class":202},[196,1033,1035],{"class":1034},"sHdIc","r",[196,1037,1038],{"class":202}," *",[196,1040,213],{"class":206},[196,1042,216],{"class":202},[196,1044,1045],{"class":206},"Request",[196,1047,222],{"class":202},[196,1049,1050],{"class":549}," string",[196,1052,1053],{"class":202}," {\n",[196,1055,1056,1059,1062,1064,1067,1069,1072,1074,1076,1079,1081],{"class":198,"line":356},[196,1057,1058],{"class":314},"    return",[196,1060,1061],{"class":337}," r",[196,1063,216],{"class":202},[196,1065,1066],{"class":337},"Header",[196,1068,216],{"class":202},[196,1070,1071],{"class":349},"Get",[196,1073,376],{"class":202},[196,1075,556],{"class":202},[196,1077,1078],{"class":559},"X-Tenant-ID",[196,1080,556],{"class":202},[196,1082,1001],{"class":202},[196,1084,1085],{"class":198,"line":365},[196,1086,686],{"class":202},[178,1088,1089,1092,1093,1096,1097,1100,1101,1104],{},[182,1090,1091],{},"middleware.CustomRateLimitMiddleware(capacity, refill, keyFunc)"," is the same thing in one call. Exceeding the limit returns ",[182,1094,1095],{},"429"," with a plain-text body. Anything implementing ",[182,1098,1099],{},"Allow(key string) bool"," and ",[182,1102,1103],{},"Reset(key string)"," can be passed as a limiter, so a shared store is easy to slot in.",[1106,1107,1108,1109,1114],"note",{},"These limiters are per process. Several replicas each enforce the limit separately. For a shared counter, use the Redis limiter in ",[495,1110,1111],{"href":116},[182,1112,1113],{},"auth\u002Fratelimit"," or your own implementation of the interface.",[232,1116,1118],{"id":1117},"add-middleware-to-a-group","Add middleware to a group",[178,1120,1121],{},"Group middleware only wraps the routes registered on that group, which is the natural place for per-area concerns:",[187,1123,1125],{"className":189,"code":1124,"language":191,"meta":192,"style":192},"api := app.Group(\"\u002Fapi\u002Fv1\").Use(middleware.Gzip())\ninternal := app.Group(\"\u002Finternal\").Use(requireInternalNetwork())\n",[182,1126,1127,1166],{"__ignoreMap":192},[196,1128,1129,1132,1134,1137,1139,1142,1144,1146,1149,1151,1154,1156,1158,1160,1162,1164],{"class":198,"line":199},[196,1130,1131],{"class":337},"api ",[196,1133,341],{"class":202},[196,1135,1136],{"class":337}," app",[196,1138,216],{"class":202},[196,1140,1141],{"class":349},"Group",[196,1143,376],{"class":202},[196,1145,556],{"class":202},[196,1147,1148],{"class":559},"\u002Fapi\u002Fv1",[196,1150,556],{"class":202},[196,1152,1153],{"class":202},").",[196,1155,303],{"class":349},[196,1157,376],{"class":202},[196,1159,732],{"class":337},[196,1161,216],{"class":202},[196,1163,398],{"class":349},[196,1165,739],{"class":202},[196,1167,1168,1171,1173,1175,1177,1179,1181,1183,1186,1188,1190,1192,1194,1197],{"class":198,"line":327},[196,1169,1170],{"class":337},"internal ",[196,1172,341],{"class":202},[196,1174,1136],{"class":337},[196,1176,216],{"class":202},[196,1178,1141],{"class":349},[196,1180,376],{"class":202},[196,1182,556],{"class":202},[196,1184,1185],{"class":559},"\u002Finternal",[196,1187,556],{"class":202},[196,1189,1153],{"class":202},[196,1191,303],{"class":349},[196,1193,376],{"class":202},[196,1195,1196],{"class":349},"requireInternalNetwork",[196,1198,739],{"class":202},[232,1200,1202],{"id":1201},"write-your-own","Write your own",[187,1204,1206],{"className":189,"code":1205,"language":191,"meta":192,"style":192},"func Timeout(d time.Duration) ezz.Middleware {\n    return func(next http.Handler) http.Handler {\n        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n            ctx, cancel := context.WithTimeout(r.Context(), d)\n            defer cancel()\n            next.ServeHTTP(w, r.WithContext(ctx))\n        })\n    }\n}\n\napp.Use(Timeout(5 * time.Second))\n",[182,1207,1208,1238,1263,1304,1340,1351,1382,1387,1392,1398,1403],{"__ignoreMap":192},[196,1209,1210,1213,1216,1218,1221,1223,1225,1228,1230,1232,1234,1236],{"class":198,"line":199},[196,1211,1212],{"class":202},"func",[196,1214,1215],{"class":349}," Timeout",[196,1217,376],{"class":202},[196,1219,1220],{"class":1034},"d",[196,1222,977],{"class":206},[196,1224,216],{"class":202},[196,1226,1227],{"class":206},"Duration",[196,1229,222],{"class":202},[196,1231,344],{"class":206},[196,1233,216],{"class":202},[196,1235,63],{"class":206},[196,1237,1053],{"class":202},[196,1239,1240,1242,1244,1247,1249,1251,1253,1255,1257,1259,1261],{"class":198,"line":327},[196,1241,1058],{"class":314},[196,1243,210],{"class":202},[196,1245,1246],{"class":1034},"next",[196,1248,225],{"class":206},[196,1250,216],{"class":202},[196,1252,219],{"class":206},[196,1254,222],{"class":202},[196,1256,225],{"class":206},[196,1258,216],{"class":202},[196,1260,219],{"class":206},[196,1262,1053],{"class":202},[196,1264,1265,1268,1270,1272,1275,1278,1281,1283,1285,1288,1290,1292,1294,1296,1298,1300,1302],{"class":198,"line":334},[196,1266,1267],{"class":314},"        return",[196,1269,225],{"class":337},[196,1271,216],{"class":202},[196,1273,1274],{"class":349},"HandlerFunc",[196,1276,1277],{"class":202},"(func(",[196,1279,1280],{"class":1034},"w",[196,1282,225],{"class":206},[196,1284,216],{"class":202},[196,1286,1287],{"class":206},"ResponseWriter",[196,1289,565],{"class":202},[196,1291,1061],{"class":1034},[196,1293,1038],{"class":202},[196,1295,213],{"class":206},[196,1297,216],{"class":202},[196,1299,1045],{"class":206},[196,1301,222],{"class":202},[196,1303,1053],{"class":202},[196,1305,1306,1309,1311,1314,1316,1319,1321,1324,1326,1328,1330,1332,1335,1338],{"class":198,"line":356},[196,1307,1308],{"class":337},"            ctx",[196,1310,565],{"class":202},[196,1312,1313],{"class":337}," cancel ",[196,1315,341],{"class":202},[196,1317,1318],{"class":337}," context",[196,1320,216],{"class":202},[196,1322,1323],{"class":349},"WithTimeout",[196,1325,376],{"class":202},[196,1327,1035],{"class":337},[196,1329,216],{"class":202},[196,1331,53],{"class":349},[196,1333,1334],{"class":202},"(),",[196,1336,1337],{"class":337}," d",[196,1339,1001],{"class":202},[196,1341,1342,1345,1348],{"class":198,"line":365},[196,1343,1344],{"class":314},"            defer",[196,1346,1347],{"class":349}," cancel",[196,1349,1350],{"class":202},"()\n",[196,1352,1353,1356,1358,1361,1363,1365,1367,1369,1371,1374,1376,1379],{"class":198,"line":390},[196,1354,1355],{"class":337},"            next",[196,1357,216],{"class":202},[196,1359,1360],{"class":349},"ServeHTTP",[196,1362,376],{"class":202},[196,1364,1280],{"class":337},[196,1366,565],{"class":202},[196,1368,1061],{"class":337},[196,1370,216],{"class":202},[196,1372,1373],{"class":349},"WithContext",[196,1375,376],{"class":202},[196,1377,1378],{"class":337},"ctx",[196,1380,1381],{"class":202},"))\n",[196,1383,1384],{"class":198,"line":404},[196,1385,1386],{"class":202},"        })\n",[196,1388,1389],{"class":198,"line":423},[196,1390,1391],{"class":202},"    }\n",[196,1393,1395],{"class":198,"line":1394},9,[196,1396,1397],{"class":202},"}\n",[196,1399,1401],{"class":198,"line":1400},10,[196,1402,331],{"emptyLinePlaceholder":330},[196,1404,1406,1408,1410,1412,1414,1417,1419,1422,1424,1426,1428,1430],{"class":198,"line":1405},11,[196,1407,512],{"class":337},[196,1409,216],{"class":202},[196,1411,303],{"class":349},[196,1413,376],{"class":202},[196,1415,1416],{"class":349},"Timeout",[196,1418,376],{"class":202},[196,1420,1421],{"class":416},"5",[196,1423,1038],{"class":202},[196,1425,977],{"class":337},[196,1427,216],{"class":202},[196,1429,982],{"class":337},[196,1431,1381],{"class":202},[178,1433,1434,1435,304],{},"To pass values down to handlers, put them on the request context and read them with ",[182,1436,1437],{},"ctx.Value",[187,1439,1441],{"className":189,"code":1440,"language":191,"meta":192,"style":192},"type tenantKey struct{}\n\nfunc Tenant(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        tenant := lookupTenant(r.Host)\n        next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), tenantKey{}, tenant)))\n    })\n}\n\n\u002F\u002F in a handler\ntenant, _ := ctx.Value(tenantKey{}).(*Tenant)\n",[182,1442,1443,1453,1457,1484,1520,1541,1593,1598,1602,1606,1611],{"__ignoreMap":192},[196,1444,1445,1447,1450],{"class":198,"line":199},[196,1446,203],{"class":202},[196,1448,1449],{"class":206}," tenantKey",[196,1451,1452],{"class":202}," struct{}\n",[196,1454,1455],{"class":198,"line":327},[196,1456,331],{"emptyLinePlaceholder":330},[196,1458,1459,1461,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482],{"class":198,"line":334},[196,1460,1212],{"class":202},[196,1462,1463],{"class":349}," Tenant",[196,1465,376],{"class":202},[196,1467,1246],{"class":1034},[196,1469,225],{"class":206},[196,1471,216],{"class":202},[196,1473,219],{"class":206},[196,1475,222],{"class":202},[196,1477,225],{"class":206},[196,1479,216],{"class":202},[196,1481,219],{"class":206},[196,1483,1053],{"class":202},[196,1485,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518],{"class":198,"line":356},[196,1487,1058],{"class":314},[196,1489,225],{"class":337},[196,1491,216],{"class":202},[196,1493,1274],{"class":349},[196,1495,1277],{"class":202},[196,1497,1280],{"class":1034},[196,1499,225],{"class":206},[196,1501,216],{"class":202},[196,1503,1287],{"class":206},[196,1505,565],{"class":202},[196,1507,1061],{"class":1034},[196,1509,1038],{"class":202},[196,1511,213],{"class":206},[196,1513,216],{"class":202},[196,1515,1045],{"class":206},[196,1517,222],{"class":202},[196,1519,1053],{"class":202},[196,1521,1522,1525,1527,1530,1532,1534,1536,1539],{"class":198,"line":365},[196,1523,1524],{"class":337},"        tenant ",[196,1526,341],{"class":202},[196,1528,1529],{"class":349}," lookupTenant",[196,1531,376],{"class":202},[196,1533,1035],{"class":337},[196,1535,216],{"class":202},[196,1537,1538],{"class":337},"Host",[196,1540,1001],{"class":202},[196,1542,1543,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1567,1569,1572,1574,1576,1578,1580,1582,1584,1587,1590],{"class":198,"line":390},[196,1544,1545],{"class":337},"        next",[196,1547,216],{"class":202},[196,1549,1360],{"class":349},[196,1551,376],{"class":202},[196,1553,1280],{"class":337},[196,1555,565],{"class":202},[196,1557,1061],{"class":337},[196,1559,216],{"class":202},[196,1561,1373],{"class":349},[196,1563,376],{"class":202},[196,1565,1566],{"class":337},"context",[196,1568,216],{"class":202},[196,1570,1571],{"class":349},"WithValue",[196,1573,376],{"class":202},[196,1575,1035],{"class":337},[196,1577,216],{"class":202},[196,1579,53],{"class":349},[196,1581,1334],{"class":202},[196,1583,1449],{"class":206},[196,1585,1586],{"class":202},"{},",[196,1588,1589],{"class":337}," tenant",[196,1591,1592],{"class":202},")))\n",[196,1594,1595],{"class":198,"line":404},[196,1596,1597],{"class":202},"    })\n",[196,1599,1600],{"class":198,"line":423},[196,1601,1397],{"class":202},[196,1603,1604],{"class":198,"line":1394},[196,1605,331],{"emptyLinePlaceholder":330},[196,1607,1608],{"class":198,"line":1400},[196,1609,1610],{"class":748},"\u002F\u002F in a handler\n",[196,1612,1613,1616,1618,1621,1623,1626,1628,1631,1633,1636,1639,1642],{"class":198,"line":1405},[196,1614,1615],{"class":337},"tenant",[196,1617,565],{"class":202},[196,1619,1620],{"class":337}," _ ",[196,1622,341],{"class":202},[196,1624,1625],{"class":337}," ctx",[196,1627,216],{"class":202},[196,1629,1630],{"class":349},"Value",[196,1632,376],{"class":202},[196,1634,1635],{"class":206},"tenantKey",[196,1637,1638],{"class":202},"{}).(*",[196,1640,1641],{"class":206},"Tenant",[196,1643,1001],{"class":202},[178,1645,1646,1647,1650,1651,1654,1655,1658],{},"If your middleware needs to inspect the status or body, wrap the ",[182,1648,1649],{},"http.ResponseWriter"," as usual. The writer ezz passes down implements ",[182,1652,1653],{},"Unwrap() http.ResponseWriter",", so ",[182,1656,1657],{},"http.ResponseController"," works for flushing and deadlines.",[232,1660,1662],{"id":1661},"next-steps","Next steps",[1664,1665,1666,1673],"ul",{},[1667,1668,1669,1672],"li",{},[495,1670,1671],{"href":75},"Auth overview"," for how authentication fits into the same pipeline.",[1667,1674,1675,1677],{},[495,1676,152],{"href":153}," for what is pooled and precompiled.",[1679,1680,1681],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":192,"searchDepth":327,"depth":327,"links":1683},[1684,1685,1686,1687,1688,1689,1690,1691],{"id":234,"depth":327,"text":235},{"id":442,"depth":327,"text":443},{"id":501,"depth":327,"text":502},{"id":713,"depth":327,"text":714},{"id":851,"depth":327,"text":852},{"id":1117,"depth":327,"text":1118},{"id":1201,"depth":327,"text":1202},{"id":1661,"depth":327,"text":1662},"Use the built-in middleware, add your own, and understand the order everything runs in.","md",null,{},{"icon":66},{"title":63,"description":1692},"lplgkY0e-hL_MHRTPKKBssAIceC7zhJ75ZCmO9hTcsQ",[1700,1702],{"title":58,"path":59,"stem":60,"description":1701,"icon":61,"children":-1},"Map Go errors to status codes and control the error body.",{"title":74,"path":75,"stem":76,"description":1703,"icon":77,"children":-1},"How authentication is wired into the app, and what happens on every request.",1784970045269]