1398 lines
69 KiB
HTML
1398 lines
69 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="generator" content="Asciidoctor 1.5.3">
|
|
<title>Spring Cloud Gateway</title>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
|
|
<style>
|
|
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
|
|
/* Remove comment around @import statement below when using as a custom stylesheet */
|
|
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/
|
|
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
|
|
audio,canvas,video{display:inline-block}
|
|
audio:not([controls]){display:none;height:0}
|
|
[hidden],template{display:none}
|
|
script{display:none!important}
|
|
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
|
|
body{margin:0}
|
|
a{background:transparent}
|
|
a:focus{outline:thin dotted}
|
|
a:active,a:hover{outline:0}
|
|
h1{font-size:2em;margin:.67em 0}
|
|
abbr[title]{border-bottom:1px dotted}
|
|
b,strong{font-weight:bold}
|
|
dfn{font-style:italic}
|
|
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
|
|
mark{background:#ff0;color:#000}
|
|
code,kbd,pre,samp{font-family:monospace;font-size:1em}
|
|
pre{white-space:pre-wrap}
|
|
q{quotes:"\201C" "\201D" "\2018" "\2019"}
|
|
small{font-size:80%}
|
|
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
|
sup{top:-.5em}
|
|
sub{bottom:-.25em}
|
|
img{border:0}
|
|
svg:not(:root){overflow:hidden}
|
|
figure{margin:0}
|
|
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
|
|
legend{border:0;padding:0}
|
|
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
|
|
button,input{line-height:normal}
|
|
button,select{text-transform:none}
|
|
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
|
|
button[disabled],html input[disabled]{cursor:default}
|
|
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
|
|
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
|
|
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
|
|
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
|
|
textarea{overflow:auto;vertical-align:top}
|
|
table{border-collapse:collapse;border-spacing:0}
|
|
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
|
|
html,body{font-size:100%}
|
|
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto}
|
|
a:hover{cursor:pointer}
|
|
img,object,embed{max-width:100%;height:auto}
|
|
object,embed{height:100%}
|
|
img{-ms-interpolation-mode:bicubic}
|
|
.left{float:left!important}
|
|
.right{float:right!important}
|
|
.text-left{text-align:left!important}
|
|
.text-right{text-align:right!important}
|
|
.text-center{text-align:center!important}
|
|
.text-justify{text-align:justify!important}
|
|
.hide{display:none}
|
|
body{-webkit-font-smoothing:antialiased}
|
|
img,object,svg{display:inline-block;vertical-align:middle}
|
|
textarea{height:auto;min-height:50px}
|
|
select{width:100%}
|
|
.center{margin-left:auto;margin-right:auto}
|
|
.spread{width:100%}
|
|
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
|
|
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
|
|
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
|
|
a{color:#2156a5;text-decoration:underline;line-height:inherit}
|
|
a:hover,a:focus{color:#1d4b8f}
|
|
a img{border:none}
|
|
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
|
|
p aside{font-size:.875em;line-height:1.35;font-style:italic}
|
|
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
|
|
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
|
|
h1{font-size:2.125em}
|
|
h2{font-size:1.6875em}
|
|
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
|
|
h4,h5{font-size:1.125em}
|
|
h6{font-size:1em}
|
|
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
|
|
em,i{font-style:italic;line-height:inherit}
|
|
strong,b{font-weight:bold;line-height:inherit}
|
|
small{font-size:60%;line-height:inherit}
|
|
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
|
|
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
|
|
ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em}
|
|
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
|
|
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
|
|
ul.square{list-style-type:square}
|
|
ul.circle{list-style-type:circle}
|
|
ul.disc{list-style-type:disc}
|
|
ul.no-bullet{list-style:none}
|
|
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
|
|
dl dt{margin-bottom:.3125em;font-weight:bold}
|
|
dl dd{margin-bottom:1.25em}
|
|
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
|
|
abbr{text-transform:none}
|
|
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
|
|
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
|
|
blockquote cite:before{content:"\2014 \0020"}
|
|
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
|
|
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
|
|
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
|
|
h1{font-size:2.75em}
|
|
h2{font-size:2.3125em}
|
|
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
|
|
h4{font-size:1.4375em}}
|
|
table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
|
|
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
|
|
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
|
|
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
|
|
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
|
|
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
|
|
body{tab-size:4}
|
|
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
|
|
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
|
|
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
|
|
.clearfix:after,.float-group:after{clear:both}
|
|
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
|
|
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
|
|
.keyseq{color:rgba(51,51,51,.8)}
|
|
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
|
|
.keyseq kbd:first-child{margin-left:0}
|
|
.keyseq kbd:last-child{margin-right:0}
|
|
.menuseq,.menu{color:rgba(0,0,0,.8)}
|
|
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
|
|
b.button:before{content:"[";padding:0 3px 0 2px}
|
|
b.button:after{content:"]";padding:0 2px 0 3px}
|
|
p a>code:hover{color:rgba(0,0,0,.9)}
|
|
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
|
|
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
|
|
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
|
|
#content{margin-top:1.25em}
|
|
#content:before{content:none}
|
|
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
|
|
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
|
|
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
|
|
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
|
|
#header .details span:first-child{margin-left:-.125em}
|
|
#header .details span.email a{color:rgba(0,0,0,.85)}
|
|
#header .details br{display:none}
|
|
#header .details br+span:before{content:"\00a0\2013\00a0"}
|
|
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
|
|
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
|
|
#header #revnumber{text-transform:capitalize}
|
|
#header #revnumber:after{content:"\00a0"}
|
|
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
|
|
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
|
|
#toc>ul{margin-left:.125em}
|
|
#toc ul.sectlevel0>li>a{font-style:italic}
|
|
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
|
|
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
|
|
#toc li{line-height:1.3334;margin-top:.3334em}
|
|
#toc a{text-decoration:none}
|
|
#toc a:active{text-decoration:underline}
|
|
#toctitle{color:#7a2518;font-size:1.2em}
|
|
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
|
|
body.toc2{padding-left:15em;padding-right:0}
|
|
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
|
|
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
|
|
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
|
|
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
|
|
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
|
|
body.toc2.toc-right{padding-left:0;padding-right:15em}
|
|
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}
|
|
@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
|
|
#toc.toc2{width:20em}
|
|
#toc.toc2 #toctitle{font-size:1.375em}
|
|
#toc.toc2>ul{font-size:.95em}
|
|
#toc.toc2 ul ul{padding-left:1.25em}
|
|
body.toc2.toc-right{padding-left:0;padding-right:20em}}
|
|
#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
|
#content #toc>:first-child{margin-top:0}
|
|
#content #toc>:last-child{margin-bottom:0}
|
|
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
|
|
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
|
|
.sect1{padding-bottom:.625em}
|
|
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}
|
|
.sect1+.sect1{border-top:1px solid #efefed}
|
|
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
|
|
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
|
|
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
|
|
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
|
|
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
|
|
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
|
|
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
|
|
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
|
|
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
|
|
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
|
|
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
|
|
.admonitionblock>table td.icon{text-align:center;width:80px}
|
|
.admonitionblock>table td.icon img{max-width:none}
|
|
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
|
|
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
|
|
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
|
|
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
|
|
.exampleblock>.content>:first-child{margin-top:0}
|
|
.exampleblock>.content>:last-child{margin-bottom:0}
|
|
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
|
.sidebarblock>:first-child{margin-top:0}
|
|
.sidebarblock>:last-child{margin-bottom:0}
|
|
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
|
|
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
|
|
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
|
|
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
|
|
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
|
|
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
|
|
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}
|
|
@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}
|
|
.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
|
|
.listingblock pre.highlightjs{padding:0}
|
|
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
|
|
.listingblock pre.prettyprint{border-width:0}
|
|
.listingblock>.content{position:relative}
|
|
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
|
|
.listingblock:hover code[data-lang]:before{display:block}
|
|
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
|
|
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
|
|
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
|
|
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
|
|
table.pyhltable td.code{padding-left:.75em;padding-right:0}
|
|
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
|
|
pre.pygments .lineno{display:inline-block;margin-right:.25em}
|
|
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
|
|
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
|
|
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
|
|
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
|
|
.quoteblock blockquote{margin:0;padding:0;border:0}
|
|
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
|
|
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
|
|
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
|
|
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
|
|
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
|
|
.quoteblock .quoteblock blockquote:before{display:none}
|
|
.verseblock{margin:0 1em 1.25em 1em}
|
|
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
|
|
.verseblock pre strong{font-weight:400}
|
|
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
|
|
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
|
|
.quoteblock .attribution br,.verseblock .attribution br{display:none}
|
|
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
|
|
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
|
|
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
|
|
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
|
|
table.tableblock{max-width:100%;border-collapse:separate}
|
|
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
|
|
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
|
|
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
|
|
table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0}
|
|
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
|
|
table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0}
|
|
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0}
|
|
table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0}
|
|
table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0}
|
|
table.frame-all{border-width:1px}
|
|
table.frame-sides{border-width:0 1px}
|
|
table.frame-topbot{border-width:1px 0}
|
|
th.halign-left,td.halign-left{text-align:left}
|
|
th.halign-right,td.halign-right{text-align:right}
|
|
th.halign-center,td.halign-center{text-align:center}
|
|
th.valign-top,td.valign-top{vertical-align:top}
|
|
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
|
|
th.valign-middle,td.valign-middle{vertical-align:middle}
|
|
table thead th,table tfoot th{font-weight:bold}
|
|
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
|
|
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
|
|
p.tableblock>code:only-child{background:none;padding:0}
|
|
p.tableblock{font-size:1em}
|
|
td>div.verse{white-space:pre}
|
|
ol{margin-left:1.75em}
|
|
ul li ol{margin-left:1.5em}
|
|
dl dd{margin-left:1.125em}
|
|
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
|
|
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
|
|
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
|
|
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
|
|
ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1em;font-size:.85em}
|
|
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{width:1em;position:relative;top:1px}
|
|
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
|
|
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
|
|
ul.inline>li>*{display:block}
|
|
.unstyled dl dt{font-weight:400;font-style:normal}
|
|
ol.arabic{list-style-type:decimal}
|
|
ol.decimal{list-style-type:decimal-leading-zero}
|
|
ol.loweralpha{list-style-type:lower-alpha}
|
|
ol.upperalpha{list-style-type:upper-alpha}
|
|
ol.lowerroman{list-style-type:lower-roman}
|
|
ol.upperroman{list-style-type:upper-roman}
|
|
ol.lowergreek{list-style-type:lower-greek}
|
|
.hdlist>table,.colist>table{border:0;background:none}
|
|
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
|
|
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
|
|
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
|
|
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
|
|
.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1}
|
|
.colist>table tr>td:last-of-type{padding:.25em 0}
|
|
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
|
|
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
|
|
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
|
|
.imageblock>.title{margin-bottom:0}
|
|
.imageblock.thumb,.imageblock.th{border-width:6px}
|
|
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
|
|
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
|
|
.image.left{margin-right:.625em}
|
|
.image.right{margin-left:.625em}
|
|
a.image{text-decoration:none;display:inline-block}
|
|
a.image object{pointer-events:none}
|
|
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
|
|
sup.footnote a,sup.footnoteref a{text-decoration:none}
|
|
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
|
|
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
|
|
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
|
|
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}
|
|
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
|
|
#footnotes .footnote:last-of-type{margin-bottom:0}
|
|
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
|
|
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
|
|
.gist .file-data>table td.line-data{width:99%}
|
|
div.unbreakable{page-break-inside:avoid}
|
|
.big{font-size:larger}
|
|
.small{font-size:smaller}
|
|
.underline{text-decoration:underline}
|
|
.overline{text-decoration:overline}
|
|
.line-through{text-decoration:line-through}
|
|
.aqua{color:#00bfbf}
|
|
.aqua-background{background-color:#00fafa}
|
|
.black{color:#000}
|
|
.black-background{background-color:#000}
|
|
.blue{color:#0000bf}
|
|
.blue-background{background-color:#0000fa}
|
|
.fuchsia{color:#bf00bf}
|
|
.fuchsia-background{background-color:#fa00fa}
|
|
.gray{color:#606060}
|
|
.gray-background{background-color:#7d7d7d}
|
|
.green{color:#006000}
|
|
.green-background{background-color:#007d00}
|
|
.lime{color:#00bf00}
|
|
.lime-background{background-color:#00fa00}
|
|
.maroon{color:#600000}
|
|
.maroon-background{background-color:#7d0000}
|
|
.navy{color:#000060}
|
|
.navy-background{background-color:#00007d}
|
|
.olive{color:#606000}
|
|
.olive-background{background-color:#7d7d00}
|
|
.purple{color:#600060}
|
|
.purple-background{background-color:#7d007d}
|
|
.red{color:#bf0000}
|
|
.red-background{background-color:#fa0000}
|
|
.silver{color:#909090}
|
|
.silver-background{background-color:#bcbcbc}
|
|
.teal{color:#006060}
|
|
.teal-background{background-color:#007d7d}
|
|
.white{color:#bfbfbf}
|
|
.white-background{background-color:#fafafa}
|
|
.yellow{color:#bfbf00}
|
|
.yellow-background{background-color:#fafa00}
|
|
span.icon>.fa{cursor:default}
|
|
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
|
|
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
|
|
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
|
|
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
|
|
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
|
|
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
|
|
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
|
|
.conum[data-value] *{color:#fff!important}
|
|
.conum[data-value]+b{display:none}
|
|
.conum[data-value]:after{content:attr(data-value)}
|
|
pre .conum[data-value]{position:relative;top:-.125em}
|
|
b.conum *{color:inherit!important}
|
|
.conum:not([data-value]):empty{display:none}
|
|
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
|
|
h1,h2,p,td.content,span.alt{letter-spacing:-.01em}
|
|
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
|
|
p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}
|
|
p{margin-bottom:1.25rem}
|
|
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
|
|
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
|
|
.print-only{display:none!important}
|
|
@media print{@page{margin:1.25cm .75cm}
|
|
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
|
|
a{color:inherit!important;text-decoration:underline!important}
|
|
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
|
|
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
|
|
abbr[title]:after{content:" (" attr(title) ")"}
|
|
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
|
|
thead{display:table-header-group}
|
|
svg{max-width:100%}
|
|
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
|
|
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
|
|
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
|
|
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
|
|
.sect1{padding-bottom:0!important}
|
|
.sect1+.sect1{border:0!important}
|
|
#header>h1:first-child{margin-top:1.25rem}
|
|
body.book #header{text-align:center}
|
|
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
|
|
body.book #header .details{border:0!important;display:block;padding:0!important}
|
|
body.book #header .details span:first-child{margin-left:0!important}
|
|
body.book #header .details br{display:block}
|
|
body.book #header .details br+span:before{content:none!important}
|
|
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
|
|
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
|
|
.listingblock code[data-lang]:before{display:block}
|
|
#footer{background:none!important;padding:0 .9375em}
|
|
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
|
|
.hide-on-print{display:none!important}
|
|
.print-only{display:block!important}
|
|
.hide-for-print{display:none!important}
|
|
.show-for-print{display:inherit!important}}
|
|
</style>
|
|
</head>
|
|
<body class="article">
|
|
<div id="header">
|
|
<h1>Spring Cloud Gateway</h1>
|
|
</div>
|
|
<div id="content">
|
|
<div id="preamble">
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p><strong>2.0.0.M2</strong></p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="gateway-starter">How to Include Spring Cloud Gateway</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>To include Spring Cloud Gateway in your project use the starter with group <code>org.springframework.cloud</code>
|
|
and artifact id <code>spring-cloud-starter-gateway</code>. See the <a href="http://projects.spring.io/spring-cloud/">Spring Cloud Project page</a>
|
|
for details on setting up your build system with the current Spring Cloud Release Train.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>If you include the starter, but, for some reason, you do not want the gateway to be enabled, set <code>spring.cloud.gateway.enabled=false</code>.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_glossary">Glossary</h2>
|
|
<div class="sectionbody">
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p><strong>Route</strong>: Route the basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates and a collection of filters. A route is matched if aggregate predicate is true.</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Predicate</strong>: This is a <a href="http://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html">Java 8 Function Predicate</a>. The input type is a <a href="http://docs.spring.io/spring/docs/5.0.x/javadoc-api/org/springframework/web/server/ServerWebExchange.html">Spring Framework <code>ServerWebExchange</code></a>. This allows developers to match on anything from the HTTP request, such as headers or parameters.</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Filter</strong>: These are instances <a href="http://docs.spring.io/spring/docs/5.0.x/javadoc-api/org/springframework/web/server/WebFilter.html">Spring Framework <code>WebFilter</code></a> constructed in with a specific factory. Here, requests and responses can be modified before or after sending the downstream request.</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="gateway-how-it-works">How It Works</h2>
|
|
<div class="sectionbody">
|
|
<div class="imageblock">
|
|
<div class="content">
|
|
<img src="https://raw.githubusercontent.com/spring-cloud/spring-cloud-gateway/2.0.x/docs/src/main/asciidoc/images/spring_cloud_gateway_diagram.png" alt="Spring Cloud Gateway Diagram">
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a Route, it is sent to the Gateway Web Handler. This handler runs sends the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line, is that filters may execute logic before the proxy request is sent or after. All "pre" filter logic is executed, then the proxy request is made. After the proxy request is made, the "post" filter logic is executed.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="gateway-request-predicates-factories">Route Predicate Factories</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Spring Cloud Gateway matches routes as part of the Spring WebFlux <code>HandlerMapping</code> infrastructure. Spring Cloud Gateway includes many built-in Route Predicate Factorys. All of these predicates match on different attributes of the HTTP request. Multiple Route Predicate Factorys can be combined and are combined via logical <code>and</code>.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_after_route_predicate_factory">After Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The After Route Predicate Factory takes one parameter, a datetime. This predicate matches requests that happen after the current datetime.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: after_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- After=2017-01-20T17:42:47.789-07:00[America/Denver]</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route matches any request after Jan 20, 2017 17:42 Mountain Time (Denver).</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_before_route_predicate_factory">Before Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Before Route Predicate Factory takes one parameter, a datetime. This predicate matches requests that happen before the current datetime.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: before_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route matches any request before Jan 20, 2017 17:42 Mountain Time (Denver).</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_between_route_predicate_factory">Between Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Between Route Predicate Factory takes two parameters, datetime1 and datetime2. This predicate matches requests that happen after datetime1 and before datetime2. The datetime2 parameter must be after datetime1.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: between_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Betweeen=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route matches any request after Jan 20, 2017 17:42 Mountain Time (Denver) and before Jan 21, 2017 17:42 Mountain Time (Denver). This could be useful for maintenance windows.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_cookie_route_predicate_factory">Cookie Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Cookie Route Predicate Factory takes two parameters, the cookie name and a regular expression. This predicate matches cookies that have the given name and the value matches the regular expression.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: cookie_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Cookie=chocolate, ch.p</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route matches the request has a cookie named <code>chocolate</code> who’s value matches the <code>ch.p</code> regular expression.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_header_route_predicate_factory">Header Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Header Route Predicate Factory takes two parameters, the header name and a regular expression. This predicate matches with a header that has the given name and the value matches the regular expression.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: header_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Header=X-Request-Id, \d+</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route matches if the request has a header named <code>X-Request-Id</code> whos value matches the <code>\d+</code> regular expression (has a value of one or more digits).</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_host_route_predicate_factory">Host Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Host Route Predicate Factory takes one parameter: the host name pattern. The pattern is an Ant style pattern with <code>.</code> as the separator. This predicates matches the <code>Host</code> header that matches the pattern.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: host_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Host=**.somehost.org</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the request has a <code>Host</code> header has the value <code>www.somehost.org</code> or <code>beta.somehost.org</code>.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_method_route_predicate_factory">Method Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Method Route Predicate Factory takes one parameter: the HTTP method to match.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: method_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Method=GET</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the request method was a <code>GET</code>.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_path_route_predicate_factory">Path Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Path Route Predicate Factory takes one parameter: a Spring <code>PathMatcher</code> pattern.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: host_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Path=/foo/{segment}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the request path was, for example: <code>/foo/1</code> or <code>/foo/bar</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This predicate extracts the URI template variables (like <code>segment</code> defined in the example above) as a map of names and values and places it in the <code>ServerWebExchange.getAttributes()</code> with a key defined in <code>PathRoutePredicate.URL_PREDICATE_VARS_ATTR</code>. Those values are then available for use by <a href="#gateway-route-filters">WebFilter Factorys</a></p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_query_route_predicate_factory">Query Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Query Route Predicate Factory takes two parameters: a required <code>param</code> and an optional <code>regexp</code>.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: query_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Query=baz</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the request contained a <code>baz</code> query parameter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: query_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Query=foo, ba.</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the request contained a <code>foo</code> query parameter whose value matched the <code>ba.</code> regexp, so <code>bar</code> and <code>baz</code> would match.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_remoteaddr_route_predicate_factory">RemoteAddr Route Predicate Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RemoteAddr Route Predicate Factory takes a list (min size 1) of CIDR-notation strings, e.g. <code>192.168.0.1/16</code> (where <code>192.168.0.1</code> is an IP address and <code>16</code> is a subnet mask.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: remoteaddr_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- RemoteAddr=192.168.1.1/24</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This route would match if the remote address of the request was, for example, <code>192.168.1.10</code>.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="gateway-route-filters">WebFilter Factorys</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in WebFilter Factories.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_addrequestheader_webfilter_factory">AddRequestHeader WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The AddRequestHeader WebFilter Factory takes a name and value parameter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: add_request_header_route
|
|
uri: http://example.org
|
|
filters:
|
|
- AddRequestHeader=X-Request-Foo, Bar</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will add <code>X-Request-Foo:Bar</code> header to the downstream request’s headers for all matching requests.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_addrequestparameter_webfilter_factory">AddRequestParameter WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The AddRequestParameter WebFilter Factory takes a name and value parameter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: add_request_parameter_route
|
|
uri: http://example.org
|
|
filters:
|
|
- AddRequestParameter=foo, bar</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will add <code>foo=bar</code> to the downstream request’s query string for all matching requests.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_addresponseheader_webfilter_factory">AddResponseHeader WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The AddResponseHeader WebFilter Factory takes a name and value parameter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: add_request_header_route
|
|
uri: http://example.org
|
|
filters:
|
|
- AddResponseHeader=X-Response-Foo, Bar</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will add <code>X-Response-Foo:Bar</code> header to the downstream response’s headers for all matching requests.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_hystrix_webfilter_factory">Hystrix WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The Hystrix WebFilter Factory takes a single <code>name</code> parameters, which is the name of the <code>HystrixCommand</code>. (More options might be added in future releases).</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: hytstrix_route
|
|
uri: http://example.org
|
|
filters:
|
|
- Hystrix=myCommandName</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This wraps the remaining filters in a <code>HystrixCommand</code> with command name <code>myCommandName</code>.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_prefixpath_webfilter_factory">PrefixPath WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The PrefixPath WebFilter Factory takes a single <code>prefix</code> parameter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: prefixpath_route
|
|
uri: http://example.org
|
|
filters:
|
|
- PrefixPath=/mypath</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will prefix <code>/mypath</code> to the path of all matching requests. So a request to <code>/hello</code>, would be sent to <code>/mypath/hello</code>.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_requestratelimiter_webfilter_factory">RequestRateLimiter WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RequestRateLimiter WebFilter Factory takes three parameters: <code>replenishRate</code>, <code>burstCapacity</code> & <code>keyResolverName</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><code>replenishRate</code> is how many requests per second do you want a user to be allowed to do.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><code>burstCapacity</code> TODO: document burst capacity</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><code>keyResolverName</code> is the name of a bean that implements the <code>KeyResolver</code> interface.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">KeyResolver.java</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">public interface KeyResolver {
|
|
Mono<String> resolve(ServerWebExchange exchange);
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The <code>KeyResolver</code> interface allows pluggable strategies to derive the key for limiting requests. In future milestones, there will be some <code>KeyResolver</code> implementations.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The redis implementation is based off of work done at <a href="https://stripe.com/blog/rate-limiters">Stripe</a>. It requires the use of the <code>spring-boot-starter-data-redis-reactive</code> Spring Boot starter.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: requestratelimiter_route
|
|
uri: http://example.org
|
|
filters:
|
|
- RequestRateLimiter=10, 20, userKeyResolver</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">Config.java</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@Bean
|
|
KeyResolver userKeyResolver() {
|
|
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This defines a request rate limit of 10 per user. The <code>KeyResolver</code> is a simple one that gets the <code>user</code> request parameter (note: this is not recommended for production).</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_redirectto_webfilter_factory">RedirectTo WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RedirectTo WebFilter Factory takes a <code>status</code> and a <code>url</code> parameter. The status should be a 300 series redirect http code, such as 301. The url should be a valid url. This will be the value of the <code>Location</code> header.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: prefixpath_route
|
|
uri: http://example.org
|
|
filters:
|
|
- RedirectTo=302, http://acme.org</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will send a status 302 with a <code>Location:http://acme.org</code> header to perform a redirect.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_removenonproxyheaders_webfilter_factory">RemoveNonProxyHeaders WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RemoveNonProxyHeaders WebFilter Factory removes headers from forwarded requests. The default list of headers that is removed comes from the <a href="https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3">IETF</a>.</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<div class="title">The default removed headers are:</div>
|
|
<ul>
|
|
<li>
|
|
<p>Connection</p>
|
|
</li>
|
|
<li>
|
|
<p>Keep-Alive</p>
|
|
</li>
|
|
<li>
|
|
<p>Proxy-Authenticate</p>
|
|
</li>
|
|
<li>
|
|
<p>Proxy-Authorization</p>
|
|
</li>
|
|
<li>
|
|
<p>TE</p>
|
|
</li>
|
|
<li>
|
|
<p>Trailer</p>
|
|
</li>
|
|
<li>
|
|
<p>Transfer-Encoding</p>
|
|
</li>
|
|
<li>
|
|
<p>Upgrade</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>To change this, set the <code>spring.cloud.gateway.filter.remove-non-proxy-headers.headers</code> property to the list of header names to remove.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_removerequestheader_webfilter_factory">RemoveRequestHeader WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RemoveRequestHeader WebFilter Factory takes a <code>name</code> parameter. It is the name of the header to be removed.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: removerequestheader_route
|
|
uri: http://example.org
|
|
filters:
|
|
- RemoveRequestHeader=X-Request-Foo</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will remove the <code>X-Request-Foo</code> header before it is sent downstream.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_removeresponseheader_webfilter_factory">RemoveResponseHeader WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RemoveResponseHeader WebFilter Factory takes a <code>name</code> parameter. It is the name of the header to be removed.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: removeresponseheader_route
|
|
uri: http://example.org
|
|
filters:
|
|
- RemoveResponseHeader=X-Response-Foo</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This will remove the <code>X-Response-Foo</code> header from the response before it is returned to the gateway client.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_rewritepath_webfilter_factory">RewritePath WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The RewritePath WebFilter Factory takes a path <code>regexp</code> parameter and a <code>replacement</code> parameter. This uses Java regular expressions for a flexible way to rewrite the request path.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: rewritepath_route
|
|
uri: http://example.org
|
|
- Path=/foo/**
|
|
filters:
|
|
- RewritePath=/foo/(?<segment>.*), /$\{segment}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>For a request path of <code>/foo/bar</code>, this will set the path to <code>/bar</code> before making the downstream request. Notice the <code>$\</code> which is replaced with <code>$</code> because of the YAML spec.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_secureheaders_webfilter_factory">SecureHeaders WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The SecureHeaders WebFilter Factory adds a number of headers to the response at the reccomendation from <a href="https://blog.appcanary.com/2017/http-security-headers.html">this blog post</a>.</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<div class="title">The following headers are added (allong with default values):</div>
|
|
<ul>
|
|
<li>
|
|
<p><code>X-Xss-Protection:1; mode=block</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>Strict-Transport-Security:max-age=631138519</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>X-Frame-Options:DENY</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>X-Content-Type-Options:nosniff</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>Referrer-Policy:no-referrer</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>Content-Security-Policy:default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>X-Download-Options:noopen</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>X-Permitted-Cross-Domain-Policies:none</code></p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>To change the default values set the appropriate property in the <code>spring.cloud.gateway.filter.secure-headers</code> namespace:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<div class="title">Property to change:</div>
|
|
<ul>
|
|
<li>
|
|
<p><code>xss-protection-header</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>strict-transport-security</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>frame-options</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>content-type-options</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>referrer-policy</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>content-security-policy</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>download-options</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>permitted-cross-domain-policies</code></p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_setpath_webfilter_factory">SetPath WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The SetPath WebFilter Factory takes a path <code>template</code> parameter. It offers a simple way to manipulate the request path by allowing templated segments of the path. This uses the uri templates from Spring Framework. Multiple matching segments are allowed.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: setpath_route
|
|
uri: http://example.org
|
|
predicates:
|
|
- Path=/foo/{segment}
|
|
filters:
|
|
- SetPath=/{segment}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>For a request path of <code>/foo/bar</code>, this will set the path to <code>/bar</code> before making the downstream request.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_setresponseheader_webfilter_factory">SetResponseHeader WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The SetResponseHeader WebFilter Factory takes <code>name</code> and <code>value</code> parameters.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: setresponseheader_route
|
|
uri: http://example.org
|
|
filters:
|
|
- SetResponseHeader=X-Response-Foo, Bar</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This WebFilter replaces all headers with the given name, rather than adding. So if the downstream server responded with a <code>X-Response-Foo:1234</code>, this would be replaced with <code>X-Response-Foo:Bar</code>, which is what the gateway client would receive.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_setstatus_webfilter_factory">SetStatus WebFilter Factory</h3>
|
|
<div class="paragraph">
|
|
<p>The SetStatus WebFilter Factory takes a single <code>status</code> parameter. It must be a valid Spring <code>HttpStatus</code>. It may be the integer value <code>404</code> or the string representation of the enumeration <code>NOT_FOUND</code>.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: setstatusstring_route
|
|
uri: http://example.org
|
|
filters:
|
|
- SetStatus=BAD_REQUEST
|
|
- id: setstatusint_route
|
|
uri: http://example.org
|
|
filters:
|
|
- SetStatus=401</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>In either case, the HTTP status of the response will be set to 401.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_global_filters">Global Filters</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>The <code>GlobalFilter</code> interface has the same signature as <code>WebFilter</code>. These are special filters that are conditionally applied to all routes. (This interface and usage are subject to change in future milestones).</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_loadbalancerclient_filter">LoadBalancerClient Filter</h3>
|
|
<div class="paragraph">
|
|
<p>The <code>LoadBalancerClientFilter</code> looks for a URI in the exchange attribute <code>ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR</code>. If the url has a <code>lb</code> scheme (ie <code>lb://myservice</code>), it will use the Spring Cloud <code>LoadBalancerClient</code> to resolve the name (<code>myservice</code> in the previous example) to an actual host and port and replace the URI in the same attribute. The unmodified original url is placed in the <code>ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR</code> attribute.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_netty_routing_filter">Netty Routing Filter</h3>
|
|
<div class="paragraph">
|
|
<p>The Netty Routing Filter runs if the url located in the <code>ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR</code> exchange attribute has a <code>http</code> or <code>https</code> scheme. It uses the Netty <code>HttpClient</code> to make the downstream proxy request. The response is put in the <code>ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR</code> exchange attribute for use in a later filter. (There is an experimental <code>WebClientHttpRoutingFilter</code> that performs the same function, but does not require netty)</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_netty_write_response_filter">Netty Write Response Filter</h3>
|
|
<div class="paragraph">
|
|
<p>The <code>NettyWriteResponseFilter</code> runs if there is a Netty <code>HttpClientResponse</code> in the <code>ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR</code> exchange attribute. It is run after all other filters have completed and writes the proxy response back to the gateway client response. (There is an experimental <code>WebClientWriteResponseFilter</code> that performs the same function, but does not require netty)</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_routetorequesturl_filter">RouteToRequestUrl Filter</h3>
|
|
<div class="paragraph">
|
|
<p>The <code>RouteToRequestUrlFilter</code> runs if there is a <code>Route</code> object in the <code>ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR</code> exchange attribute. It creates a new URI, based off of the request URI, but updated with the URI attribute of the <code>Route</code> object. The new URI is placed in the <code>`ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR</code> exchange attribute`.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_websocket_routing_filter">Websocket Routing Filter</h3>
|
|
<div class="paragraph">
|
|
<p>The Websocket Routing Filter runs if the url located in the <code>ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR</code> exchange attribute has a <code>ws</code> or <code>wss</code> scheme. It uses the Spring Web Socket infrastructure to forward the Websocket request downstream.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_configuration">Configuration</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Configuration for Spring Cloud Gateway is driven by a collection of `RouteDefinitionLocator`s.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">RouteDefinitionLocator.java</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">public interface RouteDefinitionLocator {
|
|
Flux<RouteDefinition> getRouteDefinitions();
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>By default, a <code>PropertiesRouteDefinitionLocator</code> loads properties using Spring Boot’s <code>@ConfigurationProperties</code> mechanism.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The configuration examples above all use a shortcut notation that uses positional arguments rather than named ones. The two examples below are equivalent:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">application.yml</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
|
|
cloud:
|
|
gateway:
|
|
routes:
|
|
# =====================================
|
|
- id: setstatus_route
|
|
uri: http://example.org
|
|
filters:
|
|
- name: SetStatus
|
|
args:
|
|
status: 401
|
|
- id: setstatusshortcut_route
|
|
uri: http://example.org
|
|
filters:
|
|
- SetStatus=401</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>For some usages of the gateway, properties will be adequate, but some production use cases will benefit from loading configuration from an external source, such as a database. Future milestone versions will have <code>RouteDefinitionLocator</code> implementations based off of Spring Data Repositories such as: Redis, MongoDB and Cassandra.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_fluent_java_routes_api">Fluent Java Routes API</h3>
|
|
<div class="paragraph">
|
|
<p>To allow for simple configuration in Java, there is a fluent API defined in the <code>Routes</code> class.</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="title">Config.java</div>
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// static imports from WebFilterFactories and RoutePredicates
|
|
@Bean
|
|
public RouteLocator customRouteLocator(ThrottleWebFilterFactory throttle) {
|
|
return Routes.locator()
|
|
.route("test")
|
|
.uri("http://httpbin.org:80")
|
|
.predicate(host("**.abc.org").and(path("/image/png")))
|
|
.addResponseHeader("X-TestHeader", "foobar")
|
|
.and()
|
|
.route("test2")
|
|
.uri("http://httpbin.org:80")
|
|
.predicate(path("/image/webp"))
|
|
.add(addResponseHeader("X-AnotherHeader", "baz"))
|
|
.and()
|
|
.build();
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This style also allows for more custom predicate assertions. The predicates defined by <code>RouteDefinitionLocator</code> beans are combined using logical <code>and</code>. By using the fluent Java API, you can use the <code>and()</code>, <code>or()</code> and <code>negate()</code> operators on the <code>Predicate</code> class.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_actuator_api">Actuator API</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>TODO: document the <code>/gateway</code> actuator endpoint</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_developer_guide">Developer Guide</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>TODO: overview of writing custom integrations</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_writing_custom_route_predicate_factorys">Writing Custom Route Predicate Factorys</h3>
|
|
<div class="paragraph">
|
|
<p>TODO: document writing Custom Route Predicate Factorys</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_writing_custom_webfilter_factorys">Writing Custom WebFilter Factorys</h3>
|
|
<div class="paragraph">
|
|
<p>TODO: document writing Custom WebFilter Factorys</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_writing_custom_global_filters">Writing Custom Global Filters</h3>
|
|
<div class="paragraph">
|
|
<p>TODO: document writing Custom Global Filters</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_writing_custom_route_locators_and_writers">Writing Custom Route Locators and Writers</h3>
|
|
<div class="paragraph">
|
|
<p>TODO: document writing Custom Route Locators and Writers</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_building_a_simple_gateway_using_spring_mvc">Building a Simple Gateway Using Spring MVC</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Spring Cloud Gateway provides a utility object called <code>ProxyExchange</code> which you can use inside a regular Spring MVC handler as a method parameter. It supports basic downstream HTTP exchanges via methods that mirror the HTTP verbs, or forwarding to a local handler via the <code>forward()</code> method.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Example (proxying a request to "/test" downstream to a remote server):</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@RestController
|
|
@SpringBootApplication
|
|
public class GatewaySampleApplication {
|
|
|
|
@Value("${remote.home}")
|
|
private URI home;
|
|
|
|
@GetMapping("/test")
|
|
public ResponseEntity<?> proxy(ProxyExchange<Object> proxy) throws Exception {
|
|
return proxy.uri(home.toString() + "/image/png").get();
|
|
}
|
|
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>There are convenience methods on the <code>ProxyExchange</code> to enable the handler method to discover and enhance the URI path of the incoming request. For example you might want to extract the trailing elements of a path to pass them downstream:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@GetMapping("/proxy/path/**")
|
|
public ResponseEntity<?> proxyPath(ProxyExchange<?> proxy) throws Exception {
|
|
String path = proxy.path("/proxy/path/");
|
|
return proxy.uri(home.toString() + "/foos/" + path).get();
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>All the features of Spring MVC are available to Gateway handler methods. So you can inject request headers and query parameters, for instance, and you can constrain the incoming requests with declarations in the mapping annotation. See the documentation for <code>@RequestMapping</code> in Spring MVC for more details of those features.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Headers can be added to the downstream response using the <code>header()</code> methods on <code>ProxyExchange</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>You can also manipulate response headers (and anything else you like in the response) by adding a mapper to the <code>get()</code> etc. method. The mapper is a <code>Function</code> that takes the incoming <code>ResponseEntity</code> and converts it to an outgoing one.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>First class support is provided for "sensitive" headers ("cookie" and "authorization" by default) which are not passed downstream, and for "proxy" headers (<code>x-forwarded-*</code>).</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js"></script>
|
|
<script>prettyPrint()</script>
|
|
</body>
|
|
</html> |