diff --git a/404.html b/404.html index d7e7d1ed..944cd189 100644 --- a/404.html +++ b/404.html @@ -10,7 +10,7 @@ - +
diff --git a/assets/js/5d747023.1eea72f2.js b/assets/js/5d747023.b53f9bbf.js similarity index 99% rename from assets/js/5d747023.1eea72f2.js rename to assets/js/5d747023.b53f9bbf.js index 08e16d0b..ff2c87e6 100644 --- a/assets/js/5d747023.1eea72f2.js +++ b/assets/js/5d747023.b53f9bbf.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[286],{3711:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>r,toc:()=>c});var t=o(5893),a=o(1151);const i={description:"Google App Engine recipe"},s="Google App Engine",r={id:"cookbook/google-app-engine",title:"Google App Engine",description:"Google App Engine recipe",source:"@site/docs/cookbook/google-app-engine.md",sourceDirName:"cookbook",slug:"/cookbook/google-app-engine",permalink:"/docs/cookbook/google-app-engine",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/cookbook/google-app-engine.md",tags:[],version:"current",frontMatter:{description:"Google App Engine recipe"},sidebar:"docsSidebar",previous:{title:"File Upload",permalink:"/docs/cookbook/file-upload"},next:{title:"Graceful Shutdown",permalink:"/docs/cookbook/graceful-shutdown"}},l={},c=[{value:"Standalone",id:"standalone",level:2},{value:"AppEngine Classic and Managed VM(s)",id:"appengine-classic-and-managed-vms",level:2},{value:"Configuration file",id:"configuration-file",level:3},{value:"Router configuration",id:"router-configuration",level:3}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",...(0,a.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"google-app-engine",children:"Google App Engine"}),"\n",(0,t.jsx)(n.p,{children:"Google App Engine (GAE) provides a range of hosting options from pure PaaS (App Engine Classic)\nthrough Managed VMs to fully self-managed or container-driven Compute Engine instances. Echo\nworks great with all of these but requires a few changes to the usual examples to run on the\nAppEngine Classic and Managed VM options. With a small amount of effort though it's possible\nto produce a codebase that will run on these and also non-managed platforms automatically."}),"\n",(0,t.jsx)(n.p,{children:"We'll walk through the changes needed to support each option."}),"\n",(0,t.jsx)(n.h2,{id:"standalone",children:"Standalone"}),"\n",(0,t.jsx)(n.p,{children:"Wait? What? I thought this was about AppEngine! Bear with me - the easiest way to show the changes\nrequired is to start with a setup for standalone and work from there plus there's no reason we\nwouldn't want to retain the ability to run our app anywhere, right?"}),"\n",(0,t.jsxs)(n.p,{children:["We take advantage of the go ",(0,t.jsx)(n.a,{href:"http://golang.org/pkg/go/build/",children:"build constraints or tags"})," to change\nhow we create and run the Echo server for each platform while keeping the rest of the application\n(e.g. handler wireup) the same across all of them."]}),"\n",(0,t.jsxs)(n.p,{children:["First, we have the normal setup based on the examples but we split it into two files - ",(0,t.jsx)(n.code,{children:"app.go"})," will\nbe common to all variations and holds the Echo instance variable. We initialise it from a function\nand because it is a ",(0,t.jsx)(n.code,{children:"var"})," this will happen ",(0,t.jsx)(n.em,{children:"before"})," any ",(0,t.jsx)(n.code,{children:"init()"})," functions run - a feature that we'll\nuse to connect our handlers later."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["A separate source file contains the function to create the Echo instance and add the static\nfile handlers and middleware. Note the build tag on the first line which says to use this when ",(0,t.jsx)(n.em,{children:"not"}),"\nbulding with appengine or appenginevm tags (which thoese platforms automatically add for us). We also\nhave the ",(0,t.jsx)(n.code,{children:"main()"})," function to start serving our app as normal. This should all be very familiar."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-standalone.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The handler-wireup that would normally also be a part of this Echo setup moves to separate files which\ntake advantage of the ability to have multiple ",(0,t.jsx)(n.code,{children:"init()"})," functions which run ",(0,t.jsx)(n.em,{children:"after"})," the ",(0,t.jsx)(n.code,{children:"e"})," Echo var is\ninitialized but ",(0,t.jsx)(n.em,{children:"before"})," the ",(0,t.jsx)(n.code,{children:"main()"})," function is executed. These allow additional handlers to attach\nthemselves to the instance - I've found the ",(0,t.jsx)(n.code,{children:"Group"})," feature naturally fits into this pattern with a file\nper REST endpoint, often with a higher-level ",(0,t.jsx)(n.code,{children:"api"})," group created that they attach to instead of the root\nEcho instance directly (so things like CORS middleware can be added at this higher common-level)."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/users.go\n"})}),"\n",(0,t.jsx)(n.p,{children:"If we run our app it should execute as it did before when everything was in one file although we have\nat least gained the ability to organize our handlers a little more cleanly."}),"\n",(0,t.jsx)(n.h2,{id:"appengine-classic-and-managed-vms",children:"AppEngine Classic and Managed VM(s)"}),"\n",(0,t.jsx)(n.p,{children:"So far we've seen how to split apart the Echo creation and setup but still have the same app that\nstill only runs standalone. Now we'll see how those changes allow us to add support for AppEngine\nhosting."}),"\n",(0,t.jsxs)(n.p,{children:["Refer to the ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/go/",children:"AppEngine site"})," for full configuration\nand deployment information."]}),"\n",(0,t.jsx)(n.h3,{id:"configuration-file",children:"Configuration file"}),"\n",(0,t.jsxs)(n.p,{children:["Both of these are Platform as as Service options running on either sandboxed micro-containers\nor managed Compute Engine instances. Both require an ",(0,t.jsx)(n.code,{children:"app.yaml"})," file to describe the app to\nthe service. While the app ",(0,t.jsx)(n.em,{children:"could"})," still serve all it's static files itself, one of the benefits\nof the platform is having Google's infrastructure handle that for us so it can be offloaded and\nthe app only has to deal with dynamic requests. The platform also handles logging and http gzip\ncompression so these can be removed from the codebase as well."]}),"\n",(0,t.jsxs)(n.p,{children:["The yaml file also contains other options to control instance size and auto-scaling so for true\ndeployment freedom you would likely have separate ",(0,t.jsx)(n.code,{children:"app-classic.yaml"})," and ",(0,t.jsx)(n.code,{children:"app-vm.yaml"})," files and\nthis can help when making the transition from AppEngine Classic to Managed VMs."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-engine.yaml\n"})}),"\n",(0,t.jsx)(n.h3,{id:"router-configuration",children:"Router configuration"}),"\n",(0,t.jsxs)(n.p,{children:["We'll now use the ",(0,t.jsx)(n.a,{href:"http://golang.org/pkg/go/build/",children:"build constraints"})," again like we did when creating\nour ",(0,t.jsx)(n.code,{children:"app-standalone.go"})," instance but this time with the opposite tags to use this file ",(0,t.jsx)(n.em,{children:"if"})," the build has\nthe appengine or appenginevm tags (added automatically when deploying to these platforms)."]}),"\n",(0,t.jsxs)(n.p,{children:["This allows us to replace the ",(0,t.jsx)(n.code,{children:"createMux()"})," function to create our Echo server ",(0,t.jsx)(n.em,{children:"without"})," any of the\nstatic file handling and logging + gzip middleware which is no longer required. Also worth nothing is\nthat GAE classic provides a wrapper to handle serving the app so instead of a ",(0,t.jsx)(n.code,{children:"main()"})," function where\nwe run the server, we instead wire up the router to the default ",(0,t.jsx)(n.code,{children:"http.Handler"})," instead."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-engine.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Managed VMs are slightly different. They are expected to respond to requests on port 8080 as well\nas special health-check requests used by the service to detect if an instance is still running in\norder to provide automated failover and instance replacement. The ",(0,t.jsx)(n.code,{children:"google.golang.org/appengine"}),"\npackage provides this for us so we have a slightly different version for Managed VMs:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-managed.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["So now we have three different configurations. We can build and run our app as normal so it can\nbe executed locally, on a full Compute Engine instance or any other traditional hosting provider\n(including EC2, Docker etc...). This build will ignore the code in appengine and appenginevm tagged\nfiles and the ",(0,t.jsx)(n.code,{children:"app.yaml"})," file is meaningless to anything other than the AppEngine platform."]}),"\n",(0,t.jsxs)(n.p,{children:["We can also run locally using the ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/downloads",children:"Google AppEngine SDK for Go"}),"\neither emulating ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/go/tools/devserver",children:"AppEngine Classic"}),":"]}),"\n",(0,t.jsx)(n.p,{children:"goapp serve"}),"\n",(0,t.jsxs)(n.p,{children:["Or ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/managed-vms/sdk#run-local",children:"Managed VM(s)"}),":"]}),"\n",(0,t.jsx)(n.p,{children:"gcloud config set project [your project id]\ngcloud preview app run ."}),"\n",(0,t.jsx)(n.p,{children:"And of course we can deploy our app to both of these platforms for easy and inexpensive auto-scaling joy."}),"\n",(0,t.jsx)(n.p,{children:"Depending on what your app actually does it's possible you may need to make other changes to allow\nswitching between AppEngine provided service such as Datastore and alternative storage implementations\nsuch as MongoDB. A combination of go interfaces and build constraints can make this fairly straightforward\nbut is outside the scope of this example."})]})}function d(e={}){const{wrapper:n}={...(0,a.a)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},1151:(e,n,o)=>{o.d(n,{Z:()=>r,a:()=>s});var t=o(7294);const a={},i=t.createContext(a);function s(e){const n=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[286],{3711:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>r,toc:()=>c});var t=o(5893),a=o(1151);const i={description:"Google App Engine recipe"},s="Google App Engine",r={id:"cookbook/google-app-engine",title:"Google App Engine",description:"Google App Engine recipe",source:"@site/docs/cookbook/google-app-engine.md",sourceDirName:"cookbook",slug:"/cookbook/google-app-engine",permalink:"/docs/cookbook/google-app-engine",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/cookbook/google-app-engine.md",tags:[],version:"current",frontMatter:{description:"Google App Engine recipe"},sidebar:"docsSidebar",previous:{title:"File Upload",permalink:"/docs/cookbook/file-upload"},next:{title:"Graceful Shutdown",permalink:"/docs/cookbook/graceful-shutdown"}},l={},c=[{value:"Standalone",id:"standalone",level:2},{value:"AppEngine Classic and Managed VM(s)",id:"appengine-classic-and-managed-vms",level:2},{value:"Configuration file",id:"configuration-file",level:3},{value:"Router configuration",id:"router-configuration",level:3}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",...(0,a.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"google-app-engine",children:"Google App Engine"}),"\n",(0,t.jsx)(n.p,{children:"Google App Engine (GAE) provides a range of hosting options from pure PaaS (App Engine Classic)\nthrough Managed VMs to fully self-managed or container-driven Compute Engine instances. Echo\nworks great with all of these but requires a few changes to the usual examples to run on the\nAppEngine Classic and Managed VM options. With a small amount of effort though it's possible\nto produce a codebase that will run on these and also non-managed platforms automatically."}),"\n",(0,t.jsx)(n.p,{children:"We'll walk through the changes needed to support each option."}),"\n",(0,t.jsx)(n.h2,{id:"standalone",children:"Standalone"}),"\n",(0,t.jsx)(n.p,{children:"Wait? What? I thought this was about AppEngine! Bear with me - the easiest way to show the changes\nrequired is to start with a setup for standalone and work from there plus there's no reason we\nwouldn't want to retain the ability to run our app anywhere, right?"}),"\n",(0,t.jsxs)(n.p,{children:["We take advantage of the go ",(0,t.jsx)(n.a,{href:"http://golang.org/pkg/go/build/",children:"build constraints or tags"})," to change\nhow we create and run the Echo server for each platform while keeping the rest of the application\n(e.g. handler wireup) the same across all of them."]}),"\n",(0,t.jsxs)(n.p,{children:["First, we have the normal setup based on the examples but we split it into two files - ",(0,t.jsx)(n.code,{children:"app.go"})," will\nbe common to all variations and holds the Echo instance variable. We initialise it from a function\nand because it is a ",(0,t.jsx)(n.code,{children:"var"})," this will happen ",(0,t.jsx)(n.em,{children:"before"})," any ",(0,t.jsx)(n.code,{children:"init()"})," functions run - a feature that we'll\nuse to connect our handlers later."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["A separate source file contains the function to create the Echo instance and add the static\nfile handlers and middleware. Note the build tag on the first line which says to use this when ",(0,t.jsx)(n.em,{children:"not"}),"\nbuilding with appengine or appenginevm tags (which those platforms automatically add for us). We also\nhave the ",(0,t.jsx)(n.code,{children:"main()"})," function to start serving our app as normal. This should all be very familiar."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-standalone.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The handler-wireup that would normally also be a part of this Echo setup moves to separate files which\ntake advantage of the ability to have multiple ",(0,t.jsx)(n.code,{children:"init()"})," functions which run ",(0,t.jsx)(n.em,{children:"after"})," the ",(0,t.jsx)(n.code,{children:"e"})," Echo var is\ninitialized but ",(0,t.jsx)(n.em,{children:"before"})," the ",(0,t.jsx)(n.code,{children:"main()"})," function is executed. These allow additional handlers to attach\nthemselves to the instance - I've found the ",(0,t.jsx)(n.code,{children:"Group"})," feature naturally fits into this pattern with a file\nper REST endpoint, often with a higher-level ",(0,t.jsx)(n.code,{children:"api"})," group created that they attach to instead of the root\nEcho instance directly (so things like CORS middleware can be added at this higher common-level)."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/users.go\n"})}),"\n",(0,t.jsx)(n.p,{children:"If we run our app it should execute as it did before when everything was in one file although we have\nat least gained the ability to organize our handlers a little more cleanly."}),"\n",(0,t.jsx)(n.h2,{id:"appengine-classic-and-managed-vms",children:"AppEngine Classic and Managed VM(s)"}),"\n",(0,t.jsx)(n.p,{children:"So far we've seen how to split apart the Echo creation and setup but still have the same app that\nstill only runs standalone. Now we'll see how those changes allow us to add support for AppEngine\nhosting."}),"\n",(0,t.jsxs)(n.p,{children:["Refer to the ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/go/",children:"AppEngine site"})," for full configuration\nand deployment information."]}),"\n",(0,t.jsx)(n.h3,{id:"configuration-file",children:"Configuration file"}),"\n",(0,t.jsxs)(n.p,{children:["Both of these are Platform as as Service options running on either sandboxed micro-containers\nor managed Compute Engine instances. Both require an ",(0,t.jsx)(n.code,{children:"app.yaml"})," file to describe the app to\nthe service. While the app ",(0,t.jsx)(n.em,{children:"could"})," still serve all it's static files itself, one of the benefits\nof the platform is having Google's infrastructure handle that for us so it can be offloaded and\nthe app only has to deal with dynamic requests. The platform also handles logging and http gzip\ncompression so these can be removed from the codebase as well."]}),"\n",(0,t.jsxs)(n.p,{children:["The yaml file also contains other options to control instance size and auto-scaling so for true\ndeployment freedom you would likely have separate ",(0,t.jsx)(n.code,{children:"app-classic.yaml"})," and ",(0,t.jsx)(n.code,{children:"app-vm.yaml"})," files and\nthis can help when making the transition from AppEngine Classic to Managed VMs."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-engine.yaml\n"})}),"\n",(0,t.jsx)(n.h3,{id:"router-configuration",children:"Router configuration"}),"\n",(0,t.jsxs)(n.p,{children:["We'll now use the ",(0,t.jsx)(n.a,{href:"http://golang.org/pkg/go/build/",children:"build constraints"})," again like we did when creating\nour ",(0,t.jsx)(n.code,{children:"app-standalone.go"})," instance but this time with the opposite tags to use this file ",(0,t.jsx)(n.em,{children:"if"})," the build has\nthe appengine or appenginevm tags (added automatically when deploying to these platforms)."]}),"\n",(0,t.jsxs)(n.p,{children:["This allows us to replace the ",(0,t.jsx)(n.code,{children:"createMux()"})," function to create our Echo server ",(0,t.jsx)(n.em,{children:"without"})," any of the\nstatic file handling and logging + gzip middleware which is no longer required. Also worth nothing is\nthat GAE classic provides a wrapper to handle serving the app so instead of a ",(0,t.jsx)(n.code,{children:"main()"})," function where\nwe run the server, we instead wire up the router to the default ",(0,t.jsx)(n.code,{children:"http.Handler"})," instead."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-engine.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Managed VMs are slightly different. They are expected to respond to requests on port 8080 as well\nas special health-check requests used by the service to detect if an instance is still running in\norder to provide automated failover and instance replacement. The ",(0,t.jsx)(n.code,{children:"google.golang.org/appengine"}),"\npackage provides this for us so we have a slightly different version for Managed VMs:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-go",metastring:"reference",children:"https://github.com/labstack/echox/blob/master/cookbook/google-app-engine/app-managed.go\n"})}),"\n",(0,t.jsxs)(n.p,{children:["So now we have three different configurations. We can build and run our app as normal so it can\nbe executed locally, on a full Compute Engine instance or any other traditional hosting provider\n(including EC2, Docker etc...). This build will ignore the code in appengine and appenginevm tagged\nfiles and the ",(0,t.jsx)(n.code,{children:"app.yaml"})," file is meaningless to anything other than the AppEngine platform."]}),"\n",(0,t.jsxs)(n.p,{children:["We can also run locally using the ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/downloads",children:"Google AppEngine SDK for Go"}),"\neither emulating ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/go/tools/devserver",children:"AppEngine Classic"}),":"]}),"\n",(0,t.jsx)(n.p,{children:"goapp serve"}),"\n",(0,t.jsxs)(n.p,{children:["Or ",(0,t.jsx)(n.a,{href:"https://cloud.google.com/appengine/docs/managed-vms/sdk#run-local",children:"Managed VM(s)"}),":"]}),"\n",(0,t.jsx)(n.p,{children:"gcloud config set project [your project id]\ngcloud preview app run ."}),"\n",(0,t.jsx)(n.p,{children:"And of course we can deploy our app to both of these platforms for easy and inexpensive auto-scaling joy."}),"\n",(0,t.jsx)(n.p,{children:"Depending on what your app actually does it's possible you may need to make other changes to allow\nswitching between AppEngine provided service such as Datastore and alternative storage implementations\nsuch as MongoDB. A combination of go interfaces and build constraints can make this fairly straightforward\nbut is outside the scope of this example."})]})}function d(e={}){const{wrapper:n}={...(0,a.a)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},1151:(e,n,o)=>{o.d(n,{Z:()=>r,a:()=>s});var t=o(7294);const a={},i=t.createContext(a);function s(e){const n=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9dd0c5f1.8546fc97.js b/assets/js/9dd0c5f1.8546fc97.js deleted file mode 100644 index 15d78e7e..00000000 --- a/assets/js/9dd0c5f1.8546fc97.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[3474],{8946:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>a,metadata:()=>c,toc:()=>o});var r=t(5893),i=t(1151);const a={description:"Jaeager tracing middleware"},s="Jaeger",c={id:"middleware/jaeger",title:"Jaeger",description:"Jaeager tracing middleware",source:"@site/docs/middleware/jaeger.md",sourceDirName:"middleware",slug:"/middleware/jaeger",permalink:"/docs/middleware/jaeger",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/middleware/jaeger.md",tags:[],version:"current",frontMatter:{description:"Jaeager tracing middleware"},sidebar:"docsSidebar",previous:{title:"Gzip",permalink:"/docs/middleware/gzip"},next:{title:"JWT",permalink:"/docs/middleware/jwt"}},d={},o=[{value:"Usage",id:"usage",level:2},{value:"Custom Configuration",id:"custom-configuration",level:2},{value:"Usage",id:"usage-1",level:3},{value:"Skipping URL(s)",id:"skipping-urls",level:3},{value:"TraceFunction",id:"tracefunction",level:3},{value:"CreateChildSpan",id:"createchildspan",level:3},{value:"References",id:"references",level:2}];function l(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{id:"jaeger",children:"Jaeger"}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsx)(n.p,{children:"Echo community contribution"})}),"\n",(0,r.jsx)(n.p,{children:"Trace requests on Echo framework with Jaeger Tracing Middleware."}),"\n",(0,r.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Enabling the tracing middleware creates a tracer and a root tracing span for every request."}),"\n",(0,r.jsx)(n.h2,{id:"custom-configuration",children:"Custom Configuration"}),"\n",(0,r.jsxs)(n.p,{children:["By default, traces are sent to ",(0,r.jsx)(n.code,{children:"localhost"})," Jaeger agent instance. To configure an external Jaeger, start your application with environment variables."]}),"\n",(0,r.jsx)(n.h3,{id:"usage-1",children:"Usage"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"$ JAEGER_AGENT_HOST=192.168.1.10 JAEGER_AGENT_PORT=6831 ./myserver\n"})}),"\n",(0,r.jsx)(n.p,{children:"The tracer can be initialized with values coming from environment variables. None of the env vars are required\nand all of them can be overriden via direct setting of the property on the configuration object."}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Property"}),(0,r.jsx)(n.th,{children:"Description"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SERVICE_NAME"}),(0,r.jsx)(n.td,{children:"The service name"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_AGENT_HOST"}),(0,r.jsx)(n.td,{children:"The hostname for communicating with agent via UDP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_AGENT_PORT"}),(0,r.jsx)(n.td,{children:"The port for communicating with agent via UDP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_ENDPOINT"}),(0,r.jsxs)(n.td,{children:["The HTTP endpoint for sending spans directly to a collector, i.e. ",(0,r.jsx)(n.a,{href:"http://jaeger-collector:14268/api/traces",children:"http://jaeger-collector:14268/api/traces"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_USER"}),(0,r.jsx)(n.td,{children:'Username to send as part of "Basic" authentication to the collector endpoint'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_PASSWORD"}),(0,r.jsx)(n.td,{children:'Password to send as part of "Basic" authentication to the collector endpoint'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_LOG_SPANS"}),(0,r.jsx)(n.td,{children:"Whether the reporter should also log the spans"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_MAX_QUEUE_SIZE"}),(0,r.jsx)(n.td,{children:"The reporter's maximum queue size"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_FLUSH_INTERVAL"}),(0,r.jsx)(n.td,{children:'The reporter\'s flush interval, with units, e.g. "500ms" or "2s" ([valid units][timeunits])'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_TYPE"}),(0,r.jsx)(n.td,{children:"The sampler type"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_PARAM"}),(0,r.jsx)(n.td,{children:"The sampler parameter (number)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_MANAGER_HOST_PORT"}),(0,r.jsxs)(n.td,{children:["The HTTP endpoint when using the remote sampler, i.e. ",(0,r.jsx)(n.a,{href:"http://jaeger-agent:5778/sampling",children:"http://jaeger-agent:5778/sampling"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_MAX_OPERATIONS"}),(0,r.jsx)(n.td,{children:"The maximum number of operations that the sampler will keep track of"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_REFRESH_INTERVAL"}),(0,r.jsx)(n.td,{children:'How often the remotely controlled sampler will poll jaeger-agent for the appropriate sampling strategy, with units, e.g. "1m" or "30s" ([valid units][timeunits])'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_TAGS"}),(0,r.jsxs)(n.td,{children:["A comma separated list of ",(0,r.jsx)(n.code,{children:"name = value"})," tracer level tags, which get added to all reported spans. The value can also refer to an environment variable using the format ",(0,r.jsx)(n.code,{children:"${envVarName:default}"}),", where the ",(0,r.jsx)(n.code,{children:":default"})," is optional, and identifies a value to be used if the environment variable cannot be found"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_DISABLED"}),(0,r.jsxs)(n.td,{children:["Whether the tracer is disabled or not. If true, the default ",(0,r.jsx)(n.code,{children:"opentracing.NoopTracer"})," is used."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_RPC_METRICS"}),(0,r.jsx)(n.td,{children:"Whether to store RPC metrics"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["By default, the client sends traces via UDP to the agent at ",(0,r.jsx)(n.code,{children:"localhost:6831"}),". Use ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_HOST"})," and\n",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_PORT"})," to send UDP traces to a different ",(0,r.jsx)(n.code,{children:"host:port"}),". If ",(0,r.jsx)(n.code,{children:"JAEGER_ENDPOINT"})," is set, the client sends traces\nto the endpoint via ",(0,r.jsx)(n.code,{children:"HTTP"}),", making the ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_HOST"})," and ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_PORT"})," unused. If ",(0,r.jsx)(n.code,{children:"JAEGER_ENDPOINT"})," is\nsecured, HTTP basic authentication can be performed by setting the ",(0,r.jsx)(n.code,{children:"JAEGER_USER"})," and ",(0,r.jsx)(n.code,{children:"JAEGER_PASSWORD"})," environment\nvariables."]}),"\n",(0,r.jsx)(n.h3,{id:"skipping-urls",children:"Skipping URL(s)"}),"\n",(0,r.jsx)(n.p,{children:"A middleware skipper can be passed to avoid tracing spans to certain URL(s)."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n\t"strings"\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\n\n// urlSkipper ignores metrics route on some middleware\nfunc urlSkipper(c echo.Context) bool {\n if strings.HasPrefix(c.Path(), "/testurl") {\n return true\n }\n return false\n}\n\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, urlSkipper)\n defer c.Close()\n\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"tracefunction",children:"TraceFunction"}),"\n",(0,r.jsx)(n.p,{children:"This is a wrapper function that can be used to seamlessly add a span for\nthe duration of the invoked function. There is no need to change function arguments."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n "net/http"\n "time"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n e.GET("/", func(c echo.Context) error {\n // Wrap slowFunc on a new span to trace it\'s execution passing the function arguments\n\t\tjaegertracing.TraceFunction(c, slowFunc, "Test String")\n return c.String(http.StatusOK, "Hello, World!")\n })\n e.Logger.Fatal(e.Start(":1323"))\n}\n\n// A function to be wrapped. No need to change it\'s arguments due to tracing\nfunc slowFunc(s string) {\n\ttime.Sleep(200 * time.Millisecond)\n\treturn\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"createchildspan",children:"CreateChildSpan"}),"\n",(0,r.jsxs)(n.p,{children:["For more control over the Span, the function ",(0,r.jsx)(n.code,{children:"CreateChildSpan"})," can be called\ngiving control on data to be appended to the span like log messages, baggages and tags."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n e.GET("/", func(c echo.Context) error {\n // Do something before creating the child span\n time.Sleep(40 * time.Millisecond)\n sp := jaegertracing.CreateChildSpan(c, "Child span for additional processing")\n defer sp.Finish()\n sp.LogEvent("Test log")\n sp.SetBaggageItem("Test baggage", "baggage")\n sp.SetTag("Test tag", "New Tag")\n time.Sleep(100 * time.Millisecond)\n return c.String(http.StatusOK, "Hello, World!")\n })\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"references",children:"References"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["[",(0,r.jsx)(n.a,{href:"https://github.com/opentracing/opentracing-go%5D(Opentracing",children:"https://github.com/opentracing/opentracing-go](Opentracing"})," Library)"]}),"\n",(0,r.jsxs)(n.li,{children:["[",(0,r.jsx)(n.a,{href:"https://github.com/jaegertracing/jaeger-client-go#environment-variables%5D(Jaeger",children:"https://github.com/jaegertracing/jaeger-client-go#environment-variables](Jaeger"})," configuration)"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,i.a)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},1151:(e,n,t)=>{t.d(n,{Z:()=>c,a:()=>s});var r=t(7294);const i={},a=r.createContext(i);function s(e){const n=r.useContext(a);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),r.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9dd0c5f1.9d034f09.js b/assets/js/9dd0c5f1.9d034f09.js new file mode 100644 index 00000000..f9365f2c --- /dev/null +++ b/assets/js/9dd0c5f1.9d034f09.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[3474],{8946:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>a,metadata:()=>c,toc:()=>o});var r=t(5893),i=t(1151);const a={description:"Jaeager tracing middleware"},s="Jaeger",c={id:"middleware/jaeger",title:"Jaeger",description:"Jaeager tracing middleware",source:"@site/docs/middleware/jaeger.md",sourceDirName:"middleware",slug:"/middleware/jaeger",permalink:"/docs/middleware/jaeger",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/middleware/jaeger.md",tags:[],version:"current",frontMatter:{description:"Jaeager tracing middleware"},sidebar:"docsSidebar",previous:{title:"Gzip",permalink:"/docs/middleware/gzip"},next:{title:"JWT",permalink:"/docs/middleware/jwt"}},d={},o=[{value:"Usage",id:"usage",level:2},{value:"Custom Configuration",id:"custom-configuration",level:2},{value:"Usage",id:"usage-1",level:3},{value:"Skipping URL(s)",id:"skipping-urls",level:3},{value:"TraceFunction",id:"tracefunction",level:3},{value:"CreateChildSpan",id:"createchildspan",level:3},{value:"References",id:"references",level:2}];function l(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{id:"jaeger",children:"Jaeger"}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsx)(n.p,{children:"Echo community contribution"})}),"\n",(0,r.jsx)(n.p,{children:"Trace requests on Echo framework with Jaeger Tracing Middleware."}),"\n",(0,r.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Enabling the tracing middleware creates a tracer and a root tracing span for every request."}),"\n",(0,r.jsx)(n.h2,{id:"custom-configuration",children:"Custom Configuration"}),"\n",(0,r.jsxs)(n.p,{children:["By default, traces are sent to ",(0,r.jsx)(n.code,{children:"localhost"})," Jaeger agent instance. To configure an external Jaeger, start your application with environment variables."]}),"\n",(0,r.jsx)(n.h3,{id:"usage-1",children:"Usage"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"$ JAEGER_AGENT_HOST=192.168.1.10 JAEGER_AGENT_PORT=6831 ./myserver\n"})}),"\n",(0,r.jsx)(n.p,{children:"The tracer can be initialized with values coming from environment variables. None of the env vars are required\nand all of them can be overridden via direct setting of the property on the configuration object."}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Property"}),(0,r.jsx)(n.th,{children:"Description"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SERVICE_NAME"}),(0,r.jsx)(n.td,{children:"The service name"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_AGENT_HOST"}),(0,r.jsx)(n.td,{children:"The hostname for communicating with agent via UDP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_AGENT_PORT"}),(0,r.jsx)(n.td,{children:"The port for communicating with agent via UDP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_ENDPOINT"}),(0,r.jsxs)(n.td,{children:["The HTTP endpoint for sending spans directly to a collector, i.e. ",(0,r.jsx)(n.a,{href:"http://jaeger-collector:14268/api/traces",children:"http://jaeger-collector:14268/api/traces"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_USER"}),(0,r.jsx)(n.td,{children:'Username to send as part of "Basic" authentication to the collector endpoint'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_PASSWORD"}),(0,r.jsx)(n.td,{children:'Password to send as part of "Basic" authentication to the collector endpoint'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_LOG_SPANS"}),(0,r.jsx)(n.td,{children:"Whether the reporter should also log the spans"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_MAX_QUEUE_SIZE"}),(0,r.jsx)(n.td,{children:"The reporter's maximum queue size"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_REPORTER_FLUSH_INTERVAL"}),(0,r.jsx)(n.td,{children:'The reporter\'s flush interval, with units, e.g. "500ms" or "2s" ([valid units][timeunits])'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_TYPE"}),(0,r.jsx)(n.td,{children:"The sampler type"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_PARAM"}),(0,r.jsx)(n.td,{children:"The sampler parameter (number)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_MANAGER_HOST_PORT"}),(0,r.jsxs)(n.td,{children:["The HTTP endpoint when using the remote sampler, i.e. ",(0,r.jsx)(n.a,{href:"http://jaeger-agent:5778/sampling",children:"http://jaeger-agent:5778/sampling"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_MAX_OPERATIONS"}),(0,r.jsx)(n.td,{children:"The maximum number of operations that the sampler will keep track of"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_SAMPLER_REFRESH_INTERVAL"}),(0,r.jsx)(n.td,{children:'How often the remotely controlled sampler will poll jaeger-agent for the appropriate sampling strategy, with units, e.g. "1m" or "30s" ([valid units][timeunits])'})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_TAGS"}),(0,r.jsxs)(n.td,{children:["A comma separated list of ",(0,r.jsx)(n.code,{children:"name = value"})," tracer level tags, which get added to all reported spans. The value can also refer to an environment variable using the format ",(0,r.jsx)(n.code,{children:"${envVarName:default}"}),", where the ",(0,r.jsx)(n.code,{children:":default"})," is optional, and identifies a value to be used if the environment variable cannot be found"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_DISABLED"}),(0,r.jsxs)(n.td,{children:["Whether the tracer is disabled or not. If true, the default ",(0,r.jsx)(n.code,{children:"opentracing.NoopTracer"})," is used."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JAEGER_RPC_METRICS"}),(0,r.jsx)(n.td,{children:"Whether to store RPC metrics"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["By default, the client sends traces via UDP to the agent at ",(0,r.jsx)(n.code,{children:"localhost:6831"}),". Use ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_HOST"})," and\n",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_PORT"})," to send UDP traces to a different ",(0,r.jsx)(n.code,{children:"host:port"}),". If ",(0,r.jsx)(n.code,{children:"JAEGER_ENDPOINT"})," is set, the client sends traces\nto the endpoint via ",(0,r.jsx)(n.code,{children:"HTTP"}),", making the ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_HOST"})," and ",(0,r.jsx)(n.code,{children:"JAEGER_AGENT_PORT"})," unused. If ",(0,r.jsx)(n.code,{children:"JAEGER_ENDPOINT"})," is\nsecured, HTTP basic authentication can be performed by setting the ",(0,r.jsx)(n.code,{children:"JAEGER_USER"})," and ",(0,r.jsx)(n.code,{children:"JAEGER_PASSWORD"})," environment\nvariables."]}),"\n",(0,r.jsx)(n.h3,{id:"skipping-urls",children:"Skipping URL(s)"}),"\n",(0,r.jsx)(n.p,{children:"A middleware skipper can be passed to avoid tracing spans to certain URL(s)."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n\t"strings"\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\n\n// urlSkipper ignores metrics route on some middleware\nfunc urlSkipper(c echo.Context) bool {\n if strings.HasPrefix(c.Path(), "/testurl") {\n return true\n }\n return false\n}\n\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, urlSkipper)\n defer c.Close()\n\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"tracefunction",children:"TraceFunction"}),"\n",(0,r.jsx)(n.p,{children:"This is a wrapper function that can be used to seamlessly add a span for\nthe duration of the invoked function. There is no need to change function arguments."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n "net/http"\n "time"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n e.GET("/", func(c echo.Context) error {\n // Wrap slowFunc on a new span to trace it\'s execution passing the function arguments\n\t\tjaegertracing.TraceFunction(c, slowFunc, "Test String")\n return c.String(http.StatusOK, "Hello, World!")\n })\n e.Logger.Fatal(e.Start(":1323"))\n}\n\n// A function to be wrapped. No need to change it\'s arguments due to tracing\nfunc slowFunc(s string) {\n\ttime.Sleep(200 * time.Millisecond)\n\treturn\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"createchildspan",children:"CreateChildSpan"}),"\n",(0,r.jsxs)(n.p,{children:["For more control over the Span, the function ",(0,r.jsx)(n.code,{children:"CreateChildSpan"})," can be called\ngiving control on data to be appended to the span like log messages, baggages and tags."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Usage"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'package main\nimport (\n "github.com/labstack/echo-contrib/jaegertracing"\n "github.com/labstack/echo/v4"\n)\nfunc main() {\n e := echo.New()\n // Enable tracing middleware\n c := jaegertracing.New(e, nil)\n defer c.Close()\n e.GET("/", func(c echo.Context) error {\n // Do something before creating the child span\n time.Sleep(40 * time.Millisecond)\n sp := jaegertracing.CreateChildSpan(c, "Child span for additional processing")\n defer sp.Finish()\n sp.LogEvent("Test log")\n sp.SetBaggageItem("Test baggage", "baggage")\n sp.SetTag("Test tag", "New Tag")\n time.Sleep(100 * time.Millisecond)\n return c.String(http.StatusOK, "Hello, World!")\n })\n e.Logger.Fatal(e.Start(":1323"))\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"references",children:"References"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["[",(0,r.jsx)(n.a,{href:"https://github.com/opentracing/opentracing-go%5D(Opentracing",children:"https://github.com/opentracing/opentracing-go](Opentracing"})," Library)"]}),"\n",(0,r.jsxs)(n.li,{children:["[",(0,r.jsx)(n.a,{href:"https://github.com/jaegertracing/jaeger-client-go#environment-variables%5D(Jaeger",children:"https://github.com/jaegertracing/jaeger-client-go#environment-variables](Jaeger"})," configuration)"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,i.a)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},1151:(e,n,t)=>{t.d(n,{Z:()=>c,a:()=>s});var r=t(7294);const i={},a=r.createContext(i);function s(e){const n=r.useContext(a);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),r.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a51c7a0a.0abc0ae6.js b/assets/js/a51c7a0a.0abc0ae6.js deleted file mode 100644 index 9e87d6ca..00000000 --- a/assets/js/a51c7a0a.0abc0ae6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[5548],{4181:(e,s,r)=>{r.r(s),r.d(s,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>n,metadata:()=>a,toc:()=>d});var t=r(5893),o=r(1151);const n={description:"IP address",slug:"/ip-address",sidebar_position:8},i="IP Address",a={id:"guide/ip-address",title:"IP Address",description:"IP address",source:"@site/docs/guide/ip-address.md",sourceDirName:"guide",slug:"/ip-address",permalink:"/docs/ip-address",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/guide/ip-address.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{description:"IP address",slug:"/ip-address",sidebar_position:8},sidebar:"docsSidebar",previous:{title:"Start Server",permalink:"/docs/start-server"},next:{title:"Request",permalink:"/docs/request"}},c={},d=[{value:"Case 1. With no proxy",id:"case-1-with-no-proxy",level:2},{value:"Case 2. With proxies using X-Forwarded-For header",id:"case-2-with-proxies-using-x-forwarded-for-header",level:2},{value:"Case 3. With proxies using X-Real-IP header",id:"case-3-with-proxies-using-x-real-ip-header",level:2},{value:"About default behavior",id:"about-default-behavior",level:2}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.h1,{id:"ip-address",children:"IP Address"}),"\n",(0,t.jsxs)(s.p,{children:["IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.\nEcho provides handy method ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#Context",children:(0,t.jsx)(s.code,{children:"Context#RealIP()"})})," for that."]}),"\n",(0,t.jsxs)(s.p,{children:["However, it is not trivial to retrieve the ",(0,t.jsx)(s.em,{children:"real"})," IP address from requests especially when you put L7 proxies before the application.\nIn such situation, ",(0,t.jsx)(s.em,{children:"real"})," IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.\nOtherwise you might give someone a chance of deceiving you. ",(0,t.jsx)(s.strong,{children:"A security risk!"})]}),"\n",(0,t.jsxs)(s.p,{children:["To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructrure.\nIn Echo, this can be done by configuring ",(0,t.jsx)(s.code,{children:"Echo#IPExtractor"})," appropriately.\nThis guides show you why and how."]}),"\n",(0,t.jsx)(s.admonition,{type:"caution",children:(0,t.jsxs)(s.p,{children:["Note: if you don't set ",(0,t.jsx)(s.code,{children:"Echo#IPExtractor"})," explicitly, Echo fallback to legacy behavior, which is not a good choice."]})}),"\n",(0,t.jsx)(s.p,{children:"Let's start from two questions to know the right direction:"}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Do you put any HTTP (L7) proxy in front of the application?","\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway)."}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(s.li,{children:"If yes, what HTTP header do your proxies use to pass client IP to the application?"}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"case-1-with-no-proxy",children:"Case 1. With no proxy"}),"\n",(0,t.jsx)(s.p,{children:"If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.\nAny HTTP header is untrustable because the clients have full control what headers to be set."}),"\n",(0,t.jsxs)(s.p,{children:["In this case, use ",(0,t.jsx)(s.code,{children:"echo.ExtractIPDirect()"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPDirect()\n"})}),"\n",(0,t.jsx)(s.h2,{id:"case-2-with-proxies-using-x-forwarded-for-header",children:"Case 2. With proxies using X-Forwarded-For header"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsxs)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For",children:[(0,t.jsx)(s.code,{children:"X-Forwared-For"})," (XFF)"]})," is the popular header to relay clients' IP addresses.\nAt each hop on the proxies, they append the request IP address at the end of the header."]}),"\n",(0,t.jsx)(s.p,{children:"Following example diagram illustrates this behavior."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-text",children:' \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Proxy 1 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Proxy 2 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Your app \u2502\n \u2502 (IP: b) \u2502 \u2502 (IP: c) \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nCase 1.\nXFF: "" "a" "a, b"\n ~~~~~~\nCase 2.\nXFF: "x" "x, a" "x, a, b"\n ~~~~~~~~~\n \u2191 What your app will see\n'})}),"\n",(0,t.jsxs)(s.p,{children:["In this case, use ",(0,t.jsxs)(s.strong,{children:["first ",(0,t.jsx)(s.em,{children:"untrustable"})," IP reading from right"]}),'. Never use first one reading from left, as it is configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure". In above example, if ',(0,t.jsx)(s.code,{children:"b"})," and ",(0,t.jsx)(s.code,{children:"c"})," are trustable, the IP address of the client is ",(0,t.jsx)(s.code,{children:"a"})," for both cases, never be ",(0,t.jsx)(s.code,{children:"x"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["In Echo, use ",(0,t.jsx)(s.code,{children:"ExtractIPFromXFFHeader(...TrustOption)"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromXFFHeader()\n"})}),"\n",(0,t.jsxs)(s.p,{children:["By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address from ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc6890",children:"RFC6890"}),", ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4291",children:"RFC4291"})," and ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4193",children:"RFC4193"}),"). To control this behavior, use ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:(0,t.jsx)(s.code,{children:"TrustOption"})}),"s."]}),"\n",(0,t.jsx)(s.p,{children:"E.g.:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromXFFHeader(\n echo.TrustLoopback(false), // e.g. ipv4 start with 127. \n echo.TrustLinkLocal(false), // e.g. ipv4 start with 169.254\n echo.TrustPrivateNet(false), // e.g. ipv4 start with 10. or 192.168\n echo.TrustIPRange(lbIPRange),\n)\n"})}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Ref: ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:"https://godoc.org/github.com/labstack/echo#TrustOption"})]}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"case-3-with-proxies-using-x-real-ip-header",children:"Case 3. With proxies using X-Real-IP header"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.code,{children:"X-Real-IP"})," is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF."]}),"\n",(0,t.jsxs)(s.p,{children:["If your proxies set this header, use ",(0,t.jsx)(s.code,{children:"ExtractIPFromRealIPHeader(...TrustOption)"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromRealIPHeader()\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address from ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc6890",children:"RFC6890"}),", ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4291",children:"RFC4291"})," and ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4193",children:"RFC4193"}),"). To control this behavior, use ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:(0,t.jsx)(s.code,{children:"TrustOption"})}),"s."]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Ref: ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:"https://godoc.org/github.com/labstack/echo#TrustOption"})]}),"\n"]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Never forget"})," to configure the outermost proxy (i.e.; at the edge of your infrastructure) ",(0,t.jsx)(s.strong,{children:"not to pass through incoming headers"}),".\nOtherwise there is a chance of fraud, as it is what clients can control."]}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"about-default-behavior",children:"About default behavior"}),"\n",(0,t.jsx)(s.p,{children:"In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer."}),"\n",(0,t.jsx)(s.p,{children:"As you might already notice, after reading this article, this is not good.\nSole reason this is default is just backward compatibility."})]})}function h(e={}){const{wrapper:s}={...(0,o.a)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(l,{...e})}):l(e)}},1151:(e,s,r)=>{r.d(s,{Z:()=>a,a:()=>i});var t=r(7294);const o={},n=t.createContext(o);function i(e){const s=t.useContext(n);return t.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(n.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a51c7a0a.32b29fae.js b/assets/js/a51c7a0a.32b29fae.js new file mode 100644 index 00000000..5e38f3dd --- /dev/null +++ b/assets/js/a51c7a0a.32b29fae.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[5548],{4181:(e,s,r)=>{r.r(s),r.d(s,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>n,metadata:()=>a,toc:()=>d});var t=r(5893),o=r(1151);const n={description:"IP address",slug:"/ip-address",sidebar_position:8},i="IP Address",a={id:"guide/ip-address",title:"IP Address",description:"IP address",source:"@site/docs/guide/ip-address.md",sourceDirName:"guide",slug:"/ip-address",permalink:"/docs/ip-address",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/guide/ip-address.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{description:"IP address",slug:"/ip-address",sidebar_position:8},sidebar:"docsSidebar",previous:{title:"Start Server",permalink:"/docs/start-server"},next:{title:"Request",permalink:"/docs/request"}},c={},d=[{value:"Case 1. With no proxy",id:"case-1-with-no-proxy",level:2},{value:"Case 2. With proxies using X-Forwarded-For header",id:"case-2-with-proxies-using-x-forwarded-for-header",level:2},{value:"Case 3. With proxies using X-Real-IP header",id:"case-3-with-proxies-using-x-real-ip-header",level:2},{value:"About default behavior",id:"about-default-behavior",level:2}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.h1,{id:"ip-address",children:"IP Address"}),"\n",(0,t.jsxs)(s.p,{children:["IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.\nEcho provides handy method ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#Context",children:(0,t.jsx)(s.code,{children:"Context#RealIP()"})})," for that."]}),"\n",(0,t.jsxs)(s.p,{children:["However, it is not trivial to retrieve the ",(0,t.jsx)(s.em,{children:"real"})," IP address from requests especially when you put L7 proxies before the application.\nIn such situation, ",(0,t.jsx)(s.em,{children:"real"})," IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.\nOtherwise you might give someone a chance of deceiving you. ",(0,t.jsx)(s.strong,{children:"A security risk!"})]}),"\n",(0,t.jsxs)(s.p,{children:["To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructrure.\nIn Echo, this can be done by configuring ",(0,t.jsx)(s.code,{children:"Echo#IPExtractor"})," appropriately.\nThis guides show you why and how."]}),"\n",(0,t.jsx)(s.admonition,{type:"caution",children:(0,t.jsxs)(s.p,{children:["Note: if you don't set ",(0,t.jsx)(s.code,{children:"Echo#IPExtractor"})," explicitly, Echo fallback to legacy behavior, which is not a good choice."]})}),"\n",(0,t.jsx)(s.p,{children:"Let's start from two questions to know the right direction:"}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Do you put any HTTP (L7) proxy in front of the application?","\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway)."}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(s.li,{children:"If yes, what HTTP header do your proxies use to pass client IP to the application?"}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"case-1-with-no-proxy",children:"Case 1. With no proxy"}),"\n",(0,t.jsx)(s.p,{children:"If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.\nAny HTTP header is untrustable because the clients have full control what headers to be set."}),"\n",(0,t.jsxs)(s.p,{children:["In this case, use ",(0,t.jsx)(s.code,{children:"echo.ExtractIPDirect()"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPDirect()\n"})}),"\n",(0,t.jsx)(s.h2,{id:"case-2-with-proxies-using-x-forwarded-for-header",children:"Case 2. With proxies using X-Forwarded-For header"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsxs)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For",children:[(0,t.jsx)(s.code,{children:"X-Forwarded-For"})," (XFF)"]})," is the popular header to relay clients' IP addresses.\nAt each hop on the proxies, they append the request IP address at the end of the header."]}),"\n",(0,t.jsx)(s.p,{children:"Following example diagram illustrates this behavior."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-text",children:' \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Proxy 1 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Proxy 2 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502 Your app \u2502\n \u2502 (IP: b) \u2502 \u2502 (IP: c) \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nCase 1.\nXFF: "" "a" "a, b"\n ~~~~~~\nCase 2.\nXFF: "x" "x, a" "x, a, b"\n ~~~~~~~~~\n \u2191 What your app will see\n'})}),"\n",(0,t.jsxs)(s.p,{children:["In this case, use ",(0,t.jsxs)(s.strong,{children:["first ",(0,t.jsx)(s.em,{children:"untrustable"})," IP reading from right"]}),'. Never use first one reading from left, as it is configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure". In above example, if ',(0,t.jsx)(s.code,{children:"b"})," and ",(0,t.jsx)(s.code,{children:"c"})," are trustable, the IP address of the client is ",(0,t.jsx)(s.code,{children:"a"})," for both cases, never be ",(0,t.jsx)(s.code,{children:"x"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["In Echo, use ",(0,t.jsx)(s.code,{children:"ExtractIPFromXFFHeader(...TrustOption)"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromXFFHeader()\n"})}),"\n",(0,t.jsxs)(s.p,{children:["By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address from ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc6890",children:"RFC6890"}),", ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4291",children:"RFC4291"})," and ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4193",children:"RFC4193"}),"). To control this behavior, use ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:(0,t.jsx)(s.code,{children:"TrustOption"})}),"s."]}),"\n",(0,t.jsx)(s.p,{children:"E.g.:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromXFFHeader(\n echo.TrustLoopback(false), // e.g. ipv4 start with 127. \n echo.TrustLinkLocal(false), // e.g. ipv4 start with 169.254\n echo.TrustPrivateNet(false), // e.g. ipv4 start with 10. or 192.168\n echo.TrustIPRange(lbIPRange),\n)\n"})}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Ref: ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:"https://godoc.org/github.com/labstack/echo#TrustOption"})]}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"case-3-with-proxies-using-x-real-ip-header",children:"Case 3. With proxies using X-Real-IP header"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.code,{children:"X-Real-IP"})," is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF."]}),"\n",(0,t.jsxs)(s.p,{children:["If your proxies set this header, use ",(0,t.jsx)(s.code,{children:"ExtractIPFromRealIPHeader(...TrustOption)"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-go",children:"e.IPExtractor = echo.ExtractIPFromRealIPHeader()\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address from ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc6890",children:"RFC6890"}),", ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4291",children:"RFC4291"})," and ",(0,t.jsx)(s.a,{href:"https://tools.ietf.org/html/rfc4193",children:"RFC4193"}),"). To control this behavior, use ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:(0,t.jsx)(s.code,{children:"TrustOption"})}),"s."]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Ref: ",(0,t.jsx)(s.a,{href:"https://godoc.org/github.com/labstack/echo#TrustOption",children:"https://godoc.org/github.com/labstack/echo#TrustOption"})]}),"\n"]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Never forget"})," to configure the outermost proxy (i.e.; at the edge of your infrastructure) ",(0,t.jsx)(s.strong,{children:"not to pass through incoming headers"}),".\nOtherwise there is a chance of fraud, as it is what clients can control."]}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"about-default-behavior",children:"About default behavior"}),"\n",(0,t.jsx)(s.p,{children:"In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer."}),"\n",(0,t.jsx)(s.p,{children:"As you might already notice, after reading this article, this is not good.\nSole reason this is default is just backward compatibility."})]})}function h(e={}){const{wrapper:s}={...(0,o.a)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(l,{...e})}):l(e)}},1151:(e,s,r)=>{r.d(s,{Z:()=>a,a:()=>i});var t=r(7294);const o={},n=t.createContext(o);function i(e){const s=t.useContext(n);return t.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(n.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/dfc29094.4bdbe0c6.js b/assets/js/dfc29094.4bdbe0c6.js deleted file mode 100644 index 72dc3fa7..00000000 --- a/assets/js/dfc29094.4bdbe0c6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2411],{3349:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>c,toc:()=>d});var s=r(5893),t=r(1151);const i={description:"Routing requests",slug:"/routing",sidebar_position:10},o="Routing",c={id:"guide/routing",title:"Routing",description:"Routing requests",source:"@site/docs/guide/routing.md",sourceDirName:"guide",slug:"/routing",permalink:"/docs/routing",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/guide/routing.md",tags:[],version:"current",sidebarPosition:10,frontMatter:{description:"Routing requests",slug:"/routing",sidebar_position:10},sidebar:"docsSidebar",previous:{title:"Response",permalink:"/docs/response"},next:{title:"Static Files",permalink:"/docs/static-files"}},a={},d=[{value:"Match-any / wildcard",id:"match-any--wildcard",level:2},{value:"Path Matching Order",id:"path-matching-order",level:2},{value:"Group",id:"group",level:2},{value:"Route Naming",id:"route-naming",level:2},{value:"URI Building",id:"uri-building",level:2},{value:"List Routes",id:"list-routes",level:2}];function l(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"routing",children:"Routing"}),"\n",(0,s.jsxs)(n.p,{children:["Echo's router is based on ",(0,s.jsx)(n.a,{href:"http://en.wikipedia.org/wiki/Radix_tree",children:"radix tree"}),", making\nroute lookup really fast. It leverages ",(0,s.jsx)(n.a,{href:"https://golang.org/pkg/sync/#Pool",children:"sync pool"}),"\nto reuse memory and achieve zero dynamic memory allocation with no GC overhead."]}),"\n",(0,s.jsxs)(n.p,{children:["Routes can be registered by specifying HTTP method, path and a matching handler.\nFor example, code below registers a route for method ",(0,s.jsx)(n.code,{children:"GET"}),", path ",(0,s.jsx)(n.code,{children:"/hello"})," and a\nhandler which sends ",(0,s.jsx)(n.code,{children:"Hello, World!"})," HTTP response."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nfunc hello(c echo.Context) error {\n \treturn c.String(http.StatusOK, "Hello, World!")\n}\n\n// Route\ne.GET("/hello", hello)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["You can use ",(0,s.jsx)(n.code,{children:"Echo.Any(path string, h Handler)"})," to register a handler for all HTTP methods.\nIf you want to register it for some methods use ",(0,s.jsx)(n.code,{children:"Echo.Match(methods []string, path string, h Handler)"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Echo defines handler function as ",(0,s.jsx)(n.code,{children:"func(echo.Context) error"})," where ",(0,s.jsx)(n.code,{children:"echo.Context"})," primarily\nholds HTTP request and response interfaces."]}),"\n",(0,s.jsx)(n.h2,{id:"match-any--wildcard",children:"Match-any / wildcard"}),"\n",(0,s.jsxs)(n.p,{children:["Matches zero or more characters in the path. For example, pattern ",(0,s.jsx)(n.code,{children:"/users/*"})," will\nmatch:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1/files/1"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/anything..."})}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"caution",children:(0,s.jsxs)(n.p,{children:["There can be only one effective match-any parameter in route. When route is added with multiple match-any\n",(0,s.jsx)(n.code,{children:"/v1/*/images/*"}),". The router matches always the first ",(0,s.jsx)(n.code,{children:"*"})," till the end of requst URL i.e. it works as ",(0,s.jsx)(n.code,{children:"/v1/*"}),"."]})}),"\n",(0,s.jsx)(n.h2,{id:"path-matching-order",children:"Path Matching Order"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Static"}),"\n",(0,s.jsx)(n.li,{children:"Param"}),"\n",(0,s.jsx)(n.li,{children:"Match any"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Example"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'e.GET("/users/:id", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/:id")\n})\n\ne.GET("/users/new", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/new")\n})\n\ne.GET("/users/1/files/*", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/1/files/*")\n})\n'})}),"\n",(0,s.jsx)(n.p,{children:"Above routes would resolve in the following order:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/new"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/:id"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1/files/*"})}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsx)(n.p,{children:"Routes can be written in any order."})}),"\n",(0,s.jsx)(n.h2,{id:"group",children:"Group"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"Echo#Group(prefix string, m ...Middleware) *Group"})}),"\n",(0,s.jsxs)(n.p,{children:["Routes with common prefix can be grouped to define a new sub-router with optional\nmiddleware. In addition to specified middleware group also inherits parent middleware.\nTo add middleware later in the group you can use ",(0,s.jsx)(n.code,{children:"Group.Use(m ...Middleware)"}),".\nGroups can also be nested."]}),"\n",(0,s.jsxs)(n.p,{children:["In the code below, we create an admin group which requires basic HTTP authentication\nfor routes ",(0,s.jsx)(n.code,{children:"/admin/*"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'g := e.Group("/admin")\ng.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {\n\tif username == "joe" && password == "secret" {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}))\n'})}),"\n",(0,s.jsx)(n.h2,{id:"route-naming",children:"Route Naming"}),"\n",(0,s.jsxs)(n.p,{children:["Each of the registration methods returns a ",(0,s.jsx)(n.code,{children:"Route"})," object, which can be used to name a route after the registration. For example:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'route := e.POST("/users", func(c echo.Context) error {\n})\nroute.Name = "create-user"\n\n// or using the inline syntax\ne.GET("/users/:id", func(c echo.Context) error {\n}).Name = "get-user"\n'})}),"\n",(0,s.jsx)(n.p,{children:"Route names can be very useful when generating URIs from the templates, where you can't access the handler references or when you have multiple routes with the same handler."}),"\n",(0,s.jsx)(n.h2,{id:"uri-building",children:"URI Building"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Echo#URI(handler HandlerFunc, params ...interface{})"})," can be used to generate URI for any handler with specified path parameters. It's helpful to centralize all your\nURI patterns which ease in refactoring your application."]}),"\n",(0,s.jsxs)(n.p,{children:["For example, ",(0,s.jsx)(n.code,{children:"e.URI(h, 1)"})," will generate ",(0,s.jsx)(n.code,{children:"/users/1"})," for the route registered below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nh := func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "OK")\n}\n\n// Route\ne.GET("/users/:id", h)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["In addition to ",(0,s.jsx)(n.code,{children:"Echo#URI"}),", there is also ",(0,s.jsx)(n.code,{children:"Echo#Reverse(name string, params ...interface{})"})," which is used to generate URIs based on the route name. For example a call to ",(0,s.jsx)(n.code,{children:'Echo#Reverse("foobar", 1234)'})," would generate the URI ",(0,s.jsx)(n.code,{children:"/users/1234"})," if the ",(0,s.jsx)(n.code,{children:"foobar"})," route is registered like below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nh := func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "OK")\n}\n\n// Route\ne.GET("/users/:id", h).Name = "foobar"\n'})}),"\n",(0,s.jsx)(n.h2,{id:"list-routes",children:"List Routes"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Echo#Routes() []*Route"})," can be used to list all registered routes in the order\nthey are defined. Each route contains HTTP method, path and an associated handler."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Example"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handlers\nfunc createUser(c echo.Context) error {\n}\n\nfunc findUser(c echo.Context) error {\n}\n\nfunc updateUser(c echo.Context) error {\n}\n\nfunc deleteUser(c echo.Context) error {\n}\n\n// Routes\ne.POST("/users", createUser)\ne.GET("/users", findUser)\ne.PUT("/users", updateUser)\ne.DELETE("/users", deleteUser)\n'})}),"\n",(0,s.jsx)(n.p,{children:"Using the following code you can output all the routes to a JSON file:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'data, err := json.MarshalIndent(e.Routes(), "", " ")\nif err != nil {\n\treturn err\n}\nos.WriteFile("routes.json", data, 0644)\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"routes.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:'[\n {\n "method": "POST",\n "path": "/users",\n "name": "main.createUser"\n },\n {\n "method": "GET",\n "path": "/users",\n "name": "main.findUser"\n },\n {\n "method": "PUT",\n "path": "/users",\n "name": "main.updateUser"\n },\n {\n "method": "DELETE",\n "path": "/users",\n "name": "main.deleteUser"\n }\n]\n'})})]})}function h(e={}){const{wrapper:n}={...(0,t.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},1151:(e,n,r)=>{r.d(n,{Z:()=>c,a:()=>o});var s=r(7294);const t={},i=s.createContext(t);function o(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:o(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/dfc29094.7cce0f14.js b/assets/js/dfc29094.7cce0f14.js new file mode 100644 index 00000000..ba615319 --- /dev/null +++ b/assets/js/dfc29094.7cce0f14.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2411],{3349:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>c,toc:()=>d});var s=r(5893),t=r(1151);const i={description:"Routing requests",slug:"/routing",sidebar_position:10},o="Routing",c={id:"guide/routing",title:"Routing",description:"Routing requests",source:"@site/docs/guide/routing.md",sourceDirName:"guide",slug:"/routing",permalink:"/docs/routing",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/guide/routing.md",tags:[],version:"current",sidebarPosition:10,frontMatter:{description:"Routing requests",slug:"/routing",sidebar_position:10},sidebar:"docsSidebar",previous:{title:"Response",permalink:"/docs/response"},next:{title:"Static Files",permalink:"/docs/static-files"}},a={},d=[{value:"Match-any / wildcard",id:"match-any--wildcard",level:2},{value:"Path Matching Order",id:"path-matching-order",level:2},{value:"Group",id:"group",level:2},{value:"Route Naming",id:"route-naming",level:2},{value:"URI Building",id:"uri-building",level:2},{value:"List Routes",id:"list-routes",level:2}];function l(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"routing",children:"Routing"}),"\n",(0,s.jsxs)(n.p,{children:["Echo's router is based on ",(0,s.jsx)(n.a,{href:"http://en.wikipedia.org/wiki/Radix_tree",children:"radix tree"}),", making\nroute lookup really fast. It leverages ",(0,s.jsx)(n.a,{href:"https://golang.org/pkg/sync/#Pool",children:"sync pool"}),"\nto reuse memory and achieve zero dynamic memory allocation with no GC overhead."]}),"\n",(0,s.jsxs)(n.p,{children:["Routes can be registered by specifying HTTP method, path and a matching handler.\nFor example, code below registers a route for method ",(0,s.jsx)(n.code,{children:"GET"}),", path ",(0,s.jsx)(n.code,{children:"/hello"})," and a\nhandler which sends ",(0,s.jsx)(n.code,{children:"Hello, World!"})," HTTP response."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nfunc hello(c echo.Context) error {\n \treturn c.String(http.StatusOK, "Hello, World!")\n}\n\n// Route\ne.GET("/hello", hello)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["You can use ",(0,s.jsx)(n.code,{children:"Echo.Any(path string, h Handler)"})," to register a handler for all HTTP methods.\nIf you want to register it for some methods use ",(0,s.jsx)(n.code,{children:"Echo.Match(methods []string, path string, h Handler)"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Echo defines handler function as ",(0,s.jsx)(n.code,{children:"func(echo.Context) error"})," where ",(0,s.jsx)(n.code,{children:"echo.Context"})," primarily\nholds HTTP request and response interfaces."]}),"\n",(0,s.jsx)(n.h2,{id:"match-any--wildcard",children:"Match-any / wildcard"}),"\n",(0,s.jsxs)(n.p,{children:["Matches zero or more characters in the path. For example, pattern ",(0,s.jsx)(n.code,{children:"/users/*"})," will\nmatch:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1/files/1"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/anything..."})}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"caution",children:(0,s.jsxs)(n.p,{children:["There can be only one effective match-any parameter in route. When route is added with multiple match-any\n",(0,s.jsx)(n.code,{children:"/v1/*/images/*"}),". The router matches always the first ",(0,s.jsx)(n.code,{children:"*"})," till the end of request URL i.e. it works as ",(0,s.jsx)(n.code,{children:"/v1/*"}),"."]})}),"\n",(0,s.jsx)(n.h2,{id:"path-matching-order",children:"Path Matching Order"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Static"}),"\n",(0,s.jsx)(n.li,{children:"Param"}),"\n",(0,s.jsx)(n.li,{children:"Match any"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Example"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'e.GET("/users/:id", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/:id")\n})\n\ne.GET("/users/new", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/new")\n})\n\ne.GET("/users/1/files/*", func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "/users/1/files/*")\n})\n'})}),"\n",(0,s.jsx)(n.p,{children:"Above routes would resolve in the following order:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/new"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/:id"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"/users/1/files/*"})}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsx)(n.p,{children:"Routes can be written in any order."})}),"\n",(0,s.jsx)(n.h2,{id:"group",children:"Group"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"Echo#Group(prefix string, m ...Middleware) *Group"})}),"\n",(0,s.jsxs)(n.p,{children:["Routes with common prefix can be grouped to define a new sub-router with optional\nmiddleware. In addition to specified middleware group also inherits parent middleware.\nTo add middleware later in the group you can use ",(0,s.jsx)(n.code,{children:"Group.Use(m ...Middleware)"}),".\nGroups can also be nested."]}),"\n",(0,s.jsxs)(n.p,{children:["In the code below, we create an admin group which requires basic HTTP authentication\nfor routes ",(0,s.jsx)(n.code,{children:"/admin/*"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'g := e.Group("/admin")\ng.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {\n\tif username == "joe" && password == "secret" {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}))\n'})}),"\n",(0,s.jsx)(n.h2,{id:"route-naming",children:"Route Naming"}),"\n",(0,s.jsxs)(n.p,{children:["Each of the registration methods returns a ",(0,s.jsx)(n.code,{children:"Route"})," object, which can be used to name a route after the registration. For example:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'route := e.POST("/users", func(c echo.Context) error {\n})\nroute.Name = "create-user"\n\n// or using the inline syntax\ne.GET("/users/:id", func(c echo.Context) error {\n}).Name = "get-user"\n'})}),"\n",(0,s.jsx)(n.p,{children:"Route names can be very useful when generating URIs from the templates, where you can't access the handler references or when you have multiple routes with the same handler."}),"\n",(0,s.jsx)(n.h2,{id:"uri-building",children:"URI Building"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Echo#URI(handler HandlerFunc, params ...interface{})"})," can be used to generate URI for any handler with specified path parameters. It's helpful to centralize all your\nURI patterns which ease in refactoring your application."]}),"\n",(0,s.jsxs)(n.p,{children:["For example, ",(0,s.jsx)(n.code,{children:"e.URI(h, 1)"})," will generate ",(0,s.jsx)(n.code,{children:"/users/1"})," for the route registered below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nh := func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "OK")\n}\n\n// Route\ne.GET("/users/:id", h)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["In addition to ",(0,s.jsx)(n.code,{children:"Echo#URI"}),", there is also ",(0,s.jsx)(n.code,{children:"Echo#Reverse(name string, params ...interface{})"})," which is used to generate URIs based on the route name. For example a call to ",(0,s.jsx)(n.code,{children:'Echo#Reverse("foobar", 1234)'})," would generate the URI ",(0,s.jsx)(n.code,{children:"/users/1234"})," if the ",(0,s.jsx)(n.code,{children:"foobar"})," route is registered like below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handler\nh := func(c echo.Context) error {\n\treturn c.String(http.StatusOK, "OK")\n}\n\n// Route\ne.GET("/users/:id", h).Name = "foobar"\n'})}),"\n",(0,s.jsx)(n.h2,{id:"list-routes",children:"List Routes"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Echo#Routes() []*Route"})," can be used to list all registered routes in the order\nthey are defined. Each route contains HTTP method, path and an associated handler."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Example"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'// Handlers\nfunc createUser(c echo.Context) error {\n}\n\nfunc findUser(c echo.Context) error {\n}\n\nfunc updateUser(c echo.Context) error {\n}\n\nfunc deleteUser(c echo.Context) error {\n}\n\n// Routes\ne.POST("/users", createUser)\ne.GET("/users", findUser)\ne.PUT("/users", updateUser)\ne.DELETE("/users", deleteUser)\n'})}),"\n",(0,s.jsx)(n.p,{children:"Using the following code you can output all the routes to a JSON file:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'data, err := json.MarshalIndent(e.Routes(), "", " ")\nif err != nil {\n\treturn err\n}\nos.WriteFile("routes.json", data, 0644)\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"routes.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:'[\n {\n "method": "POST",\n "path": "/users",\n "name": "main.createUser"\n },\n {\n "method": "GET",\n "path": "/users",\n "name": "main.findUser"\n },\n {\n "method": "PUT",\n "path": "/users",\n "name": "main.updateUser"\n },\n {\n "method": "DELETE",\n "path": "/users",\n "name": "main.deleteUser"\n }\n]\n'})})]})}function h(e={}){const{wrapper:n}={...(0,t.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},1151:(e,n,r)=>{r.d(n,{Z:()=>c,a:()=>o});var s=r(7294);const t={},i=s.createContext(t);function o(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:o(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ea770076.c8743568.js b/assets/js/ea770076.8ef8137b.js similarity index 94% rename from assets/js/ea770076.c8743568.js rename to assets/js/ea770076.8ef8137b.js index 010cbaca..670d6164 100644 --- a/assets/js/ea770076.c8743568.js +++ b/assets/js/ea770076.8ef8137b.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[8678],{8778:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>c});var i=n(5893),s=n(1151);const a={description:"Static middleware"},o="Static",r={id:"middleware/static",title:"Static",description:"Static middleware",source:"@site/docs/middleware/static.md",sourceDirName:"middleware",slug:"/middleware/static",permalink:"/docs/middleware/static",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/middleware/static.md",tags:[],version:"current",frontMatter:{description:"Static middleware"},sidebar:"docsSidebar",previous:{title:"Session",permalink:"/docs/middleware/session"},next:{title:"Timeout",permalink:"/docs/middleware/timeout"}},l={},c=[{value:"Usage",id:"usage",level:2},{value:"Custom Configuration",id:"custom-configuration",level:2},{value:"Usage",id:"usage-1",level:3},{value:"Example 1",id:"example-1",level:4},{value:"Example 2",id:"example-2",level:4},{value:"Configuration",id:"configuration",level:2},{value:"Default Configuration",id:"default-configuration",level:3}];function d(e){const t={admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",...(0,s.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h1,{id:"static",children:"Static"}),"\n",(0,i.jsx)(t.p,{children:"Static middleware can be used to serve static files from the provided root directory."}),"\n",(0,i.jsx)(t.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'e := echo.New()\ne.Use(middleware.Static("/static"))\n'})}),"\n",(0,i.jsxs)(t.p,{children:["This serves static files from ",(0,i.jsx)(t.code,{children:"static"})," directory. For example, a request to ",(0,i.jsx)(t.code,{children:"/js/main.js"}),"\nwill fetch and serve ",(0,i.jsx)(t.code,{children:"static/js/main.js"})," file."]}),"\n",(0,i.jsx)(t.h2,{id:"custom-configuration",children:"Custom Configuration"}),"\n",(0,i.jsx)(t.h3,{id:"usage-1",children:"Usage"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'e := echo.New()\ne.Use(middleware.StaticWithConfig(middleware.StaticConfig{\n Root: "static",\n Browse: true,\n}))\n'})}),"\n",(0,i.jsxs)(t.p,{children:["This serves static files from ",(0,i.jsx)(t.code,{children:"static"})," directory and enables directory browsing."]}),"\n",(0,i.jsx)(t.p,{children:"Default behavior when using with non root URL paths is to append the URL path to the filesystem path."}),"\n",(0,i.jsx)(t.h4,{id:"example-1",children:"Example 1"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'group := root.Group("somepath")\ngroup.Use(middleware.Static(filepath.Join("filesystempath")))\n// When an incoming request comes for `/somepath` the actual filesystem request goes to `filesystempath/somepath` instead of only `filesystempath`. \n'})}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsxs)(t.p,{children:["To turn off this behavior set the ",(0,i.jsx)(t.code,{children:"IgnoreBase"})," config param to ",(0,i.jsx)(t.code,{children:"true"}),"."]})}),"\n",(0,i.jsx)(t.h4,{id:"example-2",children:"Example 2"}),"\n",(0,i.jsx)(t.p,{children:"Serve SPA assets from embbeded filesystem"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'//go:embed web\nvar webAssets embed.FS\n\nfunc main() {\n\te := echo.New()\n\n\te.Use(middleware.StaticWithConfig(middleware.StaticConfig{\n\t\tHTML5: true,\n\t\tRoot: "web", // because files are located in `web` directory in `webAssets` fs\n\t\tFilesystem: http.FS(webAssets),\n\t}))\n\tapi := e.Group("/api")\n\tapi.GET("/users", func(c echo.Context) error {\n\t\treturn c.String(http.StatusOK, "users")\n\t})\n\n\tif err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\tlog.Fatal(err)\n\t}\n}\n'})}),"\n",(0,i.jsx)(t.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'StaticConfig struct {\n // Skipper defines a function to skip middleware.\n Skipper Skipper\n\n // Root directory from where the static content is served.\n // Required.\n Root string `json:"root"`\n\n // Index file for serving a directory.\n // Optional. Default value "index.html".\n Index string `json:"index"`\n\n // Enable HTML5 mode by forwarding all not-found requests to root so that\n // SPA (single-page application) can handle the routing.\n // Optional. Default value false.\n HTML5 bool `json:"html5"`\n\n // Enable directory browsing.\n // Optional. Default value false.\n Browse bool `json:"browse"`\n \n // Enable ignoring of the base of the URL path.\n // Example: when assigning a static middleware to a non root path group,\n // the filesystem path is not doubled\n // Optional. Default value false.\n IgnoreBase bool `yaml:"ignoreBase"`\n\n // Filesystem provides access to the static content.\n // Optional. Defaults to http.Dir(config.Root)\n Filesystem http.FileSystem `yaml:"-"`\n}\n'})}),"\n",(0,i.jsx)(t.h3,{id:"default-configuration",children:"Default Configuration"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'DefaultStaticConfig = StaticConfig{\n Skipper: DefaultSkipper,\n Index: "index.html",\n}\n'})})]})}function h(e={}){const{wrapper:t}={...(0,s.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},1151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>o});var i=n(7294);const s={},a=i.createContext(s);function o(e){const t=i.useContext(a);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[8678],{8778:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>d});var i=n(5893),s=n(1151);const a={description:"Static middleware"},o="Static",r={id:"middleware/static",title:"Static",description:"Static middleware",source:"@site/docs/middleware/static.md",sourceDirName:"middleware",slug:"/middleware/static",permalink:"/docs/middleware/static",draft:!1,unlisted:!1,editUrl:"https://github.com/labstack/echox/blob/master/website/docs/middleware/static.md",tags:[],version:"current",frontMatter:{description:"Static middleware"},sidebar:"docsSidebar",previous:{title:"Session",permalink:"/docs/middleware/session"},next:{title:"Timeout",permalink:"/docs/middleware/timeout"}},l={},d=[{value:"Usage",id:"usage",level:2},{value:"Custom Configuration",id:"custom-configuration",level:2},{value:"Usage",id:"usage-1",level:3},{value:"Example 1",id:"example-1",level:4},{value:"Example 2",id:"example-2",level:4},{value:"Configuration",id:"configuration",level:2},{value:"Default Configuration",id:"default-configuration",level:3}];function c(e){const t={admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",...(0,s.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h1,{id:"static",children:"Static"}),"\n",(0,i.jsx)(t.p,{children:"Static middleware can be used to serve static files from the provided root directory."}),"\n",(0,i.jsx)(t.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'e := echo.New()\ne.Use(middleware.Static("/static"))\n'})}),"\n",(0,i.jsxs)(t.p,{children:["This serves static files from ",(0,i.jsx)(t.code,{children:"static"})," directory. For example, a request to ",(0,i.jsx)(t.code,{children:"/js/main.js"}),"\nwill fetch and serve ",(0,i.jsx)(t.code,{children:"static/js/main.js"})," file."]}),"\n",(0,i.jsx)(t.h2,{id:"custom-configuration",children:"Custom Configuration"}),"\n",(0,i.jsx)(t.h3,{id:"usage-1",children:"Usage"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'e := echo.New()\ne.Use(middleware.StaticWithConfig(middleware.StaticConfig{\n Root: "static",\n Browse: true,\n}))\n'})}),"\n",(0,i.jsxs)(t.p,{children:["This serves static files from ",(0,i.jsx)(t.code,{children:"static"})," directory and enables directory browsing."]}),"\n",(0,i.jsx)(t.p,{children:"Default behavior when using with non root URL paths is to append the URL path to the filesystem path."}),"\n",(0,i.jsx)(t.h4,{id:"example-1",children:"Example 1"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'group := root.Group("somepath")\ngroup.Use(middleware.Static(filepath.Join("filesystempath")))\n// When an incoming request comes for `/somepath` the actual filesystem request goes to `filesystempath/somepath` instead of only `filesystempath`. \n'})}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsxs)(t.p,{children:["To turn off this behavior set the ",(0,i.jsx)(t.code,{children:"IgnoreBase"})," config param to ",(0,i.jsx)(t.code,{children:"true"}),"."]})}),"\n",(0,i.jsx)(t.h4,{id:"example-2",children:"Example 2"}),"\n",(0,i.jsx)(t.p,{children:"Serve SPA assets from embedded filesystem"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'//go:embed web\nvar webAssets embed.FS\n\nfunc main() {\n\te := echo.New()\n\n\te.Use(middleware.StaticWithConfig(middleware.StaticConfig{\n\t\tHTML5: true,\n\t\tRoot: "web", // because files are located in `web` directory in `webAssets` fs\n\t\tFilesystem: http.FS(webAssets),\n\t}))\n\tapi := e.Group("/api")\n\tapi.GET("/users", func(c echo.Context) error {\n\t\treturn c.String(http.StatusOK, "users")\n\t})\n\n\tif err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\tlog.Fatal(err)\n\t}\n}\n'})}),"\n",(0,i.jsx)(t.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'StaticConfig struct {\n // Skipper defines a function to skip middleware.\n Skipper Skipper\n\n // Root directory from where the static content is served.\n // Required.\n Root string `json:"root"`\n\n // Index file for serving a directory.\n // Optional. Default value "index.html".\n Index string `json:"index"`\n\n // Enable HTML5 mode by forwarding all not-found requests to root so that\n // SPA (single-page application) can handle the routing.\n // Optional. Default value false.\n HTML5 bool `json:"html5"`\n\n // Enable directory browsing.\n // Optional. Default value false.\n Browse bool `json:"browse"`\n \n // Enable ignoring of the base of the URL path.\n // Example: when assigning a static middleware to a non root path group,\n // the filesystem path is not doubled\n // Optional. Default value false.\n IgnoreBase bool `yaml:"ignoreBase"`\n\n // Filesystem provides access to the static content.\n // Optional. Defaults to http.Dir(config.Root)\n Filesystem http.FileSystem `yaml:"-"`\n}\n'})}),"\n",(0,i.jsx)(t.h3,{id:"default-configuration",children:"Default Configuration"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-go",children:'DefaultStaticConfig = StaticConfig{\n Skipper: DefaultSkipper,\n Index: "index.html",\n}\n'})})]})}function h(e={}){const{wrapper:t}={...(0,s.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},1151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>o});var i=n(7294);const s={},a=i.createContext(s);function o(e){const t=i.useContext(a);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.148b8494.js b/assets/js/runtime~main.0f8e3b56.js similarity index 92% rename from assets/js/runtime~main.148b8494.js rename to assets/js/runtime~main.0f8e3b56.js index 341ae9ad..7d2d4a98 100644 --- a/assets/js/runtime~main.148b8494.js +++ b/assets/js/runtime~main.0f8e3b56.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,d,f,c,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var d=t[e]={id:e,loaded:!1,exports:{}};return b[e].call(d.exports,d,d.exports,r),d.loaded=!0,d.exports}r.m=b,r.c=t,e=[],r.O=(a,d,f,c)=>{if(!d){var b=1/0;for(i=0;iloading...
A separate source file contains the function to create the Echo instance and add the static
file handlers and middleware. Note the build tag on the first line which says to use this when not
-bulding with appengine or appenginevm tags (which thoese platforms automatically add for us). We also
+building with appengine or appenginevm tags (which those platforms automatically add for us). We also
have the main()
function to start serving our app as normal. This should all be very familiar.
loading...
The handler-wireup that would normally also be a part of this Echo setup moves to separate files which diff --git a/docs/cookbook/graceful-shutdown.html b/docs/cookbook/graceful-shutdown.html index f6bc669b..29fdf8c0 100644 --- a/docs/cookbook/graceful-shutdown.html +++ b/docs/cookbook/graceful-shutdown.html @@ -10,7 +10,7 @@ - +
diff --git a/docs/cookbook/hello-world.html b/docs/cookbook/hello-world.html index fb948b2d..44fe7563 100644 --- a/docs/cookbook/hello-world.html +++ b/docs/cookbook/hello-world.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/http2-server-push.html b/docs/cookbook/http2-server-push.html index 2169a9da..3edfe8a3 100644 --- a/docs/cookbook/http2-server-push.html +++ b/docs/cookbook/http2-server-push.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/http2.html b/docs/cookbook/http2.html index 740b5898..bbd6ef50 100644 --- a/docs/cookbook/http2.html +++ b/docs/cookbook/http2.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/jsonp.html b/docs/cookbook/jsonp.html index 0cb3d27c..85345698 100644 --- a/docs/cookbook/jsonp.html +++ b/docs/cookbook/jsonp.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/jwt.html b/docs/cookbook/jwt.html index 0f67dec0..6e41446d 100644 --- a/docs/cookbook/jwt.html +++ b/docs/cookbook/jwt.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/load-balancing.html b/docs/cookbook/load-balancing.html index db823546..aeafbd93 100644 --- a/docs/cookbook/load-balancing.html +++ b/docs/cookbook/load-balancing.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/middleware.html b/docs/cookbook/middleware.html index 7038e69b..6f91fa6f 100644 --- a/docs/cookbook/middleware.html +++ b/docs/cookbook/middleware.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/reverse-proxy.html b/docs/cookbook/reverse-proxy.html index 3c98006a..3004a9b9 100644 --- a/docs/cookbook/reverse-proxy.html +++ b/docs/cookbook/reverse-proxy.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/sse.html b/docs/cookbook/sse.html index 0d364a60..14ba0467 100644 --- a/docs/cookbook/sse.html +++ b/docs/cookbook/sse.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/streaming-response.html b/docs/cookbook/streaming-response.html index e593a163..60223035 100644 --- a/docs/cookbook/streaming-response.html +++ b/docs/cookbook/streaming-response.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/subdomain.html b/docs/cookbook/subdomain.html index 5b688258..6ed086a1 100644 --- a/docs/cookbook/subdomain.html +++ b/docs/cookbook/subdomain.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/timeout.html b/docs/cookbook/timeout.html index 86fe94fa..23e34d2d 100644 --- a/docs/cookbook/timeout.html +++ b/docs/cookbook/timeout.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/twitter.html b/docs/cookbook/twitter.html index 45fbf883..7db522ae 100644 --- a/docs/cookbook/twitter.html +++ b/docs/cookbook/twitter.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookbook/websocket.html b/docs/cookbook/websocket.html index f1ec062e..a7bef860 100644 --- a/docs/cookbook/websocket.html +++ b/docs/cookbook/websocket.html @@ -10,7 +10,7 @@ - + diff --git a/docs/cookies.html b/docs/cookies.html index a65f2816..6029839c 100644 --- a/docs/cookies.html +++ b/docs/cookies.html @@ -10,7 +10,7 @@ - + diff --git a/docs/customization.html b/docs/customization.html index 78932060..94813d27 100644 --- a/docs/customization.html +++ b/docs/customization.html @@ -10,7 +10,7 @@ - + diff --git a/docs/error-handling.html b/docs/error-handling.html index 18482d08..e0610102 100644 --- a/docs/error-handling.html +++ b/docs/error-handling.html @@ -10,7 +10,7 @@ - + diff --git a/docs/ip-address.html b/docs/ip-address.html index 83976436..1c230b35 100644 --- a/docs/ip-address.html +++ b/docs/ip-address.html @@ -10,7 +10,7 @@ - + @@ -39,7 +39,7 @@In this case, use echo.ExtractIPDirect()
.
e.IPExtractor = echo.ExtractIPDirect()
X-Forwared-For
(XFF) is the popular header to relay clients' IP addresses.
+
X-Forwarded-For
(XFF) is the popular header to relay clients' IP addresses.
At each hop on the proxies, they append the request IP address at the end of the header.
Following example diagram illustrates this behavior.
┌──────────┐ ┌──────────┐ ┌──────────┐
───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
│ (IP: b) │ │ (IP: c) │ │ │
└──────────┘ └──────────┘ └──────────┘
Case 1.
XFF: "" "a" "a, b"
~~~~~~
Case 2.
XFF: "x" "x, a" "x, a, b"
~~~~~~~~~
↑ What your app will see
$ JAEGER_AGENT_HOST=192.168.1.10 JAEGER_AGENT_PORT=6831 ./myserver
The tracer can be initialized with values coming from environment variables. None of the env vars are required -and all of them can be overriden via direct setting of the property on the configuration object.
+and all of them can be overridden via direct setting of the property on the configuration object.Property | Description |
---|---|
JAEGER_SERVICE_NAME | The service name |
JAEGER_AGENT_HOST | The hostname for communicating with agent via UDP |
JAEGER_AGENT_PORT | The port for communicating with agent via UDP |
JAEGER_ENDPOINT | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces |
JAEGER_USER | Username to send as part of "Basic" authentication to the collector endpoint |
JAEGER_PASSWORD | Password to send as part of "Basic" authentication to the collector endpoint |
JAEGER_REPORTER_LOG_SPANS | Whether the reporter should also log the spans |
JAEGER_REPORTER_MAX_QUEUE_SIZE | The reporter's maximum queue size |
JAEGER_REPORTER_FLUSH_INTERVAL | The reporter's flush interval, with units, e.g. "500ms" or "2s" ([valid units][timeunits]) |
JAEGER_SAMPLER_TYPE | The sampler type |
JAEGER_SAMPLER_PARAM | The sampler parameter (number) |
JAEGER_SAMPLER_MANAGER_HOST_PORT | The HTTP endpoint when using the remote sampler, i.e. http://jaeger-agent:5778/sampling |
JAEGER_SAMPLER_MAX_OPERATIONS | The maximum number of operations that the sampler will keep track of |
JAEGER_SAMPLER_REFRESH_INTERVAL | How often the remotely controlled sampler will poll jaeger-agent for the appropriate sampling strategy, with units, e.g. "1m" or "30s" ([valid units][timeunits]) |
JAEGER_TAGS | A comma separated list of name = value tracer level tags, which get added to all reported spans. The value can also refer to an environment variable using the format ${envVarName:default} , where the :default is optional, and identifies a value to be used if the environment variable cannot be found |
JAEGER_DISABLED | Whether the tracer is disabled or not. If true, the default opentracing.NoopTracer is used. |
JAEGER_RPC_METRICS | Whether to store RPC metrics |
By default, the client sends traces via UDP to the agent at localhost:6831
. Use JAEGER_AGENT_HOST
and
JAEGER_AGENT_PORT
to send UDP traces to a different host:port
. If JAEGER_ENDPOINT
is set, the client sends traces
diff --git a/docs/middleware/jwt.html b/docs/middleware/jwt.html
index 87824ee0..18434d2a 100644
--- a/docs/middleware/jwt.html
+++ b/docs/middleware/jwt.html
@@ -10,7 +10,7 @@
-
+
group := root.Group("somepath")
group.Use(middleware.Static(filepath.Join("filesystempath")))
// When an incoming request comes for `/somepath` the actual filesystem request goes to `filesystempath/somepath` instead of only `filesystempath`.
To turn off this behavior set the IgnoreBase
config param to true
.
Serve SPA assets from embbeded filesystem
+Serve SPA assets from embedded filesystem
//go:embed web
var webAssets embed.FS
func main() {
e := echo.New()
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
HTML5: true,
Root: "web", // because files are located in `web` directory in `webAssets` fs
Filesystem: http.FS(webAssets),
}))
api := e.Group("/api")
api.GET("/users", func(c echo.Context) error {
return c.String(http.StatusOK, "users")
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
StaticConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Root directory from where the static content is served.
// Required.
Root string `json:"root"`
// Index file for serving a directory.
// Optional. Default value "index.html".
Index string `json:"index"`
// Enable HTML5 mode by forwarding all not-found requests to root so that
// SPA (single-page application) can handle the routing.
// Optional. Default value false.
HTML5 bool `json:"html5"`
// Enable directory browsing.
// Optional. Default value false.
Browse bool `json:"browse"`
// Enable ignoring of the base of the URL path.
// Example: when assigning a static middleware to a non root path group,
// the filesystem path is not doubled
// Optional. Default value false.
IgnoreBase bool `yaml:"ignoreBase"`
// Filesystem provides access to the static content.
// Optional. Defaults to http.Dir(config.Root)
Filesystem http.FileSystem `yaml:"-"`
}
/users/anything...
There can be only one effective match-any parameter in route. When route is added with multiple match-any
-/v1/*/images/*
. The router matches always the first *
till the end of requst URL i.e. it works as /v1/*
.
/v1/*/images/*
. The router matches always the first *
till the end of request URL i.e. it works as /v1/*
.