[{"data":1,"prerenderedAt":2619},["ShallowReactive",2],{"navigation":3,"/actix-web/client-ips":240,"/actix-web/client-ips-surround":2614},[4,34,66,87,106,145,166,176,204],{"title":5,"path":6,"stem":7,"children":8,"icon":32,"defaultOpen":33},"Getting Started","/getting-started","1.getting-started/1.index",[9,12,17,22,27],{"title":10,"path":6,"stem":7,"icon":11},"Introduction","i-lucide-info",{"title":13,"path":14,"stem":15,"icon":16},"Installation","/getting-started/installation","1.getting-started/2.installation","i-lucide-download",{"title":18,"path":19,"stem":20,"icon":21},"Quickstart (Local)","/getting-started/quickstart-local","1.getting-started/3.quickstart-local","i-lucide-zap",{"title":23,"path":24,"stem":25,"icon":26},"Quickstart (Redis)","/getting-started/quickstart-redis","1.getting-started/4.quickstart-redis","i-lucide-database",{"title":28,"path":29,"stem":30,"icon":31},"Quickstart (Hybrid)","/getting-started/quickstart-hybrid","1.getting-started/5.quickstart-hybrid","i-lucide-arrow-left-right","i-lucide-rocket",true,{"title":35,"icon":36,"defaultOpen":33,"path":37,"stem":38,"children":39,"page":65},"Concepts","i-lucide-lightbulb","/concepts","2.concepts",[40,45,50,55,60],{"title":41,"path":42,"stem":43,"icon":44},"Keys","/concepts/keys","2.concepts/1.keys","i-lucide-key-round",{"title":46,"path":47,"stem":48,"icon":49},"Rate Limits","/concepts/rate-limits","2.concepts/2.rate-limits","i-lucide-badge-percent",{"title":51,"path":52,"stem":53,"icon":54},"Sliding Windows","/concepts/sliding-windows","2.concepts/3.sliding-windows","i-lucide-timer-reset",{"title":56,"path":57,"stem":58,"icon":59},"Decisions","/concepts/decisions","2.concepts/4.decisions","i-lucide-check-check",{"title":61,"path":62,"stem":63,"icon":64},"Configuration Types","/concepts/configuration-types","2.concepts/5.configuration-types","i-lucide-box",false,{"title":67,"icon":68,"defaultOpen":33,"path":69,"stem":70,"children":71,"page":65},"Strategies","i-lucide-git-branch","/strategies","3.strategies",[72,77,82],{"title":73,"path":74,"stem":75,"icon":76},"Absolute","/strategies/absolute","3.strategies/1.absolute","i-lucide-shield",{"title":78,"path":79,"stem":80,"icon":81},"Suppressed","/strategies/suppressed","3.strategies/2.suppressed","i-lucide-activity",{"title":83,"path":84,"stem":85,"icon":86},"How Suppression Works","/strategies/how-suppression-works","3.strategies/3.how-suppression-works","i-lucide-function-square",{"title":88,"icon":89,"defaultOpen":33,"path":90,"stem":91,"children":92,"page":65},"Providers","i-lucide-layers","/providers","4.providers",[93,98,102],{"title":94,"path":95,"stem":96,"icon":97},"Local","/providers/local","4.providers/1.local","i-lucide-cpu",{"title":99,"path":100,"stem":101,"icon":26},"Redis","/providers/redis","4.providers/2.redis",{"title":103,"path":104,"stem":105,"icon":31},"Hybrid","/providers/hybrid","4.providers/3.hybrid",{"title":107,"path":108,"stem":109,"children":110,"icon":76,"defaultOpen":65},"Actix Web","/actix-web","5.actix-web/1.index",[111,113,118,122,127,131,136,140],{"title":112,"path":108,"stem":109,"icon":76},"Overview",{"title":114,"path":115,"stem":116,"icon":117},"Client IPs","/actix-web/client-ips","5.actix-web/2.client-ips","i-lucide-network",{"title":119,"path":120,"stem":121,"icon":44},"Keys and custom extractors","/actix-web/keys","5.actix-web/3.keys",{"title":123,"path":124,"stem":125,"icon":126},"Limits","/actix-web/limits","5.actix-web/4.limits","i-lucide-gauge",{"title":128,"path":129,"stem":130,"icon":26},"Backends","/actix-web/backends","5.actix-web/5.backends",{"title":132,"path":133,"stem":134,"icon":135},"Responses","/actix-web/responses","5.actix-web/6.responses","i-lucide-reply",{"title":137,"path":138,"stem":139,"icon":81},"Observability","/actix-web/observability","5.actix-web/7.observability",{"title":141,"path":142,"stem":143,"icon":144},"Performance","/actix-web/performance","5.actix-web/8.performance","i-lucide-timer",{"title":146,"icon":147,"defaultOpen":65,"path":148,"stem":149,"children":150,"page":65},"Guides","i-lucide-map","/guides","5.guides",[151,156,161],{"title":152,"path":153,"stem":154,"icon":155},"Configuration","/guides/configuration","5.guides/1.configuration","i-lucide-sliders-horizontal",{"title":157,"path":158,"stem":159,"icon":160},"Cleanup","/guides/cleanup","5.guides/2.cleanup","i-lucide-trash-2",{"title":162,"path":163,"stem":164,"icon":165},"Troubleshooting","/guides/troubleshooting","5.guides/4.troubleshooting","i-lucide-wrench",{"title":167,"icon":168,"defaultOpen":65,"path":169,"stem":170,"children":171,"page":65},"Reference","i-lucide-file-text","/reference","6.reference",[172],{"title":173,"path":174,"stem":175,"icon":168},"API Reference Guide","/reference/api","6.reference/1.api",{"title":177,"icon":126,"defaultOpen":65,"path":178,"stem":179,"children":180,"page":65},"Benchmarks","/benchmarks","7.benchmarks",[181,186,190],{"title":182,"path":183,"stem":184,"icon":185},"Benchmark Concepts","/benchmarks/benchmark-concepts","7.benchmarks/0.benchmark-concepts","i-lucide-book-open",{"title":187,"path":188,"stem":189,"icon":126},"Benchmarking & Load Testing","/benchmarks/benchmarking-load-testing","7.benchmarks/1.benchmarking-load-testing",{"title":191,"path":192,"stem":193,"children":194,"icon":203},"Benchmark Results","/benchmarks/benchmark-results","7.benchmarks/2.benchmark-results",[195,199],{"title":196,"path":197,"stem":198,"icon":97},"Local Benchmark Comparison","/benchmarks/benchmark-results/local-benchmark-comparison","7.benchmarks/2.benchmark-results/1.local-benchmark-comparison",{"title":200,"path":201,"stem":202,"icon":26},"Redis + Hybrid Benchmark Comparison","/benchmarks/benchmark-results/redis-benchmark-comparison","7.benchmarks/2.benchmark-results/2.redis-benchmark-comparison","i-lucide-chart-no-axes-combined",{"title":205,"path":206,"stem":207,"children":208,"icon":210,"defaultOpen":65},"Changelog","/changelog","8.changelog/0.index",[209,211,216,220,224,228,232,236],{"title":205,"path":206,"stem":207,"icon":210},"i-lucide-scroll",{"title":212,"path":213,"stem":214,"icon":215},"v2.2.1","/changelog/v2-2-1","8.changelog/1.v2-2-1","i-lucide-tag",{"title":217,"path":218,"stem":219,"icon":215},"v2.2.0","/changelog/v2-2-0","8.changelog/2.v2-2-0",{"title":221,"path":222,"stem":223,"icon":215},"v2.1.0","/changelog/v2-1-0","8.changelog/3.v2-1-0",{"title":225,"path":226,"stem":227,"icon":215},"v2.0.0","/changelog/v2-0-0","8.changelog/4.v2-0-0",{"title":229,"path":230,"stem":231,"icon":215},"v1.1.0","/changelog/v1-1-0","8.changelog/5.v1-1-0",{"title":233,"path":234,"stem":235,"icon":215},"v1.0.1","/changelog/v1-0-1","8.changelog/6.v1-0-1",{"title":237,"path":238,"stem":239,"icon":215},"v1.0.0","/changelog/v1-0-0","8.changelog/7.v1-0-0",{"id":241,"title":114,"authors":242,"badge":242,"body":243,"date":242,"description":2608,"extension":2609,"links":242,"meta":2610,"navigation":2611,"path":115,"seo":2612,"stem":116,"__hash__":2613},"docs/5.actix-web/2.client-ips.md",null,{"type":244,"value":245,"toc":2590},"minimark",[246,250,259,264,489,494,516,617,628,635,648,868,872,875,881,1055,1068,1072,1077,1212,1218,1221,1246,1257,1270,1346,1356,1363,1369,1606,1615,1629,1633,1649,1653,1659,1848,1852,1855,2586],[247,248,249],"p",{},"Limiting by IP needs the client's real address. Connected directly, that is the TCP peer.\nBehind a load balancer, every request arrives from the load balancer, and the client's address\nis in a header the proxy wrote. Headers can also be written by the client, so actix-trypema\nreads them only when the TCP peer is a proxy you trust.",[251,252,254,255],"h2",{"id":253},"clients-connect-directly-peerip","Clients connect directly: ",[256,257,258],"code",{},"PeerIp",[247,260,261,263],{},[256,262,258],{}," keys by the TCP peer address and never reads headers, so it cannot be spoofed.",[265,266,271],"pre",{"className":267,"code":268,"language":269,"meta":270,"style":270},"language-rust shiki shiki-themes material-theme-lighter github-light github-dark monokai","use actix_trypema::{Local, TrypemaLimiter, extract::PeerIp};\nuse trypema::{RateLimit, WindowSize};\n\nlet limiter = TrypemaLimiter::builder(Local::new(WindowSize::seconds_or_panic(60))?)\n    .namespace(\"ip\")\n    .extractor(PeerIp::default())\n    .rate(RateLimit::per_minute_or_panic(60.0))\n    .build()?;\n","rust","",[256,272,273,314,336,342,399,422,442,473],{"__ignoreMap":270},[274,275,278,282,286,290,294,296,299,302,304,307,309,311],"span",{"class":276,"line":277},"line",1,[274,279,281],{"class":280},"shWJe","use",[274,283,285],{"class":284},"sKvfc"," actix_trypema",[274,287,289],{"class":288},"sGXK2","::",[274,291,293],{"class":292},"swvn1","{",[274,295,94],{"class":284},[274,297,298],{"class":292},",",[274,300,301],{"class":284}," TrypemaLimiter",[274,303,298],{"class":292},[274,305,306],{"class":284}," extract",[274,308,289],{"class":288},[274,310,258],{"class":284},[274,312,313],{"class":292},"};\n",[274,315,317,319,322,324,326,329,331,334],{"class":276,"line":316},2,[274,318,281],{"class":280},[274,320,321],{"class":284}," trypema",[274,323,289],{"class":288},[274,325,293],{"class":292},[274,327,328],{"class":284},"RateLimit",[274,330,298],{"class":292},[274,332,333],{"class":284}," WindowSize",[274,335,313],{"class":292},[274,337,339],{"class":276,"line":338},3,[274,340,341],{"emptyLinePlaceholder":33},"\n",[274,343,345,349,353,356,358,360,364,367,369,371,374,376,379,381,384,386,390,393,396],{"class":276,"line":344},4,[274,346,348],{"class":347},"srJo8","let",[274,350,352],{"class":351},"ss--_"," limiter ",[274,354,355],{"class":288},"=",[274,357,301],{"class":284},[274,359,289],{"class":288},[274,361,363],{"class":362},"sD0ED","builder",[274,365,366],{"class":292},"(",[274,368,94],{"class":284},[274,370,289],{"class":288},[274,372,373],{"class":362},"new",[274,375,366],{"class":292},[274,377,378],{"class":284},"WindowSize",[274,380,289],{"class":288},[274,382,383],{"class":362},"seconds_or_panic",[274,385,366],{"class":292},[274,387,389],{"class":388},"sYThS","60",[274,391,392],{"class":292},"))",[274,394,395],{"class":288},"?",[274,397,398],{"class":292},")\n",[274,400,402,405,408,410,414,418,420],{"class":276,"line":401},5,[274,403,404],{"class":288},"    .",[274,406,407],{"class":362},"namespace",[274,409,366],{"class":292},[274,411,413],{"class":412},"siCPE","\"",[274,415,417],{"class":416},"sLACW","ip",[274,419,413],{"class":412},[274,421,398],{"class":292},[274,423,425,427,430,432,434,436,439],{"class":276,"line":424},6,[274,426,404],{"class":288},[274,428,429],{"class":362},"extractor",[274,431,366],{"class":292},[274,433,258],{"class":284},[274,435,289],{"class":288},[274,437,438],{"class":362},"default",[274,440,441],{"class":292},"())\n",[274,443,445,447,450,452,454,456,459,461,463,467,470],{"class":276,"line":444},7,[274,446,404],{"class":288},[274,448,449],{"class":362},"rate",[274,451,366],{"class":292},[274,453,328],{"class":284},[274,455,289],{"class":288},[274,457,458],{"class":362},"per_minute_or_panic",[274,460,366],{"class":292},[274,462,389],{"class":388},[274,464,466],{"class":465},"sMTiH",".",[274,468,469],{"class":388},"0",[274,471,472],{"class":292},"))\n",[274,474,476,478,481,484,486],{"class":276,"line":475},8,[274,477,404],{"class":288},[274,479,480],{"class":362},"build",[274,482,483],{"class":292},"()",[274,485,395],{"class":288},[274,487,488],{"class":292},";\n",[490,491,493],"h3",{"id":492},"ipv6-clients","IPv6 clients",[247,495,496,497,500,501,503,504,507,508,511,512,515],{},"A single IPv6 client usually controls a whole ",[256,498,499],{},"/64"," network and can rotate through its\naddresses, so IPv6 clients are grouped by their ",[256,502,499],{}," by default: ",[256,505,506],{},"2001:db8:1:2::7"," and\n",[256,509,510],{},"2001:db8:1:2::8"," share the key ",[256,513,514],{},"2001:db8:1:2::/64",". Change the grouping when your clients get\nlarger or smaller allocations:",[265,517,519],{"className":267,"code":518,"language":269,"meta":270,"style":270},"use actix_trypema::extract::PeerIp;\n\nlet per_56 = PeerIp::default().ipv6_prefix_len(56)?; // group by /56\nlet per_address = PeerIp::default().ipv6_prefix_len(128)?; // one bucket per address\n",[256,520,521,538,542,582],{"__ignoreMap":270},[274,522,523,525,527,529,532,534,536],{"class":276,"line":277},[274,524,281],{"class":280},[274,526,285],{"class":284},[274,528,289],{"class":288},[274,530,531],{"class":284},"extract",[274,533,289],{"class":288},[274,535,258],{"class":284},[274,537,488],{"class":292},[274,539,540],{"class":276,"line":316},[274,541,341],{"emptyLinePlaceholder":33},[274,543,544,546,549,551,554,556,558,560,562,565,567,570,573,575,578],{"class":276,"line":338},[274,545,348],{"class":347},[274,547,548],{"class":351}," per_56 ",[274,550,355],{"class":288},[274,552,553],{"class":284}," PeerIp",[274,555,289],{"class":288},[274,557,438],{"class":362},[274,559,483],{"class":292},[274,561,466],{"class":288},[274,563,564],{"class":362},"ipv6_prefix_len",[274,566,366],{"class":292},[274,568,569],{"class":388},"56",[274,571,572],{"class":292},")",[274,574,395],{"class":288},[274,576,577],{"class":292},";",[274,579,581],{"class":580},"ss7Ak"," // group by /56\n",[274,583,584,586,589,591,593,595,597,599,601,603,605,608,610,612,614],{"class":276,"line":344},[274,585,348],{"class":347},[274,587,588],{"class":351}," per_address ",[274,590,355],{"class":288},[274,592,553],{"class":284},[274,594,289],{"class":288},[274,596,438],{"class":362},[274,598,483],{"class":292},[274,600,466],{"class":288},[274,602,564],{"class":362},[274,604,366],{"class":292},[274,606,607],{"class":388},"128",[274,609,572],{"class":292},[274,611,395],{"class":288},[274,613,577],{"class":292},[274,615,616],{"class":580}," // one bucket per address\n",[247,618,619,620,623,624,627],{},"IPv4-mapped (",[256,621,622],{},"::ffff:203.0.113.7",") and NAT64 (",[256,625,626],{},"64:ff9b::cb00:7107",") addresses are keyed as the\nIPv4 address they carry, so a dual-stack listener cannot split one client into two buckets.",[251,629,631,632],{"id":630},"behind-a-load-balancer-realipxff","Behind a load balancer: ",[256,633,634],{},"RealIp::xff",[247,636,637,639,640,643,644,647],{},[256,638,634],{}," reads ",[256,641,642],{},"X-Forwarded-For",", but only when the TCP peer is in your ",[256,645,646],{},"TrustedProxies",":",[265,649,651],{"className":267,"code":650,"language":269,"meta":270,"style":270},"use actix_trypema::{Local, TrypemaLimiter, extract::{RealIp, TrustedProxies}};\nuse trypema::{RateLimit, WindowSize};\n\n// The load balancers in front of the app, and nothing else.\nlet trusted = TrustedProxies::new([\"10.0.0.0/16\"])?;\n\nlet limiter = TrypemaLimiter::builder(Local::new(WindowSize::seconds_or_panic(60))?)\n    .namespace(\"ip\")\n    .extractor(RealIp::xff(trusted))\n    .rate(RateLimit::per_minute_or_panic(60.0))\n    .build()?;\n",[256,652,653,688,706,710,715,747,751,791,807,830,855],{"__ignoreMap":270},[274,654,655,657,659,661,663,665,667,669,671,673,675,677,680,682,685],{"class":276,"line":277},[274,656,281],{"class":280},[274,658,285],{"class":284},[274,660,289],{"class":288},[274,662,293],{"class":292},[274,664,94],{"class":284},[274,666,298],{"class":292},[274,668,301],{"class":284},[274,670,298],{"class":292},[274,672,306],{"class":284},[274,674,289],{"class":288},[274,676,293],{"class":292},[274,678,679],{"class":284},"RealIp",[274,681,298],{"class":292},[274,683,684],{"class":284}," TrustedProxies",[274,686,687],{"class":292},"}};\n",[274,689,690,692,694,696,698,700,702,704],{"class":276,"line":316},[274,691,281],{"class":280},[274,693,321],{"class":284},[274,695,289],{"class":288},[274,697,293],{"class":292},[274,699,328],{"class":284},[274,701,298],{"class":292},[274,703,333],{"class":284},[274,705,313],{"class":292},[274,707,708],{"class":276,"line":338},[274,709,341],{"emptyLinePlaceholder":33},[274,711,712],{"class":276,"line":344},[274,713,714],{"class":580},"// The load balancers in front of the app, and nothing else.\n",[274,716,717,719,722,724,726,728,730,733,735,738,740,743,745],{"class":276,"line":401},[274,718,348],{"class":347},[274,720,721],{"class":351}," trusted ",[274,723,355],{"class":288},[274,725,684],{"class":284},[274,727,289],{"class":288},[274,729,373],{"class":362},[274,731,732],{"class":292},"([",[274,734,413],{"class":412},[274,736,737],{"class":416},"10.0.0.0/16",[274,739,413],{"class":412},[274,741,742],{"class":292},"])",[274,744,395],{"class":288},[274,746,488],{"class":292},[274,748,749],{"class":276,"line":424},[274,750,341],{"emptyLinePlaceholder":33},[274,752,753,755,757,759,761,763,765,767,769,771,773,775,777,779,781,783,785,787,789],{"class":276,"line":444},[274,754,348],{"class":347},[274,756,352],{"class":351},[274,758,355],{"class":288},[274,760,301],{"class":284},[274,762,289],{"class":288},[274,764,363],{"class":362},[274,766,366],{"class":292},[274,768,94],{"class":284},[274,770,289],{"class":288},[274,772,373],{"class":362},[274,774,366],{"class":292},[274,776,378],{"class":284},[274,778,289],{"class":288},[274,780,383],{"class":362},[274,782,366],{"class":292},[274,784,389],{"class":388},[274,786,392],{"class":292},[274,788,395],{"class":288},[274,790,398],{"class":292},[274,792,793,795,797,799,801,803,805],{"class":276,"line":475},[274,794,404],{"class":288},[274,796,407],{"class":362},[274,798,366],{"class":292},[274,800,413],{"class":412},[274,802,417],{"class":416},[274,804,413],{"class":412},[274,806,398],{"class":292},[274,808,810,812,814,816,818,820,823,825,828],{"class":276,"line":809},9,[274,811,404],{"class":288},[274,813,429],{"class":362},[274,815,366],{"class":292},[274,817,679],{"class":284},[274,819,289],{"class":288},[274,821,822],{"class":362},"xff",[274,824,366],{"class":292},[274,826,827],{"class":351},"trusted",[274,829,472],{"class":292},[274,831,833,835,837,839,841,843,845,847,849,851,853],{"class":276,"line":832},10,[274,834,404],{"class":288},[274,836,449],{"class":362},[274,838,366],{"class":292},[274,840,328],{"class":284},[274,842,289],{"class":288},[274,844,458],{"class":362},[274,846,366],{"class":292},[274,848,389],{"class":388},[274,850,466],{"class":465},[274,852,469],{"class":388},[274,854,472],{"class":292},[274,856,858,860,862,864,866],{"class":276,"line":857},11,[274,859,404],{"class":288},[274,861,480],{"class":362},[274,863,483],{"class":292},[274,865,395],{"class":288},[274,867,488],{"class":292},[490,869,871],{"id":870},"how-the-client-is-found","How the client is found",[247,873,874],{},"Proxies append the address they received a connection from, so the right end of the header is\nthe part your own proxies wrote. actix-trypema walks it from the right, skips addresses of\ntrusted proxies, and stops at the first address that is not trusted. That address is the\nclient; anything to its left was written by the client and is never read.",[247,876,877,878,647],{},"With ",[256,879,880],{},"TrustedProxies::new([\"10.0.0.0/16\"])",[882,883,884,904],"table",{},[885,886,887],"thead",{},[888,889,890,894,898,901],"tr",{},[891,892,893],"th",{},"TCP peer",[891,895,896],{},[256,897,642],{},[891,899,900],{},"Key",[891,902,903],{},"Why",[905,906,907,925,944,965,987,1003,1020,1038],"tbody",{},[888,908,909,915,918,922],{},[910,911,912],"td",{},[256,913,914],{},"203.0.113.7",[910,916,917],{},"anything",[910,919,920],{},[256,921,914],{},[910,923,924],{},"The peer is not trusted, so the header is ignored.",[888,926,927,932,937,941],{},[910,928,929],{},[256,930,931],{},"10.0.0.5",[910,933,934],{},[256,935,936],{},"198.51.100.4",[910,938,939],{},[256,940,936],{},[910,942,943],{},"The first untrusted hop.",[888,945,946,950,955,959],{},[910,947,948],{},[256,949,931],{},[910,951,952],{},[256,953,954],{},"1.2.3.4, 198.51.100.4",[910,956,957],{},[256,958,936],{},[910,960,961,964],{},[256,962,963],{},"1.2.3.4"," was written by the client and is never parsed.",[888,966,967,971,976,980],{},[910,968,969],{},[256,970,931],{},[910,972,973],{},[256,974,975],{},"198.51.100.4, 10.0.0.9",[910,977,978],{},[256,979,936],{},[910,981,982,983,986],{},"The trusted hop ",[256,984,985],{},"10.0.0.9"," is skipped.",[888,988,989,993,996,1000],{},[910,990,991],{},[256,992,931],{},[910,994,995],{},"absent",[910,997,998],{},[256,999,931],{},[910,1001,1002],{},"No header: the peer itself is the client.",[888,1004,1005,1009,1013,1017],{},[910,1006,1007],{},[256,1008,931],{},[910,1010,1011],{},[256,1012,985],{},[910,1014,1015],{},[256,1016,985],{},[910,1018,1019],{},"Every hop is trusted: the leftmost one is used.",[888,1021,1022,1026,1031,1035],{},[910,1023,1024],{},[256,1025,931],{},[910,1027,1028],{},[256,1029,1030],{},"198.51.100.4:5123",[910,1032,1033],{},[256,1034,936],{},[910,1036,1037],{},"Ports are accepted and dropped.",[888,1039,1040,1044,1049,1052],{},[910,1041,1042],{},[256,1043,931],{},[910,1045,1046],{},[256,1047,1048],{},"garbage",[910,1050,1051],{},"key error",[910,1053,1054],{},"A malformed trusted chain never falls back silently.",[247,1056,1057,1058,1060,1061,1063,1064,1067],{},"Several ",[256,1059,642],{}," header lines are read as one list, in order. Hops may carry a port\n(",[256,1062,1030],{},", ",[256,1065,1066],{},"[2001:db8::1]:443","). A walk longer than 32 trusted hops is a key error.",[490,1069,1071],{"id":1070},"choosing-trusted-proxies","Choosing trusted proxies",[247,1073,1074,1076],{},[256,1075,646],{}," takes CIDR blocks and single addresses, IPv4 or IPv6:",[265,1078,1080],{"className":267,"code":1079,"language":269,"meta":270,"style":270},"use actix_trypema::extract::TrustedProxies;\n\nlet one_load_balancer = TrustedProxies::new([\"192.0.2.10\"])?;\nlet vpc_and_ipv6 = TrustedProxies::new([\"10.0.0.0/16\", \"fd00:1::/64\"])?;\n\n// Rejected: trusting every address would let any client choose its own key.\nassert!(TrustedProxies::new([\"0.0.0.0/0\"]).is_err());\n",[256,1081,1082,1098,1102,1132,1171,1175,1180],{"__ignoreMap":270},[274,1083,1084,1086,1088,1090,1092,1094,1096],{"class":276,"line":277},[274,1085,281],{"class":280},[274,1087,285],{"class":284},[274,1089,289],{"class":288},[274,1091,531],{"class":284},[274,1093,289],{"class":288},[274,1095,646],{"class":284},[274,1097,488],{"class":292},[274,1099,1100],{"class":276,"line":316},[274,1101,341],{"emptyLinePlaceholder":33},[274,1103,1104,1106,1109,1111,1113,1115,1117,1119,1121,1124,1126,1128,1130],{"class":276,"line":338},[274,1105,348],{"class":347},[274,1107,1108],{"class":351}," one_load_balancer ",[274,1110,355],{"class":288},[274,1112,684],{"class":284},[274,1114,289],{"class":288},[274,1116,373],{"class":362},[274,1118,732],{"class":292},[274,1120,413],{"class":412},[274,1122,1123],{"class":416},"192.0.2.10",[274,1125,413],{"class":412},[274,1127,742],{"class":292},[274,1129,395],{"class":288},[274,1131,488],{"class":292},[274,1133,1134,1136,1139,1141,1143,1145,1147,1149,1151,1153,1155,1157,1160,1163,1165,1167,1169],{"class":276,"line":344},[274,1135,348],{"class":347},[274,1137,1138],{"class":351}," vpc_and_ipv6 ",[274,1140,355],{"class":288},[274,1142,684],{"class":284},[274,1144,289],{"class":288},[274,1146,373],{"class":362},[274,1148,732],{"class":292},[274,1150,413],{"class":412},[274,1152,737],{"class":416},[274,1154,413],{"class":412},[274,1156,298],{"class":292},[274,1158,1159],{"class":412}," \"",[274,1161,1162],{"class":416},"fd00:1::/64",[274,1164,413],{"class":412},[274,1166,742],{"class":292},[274,1168,395],{"class":288},[274,1170,488],{"class":292},[274,1172,1173],{"class":276,"line":401},[274,1174,341],{"emptyLinePlaceholder":33},[274,1176,1177],{"class":276,"line":424},[274,1178,1179],{"class":580},"// Rejected: trusting every address would let any client choose its own key.\n",[274,1181,1182,1185,1187,1189,1191,1193,1195,1197,1200,1202,1204,1206,1209],{"class":276,"line":444},[274,1183,1184],{"class":362},"assert!",[274,1186,366],{"class":292},[274,1188,646],{"class":284},[274,1190,289],{"class":288},[274,1192,373],{"class":362},[274,1194,732],{"class":292},[274,1196,413],{"class":412},[274,1198,1199],{"class":416},"0.0.0.0/0",[274,1201,413],{"class":412},[274,1203,742],{"class":292},[274,1205,466],{"class":288},[274,1207,1208],{"class":362},"is_err",[274,1210,1211],{"class":292},"());\n",[247,1213,1214,1215,1217],{},"List only the proxies in front of the application. A client whose own address falls inside a\ntrusted range can write any ",[256,1216,642],{}," it likes and pick its key. That is why there is no\n\"all private networks\" preset: inside a VPC, a Kubernetes cluster or an office network, the\nclients live in those ranges too.",[247,1219,1220],{},"Common setups:",[1222,1223,1224,1232,1240],"ul",{},[1225,1226,1227,1231],"li",{},[1228,1229,1230],"strong",{},"Cloud load balancer in a VPC"," (AWS ALB, GCP, Azure): trust the load balancer's subnet, not\nthe whole VPC.",[1225,1233,1234,1237,1238,466],{},[1228,1235,1236],{},"Kubernetes ingress",": trust the ingress controller's pod or node addresses, and set the\ningress to append ",[256,1239,642],{},[1225,1241,1242,1245],{},[1228,1243,1244],{},"CDN in front of a load balancer",": trust both the CDN's published ranges and your load\nbalancer. The walk skips both and lands on the visitor.",[251,1247,1249,1250,1253,1254],{"id":1248},"rfc-7239-forwarded-realipforwarded","RFC 7239 ",[256,1251,1252],{},"Forwarded",": ",[256,1255,1256],{},"RealIp::forwarded",[247,1258,1259,1260,1262,1263,1265,1266,1269],{},"Proxies that write the standard ",[256,1261,1252],{}," header are read with ",[256,1264,1256],{},". It uses\neach element's ",[256,1267,1268],{},"for="," parameter and follows the same right-to-left rules:",[265,1271,1273],{"className":267,"code":1272,"language":269,"meta":270,"style":270},"use actix_trypema::extract::{RealIp, TrustedProxies};\n\n// Forwarded: for=198.51.100.4;proto=https, for=\"[2001:db8::1]:4711\"\nlet extractor = RealIp::forwarded(TrustedProxies::new([\"10.0.0.0/16\"])?);\n",[256,1274,1275,1297,1301,1306],{"__ignoreMap":270},[274,1276,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295],{"class":276,"line":277},[274,1278,281],{"class":280},[274,1280,285],{"class":284},[274,1282,289],{"class":288},[274,1284,531],{"class":284},[274,1286,289],{"class":288},[274,1288,293],{"class":292},[274,1290,679],{"class":284},[274,1292,298],{"class":292},[274,1294,684],{"class":284},[274,1296,313],{"class":292},[274,1298,1299],{"class":276,"line":316},[274,1300,341],{"emptyLinePlaceholder":33},[274,1302,1303],{"class":276,"line":338},[274,1304,1305],{"class":580},"// Forwarded: for=198.51.100.4;proto=https, for=\"[2001:db8::1]:4711\"\n",[274,1307,1308,1310,1313,1315,1318,1320,1323,1325,1327,1329,1331,1333,1335,1337,1339,1341,1343],{"class":276,"line":344},[274,1309,348],{"class":347},[274,1311,1312],{"class":351}," extractor ",[274,1314,355],{"class":288},[274,1316,1317],{"class":284}," RealIp",[274,1319,289],{"class":288},[274,1321,1322],{"class":362},"forwarded",[274,1324,366],{"class":292},[274,1326,646],{"class":284},[274,1328,289],{"class":288},[274,1330,373],{"class":362},[274,1332,732],{"class":292},[274,1334,413],{"class":412},[274,1336,737],{"class":416},[274,1338,413],{"class":412},[274,1340,742],{"class":292},[274,1342,395],{"class":288},[274,1344,1345],{"class":292},");\n",[247,1347,1348,1349,1063,1352,1355],{},"Obfuscated identifiers (",[256,1350,1351],{},"for=_hidden",[256,1353,1354],{},"for=unknown",") cannot be keyed. If the walk reaches one,\nthe request is a key error.",[251,1357,1359,1360],{"id":1358},"cdn-and-proxy-headers-realipheader","CDN and proxy headers: ",[256,1361,1362],{},"RealIp::header",[247,1364,1365,1366,1368],{},"Many CDNs and proxies send the client in their own header. ",[256,1367,1362],{}," reads any\ncomma-separated header with the same trust rules:",[265,1370,1372],{"className":267,"code":1371,"language":269,"meta":270,"style":270},"use actix_trypema::extract::{RealIp, TrustedProxies};\n\n// Cloudflare: trust Cloudflare's published IP ranges (two shown here; list them all).\nlet cloudflare = RealIp::header(\n    \"cf-connecting-ip\",\n    TrustedProxies::new([\"173.245.48.0/20\", \"2400:cb00::/32\"])?,\n)?;\n\n// nginx with `proxy_set_header X-Real-IP $remote_addr;`\nlet nginx = RealIp::header(\"x-real-ip\", TrustedProxies::new([\"10.0.0.2\"])?)?;\n\n// Other CDNs work the same way: their header name, and their published ranges in place of\n// this placeholder.\nlet other_cdn = RealIp::header(\"true-client-ip\", TrustedProxies::new([\"192.0.2.0/24\"])?)?;\n",[256,1373,1374,1396,1400,1405,1424,1437,1470,1478,1482,1487,1538,1542,1548,1554],{"__ignoreMap":270},[274,1375,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394],{"class":276,"line":277},[274,1377,281],{"class":280},[274,1379,285],{"class":284},[274,1381,289],{"class":288},[274,1383,531],{"class":284},[274,1385,289],{"class":288},[274,1387,293],{"class":292},[274,1389,679],{"class":284},[274,1391,298],{"class":292},[274,1393,684],{"class":284},[274,1395,313],{"class":292},[274,1397,1398],{"class":276,"line":316},[274,1399,341],{"emptyLinePlaceholder":33},[274,1401,1402],{"class":276,"line":338},[274,1403,1404],{"class":580},"// Cloudflare: trust Cloudflare's published IP ranges (two shown here; list them all).\n",[274,1406,1407,1409,1412,1414,1416,1418,1421],{"class":276,"line":344},[274,1408,348],{"class":347},[274,1410,1411],{"class":351}," cloudflare ",[274,1413,355],{"class":288},[274,1415,1317],{"class":284},[274,1417,289],{"class":288},[274,1419,1420],{"class":362},"header",[274,1422,1423],{"class":292},"(\n",[274,1425,1426,1429,1432,1434],{"class":276,"line":401},[274,1427,1428],{"class":412},"    \"",[274,1430,1431],{"class":416},"cf-connecting-ip",[274,1433,413],{"class":412},[274,1435,1436],{"class":292},",\n",[274,1438,1439,1442,1444,1446,1448,1450,1453,1455,1457,1459,1462,1464,1466,1468],{"class":276,"line":424},[274,1440,1441],{"class":284},"    TrustedProxies",[274,1443,289],{"class":288},[274,1445,373],{"class":362},[274,1447,732],{"class":292},[274,1449,413],{"class":412},[274,1451,1452],{"class":416},"173.245.48.0/20",[274,1454,413],{"class":412},[274,1456,298],{"class":292},[274,1458,1159],{"class":412},[274,1460,1461],{"class":416},"2400:cb00::/32",[274,1463,413],{"class":412},[274,1465,742],{"class":292},[274,1467,395],{"class":288},[274,1469,1436],{"class":292},[274,1471,1472,1474,1476],{"class":276,"line":444},[274,1473,572],{"class":292},[274,1475,395],{"class":288},[274,1477,488],{"class":292},[274,1479,1480],{"class":276,"line":475},[274,1481,341],{"emptyLinePlaceholder":33},[274,1483,1484],{"class":276,"line":809},[274,1485,1486],{"class":580},"// nginx with `proxy_set_header X-Real-IP $remote_addr;`\n",[274,1488,1489,1491,1494,1496,1498,1500,1502,1504,1506,1509,1511,1513,1515,1517,1519,1521,1523,1526,1528,1530,1532,1534,1536],{"class":276,"line":832},[274,1490,348],{"class":347},[274,1492,1493],{"class":351}," nginx ",[274,1495,355],{"class":288},[274,1497,1317],{"class":284},[274,1499,289],{"class":288},[274,1501,1420],{"class":362},[274,1503,366],{"class":292},[274,1505,413],{"class":412},[274,1507,1508],{"class":416},"x-real-ip",[274,1510,413],{"class":412},[274,1512,298],{"class":292},[274,1514,684],{"class":284},[274,1516,289],{"class":288},[274,1518,373],{"class":362},[274,1520,732],{"class":292},[274,1522,413],{"class":412},[274,1524,1525],{"class":416},"10.0.0.2",[274,1527,413],{"class":412},[274,1529,742],{"class":292},[274,1531,395],{"class":288},[274,1533,572],{"class":292},[274,1535,395],{"class":288},[274,1537,488],{"class":292},[274,1539,1540],{"class":276,"line":857},[274,1541,341],{"emptyLinePlaceholder":33},[274,1543,1545],{"class":276,"line":1544},12,[274,1546,1547],{"class":580},"// Other CDNs work the same way: their header name, and their published ranges in place of\n",[274,1549,1551],{"class":276,"line":1550},13,[274,1552,1553],{"class":580},"// this placeholder.\n",[274,1555,1557,1559,1562,1564,1566,1568,1570,1572,1574,1577,1579,1581,1583,1585,1587,1589,1591,1594,1596,1598,1600,1602,1604],{"class":276,"line":1556},14,[274,1558,348],{"class":347},[274,1560,1561],{"class":351}," other_cdn ",[274,1563,355],{"class":288},[274,1565,1317],{"class":284},[274,1567,289],{"class":288},[274,1569,1420],{"class":362},[274,1571,366],{"class":292},[274,1573,413],{"class":412},[274,1575,1576],{"class":416},"true-client-ip",[274,1578,413],{"class":412},[274,1580,298],{"class":292},[274,1582,684],{"class":284},[274,1584,289],{"class":288},[274,1586,373],{"class":362},[274,1588,732],{"class":292},[274,1590,413],{"class":412},[274,1592,1593],{"class":416},"192.0.2.0/24",[274,1595,413],{"class":412},[274,1597,742],{"class":292},[274,1599,395],{"class":288},[274,1601,572],{"class":292},[274,1603,395],{"class":288},[274,1605,488],{"class":292},[247,1607,1608,1609,1611,1612,1614],{},"Header names must be lowercase. ",[256,1610,1322],{}," is rejected here, because its format is different;\nuse ",[256,1613,1256],{}," for it.",[247,1616,1617,1619,1620,1622,1623,1625,1626,466],{},[256,1618,679],{}," groups IPv6 clients by ",[256,1621,499],{}," like ",[256,1624,258],{},", and accepts the same\n",[256,1627,1628],{},".ipv6_prefix_len(..)",[251,1630,1632],{"id":1631},"when-the-address-cannot-be-read","When the address cannot be read",[247,1634,1635,1636,1639,1640,1643,1644,466],{},"A missing peer address (rare outside tests) or a malformed trusted chain is a key error. By\ndefault the request gets ",[256,1637,1638],{},"400 Bad Request",". Choose another policy with ",[256,1641,1642],{},"on_key_error","; see\n",[1645,1646,1648],"a",{"href":1647},"/actix-web/keys#key-errors","Key errors",[251,1650,1652],{"id":1651},"never-limit-some-addresses","Never limit some addresses",[247,1654,1655,1658],{},[256,1656,1657],{},"allow_keys"," forwards matching clients without counting them. Entries are compared with the\nextracted key, so IPv6 entries use the masked form:",[265,1660,1662],{"className":267,"code":1661,"language":269,"meta":270,"style":270},"use actix_trypema::{Local, TrypemaLimiter, extract::PeerIp};\nuse trypema::{RateLimit, WindowSize};\n\nlet limiter = TrypemaLimiter::builder(Local::new(WindowSize::seconds_or_panic(60))?)\n    .namespace(\"ip\")\n    .extractor(PeerIp::default())\n    .rate(RateLimit::per_minute_or_panic(60.0))\n    .allow_keys([\"203.0.113.10\", \"2001:db8:1:2::/64\"]) // office IPv4 and IPv6 network\n    .build()?;\n",[256,1663,1664,1690,1708,1712,1752,1768,1784,1808,1836],{"__ignoreMap":270},[274,1665,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688],{"class":276,"line":277},[274,1667,281],{"class":280},[274,1669,285],{"class":284},[274,1671,289],{"class":288},[274,1673,293],{"class":292},[274,1675,94],{"class":284},[274,1677,298],{"class":292},[274,1679,301],{"class":284},[274,1681,298],{"class":292},[274,1683,306],{"class":284},[274,1685,289],{"class":288},[274,1687,258],{"class":284},[274,1689,313],{"class":292},[274,1691,1692,1694,1696,1698,1700,1702,1704,1706],{"class":276,"line":316},[274,1693,281],{"class":280},[274,1695,321],{"class":284},[274,1697,289],{"class":288},[274,1699,293],{"class":292},[274,1701,328],{"class":284},[274,1703,298],{"class":292},[274,1705,333],{"class":284},[274,1707,313],{"class":292},[274,1709,1710],{"class":276,"line":338},[274,1711,341],{"emptyLinePlaceholder":33},[274,1713,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750],{"class":276,"line":344},[274,1715,348],{"class":347},[274,1717,352],{"class":351},[274,1719,355],{"class":288},[274,1721,301],{"class":284},[274,1723,289],{"class":288},[274,1725,363],{"class":362},[274,1727,366],{"class":292},[274,1729,94],{"class":284},[274,1731,289],{"class":288},[274,1733,373],{"class":362},[274,1735,366],{"class":292},[274,1737,378],{"class":284},[274,1739,289],{"class":288},[274,1741,383],{"class":362},[274,1743,366],{"class":292},[274,1745,389],{"class":388},[274,1747,392],{"class":292},[274,1749,395],{"class":288},[274,1751,398],{"class":292},[274,1753,1754,1756,1758,1760,1762,1764,1766],{"class":276,"line":401},[274,1755,404],{"class":288},[274,1757,407],{"class":362},[274,1759,366],{"class":292},[274,1761,413],{"class":412},[274,1763,417],{"class":416},[274,1765,413],{"class":412},[274,1767,398],{"class":292},[274,1769,1770,1772,1774,1776,1778,1780,1782],{"class":276,"line":424},[274,1771,404],{"class":288},[274,1773,429],{"class":362},[274,1775,366],{"class":292},[274,1777,258],{"class":284},[274,1779,289],{"class":288},[274,1781,438],{"class":362},[274,1783,441],{"class":292},[274,1785,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806],{"class":276,"line":444},[274,1787,404],{"class":288},[274,1789,449],{"class":362},[274,1791,366],{"class":292},[274,1793,328],{"class":284},[274,1795,289],{"class":288},[274,1797,458],{"class":362},[274,1799,366],{"class":292},[274,1801,389],{"class":388},[274,1803,466],{"class":465},[274,1805,469],{"class":388},[274,1807,472],{"class":292},[274,1809,1810,1812,1814,1816,1818,1821,1823,1825,1827,1829,1831,1833],{"class":276,"line":475},[274,1811,404],{"class":288},[274,1813,1657],{"class":362},[274,1815,732],{"class":292},[274,1817,413],{"class":412},[274,1819,1820],{"class":416},"203.0.113.10",[274,1822,413],{"class":412},[274,1824,298],{"class":292},[274,1826,1159],{"class":412},[274,1828,514],{"class":416},[274,1830,413],{"class":412},[274,1832,742],{"class":292},[274,1834,1835],{"class":580}," // office IPv4 and IPv6 network\n",[274,1837,1838,1840,1842,1844,1846],{"class":276,"line":809},[274,1839,404],{"class":288},[274,1841,480],{"class":362},[274,1843,483],{"class":292},[274,1845,395],{"class":288},[274,1847,488],{"class":292},[251,1849,1851],{"id":1850},"testing-client-ip-resolution","Testing client IP resolution",[247,1853,1854],{},"actix's test helpers can set both the peer and the headers, which makes the trust rules easy to\ncheck in your own tests:",[265,1856,1858],{"className":267,"code":1857,"language":269,"meta":270,"style":270},"use std::net::SocketAddr;\n\nuse actix_trypema::{Local, TrypemaLimiter, extract::{RealIp, TrustedProxies}};\nuse actix_web::{App, http::StatusCode, test, web};\nuse trypema::{RateLimit, WindowSize};\n\n#[actix_web::test]\nasync fn forwarded_clients_get_their_own_limit() {\n    let limiter = TrypemaLimiter::builder(Local::new(WindowSize::seconds_or_panic(60)).unwrap())\n        .namespace(\"ip\")\n        .extractor(RealIp::xff(TrustedProxies::new_or_panic([\"10.0.0.0/16\"])))\n        .rate(RateLimit::per_minute_or_panic(1.0))\n        .build()\n        .unwrap();\n    let app = test::init_service(\n        App::new().wrap(limiter).route(\"/\", web::get().to(|| async { \"ok\" })),\n    )\n    .await;\n\n    let from = |client: &str| {\n        test::TestRequest::get()\n            .peer_addr(\"10.0.0.5:4000\".parse::\u003CSocketAddr>().unwrap())\n            .insert_header((\"x-forwarded-for\", client.to_string()))\n            .to_request()\n    };\n\n    // Two clients behind the same load balancer: each gets its own one request per minute.\n    assert_eq!(test::call_service(&app, from(\"198.51.100.4\")).await.status(), StatusCode::OK);\n    assert_eq!(test::call_service(&app, from(\"198.51.100.5\")).await.status(), StatusCode::OK);\n    assert_eq!(\n        test::call_service(&app, from(\"198.51.100.4\")).await.status(),\n        StatusCode::TOO_MANY_REQUESTS\n    );\n}\n",[256,1859,1860,1879,1883,1915,1952,1970,1974,1990,2006,2050,2067,2101,2126,2135,2144,2163,2238,2244,2255,2260,2289,2306,2345,2376,2386,2392,2397,2403,2464,2516,2523,2563,2574,2580],{"__ignoreMap":270},[274,1861,1862,1864,1867,1869,1872,1874,1877],{"class":276,"line":277},[274,1863,281],{"class":280},[274,1865,1866],{"class":284}," std",[274,1868,289],{"class":288},[274,1870,1871],{"class":284},"net",[274,1873,289],{"class":288},[274,1875,1876],{"class":284},"SocketAddr",[274,1878,488],{"class":292},[274,1880,1881],{"class":276,"line":316},[274,1882,341],{"emptyLinePlaceholder":33},[274,1884,1885,1887,1889,1891,1893,1895,1897,1899,1901,1903,1905,1907,1909,1911,1913],{"class":276,"line":338},[274,1886,281],{"class":280},[274,1888,285],{"class":284},[274,1890,289],{"class":288},[274,1892,293],{"class":292},[274,1894,94],{"class":284},[274,1896,298],{"class":292},[274,1898,301],{"class":284},[274,1900,298],{"class":292},[274,1902,306],{"class":284},[274,1904,289],{"class":288},[274,1906,293],{"class":292},[274,1908,679],{"class":284},[274,1910,298],{"class":292},[274,1912,684],{"class":284},[274,1914,687],{"class":292},[274,1916,1917,1919,1922,1924,1926,1929,1931,1934,1936,1939,1941,1945,1947,1950],{"class":276,"line":344},[274,1918,281],{"class":280},[274,1920,1921],{"class":284}," actix_web",[274,1923,289],{"class":288},[274,1925,293],{"class":292},[274,1927,1928],{"class":284},"App",[274,1930,298],{"class":292},[274,1932,1933],{"class":284}," http",[274,1935,289],{"class":288},[274,1937,1938],{"class":284},"StatusCode",[274,1940,298],{"class":292},[274,1942,1944],{"class":1943},"s6Ffr"," test",[274,1946,298],{"class":292},[274,1948,1949],{"class":1943}," web",[274,1951,313],{"class":292},[274,1953,1954,1956,1958,1960,1962,1964,1966,1968],{"class":276,"line":401},[274,1955,281],{"class":280},[274,1957,321],{"class":284},[274,1959,289],{"class":288},[274,1961,293],{"class":292},[274,1963,328],{"class":284},[274,1965,298],{"class":292},[274,1967,333],{"class":284},[274,1969,313],{"class":292},[274,1971,1972],{"class":276,"line":424},[274,1973,341],{"emptyLinePlaceholder":33},[274,1975,1976,1979,1982,1984,1987],{"class":276,"line":444},[274,1977,1978],{"class":292},"#[",[274,1980,1981],{"class":351},"actix_web",[274,1983,289],{"class":288},[274,1985,1986],{"class":351},"test",[274,1988,1989],{"class":292},"]\n",[274,1991,1992,1995,1998,2001,2003],{"class":276,"line":475},[274,1993,1994],{"class":280},"async",[274,1996,1997],{"class":280}," fn",[274,1999,2000],{"class":362}," forwarded_clients_get_their_own_limit",[274,2002,483],{"class":292},[274,2004,2005],{"class":292}," {\n",[274,2007,2008,2011,2013,2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045,2048],{"class":276,"line":809},[274,2009,2010],{"class":347},"    let",[274,2012,352],{"class":351},[274,2014,355],{"class":288},[274,2016,301],{"class":284},[274,2018,289],{"class":288},[274,2020,363],{"class":362},[274,2022,366],{"class":292},[274,2024,94],{"class":284},[274,2026,289],{"class":288},[274,2028,373],{"class":362},[274,2030,366],{"class":292},[274,2032,378],{"class":284},[274,2034,289],{"class":288},[274,2036,383],{"class":362},[274,2038,366],{"class":292},[274,2040,389],{"class":388},[274,2042,392],{"class":292},[274,2044,466],{"class":288},[274,2046,2047],{"class":362},"unwrap",[274,2049,441],{"class":292},[274,2051,2052,2055,2057,2059,2061,2063,2065],{"class":276,"line":832},[274,2053,2054],{"class":288},"        .",[274,2056,407],{"class":362},[274,2058,366],{"class":292},[274,2060,413],{"class":412},[274,2062,417],{"class":416},[274,2064,413],{"class":412},[274,2066,398],{"class":292},[274,2068,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2090,2092,2094,2096,2098],{"class":276,"line":857},[274,2070,2054],{"class":288},[274,2072,429],{"class":362},[274,2074,366],{"class":292},[274,2076,679],{"class":284},[274,2078,289],{"class":288},[274,2080,822],{"class":362},[274,2082,366],{"class":292},[274,2084,646],{"class":284},[274,2086,289],{"class":288},[274,2088,2089],{"class":362},"new_or_panic",[274,2091,732],{"class":292},[274,2093,413],{"class":412},[274,2095,737],{"class":416},[274,2097,413],{"class":412},[274,2099,2100],{"class":292},"])))\n",[274,2102,2103,2105,2107,2109,2111,2113,2115,2117,2120,2122,2124],{"class":276,"line":1544},[274,2104,2054],{"class":288},[274,2106,449],{"class":362},[274,2108,366],{"class":292},[274,2110,328],{"class":284},[274,2112,289],{"class":288},[274,2114,458],{"class":362},[274,2116,366],{"class":292},[274,2118,2119],{"class":388},"1",[274,2121,466],{"class":465},[274,2123,469],{"class":388},[274,2125,472],{"class":292},[274,2127,2128,2130,2132],{"class":276,"line":1550},[274,2129,2054],{"class":288},[274,2131,480],{"class":362},[274,2133,2134],{"class":292},"()\n",[274,2136,2137,2139,2141],{"class":276,"line":1556},[274,2138,2054],{"class":288},[274,2140,2047],{"class":362},[274,2142,2143],{"class":292},"();\n",[274,2145,2147,2149,2152,2154,2156,2158,2161],{"class":276,"line":2146},15,[274,2148,2010],{"class":347},[274,2150,2151],{"class":351}," app ",[274,2153,355],{"class":288},[274,2155,1944],{"class":284},[274,2157,289],{"class":288},[274,2159,2160],{"class":362},"init_service",[274,2162,1423],{"class":292},[274,2164,2166,2169,2171,2173,2175,2177,2180,2182,2185,2187,2189,2192,2194,2196,2199,2201,2203,2205,2207,2210,2212,2214,2217,2219,2222,2225,2228,2230,2233,2235],{"class":276,"line":2165},16,[274,2167,2168],{"class":284},"        App",[274,2170,289],{"class":288},[274,2172,373],{"class":362},[274,2174,483],{"class":292},[274,2176,466],{"class":288},[274,2178,2179],{"class":362},"wrap",[274,2181,366],{"class":292},[274,2183,2184],{"class":351},"limiter",[274,2186,572],{"class":292},[274,2188,466],{"class":288},[274,2190,2191],{"class":362},"route",[274,2193,366],{"class":292},[274,2195,413],{"class":412},[274,2197,2198],{"class":416},"/",[274,2200,413],{"class":412},[274,2202,298],{"class":292},[274,2204,1949],{"class":284},[274,2206,289],{"class":288},[274,2208,2209],{"class":362},"get",[274,2211,483],{"class":292},[274,2213,466],{"class":288},[274,2215,2216],{"class":362},"to",[274,2218,366],{"class":292},[274,2220,2221],{"class":288},"||",[274,2223,2224],{"class":280}," async",[274,2226,2227],{"class":292}," {",[274,2229,1159],{"class":412},[274,2231,2232],{"class":416},"ok",[274,2234,413],{"class":412},[274,2236,2237],{"class":292}," })),\n",[274,2239,2241],{"class":276,"line":2240},17,[274,2242,2243],{"class":292},"    )\n",[274,2245,2247,2249,2253],{"class":276,"line":2246},18,[274,2248,404],{"class":288},[274,2250,2252],{"class":2251},"sRxSC","await",[274,2254,488],{"class":292},[274,2256,2258],{"class":276,"line":2257},19,[274,2259,341],{"emptyLinePlaceholder":33},[274,2261,2263,2265,2268,2270,2273,2276,2278,2281,2284,2287],{"class":276,"line":2262},20,[274,2264,2010],{"class":347},[274,2266,2267],{"class":351}," from ",[274,2269,355],{"class":288},[274,2271,2272],{"class":288}," |",[274,2274,2275],{"class":351},"client",[274,2277,647],{"class":288},[274,2279,2280],{"class":288}," &",[274,2282,2283],{"class":284},"str",[274,2285,2286],{"class":288},"|",[274,2288,2005],{"class":292},[274,2290,2292,2295,2297,2300,2302,2304],{"class":276,"line":2291},21,[274,2293,2294],{"class":284},"        test",[274,2296,289],{"class":288},[274,2298,2299],{"class":284},"TestRequest",[274,2301,289],{"class":288},[274,2303,2209],{"class":362},[274,2305,2134],{"class":292},[274,2307,2309,2312,2315,2317,2319,2322,2324,2326,2329,2331,2334,2336,2339,2341,2343],{"class":276,"line":2308},22,[274,2310,2311],{"class":288},"            .",[274,2313,2314],{"class":362},"peer_addr",[274,2316,366],{"class":292},[274,2318,413],{"class":412},[274,2320,2321],{"class":416},"10.0.0.5:4000",[274,2323,413],{"class":412},[274,2325,466],{"class":288},[274,2327,2328],{"class":362},"parse",[274,2330,289],{"class":288},[274,2332,2333],{"class":292},"\u003C",[274,2335,1876],{"class":284},[274,2337,2338],{"class":292},">()",[274,2340,466],{"class":288},[274,2342,2047],{"class":362},[274,2344,441],{"class":292},[274,2346,2348,2350,2353,2356,2358,2361,2363,2365,2368,2370,2373],{"class":276,"line":2347},23,[274,2349,2311],{"class":288},[274,2351,2352],{"class":362},"insert_header",[274,2354,2355],{"class":292},"((",[274,2357,413],{"class":412},[274,2359,2360],{"class":416},"x-forwarded-for",[274,2362,413],{"class":412},[274,2364,298],{"class":292},[274,2366,2367],{"class":351}," client",[274,2369,466],{"class":288},[274,2371,2372],{"class":362},"to_string",[274,2374,2375],{"class":292},"()))\n",[274,2377,2379,2381,2384],{"class":276,"line":2378},24,[274,2380,2311],{"class":288},[274,2382,2383],{"class":362},"to_request",[274,2385,2134],{"class":292},[274,2387,2389],{"class":276,"line":2388},25,[274,2390,2391],{"class":292},"    };\n",[274,2393,2395],{"class":276,"line":2394},26,[274,2396,341],{"emptyLinePlaceholder":33},[274,2398,2400],{"class":276,"line":2399},27,[274,2401,2402],{"class":580},"    // Two clients behind the same load balancer: each gets its own one request per minute.\n",[274,2404,2406,2409,2411,2413,2415,2418,2420,2423,2426,2428,2431,2433,2435,2437,2439,2441,2443,2445,2447,2450,2453,2456,2458,2462],{"class":276,"line":2405},28,[274,2407,2408],{"class":362},"    assert_eq!",[274,2410,366],{"class":292},[274,2412,1986],{"class":284},[274,2414,289],{"class":288},[274,2416,2417],{"class":362},"call_service",[274,2419,366],{"class":292},[274,2421,2422],{"class":288},"&",[274,2424,2425],{"class":351},"app",[274,2427,298],{"class":292},[274,2429,2430],{"class":362}," from",[274,2432,366],{"class":292},[274,2434,413],{"class":412},[274,2436,936],{"class":416},[274,2438,413],{"class":412},[274,2440,392],{"class":292},[274,2442,466],{"class":288},[274,2444,2252],{"class":2251},[274,2446,466],{"class":288},[274,2448,2449],{"class":362},"status",[274,2451,2452],{"class":292},"(),",[274,2454,2455],{"class":284}," StatusCode",[274,2457,289],{"class":288},[274,2459,2461],{"class":2460},"sQeA1","OK",[274,2463,1345],{"class":292},[274,2465,2467,2469,2471,2473,2475,2477,2479,2481,2483,2485,2487,2489,2491,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514],{"class":276,"line":2466},29,[274,2468,2408],{"class":362},[274,2470,366],{"class":292},[274,2472,1986],{"class":284},[274,2474,289],{"class":288},[274,2476,2417],{"class":362},[274,2478,366],{"class":292},[274,2480,2422],{"class":288},[274,2482,2425],{"class":351},[274,2484,298],{"class":292},[274,2486,2430],{"class":362},[274,2488,366],{"class":292},[274,2490,413],{"class":412},[274,2492,2493],{"class":416},"198.51.100.5",[274,2495,413],{"class":412},[274,2497,392],{"class":292},[274,2499,466],{"class":288},[274,2501,2252],{"class":2251},[274,2503,466],{"class":288},[274,2505,2449],{"class":362},[274,2507,2452],{"class":292},[274,2509,2455],{"class":284},[274,2511,289],{"class":288},[274,2513,2461],{"class":2460},[274,2515,1345],{"class":292},[274,2517,2519,2521],{"class":276,"line":2518},30,[274,2520,2408],{"class":362},[274,2522,1423],{"class":292},[274,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560],{"class":276,"line":2525},31,[274,2527,2294],{"class":284},[274,2529,289],{"class":288},[274,2531,2417],{"class":362},[274,2533,366],{"class":292},[274,2535,2422],{"class":288},[274,2537,2425],{"class":351},[274,2539,298],{"class":292},[274,2541,2430],{"class":362},[274,2543,366],{"class":292},[274,2545,413],{"class":412},[274,2547,936],{"class":416},[274,2549,413],{"class":412},[274,2551,392],{"class":292},[274,2553,466],{"class":288},[274,2555,2252],{"class":2251},[274,2557,466],{"class":288},[274,2559,2449],{"class":362},[274,2561,2562],{"class":292},"(),\n",[274,2564,2566,2569,2571],{"class":276,"line":2565},32,[274,2567,2568],{"class":284},"        StatusCode",[274,2570,289],{"class":288},[274,2572,2573],{"class":2460},"TOO_MANY_REQUESTS\n",[274,2575,2577],{"class":276,"line":2576},33,[274,2578,2579],{"class":292},"    );\n",[274,2581,2583],{"class":276,"line":2582},34,[274,2584,2585],{"class":292},"}\n",[2587,2588,2589],"style",{},"html pre.shiki code .shWJe, html code.shiki .shWJe{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .s6Ffr, html code.shiki .s6Ffr{--shiki-light:#E2931D;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .sQeA1, html code.shiki .sQeA1{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":270,"searchDepth":277,"depth":316,"links":2591},[2592,2596,2601,2603,2605,2606,2607],{"id":253,"depth":316,"text":2593,"children":2594},"Clients connect directly: PeerIp",[2595],{"id":492,"depth":338,"text":493},{"id":630,"depth":316,"text":2597,"children":2598},"Behind a load balancer: RealIp::xff",[2599,2600],{"id":870,"depth":338,"text":871},{"id":1070,"depth":338,"text":1071},{"id":1248,"depth":316,"text":2602},"RFC 7239 Forwarded: RealIp::forwarded",{"id":1358,"depth":316,"text":2604},"CDN and proxy headers: RealIp::header",{"id":1631,"depth":316,"text":1632},{"id":1651,"depth":316,"text":1652},{"id":1850,"depth":316,"text":1851},"Key limits by client IP, directly or behind load balancers, proxies and CDNs, without letting clients spoof their address.","md",{},{"icon":117},{"title":114,"description":2608},"D1hUL0aM0iTlc-lPAUiMK2Eyb77NLJmAFzbbhi4gguo",[2615,2617],{"title":112,"path":108,"stem":109,"description":2616,"icon":76,"children":-1},"Rate limit actix-web routes with actix-trypema, the Trypema middleware for local, Redis and hybrid limits.",{"title":119,"path":120,"stem":121,"description":2618,"icon":44,"children":-1},"Limit by API key, tenant, user or anything else in the request, with the built-in extractors or your own.",1791663192234]