Skip to content

Commit

Permalink
new x/errors package to handle HTTP wire errors
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Feb 24, 2022
1 parent 8ded69f commit 37c766f
Show file tree
Hide file tree
Showing 9 changed files with 646 additions and 2 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The codebase for Dependency Injection, Internationalization and localization and

## Fixes and Improvements

- New [x/errors](x/errors) sub-package, helps with HTTP Wire Errors. Example can be found [here](_examples/routing/http-wire-errors/main.go).

- New [x/timex](x/timex) sub-package, helps working with weekdays.

- Minor improvements to the [JSON Kitchen Time](x/jsonx/kitchen_time.go).
Expand Down
1 change: 1 addition & 0 deletions _examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
* [Overview](routing/overview/main.go)
* [Basic](routing/basic/main.go)
* [Custom HTTP Errors](routing/http-errors/main.go)
* [HTTP Wire Errors](routing/http-wire-errors/main.go) **NEW**
* [Not Found - Intelligence](routing/intelligence/main.go)
* [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
* [Dynamic Path](routing/dynamic-path/main.go)
Expand Down
1 change: 1 addition & 0 deletions _examples/http-server/listen-addr-public/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
{
Name: "MyApp",
Addr: ":8080",
Hostname: "your-custom-sub-domain.ngrok.io", // optionally
},
},
},
Expand Down
1 change: 1 addition & 0 deletions _examples/routing/http-errors/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/kataras/iris/v12"
)

// See _examples/routing/http-wire-errors as well.
func main() {
app := iris.New()

Expand Down
110 changes: 110 additions & 0 deletions _examples/routing/http-wire-errors/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package main

import (
"github.com/kataras/iris/v12"
// IMPORTANT, import this sub-package.
// Note tht it does NOT break compatibility with the
// standard "errors" package as the New,
// Is, As, Unwrap functions are aliases to the standard package.
"github.com/kataras/iris/v12/x/errors"
)

// Optionally, register custom error codes.
//
// The default list of error code names:
// errors.Cancelled
// errors.Unknown
// errors.InvalidArgument
// errors.DeadlineExceeded
// errors.NotFound
// errors.AlreadyExists
// errors.PermissionDenied
// errors.Unauthenticated
// errors.ResourceExhausted
// errors.FailedPrecondition
// errors.Aborted
// errors.OutOfRange
// errors.Unimplemented
// errors.Internal
// errors.Unavailable
// errors.DataLoss
var (
Custom = errors.E("CUSTOM_CANONICAL_ERROR_NAME", iris.StatusBadRequest)
)

func main() {
app := iris.New()

// Custom error code name.
app.Get("/custom", fireCustomErrorCodeName)

// Send a simple 400 request with message and an error
// or with more details and data.
app.Post("/invalid_argument", fireInvalidArgument)

// Compatibility with the iris.Problem type (and any other custom type).
app.Get("/problem", fireErrorWithProblem)

app.Listen(":8080")
}

func fireCustomErrorCodeName(ctx iris.Context) {
Custom.Details(ctx, "message", "details with arguments: %s", "an argument")
}

func fireInvalidArgument(ctx iris.Context) {
var req = struct {
Username string `json:"username"`
}{}
if err := ctx.ReadJSON(&req); err != nil {
errors.InvalidArgument.Err(ctx, err)
return
}

ctx.WriteString(req.Username)

// Other examples: errors.InvalidArgument/NotFound/Internal and e.t.c.
// .Message(ctx, "message %s", "optional argument")
// .Details(ctx, "message", "details %s", "optional details argument")
// .Data(ctx, "message", anyTypeOfValue)
// .DataWithDetails(ctx, "unable to read the body", "malformed json", iris.Map{"custom": "data of any type"})
// .Log(ctx, "message %s", "optional argument")
// .LogErr(ctx, err)
}

func fireErrorWithProblem(ctx iris.Context) {
myCondition := true
if myCondition {
problem := iris.NewProblem().
// The type URI, if relative it automatically convert to absolute.
Type("/product-error").
// The title, if empty then it gets it from the status code.
Title("Product validation problem").
// Any optional details.
Detail("details about the product error").
// The status error code of the problem, can be optional here.
// Status(iris.StatusBadRequest).
// Any custom key-value pair.
Key("product_name", "the product name")

errors.InvalidArgument.Data(ctx, "unable to process the request", problem)
return

/* Prints to the client:
{
"http_error_code": {
"canonical_name": "INVALID_ARGUMENT",
"status": 400
},
"message": "unable to process the request",
"data": {
"detail": "details about the product error",
"product_name": "the product name",
"title": "Product validation problem",
"type": "/product-error"
}
}
*/
}

}
18 changes: 16 additions & 2 deletions x/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type Client struct {

// Optional handlers that are being fired before and after each new request.
requestHandlers []RequestHandler

// store it here for future use.
keepAlive bool
}

// New returns a new Iris HTTP Client.
Expand All @@ -57,6 +60,10 @@ func New(opts ...Option) *Client {
opt(c)
}

if transport, ok := c.HTTPClient.Transport.(*http.Transport); ok {
c.keepAlive = !transport.DisableKeepAlives
}

return c
}

Expand Down Expand Up @@ -271,6 +278,13 @@ func (c *Client) Do(ctx context.Context, method, urlpath string, payload interfa
return resp, respErr
}

// DrainResponseBody drains response body and close it, allowing the transport to reuse TCP connections.
// It's automatically called on Client.ReadXXX methods on the end.
func (c *Client) DrainResponseBody(resp *http.Response) {
_, _ = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}

const (
acceptKey = "Accept"
contentTypeKey = "Content-Type"
Expand Down Expand Up @@ -377,7 +391,7 @@ func (c *Client) ReadJSON(ctx context.Context, dest interface{}, method, urlpath
if err != nil {
return err
}
defer resp.Body.Close()
defer c.DrainResponseBody(resp)

if resp.StatusCode >= http.StatusBadRequest {
return ExtractError(resp)
Expand All @@ -398,7 +412,7 @@ func (c *Client) ReadPlain(ctx context.Context, dest interface{}, method, urlpat
if err != nil {
return err
}
defer resp.Body.Close()
defer c.DrainResponseBody(resp)

if resp.StatusCode >= http.StatusBadRequest {
return ExtractError(resp)
Expand Down
25 changes: 25 additions & 0 deletions x/errors/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package errors

import (
"errors"
"fmt"
)

var (
// Is is an alias of the standard errors.Is function.
Is = errors.Is
// As is an alias of the standard errors.As function.
As = errors.As
// New is an alias of the standard errors.New function.
New = errors.New
// Unwrap is an alias of the standard errors.Unwrap function.
Unwrap = errors.Unwrap
)

func sprintf(format string, args ...interface{}) string {
if len(args) > 0 {
return fmt.Sprintf(format, args...)
}

return format
}
Loading

0 comments on commit 37c766f

Please sign in to comment.