diff --git a/HISTORY.md b/HISTORY.md index a850939b..ada11bab 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -448,7 +448,7 @@ New Package-level Variables: - `iris.DirListRichOptions` to pass on `iris.DirListRich` method. - `iris.DirListRich` to override the default look and feel if the `DirOptions.ShowList` was set to true, can be passed to `DirOptions.DirList` field. - `DirOptions.PushTargets` for http/2 push on index [*](https://github.com/kataras/iris/tree/master/_examples/file-server/http2push/main.go). -- `iris.Compress` and `iris.CompressReader` middleware to compress responses and decode compressed request data respectfully. +- `iris.Compression` middleware to compress responses and decode compressed request data respectfully. - `iris.B, KB, MB, GB, TB, PB, EB` for byte units. - `TLSNoRedirect` to disable automatic "http://" to "https://" redirections (see below) - `CookieAllowReclaim`, `CookieAllowSubdomains`, `CookieSameSite`, `CookieSecure` and `CookieEncoding` to bring previously sessions-only features to all cookies in the request. @@ -456,7 +456,7 @@ New Package-level Variables: New Context Methods: - `Context.SetErr(error)` and `Context.GetErr() error` helpers -- `Context.Compress(bool) error` and `Context.CompressReader(bool) error` +- `Context.CompressWriter(bool) error` and `Context.CompressReader(bool) error` - `Context.Clone() Context` returns a copy of the Context. - `Context.IsCanceled() bool` reports whether the request has been canceled by the client. - `Context.IsSSL() bool` reports whether the request is under HTTPS SSL (New `Configuration.SSLProxyHeaders` and `HostProxyHeaders` fields too). @@ -487,10 +487,9 @@ New Context Methods: Breaking Changes: -- `ctx.Gzip(boolean)` replaced with `ctx.Compress(boolean) error`. +- `ctx.Gzip(boolean)` replaced with `ctx.CompressWriter(boolean) error`. - `ctx.GzipReader(boolean) error` replaced with `ctx.CompressReader(boolean) error`. -- `iris.Gzip` replaced with `iris.Compress` (middleware). -- `iris.GzipReader` replaced with `iris.CompressReader` (middleware). +- `iris.Gzip` and `iris.GzipReader` replaced with `iris.Compression` (middleware). - `ctx.ClientSupportsGzip() bool` replaced with `ctx.ClientSupportsEncoding("gzip", "br" ...) bool`. - `ctx.GzipResponseWriter()` is **removed**. - `Party.HandleDir` now returns a list of `[]*Route` (GET and HEAD) instead of GET only. @@ -502,7 +501,7 @@ Breaking Changes: - `sessions#Config.Encode` and `Decode` are removed in favor of (the existing) `Encoding` field. - `versioning.GetVersion` now returns an empty string if version wasn't found. - Change the MIME type of `Javascript .js` and `JSONP` as the HTML specification now recommends to `"text/javascript"` instead of the obselete `"application/javascript"`. This change was pushed to the `Go` language itself as well. See . -- Remove the last input argument of `enableGzipCompression` in `Context.ServeContent`, `ServeFile` methods. This was deprecated a few versions ago. A middleware (`app.Use(iris.Compress)`) or a prior call to `Context.Compress(true)` will enable compression. Also these two methods and `Context.SendFile` one now support `Content-Range` and `Accept-Ranges` correctly out of the box (`net/http` had a bug, which is now fixed). +- Remove the last input argument of `enableGzipCompression` in `Context.ServeContent`, `ServeFile` methods. This was deprecated a few versions ago. A middleware (`app.Use(iris.CompressWriter)`) or a prior call to `Context.CompressWriter(true)` will enable compression. Also these two methods and `Context.SendFile` one now support `Content-Range` and `Accept-Ranges` correctly out of the box (`net/http` had a bug, which is now fixed). - `Context.ServeContent` no longer returns an error, see `ServeContentWithRate`, `ServeFileWithRate` and `SendFileWithRate` new methods too. - `route.Trace() string` changed to `route.Trace(w io.Writer)`, to achieve the same result just pass a `bytes.Buffer` - `var mvc.AutoBinding` removed as the default behavior now resolves such dependencies automatically (see [[FEATURE REQUEST] MVC serving gRPC-compatible controller](https://github.com/kataras/iris/issues/1449)). diff --git a/README.md b/README.md index 033c3c4f..67c30f6c 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,6 @@ Learn what [others saying about Iris](https://iris-go.com/testimonials/) and **[ ## 📖 Learning Iris -
-Quick start - ```sh # https://github.com/kataras/iris/wiki/Installation $ go get github.com/kataras/iris/v12@master @@ -42,18 +39,110 @@ package main import "github.com/kataras/iris/v12" func main() { - app := iris.New() - app.Get("/", index) - app.Listen(":8080") + app := iris.New() + + booksAPI := app.Party("/books") + { + booksAPI.Use(iris.Compression) + + // GET: http://localhost:8080/books + booksAPI.Get("/", list) + // POST: http://localhost:8080/books + booksAPI.Post("/", create) + } + + app.Listen(":8080") } -func index(ctx iris.Context) { - ctx.HTML("

Hello, World!

") +// Book example. +type Book struct { + Title string `json:"title"` +} + +func list(ctx iris.Context) { + books := []Book{ + {"Mastering Concurrency in Go"}, + {"Go Design Patterns"}, + {"Black Hat Go"}, + } + + ctx.JSON(books) + // TIP: negotiate the response between server's prioritizes + // and client's requirements, instead of ctx.JSON: + // ctx.Negotiation().JSON().MsgPack().Protobuf() + // ctx.Negotiate(books) +} + +func create(ctx iris.Context) { + var b Book + err := ctx.ReadJSON(&b) + // TIP: use ctx.ReadBody(&b) to bind + // any type of incoming data instead. + if err != nil { + ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem(). + Title("Book creation failure").DetailErr(err)) + // TIP: use ctx.StopWithError(code, err) when only + // plain text responses are expected on errors. + return + } + + println("Received Book: " + b.Title) + + ctx.StatusCode(iris.StatusCreated) } ``` +**Run** your Iris web server: + ```sh $ go run main.go +> Now listening on: http://localhost:8080 +> Application started. Press CTRL+C to shut down. +``` + +**List** Books: + +```sh +$ curl --header 'Accept-Encoding:gzip' http://localhost:8080/books + +[ + { + "title": "Mastering Concurrency in Go" + }, + { + "title": "Go Design Patterns" + }, + { + "title": "Black Hat Go" + } +] +``` + +**Create** a new Book: + +```sh +$ curl -i -X POST \ +--header 'Content-Encoding:gzip' \ +--header 'Content-Type:application/json' \ +--data "{\"title\":\"Writing An Interpreter In Go\"}" \ +http://localhost:8080/books + +> HTTP/1.1 201 Created +``` + +That's how an **error** response looks like: + +```sh +$ curl -X POST --data "{\"title\" \"not valid one\"}" \ +http://localhost:8080/books + +> HTTP/1.1 400 Bad Request + +{ + "status": 400, + "title": "Book creation failure" + "detail": "invalid character '\"' after object key", +} ```
diff --git a/_examples/compression/main.go b/_examples/compression/main.go index 6f73a7e0..e1d989d7 100644 --- a/_examples/compression/main.go +++ b/_examples/compression/main.go @@ -11,7 +11,7 @@ func main() { func newApp() *iris.Application { app := iris.New() // HERE and you are ready to GO: - app.Use(iris.Compress, iris.CompressReader) + app.Use(iris.Compression) app.Get("/", send) app.Post("/", receive) @@ -41,7 +41,7 @@ func receive(ctx iris.Context) { /* Manually: func enableCompression(ctx iris.Context) { // Enable writing using compression (deflate, gzip, brotli, snappy, s2): - err := ctx.Compress(true) + err := ctx.CompressWriter(true) if err != nil { ctx.Application().Logger().Debugf("writer: %v", err) // if you REQUIRE server to SEND compressed data then `return` here. diff --git a/_examples/testing/httptest/main_test.go b/_examples/testing/httptest/main_test.go index b542ae90..9c3723bc 100644 --- a/_examples/testing/httptest/main_test.go +++ b/_examples/testing/httptest/main_test.go @@ -35,8 +35,10 @@ func TestHandlerUsingNetHTTP(t *testing.T) { ctx.WriteString("Hello, World!") } + // A shortcut for net/http/httptest.NewRecorder/NewRequest. w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/", nil) + httptest.Do(w, r, handler) if expected, got := "Hello, World!", w.Body.String(); expected != got { t.Fatalf("expected body: %s but got: %s", expected, got) diff --git a/_examples/view/herotemplate/app.go b/_examples/view/herotemplate/app.go index 76f57812..62f7a53a 100644 --- a/_examples/view/herotemplate/app.go +++ b/_examples/view/herotemplate/app.go @@ -17,7 +17,7 @@ func main() { app := iris.New() app.Get("/users", func(ctx iris.Context) { - ctx.Compress(true) + ctx.CompressWriter(true) ctx.ContentType("text/html") userList := []string{ diff --git a/_examples/view/overview/main.go b/_examples/view/overview/main.go index 8222175e..8aeaabf1 100644 --- a/_examples/view/overview/main.go +++ b/_examples/view/overview/main.go @@ -14,7 +14,7 @@ func main() { // - {{ current }} app.RegisterView(iris.HTML("./templates", ".html")) app.Get("/", func(ctx iris.Context) { - ctx.Compress(true) // enable compression based on Accept-Encoding (e.g. "gzip"). + ctx.CompressWriter(true) // enable compression based on Accept-Encoding (e.g. "gzip"). ctx.ViewData("Name", "iris") // the .Name inside the ./templates/hi.html. ctx.View("hi.html") // render the template with the file name relative to the './templates'. }) diff --git a/_examples/view/quicktemplate/controllers/execute_template.go b/_examples/view/quicktemplate/controllers/execute_template.go index 153f6136..6baf71a4 100644 --- a/_examples/view/quicktemplate/controllers/execute_template.go +++ b/_examples/view/quicktemplate/controllers/execute_template.go @@ -8,7 +8,7 @@ import ( // ExecuteTemplate renders a "tmpl" partial template to the `Context.ResponseWriter`. func ExecuteTemplate(ctx iris.Context, tmpl templates.Partial) { - ctx.Compress(true) + ctx.CompressWriter(true) ctx.ContentType("text/html") templates.WriteTemplate(ctx, tmpl) } diff --git a/_examples/view/template_html_1/main.go b/_examples/view/template_html_1/main.go index 9e8508ee..a2a3b45a 100644 --- a/_examples/view/template_html_1/main.go +++ b/_examples/view/template_html_1/main.go @@ -16,7 +16,7 @@ func main() { // TIP: append .Reload(true) to reload the templates on each request. app.Get("/", func(ctx iris.Context) { - ctx.Compress(true) + ctx.CompressWriter(true) ctx.ViewData("", mypage{"My Page title", "Hello world!"}) ctx.View("mypage.html") // Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property diff --git a/_examples/webassembly/main.go b/_examples/webassembly/main.go index 37803c84..941839a9 100644 --- a/_examples/webassembly/main.go +++ b/_examples/webassembly/main.go @@ -17,7 +17,7 @@ func main() { app.HandleDir("/", "./client") app.Get("/", func(ctx iris.Context) { - // ctx.Compress(true) + // ctx.CompressWriter(true) ctx.ServeFile("./client/hello.html") }) diff --git a/aliases.go b/aliases.go index 77f85c6d..8a644f17 100644 --- a/aliases.go +++ b/aliases.go @@ -200,20 +200,10 @@ var ( ) var ( - // Compress is a middleware which enables writing - // using compression, if client supports. - Compress = func(ctx Context) { - ctx.Compress(true) - ctx.Next() - } - // CompressReader is a middleware which enables decompression, - // when client sends compressed data. - // - // Similar to: func(ctx iris.Context) { - // ctx.CompressReader(true) - // ctx.Next() - // } - CompressReader = func(ctx Context) { + // Compression is a middleware which enables + // writing and reading using the best offered compression. + Compression = func(ctx Context) { + ctx.CompressWriter(true) ctx.CompressReader(true) ctx.Next() } diff --git a/context/context.go b/context/context.go index a3270476..352a8457 100644 --- a/context/context.go +++ b/context/context.go @@ -2238,8 +2238,9 @@ func (ctx *Context) StreamWriter(writer func(w io.Writer) error) error { // ClientSupportsEncoding reports whether the // client expects one of the given "encodings" compression. // -// Note, instead of `Compress` method, this one just reports back the first valid encoding it sees, +// Note, this method just reports back the first valid encoding it sees, // meaning that request accept-encoding offers don't matter here. +// See `CompressWriter` too. func (ctx *Context) ClientSupportsEncoding(encodings ...string) bool { if len(encodings) == 0 { return false @@ -2258,20 +2259,20 @@ func (ctx *Context) ClientSupportsEncoding(encodings ...string) bool { return false } -// Compress enables or disables the compress response writer. +// CompressWriter enables or disables the compress response writer. // if the client expects a valid compression algorithm then this // will change the response writer to a compress writer instead. // All future write and rich write methods will respect this option. // Usage: // app.Use(func(ctx iris.Context){ -// err := ctx.Compress(true) +// err := ctx.CompressWriter(true) // ctx.Next() // }) // The recommendation is to compress data as much as possible and therefore to use this field, // but some types of resources, such as jpeg images, are already compressed. // Sometimes, using additional compression doesn't reduce payload size and // can even make the payload longer. -func (ctx *Context) Compress(enable bool) error { +func (ctx *Context) CompressWriter(enable bool) error { cw, ok := ctx.writer.(*CompressResponseWriter) if enable { if ok { @@ -2592,7 +2593,7 @@ var ( ) // WriteJSON marshals the given interface object and writes the JSON response to the 'writer'. -// Ignores StatusCode, Compress, StreamingJSON options. +// Ignores StatusCode and StreamingJSON options. func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (int, error) { var ( result []byte @@ -3176,7 +3177,7 @@ func (ctx *Context) Negotiate(v interface{}) (int, error) { } if encoding != "" { - ctx.Compress(true) + ctx.CompressWriter(true) } ctx.contentTypeOnce(contentType, charset) @@ -3679,7 +3680,8 @@ func (n *NegotiationAcceptBuilder) EncodingGzip() *NegotiationAcceptBuilder { // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range. // // Note that *os.File implements the io.ReadSeeker interface. -// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. +// Note that compression can be registered +// through `ctx.CompressWriter(true)` or `app.Use(iris.Compression)`. func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time) { ctx.ServeContentWithRate(content, filename, modtime, 0, 0) } @@ -3730,7 +3732,8 @@ func (ctx *Context) ServeContentWithRate(content io.ReadSeeker, filename string, // // Use it when you want to serve assets like css and javascript files. // If client should confirm and save the file use the `SendFile` instead. -// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. +// Note that compression can be registered +// through `ctx.CompressWriter(true)` or `app.Use(iris.Compression)`. func (ctx *Context) ServeFile(filename string) error { return ctx.ServeFileWithRate(filename, 0, 0) } @@ -3769,7 +3772,8 @@ func (ctx *Context) ServeFileWithRate(filename string, limit float64, burst int) } // SendFile sends a file as an attachment, that is downloaded and saved locally from client. -// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. +// Note that compression can be registered +// through `ctx.CompressWriter(true)` or `app.Use(iris.Compression)`. // Use `ServeFile` if a file should be served as a page asset instead. func (ctx *Context) SendFile(src string, destName string) error { return ctx.SendFileWithRate(src, destName, 0, 0) diff --git a/context/problem.go b/context/problem.go index 27031b78..b70a427e 100644 --- a/context/problem.go +++ b/context/problem.go @@ -170,6 +170,15 @@ func (p Problem) Detail(detail string) Problem { return p.Key("detail", detail) } +// DetailErr calls `Detail(err.Error())`. +func (p Problem) DetailErr(err error) Problem { + if err == nil { + return p + } + + return p.Key("detail", err.Error()) +} + // Instance sets the problem's instance field. // A URI reference that identifies the specific // occurrence of the problem. It may or may not yield further diff --git a/core/router/fs.go b/core/router/fs.go index e621345f..ea7033fc 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -573,7 +573,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { ctx.ResponseWriter().Header().Set(context.ContentDispositionHeaderKey, "attachment;filename="+destName) } - ctx.Compress(options.Compress) + ctx.CompressWriter(options.Compress) // If limit is 0 then same as ServeContent. ctx.ServeContentWithRate(f, info.Name(), info.ModTime(), options.Attachments.Limit, options.Attachments.Burst) diff --git a/httptest/httptest.go b/httptest/httptest.go index 38a2432e..379e385e 100644 --- a/httptest/httptest.go +++ b/httptest/httptest.go @@ -159,11 +159,12 @@ var ( func Do(w http.ResponseWriter, r *http.Request, handler iris.Handler, irisConfigurators ...iris.Configurator) { app := new(iris.Application) app.Configure(iris.WithConfiguration(iris.DefaultConfiguration()), iris.WithLogLevel("disable")) + app.Configure(irisConfigurators...) + app.HTTPErrorHandler = router.NewDefaultHandler(app.ConfigurationReadOnly(), app.Logger()) app.ContextPool = context.New(func() interface{} { return context.NewContext(app) }) - app.Configure(irisConfigurators...) ctx := app.ContextPool.Acquire(w, r) handler(ctx) diff --git a/iris.go b/iris.go index 586572c3..f98640be 100644 --- a/iris.go +++ b/iris.go @@ -111,8 +111,7 @@ func Default() *Application { app := New() app.Use(recover.New()) app.Use(requestLogger.New()) - app.Use(Compress) - app.Use(CompressReader) + app.Use(Compression) app.defaultMode = true