mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
add accesslog+proxy example
This commit is contained in:
parent
fc2aada3c7
commit
a04a6b5011
|
@ -31,7 +31,7 @@ With your help, we can improve Open Source web development for everyone!
|
||||||
> Donations from **China** are now accepted!
|
> Donations from **China** are now accepted!
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://github.com/lihaotian0607"><img src="https://avatars1.githubusercontent.com/u/32523475?v=4" alt ="LiHaotian " title="lihaotian0607" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/lihaotian0607"><img src="https://avatars1.githubusercontent.com/u/32523475?v=4" alt ="LiHaotian" title="lihaotian0607" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/Little-YangYang"><img src="https://avatars1.githubusercontent.com/u/10755202?v=4" alt ="Muyang Li" title="Little-YangYang" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/Little-YangYang"><img src="https://avatars1.githubusercontent.com/u/10755202?v=4" alt ="Muyang Li" title="Little-YangYang" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/tuhao1020"><img src="https://avatars1.githubusercontent.com/u/26807520?v=4" alt ="Hao Tu" title="tuhao1020" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/tuhao1020"><img src="https://avatars1.githubusercontent.com/u/26807520?v=4" alt ="Hao Tu" title="tuhao1020" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/CetinBasoz"><img src="https://avatars1.githubusercontent.com/u/3152637?v=4" alt ="Cetin Basoz" title="CetinBasoz" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/CetinBasoz"><img src="https://avatars1.githubusercontent.com/u/3152637?v=4" alt ="Cetin Basoz" title="CetinBasoz" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
* [Listen and render Logs to a Client](logging/request-logger/accesslog-broker/main.go)
|
* [Listen and render Logs to a Client](logging/request-logger/accesslog-broker/main.go)
|
||||||
* [The CSV Formatter](logging/request-logger/accesslog-csv/main.go)
|
* [The CSV Formatter](logging/request-logger/accesslog-csv/main.go)
|
||||||
* [Create your own Formatter](logging/request-logger/accesslog-formatter/main.go)
|
* [Create your own Formatter](logging/request-logger/accesslog-formatter/main.go)
|
||||||
|
* [Root and Proxy AccessLog instances](logging/request-logger/accesslog-proxy/main.go)
|
||||||
<!-- * [Log Requests to a JSON File](logging/request-logger/request-logger-file-json/main.go) -->
|
<!-- * [Log Requests to a JSON File](logging/request-logger/request-logger-file-json/main.go) -->
|
||||||
* API Documentation
|
* API Documentation
|
||||||
* [Yaag](apidoc/yaag/main.go)
|
* [Yaag](apidoc/yaag/main.go)
|
||||||
|
|
86
_examples/logging/request-logger/accesslog-proxy/main.go
Normal file
86
_examples/logging/request-logger/accesslog-proxy/main.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*Package main is a proxy + accesslog example.
|
||||||
|
In this example we will make a small proxy which listens requests on "/proxy/+path".
|
||||||
|
With two accesslog instances, one for the main application and one for the /proxy/ requests.
|
||||||
|
Of cource, you could a single accesslog for the whole application, but for the sake of the example
|
||||||
|
let's log them separately.
|
||||||
|
|
||||||
|
We will make use of iris.StripPrefix and host.ProxyHandler.*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/core/host"
|
||||||
|
"github.com/kataras/iris/v12/middleware/accesslog"
|
||||||
|
"github.com/kataras/iris/v12/middleware/recover"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
app.Get("/", index)
|
||||||
|
|
||||||
|
ac := accesslog.File("access.log")
|
||||||
|
defer ac.Close()
|
||||||
|
ac.Async = true
|
||||||
|
ac.RequestBody = true
|
||||||
|
ac.ResponseBody = true
|
||||||
|
ac.BytesReceived = false
|
||||||
|
ac.BytesSent = false
|
||||||
|
|
||||||
|
app.UseRouter(ac.Handler)
|
||||||
|
app.UseRouter(recover.New())
|
||||||
|
|
||||||
|
proxy := app.Party("/proxy")
|
||||||
|
{
|
||||||
|
acProxy := accesslog.File("proxy_access.log")
|
||||||
|
defer acProxy.Close()
|
||||||
|
acProxy.Async = true
|
||||||
|
acProxy.RequestBody = true
|
||||||
|
acProxy.ResponseBody = true
|
||||||
|
acProxy.BytesReceived = false
|
||||||
|
acProxy.BytesSent = false
|
||||||
|
|
||||||
|
// Unlike Use, the UseRouter method replaces any duplications automatically.
|
||||||
|
// (see UseOnce for the same behavior on Use).
|
||||||
|
// Therefore, this statement removes the parent's accesslog and registers this new one.
|
||||||
|
proxy.UseRouter(acProxy.Handler)
|
||||||
|
proxy.UseRouter(recover.New())
|
||||||
|
proxy.Use(func(ctx iris.Context) {
|
||||||
|
ctx.CompressReader(true)
|
||||||
|
ctx.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Listen for specific proxy paths:
|
||||||
|
// Listen on "/proxy" for "http://localhost:9090/read-write"
|
||||||
|
proxy.Any("/", iris.StripPrefix("/proxy",
|
||||||
|
newProxyHandler("http://localhost:9090/read-write")))
|
||||||
|
*/
|
||||||
|
|
||||||
|
// You can register an access log only for proxied requests, e.g. proxy_access.log:
|
||||||
|
// proxy.UseRouter(ac2.Handler)
|
||||||
|
|
||||||
|
// Listen for any proxy path.
|
||||||
|
// Proxies the "/proxy/+$path" to "http://localhost:9090/$path".
|
||||||
|
proxy.Any("/{p:path}", iris.StripPrefix("/proxy",
|
||||||
|
newProxyHandler("http://localhost:9090")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// $ go run target/main.go
|
||||||
|
// open new terminal
|
||||||
|
// $ go run main.go
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(ctx iris.Context) {
|
||||||
|
ctx.WriteString("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProxyHandler(proxyURL string) iris.Handler {
|
||||||
|
target, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
reverseProxy := host.ProxyHandler(target)
|
||||||
|
return iris.FromStd(reverseProxy)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kataras/iris/v12"
|
||||||
|
|
||||||
|
// The target server, can be written using any programming language and any web framework, of course.
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
app.Logger().SetLevel("debug")
|
||||||
|
|
||||||
|
// Just a test route which reads some data and responds back with json.
|
||||||
|
app.Post("/read-write", readWriteHandler)
|
||||||
|
|
||||||
|
app.Get("/get", getHandler)
|
||||||
|
|
||||||
|
// The target ip:port.
|
||||||
|
app.Listen(":9090")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readWriteHandler(ctx iris.Context) {
|
||||||
|
var req interface{}
|
||||||
|
ctx.ReadBody(&req)
|
||||||
|
|
||||||
|
ctx.JSON(iris.Map{
|
||||||
|
"message": "OK",
|
||||||
|
"request": req,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHandler(ctx iris.Context) {
|
||||||
|
// ctx.CompressWriter(true)
|
||||||
|
ctx.WriteString("Compressed data")
|
||||||
|
}
|
|
@ -17,29 +17,35 @@ func TestReadHeaders(t *testing.T) {
|
||||||
headers map[string]string
|
headers map[string]string
|
||||||
code int
|
code int
|
||||||
body string
|
body string
|
||||||
|
regex bool
|
||||||
}{
|
}{
|
||||||
{headers: map[string]string{
|
{headers: map[string]string{
|
||||||
"X-Request-Id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
"X-Request-Id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
||||||
"Authentication": "Bearer my-token",
|
"Authentication": "Bearer my-token",
|
||||||
}, code: 200, body: expectedOKBody},
|
}, code: 200, body: expectedOKBody, regex: false},
|
||||||
{headers: map[string]string{
|
{headers: map[string]string{
|
||||||
"x-request-id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
"x-request-id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
||||||
"authentication": "Bearer my-token",
|
"authentication": "Bearer my-token",
|
||||||
}, code: 200, body: expectedOKBody},
|
}, code: 200, body: expectedOKBody, regex: false},
|
||||||
{headers: map[string]string{
|
{headers: map[string]string{
|
||||||
"X-Request-ID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
"X-Request-Id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
||||||
"Authentication": "Bearer my-token",
|
"Authentication": "Bearer my-token",
|
||||||
}, code: 200, body: expectedOKBody},
|
}, code: 200, body: expectedOKBody, regex: false},
|
||||||
{headers: map[string]string{
|
{headers: map[string]string{
|
||||||
"Authentication": "Bearer my-token",
|
"Authentication": "Bearer my-token",
|
||||||
}, code: 500, body: "X-Request-Id is empty"},
|
}, code: 500, body: "X-Request-Id is empty", regex: false},
|
||||||
{headers: map[string]string{
|
{headers: map[string]string{
|
||||||
"X-Request-ID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
"X-Request-Id": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
|
||||||
}, code: 500, body: "Authentication is empty"},
|
}, code: 500, body: "Authentication is empty", regex: false},
|
||||||
{headers: map[string]string{}, code: 500, body: "X-Request-Id is empty (and 1 other error)"},
|
{headers: map[string]string{}, code: 500, body: ".*\\(and 1 other error\\)$", regex: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
e.GET("/").WithHeaders(tt.headers).Expect().Status(tt.code).Body().Equal(tt.body)
|
te := e.GET("/").WithHeaders(tt.headers).Expect().Status(tt.code).Body()
|
||||||
|
if tt.regex {
|
||||||
|
te.Match(tt.body)
|
||||||
|
} else {
|
||||||
|
te.Equal(tt.body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
49
context/counter.go
Normal file
49
context/counter.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Counter is the shared counter instances between Iris applications of the same process.
|
||||||
|
var Counter = NewGlobalCounter() // it's not used anywhere, currently but it's here.
|
||||||
|
|
||||||
|
// NewGlobalCounter returns a fresh instance of a global counter.
|
||||||
|
// End developers can use it as a helper for their applications.
|
||||||
|
func NewGlobalCounter() *GlobalCounter {
|
||||||
|
return &GlobalCounter{Max: math.MaxUint64}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalCounter is a counter which
|
||||||
|
// atomically increments until Max.
|
||||||
|
type GlobalCounter struct {
|
||||||
|
value uint64
|
||||||
|
Max uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment increments the Value.
|
||||||
|
// The value cannot exceed the Max one.
|
||||||
|
// It uses Compare and Swap with the atomic package.
|
||||||
|
//
|
||||||
|
// Returns the new number value.
|
||||||
|
func (c *GlobalCounter) Increment() (newValue uint64) {
|
||||||
|
for {
|
||||||
|
prev := atomic.LoadUint64(&c.value)
|
||||||
|
newValue = prev + 1
|
||||||
|
|
||||||
|
if newValue >= c.Max {
|
||||||
|
newValue = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.CompareAndSwapUint64(&c.value, prev, newValue) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the current counter without incrementing.
|
||||||
|
func (c *GlobalCounter) Get() uint64 {
|
||||||
|
return atomic.LoadUint64(&c.value)
|
||||||
|
}
|
|
@ -5,24 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/core/netutil"
|
"github.com/kataras/iris/v12/core/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func singleJoiningSlash(a, b string) string {
|
|
||||||
aslash := strings.HasSuffix(a, "/")
|
|
||||||
bslash := strings.HasPrefix(b, "/")
|
|
||||||
switch {
|
|
||||||
case aslash && bslash:
|
|
||||||
return a + b[1:]
|
|
||||||
case !aslash && !bslash:
|
|
||||||
return a + "/" + b
|
|
||||||
}
|
|
||||||
return a + b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyHandler returns a new ReverseProxy that rewrites
|
// ProxyHandler returns a new ReverseProxy that rewrites
|
||||||
// URLs to the scheme, host, and base path provided in target. If the
|
// URLs to the scheme, host, and base path provided in target. If the
|
||||||
// target's path is "/base" and the incoming request was for "/dir",
|
// target's path is "/base" and the incoming request was for "/dir",
|
||||||
|
@ -35,7 +23,9 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
|
||||||
req.URL.Scheme = target.Scheme
|
req.URL.Scheme = target.Scheme
|
||||||
req.URL.Host = target.Host
|
req.URL.Host = target.Host
|
||||||
req.Host = target.Host
|
req.Host = target.Host
|
||||||
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
|
|
||||||
|
req.URL.Path = path.Join(target.Path, req.URL.Path)
|
||||||
|
|
||||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,7 +100,7 @@ func RedirectHandler(target *url.URL, redirectStatus int) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
redirectTo := singleJoiningSlash(targetURI, r.URL.Path)
|
redirectTo := path.Join(targetURI, r.URL.Path)
|
||||||
if len(r.URL.RawQuery) > 0 {
|
if len(r.URL.RawQuery) > 0 {
|
||||||
redirectTo += "?" + r.URL.RawQuery
|
redirectTo += "?" + r.URL.RawQuery
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,8 +397,12 @@ func StripPrefix(prefix string, h context.Handler) context.Handler {
|
||||||
canonicalPrefix = toWebPath(canonicalPrefix)
|
canonicalPrefix = toWebPath(canonicalPrefix)
|
||||||
|
|
||||||
return func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
if p := strings.TrimPrefix(ctx.Request().URL.Path, canonicalPrefix); len(p) < len(ctx.Request().URL.Path) {
|
u := ctx.Request().URL
|
||||||
ctx.Request().URL.Path = p
|
if p := strings.TrimPrefix(u.Path, canonicalPrefix); len(p) < len(u.Path) {
|
||||||
|
if p == "" {
|
||||||
|
p = "/"
|
||||||
|
}
|
||||||
|
u.Path = p
|
||||||
h(ctx)
|
h(ctx)
|
||||||
} else {
|
} else {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
stdContext "context"
|
stdContext "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -788,7 +789,9 @@ func (ac *AccessLog) after(ctx *context.Context, lat time.Duration, method, path
|
||||||
bytesReceived = requestBodyLength // store it, if the total is enabled then this will be overridden.
|
bytesReceived = requestBodyLength // store it, if the total is enabled then this will be overridden.
|
||||||
}
|
}
|
||||||
if err != nil && ac.RequestBody {
|
if err != nil && ac.RequestBody {
|
||||||
|
if err != http.ErrBodyReadAfterClose { // if body was already closed, don't send it as error.
|
||||||
requestBody = ac.getErrorText(err)
|
requestBody = ac.getErrorText(err)
|
||||||
|
}
|
||||||
} else if requestBodyLength > 0 {
|
} else if requestBodyLength > 0 {
|
||||||
if ac.RequestBody {
|
if ac.RequestBody {
|
||||||
if ac.BodyMinify {
|
if ac.BodyMinify {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user