Browse Source

Add the rate_limited/2 REST callback

Loïc Hoguin 6 years ago
parent
commit
8c9ad7bf07

+ 3 - 0
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`.

+ 1 - 0
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`

BIN
doc/src/guide/rest_start.png


+ 489 - 189
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>

+ 25 - 0
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.
 

+ 29 - 1
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,

+ 24 - 0
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}.

+ 26 - 0
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),