diff options
-rw-r--r-- | doc/src/guide/resource_design.asciidoc | 3 | ||||
-rw-r--r-- | doc/src/guide/rest_handlers.asciidoc | 1 | ||||
-rw-r--r-- | doc/src/guide/rest_start.png | bin | 105640 -> 110820 bytes | |||
-rw-r--r-- | doc/src/guide/rest_start.svg | 678 | ||||
-rw-r--r-- | doc/src/manual/cowboy_rest.asciidoc | 25 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 30 | ||||
-rw-r--r-- | test/handlers/rate_limited_h.erl | 24 | ||||
-rw-r--r-- | test/rest_handler_SUITE.erl | 26 |
8 files changed, 597 insertions, 190 deletions
diff --git a/doc/src/guide/resource_design.asciidoc b/doc/src/guide/resource_design.asciidoc index fa0c612..4ea0390 100644 --- a/doc/src/guide/resource_design.asciidoc +++ b/doc/src/guide/resource_design.asciidoc @@ -122,6 +122,9 @@ Can access to a resource be forbidden regardless of access being authorized? A simple example of that is censorship of a resource. Implement the `forbidden` callback. +Can access be rate-limited for authenticated users? Use the +`rate_limited` callback. + Are there any constraints on the length of the resource URI? For example, the URI may be used as a key in storage and may have a limit in length. Implement `uri_too_long`. diff --git a/doc/src/guide/rest_handlers.asciidoc b/doc/src/guide/rest_handlers.asciidoc index dab5bea..baf8e6a 100644 --- a/doc/src/guide/rest_handlers.asciidoc +++ b/doc/src/guide/rest_handlers.asciidoc @@ -84,6 +84,7 @@ if it is undefined, moving directly to the next step. Similarly, | multiple_choices | `false` | options | `ok` | previously_existed | `false` +| rate_limited | `false` | resource_exists | `true` | service_available | `true` | uri_too_long | `false` diff --git a/doc/src/guide/rest_start.png b/doc/src/guide/rest_start.png Binary files differindex 1f1e312..4c230a0 100644 --- a/doc/src/guide/rest_start.png +++ b/doc/src/guide/rest_start.png diff --git a/doc/src/guide/rest_start.svg b/doc/src/guide/rest_start.svg index 076c619..6f1dd87 100644 --- a/doc/src/guide/rest_start.svg +++ b/doc/src/guide/rest_start.svg @@ -15,7 +15,7 @@ height="1052.3622047" id="svg2" version="1.1" - inkscape:version="0.48.4 r9939" + inkscape:version="0.92.2 2405546, 2018-03-11" sodipodi:docname="rest_start.svg" inkscape:export-filename="/home/essen/Dropbox/Public/drawing.png" inkscape:export-xdpi="90" @@ -65,15 +65,15 @@ inkscape:pageopacity="1" inkscape:pageshadow="2" inkscape:zoom="1.0000001" - inkscape:cx="171.11305" - inkscape:cy="549.52821" + inkscape:cx="213.11305" + inkscape:cy="726.77495" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" - inkscape:window-width="2560" - inkscape:window-height="1402" + inkscape:window-width="1920" + inkscape:window-height="1043" inkscape:window-x="0" - inkscape:window-y="38" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:snap-global="true" showguides="true"> @@ -111,7 +111,9 @@ style="fill:none;stroke:#6d8e41;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.99999999, 3.99999998;stroke-dashoffset:0" /> <g transform="translate(205.92143,-296.03137)" - id="g5650-7"> + id="g5650-7" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-9" @@ -137,7 +139,9 @@ </g> <g transform="translate(205.92143,-212.00698)" - id="g5650-9"> + id="g5650-9" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-7" @@ -163,7 +167,9 @@ </g> <g transform="translate(205.92143,-129.04326)" - id="g5650-0"> + id="g5650-0" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-5" @@ -189,7 +195,9 @@ </g> <g transform="translate(205.92143,-44.866334)" - id="g5650-94"> + id="g5650-94" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-71" @@ -215,7 +223,9 @@ </g> <g transform="translate(205.92143,38.329623)" - id="g5650-93"> + id="g5650-93" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-3" @@ -241,7 +251,9 @@ </g> <g transform="translate(205.92143,122.59665)" - id="g5650-3"> + id="g5650-3" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-93" @@ -267,7 +279,9 @@ </g> <g transform="translate(205.92143,206.62103)" - id="g5650-6"> + id="g5650-6" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-78" @@ -293,7 +307,9 @@ </g> <g transform="translate(205.92143,289.59516)" - id="g5650-34"> + id="g5650-34" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-30" @@ -319,7 +335,9 @@ </g> <g transform="translate(205.92143,373.37691)" - id="g5650-5"> + id="g5650-5" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-90" @@ -345,7 +363,9 @@ </g> <g transform="translate(205.92143,457.4117)" - id="g5650-1"> + id="g5650-1" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-33" @@ -371,7 +391,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,80.832304)" - id="g5650-2-0"> + id="g5650-2-0" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-5" @@ -397,7 +419,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,164.49956)" - id="g5650-2-6"> + id="g5650-2-6" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-1" @@ -423,7 +447,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,248.1668)" - id="g5650-2-4"> + id="g5650-2-4" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-0" @@ -449,7 +475,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,331.8341)" - id="g5650-2-04"> + id="g5650-2-04" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-9" @@ -475,7 +503,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,415.50138)" - id="g5650-2-8"> + id="g5650-2-8" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-50" @@ -501,7 +531,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,499.16862)" - id="g5650-2-1"> + id="g5650-2-1" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-2" @@ -527,7 +559,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,582.83589)" - id="g5650-2-3"> + id="g5650-2-3" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-10" @@ -553,7 +587,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,666.50315)" - id="g5650-2-44"> + id="g5650-2-44" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-11" @@ -579,7 +615,9 @@ </g> <g transform="matrix(0,-1,1,0,-25.673146,750.17041)" - id="g5650-2-12"> + id="g5650-2-12" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> <path inkscape:connector-curvature="0" id="path5570-2-6" @@ -610,7 +648,9 @@ height="35.209244" x="43.049091" y="204.67757" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-5" @@ -618,7 +658,9 @@ height="35.209244" x="43.049091" y="288.40311" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-8" @@ -626,7 +668,9 @@ height="35.209244" x="43.049091" y="372.01199" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-4" @@ -634,7 +678,9 @@ height="35.209244" x="43.049091" y="455.67929" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-87" @@ -642,7 +688,9 @@ height="35.209244" x="43.049091" y="539.34656" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-41" @@ -650,7 +698,9 @@ height="35.209244" x="43.049091" y="623.01385" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-7" @@ -658,7 +708,9 @@ height="35.209244" x="43.049091" y="706.68115" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-20" @@ -666,15 +718,9 @@ height="35.209244" x="43.049091" y="790.34839" - rx="15" /> - <rect - style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" - id="rect5273-1-3" - width="210.17955" - height="35.209244" - x="43.049091" - y="874.01562" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1" @@ -682,7 +728,9 @@ height="35.209244" x="43.049091" y="121.0042" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <g id="g5650" transform="translate(0,-0.47597102)"> @@ -758,171 +806,187 @@ width="744.09448" height="1052.3622" inkscape:export-filename="/home/essen/extend/cowboy/guide/http_req_resp.png" - inkscape:export-xdpi="89.926643" - inkscape:export-ydpi="89.926643" /> + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="-58.692513" y="114.39204" - id="text5371" - sodipodi:linespacing="125%"><tspan + id="text5371"><tspan sodipodi:role="line" id="tspan5373" x="-58.692513" - y="114.39204">some text</tspan></text> + y="114.39204" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="-58.692513" y="53.112247" - id="text5371-2" - sodipodi:linespacing="125%"><tspan + id="text5371-2"><tspan sodipodi:role="line" id="tspan5373-6" x="-58.692513" - y="53.112247">some text</tspan></text> + y="53.112247" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.18575" y="310.19913" id="text5371-2-3" - sodipodi:linespacing="125%" inkscape:export-filename="/home/essen/extend/cowboy/guide/http_req_resp.png" - inkscape:export-xdpi="89.926643" - inkscape:export-ydpi="89.926643"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-7" x="148.18575" - y="310.19913">uri_too_long</tspan></text> + y="310.19913" + style="font-size:16px;line-height:1.25;font-family:sans-serif">uri_too_long</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="147.60762" y="477.47531" id="text5371-2-3-0" - sodipodi:linespacing="125%" inkscape:export-filename="/home/essen/extend/cowboy/guide/http_req_resp.png" - inkscape:export-xdpi="89.926643" - inkscape:export-ydpi="89.926643"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-7-3" x="147.60762" - y="477.47531">malformed_request</tspan></text> + y="477.47531" + style="font-size:16px;line-height:1.25;font-family:sans-serif">malformed_request</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="-58.692513" y="236.95154" - id="text5371-4" - sodipodi:linespacing="125%"><tspan + id="text5371-4"><tspan sodipodi:role="line" id="tspan5373-9" x="-58.692513" - y="236.95154">some text</tspan></text> + y="236.95154" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="147.00391" y="60.912468" id="text5371-4-0" - sodipodi:linespacing="125%" inkscape:export-filename="/home/essen/extend/cowboy/guide/http_req_resp.png" - inkscape:export-xdpi="89.926643" - inkscape:export-ydpi="89.926643"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" x="147.00391" y="60.912468" - id="tspan17171">init</tspan></text> + id="tspan17171" + style="font-size:16px;line-height:1.25;font-family:sans-serif">init</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.11153" y="561.14258" id="text5371-2-9" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-8" x="148.11153" - y="561.14258">is_authorized</tspan></text> + y="561.14258" + style="font-size:16px;line-height:1.25;font-family:sans-serif">is_authorized</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.64278" y="646.58331" id="text5371-2-7" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-2" x="148.64278" - y="646.58331">forbidden</tspan></text> + y="646.58331" + style="font-size:16px;line-height:1.25;font-family:sans-serif">forbidden</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.29512" y="728.47717" id="text5371-2-73" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-5" x="148.29512" - y="728.47717">valid_content_headers</tspan></text> + y="728.47717" + style="font-size:16px;line-height:1.25;font-family:sans-serif">valid_content_headers</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="147.83809" y="812.14441" id="text5371-2-5" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-1" x="147.83809" - y="812.14441">valid_entity_length</tspan></text> + y="812.14441" + style="font-size:16px;line-height:1.25;font-family:sans-serif">valid_entity_length</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.58809" y="895.81165" id="text5371-2-1" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-71" x="148.58809" - y="895.81165">...</tspan></text> + y="895.81165" + style="font-size:16px;line-height:1.25;font-family:sans-serif">...</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.13106" y="142.80627" id="text5371-2-95" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-80" x="148.13106" - y="142.80627">service_available</tspan></text> + y="142.80627" + style="font-size:16px;line-height:1.25;font-family:sans-serif">service_available</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="147.80684" y="226.4736" id="text5371-2-32" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-27" x="147.80684" - y="226.4736">known_methods</tspan></text> + y="226.4736" + style="font-size:16px;line-height:1.25;font-family:sans-serif">known_methods</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="148.05293" y="393.80801" id="text5371-2-74" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-18" x="148.05293" - y="393.80801">allowed_methods</tspan></text> + y="393.80801" + style="font-size:16px;line-height:1.25;font-family:sans-serif">allowed_methods</tspan></text> <rect style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-1-2" @@ -933,103 +997,121 @@ rx="15" /> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="185.95248" id="text5371-2-391" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-63" x="164.26562" - y="185.95248">true</tspan></text> + y="185.95248" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="269.61978" id="text5371-2-954" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-808" x="164.26562" - y="269.61978">known*</tspan></text> + y="269.61978" + style="font-size:16px;line-height:1.25;font-family:sans-serif">known*</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="353.28702" id="text5371-2-4" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-11" x="164.26562" - y="353.28702">false</tspan></text> + y="353.28702" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="436.95425" id="text5371-2-92" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-4" x="164.26562" - y="436.95425">allowed*</tspan></text> + y="436.95425" + style="font-size:16px;line-height:1.25;font-family:sans-serif">allowed*</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="520.62152" id="text5371-2-739" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-0" x="164.26562" - y="520.62152">false</tspan></text> + y="520.62152" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="604.28876" id="text5371-2-8" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-3" x="164.26562" - y="604.28876">true</tspan></text> + y="604.28876" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="687.95599" id="text5371-2-0" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-01" x="164.26562" - y="687.95599">false</tspan></text> + y="687.95599" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="771.62329" id="text5371-2-2" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-33" x="164.26562" - y="771.62329">true</tspan></text> + y="771.62329" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" x="164.26562" y="855.29053" id="text5371-2-21" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-6-9" x="164.26562" - y="855.29053">true</tspan></text> + y="855.29053" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <g transform="matrix(0,-1,1,0,-513.31414,353.05561)" id="g5650-2"> @@ -1058,103 +1140,121 @@ </g> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="123.86062" id="text5371-4-6" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-0" x="315.26172" - y="123.86062">false</tspan></text> + y="123.86062" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="207.30568" id="text5371-4-7" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-6" x="315.26172" - y="207.30568">unknown*</tspan></text> + y="207.30568" + style="font-size:16px;line-height:1.25;font-family:sans-serif">unknown*</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="290.75076" id="text5371-4-2" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-01" x="315.26172" - y="290.75076">true</tspan></text> + y="290.75076" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="374.19577" id="text5371-4-3" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-62" x="315.26172" - y="374.19577">unallowed*</tspan></text> + y="374.19577" + style="font-size:16px;line-height:1.25;font-family:sans-serif">unallowed*</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="457.64084" id="text5371-4-4" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-2" x="315.26172" - y="457.64084">true</tspan></text> + y="457.64084" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="541.08588" id="text5371-4-5" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-09" x="315.26172" - y="541.08588">false*</tspan></text> + y="541.08588" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false*</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="624.53094" id="text5371-4-61" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-69" x="315.26172" - y="624.53094">true</tspan></text> + y="624.53094" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="707.97595" id="text5371-4-58" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-5" x="315.26172" - y="707.97595">false</tspan></text> + y="707.97595" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" x="315.26172" y="791.42102" id="text5371-4-54" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-9-012" x="315.26172" - y="791.42102">false</tspan></text> + y="791.42102" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3" @@ -1162,18 +1262,22 @@ height="34.728001" x="392.03732" y="121.24477" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.53516" y="143.02283" id="text5371-6" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" x="524.53516" y="143.02283" - id="tspan18994">503 service unavailable</tspan></text> + id="tspan18994" + style="font-size:16px;line-height:1.25;font-family:sans-serif">503 service unavailable</tspan></text> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-9" @@ -1181,7 +1285,9 @@ height="34.728001" x="392.03732" y="204.93674" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-6" @@ -1189,7 +1295,9 @@ height="34.728001" x="392.03732" y="288.62869" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-1" @@ -1197,7 +1305,9 @@ height="34.728001" x="392.03732" y="372.32065" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-90" @@ -1205,7 +1315,9 @@ height="34.728001" x="392.03732" y="456.01254" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-2" @@ -1213,7 +1325,9 @@ height="34.728001" x="392.03732" y="539.70447" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-3" @@ -1221,7 +1335,9 @@ height="34.728001" x="392.03732" y="623.39642" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-39" @@ -1229,7 +1345,9 @@ height="34.728001" x="392.03732" y="707.08838" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <rect style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5273-7-3-5" @@ -1237,95 +1355,113 @@ height="34.728001" x="392.03732" y="790.78027" - rx="15" /> + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="227.80464" id="text5371-7" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-69" x="524.26172" - y="227.80464">501 not implemented</tspan></text> + y="227.80464" + style="font-size:16px;line-height:1.25;font-family:sans-serif">501 not implemented</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="311.49661" id="text5371-9" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-5" x="524.26172" - y="311.49661">414 request URI too long</tspan></text> + y="311.49661" + style="font-size:16px;line-height:1.25;font-family:sans-serif">414 request URI too long</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="395.18857" id="text5371-43" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-3" x="524.26172" - y="395.18857">405 method not allowed</tspan></text> + y="395.18857" + style="font-size:16px;line-height:1.25;font-family:sans-serif">405 method not allowed</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="478.88046" id="text5371-3" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-67" x="524.26172" - y="478.88046">400 bad request</tspan></text> + y="478.88046" + style="font-size:16px;line-height:1.25;font-family:sans-serif">400 bad request</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="562.57239" id="text5371-1" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-53" x="524.26172" - y="562.57239">401 unauthorized</tspan></text> + y="562.57239" + style="font-size:16px;line-height:1.25;font-family:sans-serif">401 unauthorized</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="646.26434" id="text5371-27" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-64" x="524.26172" - y="646.26434">403 forbidden</tspan></text> + y="646.26434" + style="font-size:16px;line-height:1.25;font-family:sans-serif">403 forbidden</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="729.9563" id="text5371-21" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-58" x="524.26172" - y="729.9563">501 not implemented</tspan></text> + y="729.9563" + style="font-size:16px;line-height:1.25;font-family:sans-serif">501 not implemented</tspan></text> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" x="524.26172" y="813.64819" id="text5371-5" - sodipodi:linespacing="125%"><tspan + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan sodipodi:role="line" id="tspan5373-60" x="524.26172" - y="813.64819">413 request entity too large</tspan></text> + y="813.64819" + style="font-size:16px;line-height:1.25;font-family:sans-serif">413 request entity too large</tspan></text> <rect style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:2.44279909;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5367" @@ -1339,18 +1475,182 @@ inkscape:export-ydpi="89.926643" /> <text xml:space="preserve" - style="font-size:16px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" x="-544.69421" y="-354.17184" id="text5371-2-3-0-7" - sodipodi:linespacing="125%" - transform="matrix(0,-1,1,0,0,0)" + transform="rotate(-90)" inkscape:export-filename="/home/essen/extend/cowboy/guide/http_req_resp.png" inkscape:export-xdpi="89.926643" inkscape:export-ydpi="89.926643"><tspan sodipodi:role="line" id="tspan5373-6-7-3-9" x="-544.69421" - y="-354.17184">middlewares</tspan></text> + y="-354.17184" + style="font-size:16px;line-height:1.25;font-family:sans-serif">middlewares</tspan></text> + <g + transform="translate(205.92143,541.07893)" + id="g5650-1-3" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:connector-curvature="0" + id="path5570-33-6" + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.58787746,0,0,0.58787746,73.160466,163.35774)" + inkscape:transform-center-y="2.1823437" + d="m -222.73865,430.10821 -12.85982,-22.27386 25.71964,0 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="true" + sodipodi:arg2="2.6179939" + sodipodi:arg1="1.5707963" + sodipodi:r2="7.4246211" + sodipodi:r1="14.849242" + sodipodi:cy="415.25897" + sodipodi:cx="-222.73865" + sodipodi:sides="3" + id="path5576-2-7" + style="opacity:0.8;fill:#6d8e41;fill-opacity:1;fill-rule:nonzero;stroke:#6d8e41;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="star" /> + </g> + <g + transform="rotate(-90,404.08225,429.75539)" + id="g5650-2-12-5" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:connector-curvature="0" + id="path5570-2-6-3" + d="M -57.78256,275.13761 V 404.27753" + style="opacity:0.8;fill:none;stroke:#9b3b1c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.58787746,0,0,0.58787746,73.160466,163.35774)" + inkscape:transform-center-y="2.1823437" + d="m -222.73865,430.10821 -12.85982,-22.27386 25.71964,0 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="true" + sodipodi:arg2="2.6179939" + sodipodi:arg1="1.5707963" + sodipodi:r2="7.4246211" + sodipodi:r1="14.849242" + sodipodi:cy="415.25897" + sodipodi:cx="-222.73865" + sodipodi:sides="3" + id="path5576-12-04-5" + style="opacity:0.8;fill:#9b3b1c;fill-opacity:1;fill-rule:nonzero;stroke:#9b3b1c;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="star" /> + </g> + <rect + style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect5273-1-3" + width="210.17955" + height="35.209244" + x="43.049091" + y="874.01562" + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <rect + style="fill:#d1f2a5;fill-opacity:1;fill-rule:nonzero;stroke:#a9ca7d;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect5273-1-3-2" + width="210.17955" + height="35.209244" + x="43.049088" + y="957.68286" + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" + x="147.8381" + y="895.81165" + id="text5371-2-5-9" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-6-1-1" + x="147.8381" + y="895.81165" + style="font-size:16px;line-height:1.25;font-family:sans-serif">rate_limited</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#6d8e41;fill-opacity:1;stroke:none" + x="148.5881" + y="979.47888" + id="text5371-2-1-2" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-6-71-7" + x="148.5881" + y="979.47888" + style="font-size:16px;line-height:1.25;font-family:sans-serif">...</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" + x="164.26562" + y="855.29053" + id="text5371-2-2-0" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-6-33-9" + x="164.26562" + y="855.29053" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#6d8e41;fill-opacity:1;stroke:none" + x="164.26562" + y="938.95776" + id="text5371-2-21-3" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-6-9-6" + x="164.26562" + y="938.95776" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#9b3b1c;fill-opacity:1;stroke:none" + x="315.26172" + y="875.08826" + id="text5371-4-54-0" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-9-012-6" + x="315.26172" + y="875.08826" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true*</tspan></text> + <rect + style="fill:#effab4;fill-opacity:1;fill-rule:nonzero;stroke:#c7d28c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect5273-7-3-5-2" + width="264.92532" + height="34.728001" + x="392.03729" + y="874.44751" + rx="15" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#77823c;fill-opacity:1;stroke:none" + x="524.26172" + y="897.31543" + id="text5371-5-6" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan5373-60-1" + x="524.26172" + y="897.31543" + style="font-size:16px;line-height:1.25;font-family:sans-serif">429 too many requests</tspan></text> </g> </svg> diff --git a/doc/src/manual/cowboy_rest.asciidoc b/doc/src/manual/cowboy_rest.asciidoc index dd5fa7e..4babcc5 100644 --- a/doc/src/manual/cowboy_rest.asciidoc +++ b/doc/src/manual/cowboy_rest.asciidoc @@ -603,6 +603,30 @@ release. // @todo Add a way to switch to loop handler for streaming the body. +=== rate_limited + +[source,erlang] +---- +rate_limited(Req, State) -> {Result, Req, State} + +Result :: false | {true, RetryAfter} +RetryAfter :: non_neg_integer() | calendar:datetime() +Default - false +---- + +Return whether the user is rate limited. + +This function can be used to temporarily restrict +access to a resource when the user has issued too +many requests. + +When the resource is rate limited the `RetryAfter` +value will be sent in the retry-after header for the +'429 Too Many Requests' response. It indicates when +the resource will become available again and can be +specified as a number of seconds in the future or a +specific date/time. + === resource_exists [source,erlang] @@ -696,6 +720,7 @@ listed here, like the authorization header. == Changelog +* *2.6*: The callback `rate_limited` was added. * *2.1*: The `switch_handler` return value was added. * *1.0*: Behavior introduced. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 512dc5b..8ab576c 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -180,6 +180,13 @@ when Req::cowboy_req:req(), State::any(). -optional_callbacks([previously_existed/2]). +-callback rate_limited(Req, State) + -> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State} + | {stop, Req, State} + | {switch_handler(), Req, State} + when Req::cowboy_req:req(), State::any(). +-optional_callbacks([rate_limited/2]). + -callback resource_exists(Req, State) -> {boolean(), Req, State} | {stop, Req, State} @@ -363,7 +370,28 @@ is_authorized(Req, State) -> end. forbidden(Req, State) -> - expect(Req, State, forbidden, false, fun valid_content_headers/2, 403). + expect(Req, State, forbidden, false, fun rate_limited/2, 403). + +rate_limited(Req, State) -> + case call(Req, State, rate_limited) of + no_call -> + valid_content_headers(Req, State); + {stop, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {Switch, Req2, HandlerState} when element(1, Switch) =:= switch_handler -> + switch_handler(Switch, Req2, HandlerState); + {false, Req2, HandlerState} -> + valid_content_headers(Req2, State#state{handler_state=HandlerState}); + {{true, RetryAfter0}, Req2, HandlerState} -> + RetryAfter = if + is_integer(RetryAfter0), RetryAfter0 >= 0 -> + integer_to_binary(RetryAfter0); + is_tuple(RetryAfter0) -> + cowboy_clock:rfc1123(RetryAfter0) + end, + Req3 = cowboy_req:set_resp_header(<<"retry-after">>, RetryAfter, Req2), + respond(Req3, State#state{handler_state=HandlerState}, 429) + end. valid_content_headers(Req, State) -> expect(Req, State, valid_content_headers, true, diff --git a/test/handlers/rate_limited_h.erl b/test/handlers/rate_limited_h.erl new file mode 100644 index 0000000..e54249c --- /dev/null +++ b/test/handlers/rate_limited_h.erl @@ -0,0 +1,24 @@ +%% This module does rate limiting based on the query string value. + +-module(rate_limited_h). + +-export([init/2]). +-export([rate_limited/2]). +-export([content_types_provided/2]). +-export([get_text_plain/2]). + +init(Req, State) -> + {cowboy_rest, Req, State}. + +rate_limited(Req=#{qs := <<"false">>}, State) -> + {false, Req, State}; +rate_limited(Req=#{qs := <<"true-date">>}, State) -> + {{true, {{2222, 2, 22}, {11, 11, 11}}}, Req, State}; +rate_limited(Req=#{qs := <<"true">>}, State) -> + {{true, 3600}, Req, State}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. + +get_text_plain(Req, State) -> + {<<"This is REST!">>, Req, State}. diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index 93532fe..09ce8fb 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -48,6 +48,7 @@ init_dispatch(_) -> {"/charset_in_content_types_provided_implicit_no_callback", charset_in_content_types_provided_implicit_no_callback_h, []}, {"/provide_callback_missing", provide_callback_missing_h, []}, + {"/rate_limited", rate_limited_h, []}, {"/switch_handler", switch_handler_h, run}, {"/switch_handler_opts", switch_handler_h, hibernate} ]}]). @@ -287,6 +288,31 @@ provide_callback_missing(Config) -> {response, fin, 500, _} = gun:await(ConnPid, Ref), ok. +rate_limited(Config) -> + doc("A 429 response must be sent when the rate_limited callback returns true. " + "The retry-after header is specified as an integer."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/rate_limited?true", [{<<"accept-encoding">>, <<"gzip">>}]), + {response, fin, 429, Headers} = gun:await(ConnPid, Ref), + {_, <<"3600">>} = lists:keyfind(<<"retry-after">>, 1, Headers), + ok. + +rate_limited_datetime(Config) -> + doc("A 429 response must be sent when the rate_limited callback returns true. " + "The retry-after header is specified as a date/time tuple."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/rate_limited?true-date", [{<<"accept-encoding">>, <<"gzip">>}]), + {response, fin, 429, Headers} = gun:await(ConnPid, Ref), + {_, <<"Fri, 22 Feb 2222 11:11:11 GMT">>} = lists:keyfind(<<"retry-after">>, 1, Headers), + ok. + +rate_not_limited(Config) -> + doc("A success response must be sent when the rate_limited callback returns false."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/rate_limited?false", [{<<"accept-encoding">>, <<"gzip">>}]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref), + ok. + switch_handler(Config) -> doc("Switch REST to loop handler for streaming the response body."), ConnPid = gun_open(Config), |