mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
add example for #1601
This commit is contained in:
parent
e41e861c4c
commit
a018ba9b0a
|
@ -586,6 +586,7 @@ New Package-level Variables:
|
||||||
|
|
||||||
New Context Methods:
|
New Context Methods:
|
||||||
|
|
||||||
|
- `Context.URLParamsSorted() []memstore.StringEntry` returns a sorted (by key) slice of key-value entries of the URL Query parameters.
|
||||||
- `Context.ViewEngine(ViewEngine)` to set a view engine on-fly for the current chain of handlers, responsible to render templates through `ctx.View`. [Example](_examples/view/context-view-engine).
|
- `Context.ViewEngine(ViewEngine)` to set a view engine on-fly for the current chain of handlers, responsible to render templates through `ctx.View`. [Example](_examples/view/context-view-engine).
|
||||||
- `Context.SetErr(error)` and `Context.GetErr() error` helpers.
|
- `Context.SetErr(error)` and `Context.GetErr() error` helpers.
|
||||||
- `Context.CompressWriter(bool) error` and `Context.CompressReader(bool) error`.
|
- `Context.CompressWriter(bool) error` and `Context.CompressReader(bool) error`.
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
* [Sitemap](routing/sitemap/main.go)
|
* [Sitemap](routing/sitemap/main.go)
|
||||||
* Logging
|
* Logging
|
||||||
* [Request Logger](logging/request-logger/main.go)
|
* [Request Logger](logging/request-logger/main.go)
|
||||||
|
* [Log requests and responses to access.log](logging/request-logger/request-logger-access-log-file)
|
||||||
* [Log Requests to a File](logging/request-logger/request-logger-file/main.go)
|
* [Log Requests to a File](logging/request-logger/request-logger-file/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)
|
||||||
* [Application File Logger](logging/file-logger/main.go)
|
* [Application File Logger](logging/file-logger/main.go)
|
||||||
|
|
|
@ -11,7 +11,7 @@ we use the `iris.Addr` which is an `iris.Runner` type
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Listening on tcp with network address 0.0.0.0:8080
|
// Listening on tcp with network address 0.0.0.0:8080
|
||||||
// app.Listen(":8080") it's a shortcut of:
|
// app.Listen(":8080") is just a shortcut of:
|
||||||
app.Run(iris.Addr(":8080"))
|
app.Run(iris.Addr(":8080"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ func main() {
|
||||||
ctx.HTML("<h1>hi, I just exist in order to see if the server is closed</h1>")
|
ctx.HTML("<h1>hi, I just exist in order to see if the server is closed</h1>")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
idleConnsClosed := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch,
|
signal.Notify(ch,
|
||||||
|
@ -37,10 +38,12 @@ func main() {
|
||||||
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
|
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
app.Shutdown(ctx)
|
app.Shutdown(ctx)
|
||||||
|
close(idleConnsClosed)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Start the server and disable the default interrupt handler in order to
|
// Start the server and disable the default interrupt handler in order to
|
||||||
// handle it clear and simple by our own, without any issues.
|
// handle it clear and simple by our own, without any issues.
|
||||||
app.Listen(":8080", iris.WithoutInterruptHandler)
|
app.Listen(":8080", iris.WithoutInterruptHandler)
|
||||||
|
<-idleConnsClosed
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,14 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
idleConnsClosed := make(chan struct{})
|
||||||
iris.RegisterOnInterrupt(func() {
|
iris.RegisterOnInterrupt(func() {
|
||||||
timeout := 10 * time.Second
|
timeout := 10 * time.Second
|
||||||
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
|
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// close all hosts
|
// close all hosts
|
||||||
app.Shutdown(ctx)
|
app.Shutdown(ctx)
|
||||||
|
close(idleConnsClosed)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/", func(ctx iris.Context) {
|
app.Get("/", func(ctx iris.Context) {
|
||||||
|
@ -30,5 +32,6 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// http://localhost:8080
|
// http://localhost:8080
|
||||||
app.Listen(":8080", iris.WithoutInterruptHandler)
|
app.Listen(":8080", iris.WithoutInterruptHandler, iris.WithoutServerError(iris.ErrServerClosed))
|
||||||
|
<-idleConnsClosed
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
2020-08-22 00:44:20|993.3µs|POST|/read_body||200|{"id":10,"name":"Tim","age":22}|{"message":"OK"}|
|
||||||
|
2020-08-22 00:44:30|0s|POST|/read_body||400||error(invalid character 'a' looking for beginning of object key string)||
|
||||||
|
2020-08-22 03:02:41|1ms|GET|/|a=1 b=2|200||<h1>Hello index</h1>|
|
||||||
|
2020-08-22 03:15:29|968.8µs|GET|/public|file=public|404|||
|
||||||
|
2020-08-22 03:03:42|0s|GET|/user/kataras|username=kataras|200||Hello, kataras!|
|
||||||
|
2020-08-22 03:05:40|0s|GET|/user/kataras|username=kataras a_query_parameter=name|200||Hello, kataras!|
|
|
@ -0,0 +1,174 @@
|
||||||
|
package main // See https://github.com/kataras/iris/issues/1601
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/middleware/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create or use the ./access.log file.
|
||||||
|
f, err := os.OpenFile("access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
iris.RegisterOnInterrupt(func() { f.Close() })
|
||||||
|
//
|
||||||
|
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
// Init the request logger with
|
||||||
|
// the LogFuncCtx field alone.
|
||||||
|
reqLogger := logger.New(logger.Config{
|
||||||
|
LogFuncCtx: requestLogFunc(f),
|
||||||
|
})
|
||||||
|
//
|
||||||
|
|
||||||
|
// Wrap the request logger middleware
|
||||||
|
// with a response recorder because
|
||||||
|
// we want to record the response body
|
||||||
|
// sent to the client.
|
||||||
|
reqLoggerWithRecord := func(ctx iris.Context) {
|
||||||
|
// Store the requested path just in case.
|
||||||
|
ctx.Values().Set("path", ctx.Path())
|
||||||
|
ctx.Record()
|
||||||
|
reqLogger(ctx)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
// Register the middleware (UseRouter to catch http errors too).
|
||||||
|
app.UseRouter(reqLoggerWithRecord)
|
||||||
|
//
|
||||||
|
|
||||||
|
// Register some routes...
|
||||||
|
app.HandleDir("/", iris.Dir("./public"))
|
||||||
|
|
||||||
|
app.Get("/user/{username}", userHandler)
|
||||||
|
app.Post("/read_body", readBodyHandler)
|
||||||
|
//
|
||||||
|
|
||||||
|
// Start the server with `WithoutBodyConsumptionOnUnmarshal`
|
||||||
|
// option so the request body can be readen twice:
|
||||||
|
// one for our handlers and one from inside our request logger middleware.
|
||||||
|
app.Listen(":8080", iris.WithoutBodyConsumptionOnUnmarshal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBodyHandler(ctx iris.Context) {
|
||||||
|
var request interface{}
|
||||||
|
if err := ctx.ReadBody(&request); err != nil {
|
||||||
|
ctx.StopWithPlainError(iris.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(iris.Map{"message": "OK"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func userHandler(ctx iris.Context) {
|
||||||
|
ctx.Writef("Hello, %s!", ctx.Params().Get("username"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonToString(src []byte) string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := json.Compact(buf, src); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestLogFunc(w io.Writer) func(ctx iris.Context, lat time.Duration) {
|
||||||
|
return func(ctx iris.Context, lat time.Duration) {
|
||||||
|
var (
|
||||||
|
method = ctx.Method() // request method.
|
||||||
|
// Use a stored value instead of ctx.Path()
|
||||||
|
// because some handlers may change the relative path
|
||||||
|
// to perform some action.
|
||||||
|
path = ctx.Values().GetString("path")
|
||||||
|
code = ctx.GetStatusCode() // response status code
|
||||||
|
// request and response data or error reading them.
|
||||||
|
requestBody string
|
||||||
|
responseBody string
|
||||||
|
|
||||||
|
// url parameters and path parameters separated by space,
|
||||||
|
// key=value key2=value2.
|
||||||
|
requestValues string
|
||||||
|
)
|
||||||
|
|
||||||
|
// any error handler stored ( ctx.SetErr or StopWith(Plain)Error )
|
||||||
|
errHandler := ctx.GetErr()
|
||||||
|
// check if not error and client sent a response with a content-type set-ed.
|
||||||
|
if errHandler == nil {
|
||||||
|
if ctx.GetContentTypeRequested() == "application/json" {
|
||||||
|
// Read and log request body the client sent to the server:
|
||||||
|
//
|
||||||
|
// You can use ctx.ReadBody(&body)
|
||||||
|
// which will decode any body (json, xml, msgpack, protobuf...)
|
||||||
|
// and use %v inside the fmt.Fprintf to print something like:
|
||||||
|
// map[age:22 id:10 name:Tim]
|
||||||
|
//
|
||||||
|
// But if you want specific to json string,
|
||||||
|
// then do that:
|
||||||
|
var tmp json.RawMessage
|
||||||
|
if err := ctx.ReadJSON(&tmp); err != nil {
|
||||||
|
requestBody = err.Error()
|
||||||
|
} else {
|
||||||
|
requestBody = jsonToString(tmp)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
// left for exercise.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestBody = fmt.Sprintf("error(%s)", errHandler.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData := ctx.Recorder().Body()
|
||||||
|
// check if the server sent any response with content type,
|
||||||
|
// note that this will return the ;charset too
|
||||||
|
// so we check for its prefix instead.
|
||||||
|
if strings.HasPrefix(ctx.GetContentType(), "application/json") {
|
||||||
|
responseBody = jsonToString(responseData)
|
||||||
|
} else {
|
||||||
|
responseBody = string(responseData)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
ctx.Params().Visit(func(key, value string) {
|
||||||
|
buf.WriteString(key)
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(value)
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, entry := range ctx.URLParamsSorted() {
|
||||||
|
buf.WriteString(entry.Key)
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(entry.Value)
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := buf.Len(); n > 1 {
|
||||||
|
requestValues = buf.String()[0 : n-1] // remove last space.
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "%s|%s|%s|%s|%s|%d|%s|%s|\n",
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
lat,
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
requestValues,
|
||||||
|
code,
|
||||||
|
requestBody,
|
||||||
|
responseBody,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>Hello index</h1>
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -1504,8 +1505,12 @@ func (ctx *Context) URLParamBool(name string) (bool, error) {
|
||||||
return strconv.ParseBool(ctx.URLParam(name))
|
return strconv.ParseBool(ctx.URLParam(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLParams returns a map of GET query parameters separated by comma if more than one
|
// URLParams returns a map of URL Query parameters.
|
||||||
// it returns an empty map if nothing found.
|
// If the value of a URL parameter is a slice,
|
||||||
|
// then it is joined as one separated by comma.
|
||||||
|
// It returns an empty map on empty URL query.
|
||||||
|
//
|
||||||
|
// See URLParamsSorted too.
|
||||||
func (ctx *Context) URLParams() map[string]string {
|
func (ctx *Context) URLParams() map[string]string {
|
||||||
q := ctx.request.URL.Query()
|
q := ctx.request.URL.Query()
|
||||||
values := make(map[string]string, len(q))
|
values := make(map[string]string, len(q))
|
||||||
|
@ -1517,6 +1522,34 @@ func (ctx *Context) URLParams() map[string]string {
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URLParamsSorted returns a sorted (by key) slice
|
||||||
|
// of key-value entries of the URL Query parameters.
|
||||||
|
func (ctx *Context) URLParamsSorted() []memstore.StringEntry {
|
||||||
|
q := ctx.request.URL.Query()
|
||||||
|
n := len(q)
|
||||||
|
if n == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, n)
|
||||||
|
for key := range q {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
entries := make([]memstore.StringEntry, 0, n)
|
||||||
|
for _, key := range keys {
|
||||||
|
value := q[key]
|
||||||
|
entries = append(entries, memstore.StringEntry{
|
||||||
|
Key: key,
|
||||||
|
Value: strings.Join(value, ","),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
// No need anymore, net/http checks for the Form already.
|
// No need anymore, net/http checks for the Form already.
|
||||||
// func (ctx *Context) askParseForm() error {
|
// func (ctx *Context) askParseForm() error {
|
||||||
// if ctx.request.Form == nil {
|
// if ctx.request.Form == nil {
|
||||||
|
|
|
@ -16,6 +16,18 @@ type RequestParams struct {
|
||||||
memstore.Store
|
memstore.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestParamsReadOnly is the read-only access type of RequestParams.
|
||||||
|
type RequestParamsReadOnly interface {
|
||||||
|
Get(key string) string
|
||||||
|
GetEntryAt(index int) memstore.Entry
|
||||||
|
Visit(visitor func(key string, value string))
|
||||||
|
GetTrim(key string) string
|
||||||
|
GetEscape(key string) string
|
||||||
|
GetDecoded(key string) string
|
||||||
|
} // Note: currently unused.
|
||||||
|
|
||||||
|
var _ RequestParamsReadOnly = (*RequestParams)(nil)
|
||||||
|
|
||||||
// Set inserts a parameter value.
|
// Set inserts a parameter value.
|
||||||
// See `Get` too.
|
// See `Get` too.
|
||||||
func (r *RequestParams) Set(key, value string) {
|
func (r *RequestParams) Set(key, value string) {
|
||||||
|
|
|
@ -26,6 +26,13 @@ type (
|
||||||
immutable bool // if true then it can't change by its caller.
|
immutable bool // if true then it can't change by its caller.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringEntry is just a key-value wrapped by a struct.
|
||||||
|
// See Context.URLParamsSorted method.
|
||||||
|
StringEntry struct {
|
||||||
|
Key string `json:"key" msgpack:"key" yaml:"Key" toml:"Value"`
|
||||||
|
Value string `json:"value" msgpack:"value" yaml:"Value" toml:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
// Store is a collection of key-value entries with immutability capabilities.
|
// Store is a collection of key-value entries with immutability capabilities.
|
||||||
Store []Entry
|
Store []Entry
|
||||||
)
|
)
|
||||||
|
|
|
@ -80,6 +80,8 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx *context.Context) {
|
||||||
|
|
||||||
if l.config.PathAfterHandler /* we don't care if Path is disabled */ {
|
if l.config.PathAfterHandler /* we don't care if Path is disabled */ {
|
||||||
path = l.getPath(ctx)
|
path = l.getPath(ctx)
|
||||||
|
// note: we could just use the r.RequestURI which is the original one,
|
||||||
|
// but some users may need the stripped one (on HandleDir).
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.config.Status {
|
if l.config.Status {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user