From 1ec8452208b1cd30a48d09e36cad31b68ca5c703 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 4 Aug 2018 13:08:18 +0300 Subject: [PATCH] simplify readme Former-commit-id: 95158c51fc01195b60f9ee873ab416c314568198 --- README.md | 248 ++++++------------------------------------------------ 1 file changed, 27 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index 85b98927..60de9f3d 100644 --- a/README.md +++ b/README.md @@ -211,128 +211,15 @@ id: 1234; page: 1; name: manu; message: this_is_great ### Upload files -#### Single file - -Detail [example code](_examples/http_request/upload-file/main.go). +- [single file upload](_examples/http_request/upload-file/main.go) +- [multi file upload](_examples/http_request/upload-files) ```go const maxSize = 5 << 20 // 5MB -func main() { - app.Post("/upload", func(ctx iris.Context) { - // Get the file from the request. - file, info, err := ctx.FormFile("file") - if err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.HTML("Error while uploading: " + err.Error() + "") - return - } - - defer file.Close() - fname := info.Filename - - // Create a file with the same name - // assuming that you have a folder named 'uploads' - out, err := os.OpenFile("./uploads/"+fname, - os.O_WRONLY|os.O_CREATE, 0666) - - if err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.HTML("Error while uploading: " + err.Error() + "") - return - } - defer out.Close() - - io.Copy(out, file) - }) - - // start the server at http://localhost:8080 with post limit at 5 MB - // (defaults to 32MB, read below). - app.Run(iris.Addr(":8080"), iris.WithPostMaxMemory(maxSize)) -} -``` - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@./myfile.zip" \ - -H "Content-Type: multipart/form-data" -``` - -* The default post max size is 32MB, -you can extend it to read more data using the `iris.WithPostMaxMemory(maxSize)` configurator at `app.Run`, -note that this will not be enough for your needs, read below. - -* The faster way to check the size is using the `ctx.GetContentLength()` which returns the whole request's size -(plus a logical number like 2MB or even 10MB for the rest of the size like headers). You can create a -middleware to adapt this to any necessary handler. - -```go -myLimiter := func(ctx iris.Context) { - if ctx.GetContentLength() > maxSize { // + 2 << 20 { - ctx.StatusCode(iris.StatusRequestEntityTooLarge) - return - } - ctx.Next() -} - -app.Post("/upload", myLimiter, myUploadHandler) -``` - -* Most clients will set the "Content-Length" header (like browsers) but it's always better to make sure that any client -can't send data that your server can't or doesn't want to handle. This can be happen using -the `app.Use(LimitRequestBodySize(maxSize))` (as app or route middleware) -or the `ctx.SetMaxRequestBodySize(maxSize)` to limit the request based on a customized logic inside a particular handler, they're the same, -read below. - -* You can force-limit the request body size inside a handler using the `ctx.SetMaxRequestBodySize(maxSize)`, -this will force the connection to close if the incoming data are larger (most clients will receive it as "connection reset"), -use that to make sure that the client will not send data that your server can't or doesn't want to accept, as a fallback. - -```go -app.Post("/upload", iris.LimitRequestBodySize(maxSize), myUploadHandler) -``` - -OR - -```go -app.Post("/upload", func(ctx iris.Context){ - ctx.SetMaxRequestBodySize(maxSize) - - // [...] -}) -``` - -* Another way is to receive the data and check the second return value's `Size` value of the `ctx.FormFile`, i.e `info.Size`, this will give you -the exact file size, not the whole incoming request data length. - -```go -app.Post("/upload", func(ctx iris.Context){ - file, info, err := ctx.FormFile("file") - if err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.HTML("Error while uploading: " + err.Error() + "") - return - } - - defer file.Close() - - if info.Size > maxSize { - ctx.StatusCode(iris.StatusRequestEntityTooLarge) - return - } - - // [...] -}) -``` - -#### Multiple files (easy way) - -See the detail [example code](_examples/http_request/upload-files). - -```go func main() { app := iris.Default() - app.Post("/upload", func(ctx iris.Context) { + app.Post("/upload", iris.LimitRequestBodySize(maxSize), func(ctx iris.Context) { // // UploadFormFiles // uploads any number of incoming files ("multiple" property on the form input). @@ -365,56 +252,6 @@ func beforeSave(ctx iris.Context, file *multipart.FileHeader) { } ``` -#### Multiple files (manual way) - -```go -app.Post("/upload_manual", func(ctx iris.Context) { - r := ctx.Request() - // Get the max post value size passed via iris.WithPostMaxMemory. - maxSize := ctx.Application().ConfigurationReadOnly().GetPostMaxMemory() - - err := r.ParseMultipartForm(maxSize) - if err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.WriteString(err.Error()) - return - } - - form := r.MultipartForm - - files := form.File["files[]"] - failures := 0 - for _, file := range files { - _, err = saveUploadedFile(file, "./uploads") - if err != nil { - failures++ - ctx.Writef("failed to upload: %s\n", file.Filename) - } - } - ctx.Writef("%d files uploaded", len(files)-failures) -}) -``` - -```go -func saveUploadedFile(fh *multipart.FileHeader, destDirectory string) (int64, error) { - src, err := fh.Open() - if err != nil { - return 0, err - } - defer src.Close() - - out, err := os.OpenFile(filepath.Join(destDirectory, fh.Filename), - os.O_WRONLY|os.O_CREATE, os.FileMode(0666)) - - if err != nil { - return 0, err - } - defer out.Close() - - return io.Copy(out, src) -} -``` - How to `curl`: ```bash @@ -585,6 +422,8 @@ func main() { Iris uses [**go-playground/validator.v9**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v9#hdr-Baked_In_Validators_and_Tags). +Example [detail code](_examples/http_request/read-json-struct-validation/main.go). + Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. ```go @@ -660,79 +499,48 @@ func main() { fmt.Println(err.Value()) fmt.Println(err.Param()) fmt.Println() - - // Or collect these as json objects - // and send back to the client the collected errors via ctx.JSON - // { - // "namespace": err.Namespace(), - // "field": err.Field(), - // "struct_namespace": err.StructNamespace(), - // "struct_field": err.StructField(), - // "tag": err.Tag(), - // "actual_tag": err.ActualTag(), - // "kind": err.Kind().String(), - // "type": err.Type().String(), - // "value": fmt.Sprintf("%v", err.Value()), - // "param": err.Param(), - // } } - // from here you can create your own error messages in whatever language you wish. return } // save user to database. }) - // use Postman or whatever to do a POST request - // to the http://localhost:8080/user with RAW BODY: - /* - { - "fname": "", - "lname": "", - "age": 45, - "email": "mail@example.com", - "favColor": "#000", - "addresses": [{ - "street": "Eavesdown Docks", - "planet": "Persphone", - "phone": "none", - "city": "Unknown" - }] - } - */ - // Content-Type to application/json (optionally but good practise). - // This request will fail due to the empty `User.FirstName` (fname in json) - // and `User.LastName` (lname in json). - // Check your iris' application terminal output. - app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) + app.Run(iris.Addr(":8080")) } -// UserStructLevelValidation contains custom struct level validations that don't always -// make sense at the field validation level. For Example this function validates that either -// FirstName or LastName exist; could have done that with a custom field validation but then -// would have had to add it to both fields duplicating the logic + overhead, this way it's -// only validated once. -// -// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way -// hooks right into validator and you can combine with validation tags and still have a -// common error output format. func UserStructLevelValidation(sl validator.StructLevel) { - user := sl.Current().Interface().(User) if len(user.FirstName) == 0 && len(user.LastName) == 0 { sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "") sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "") } - - // plus can to more, even with different tag than "fnameorlname". } ``` -## Testing +```json +{ + "fname": "", + "lname": "", + "age": 45, + "email": "mail@example.com", + "favColor": "#000", + "addresses": [{ + "street": "Eavesdown Docks", + "planet": "Persphone", + "phone": "none", + "city": "Unknown" + }] +} +``` -First, let's write a simple application which will make use of the HTTP Cookies. +### Cookies + +> Are you looking about [http sessions instead?](_examples/sessions) + +Let's write a simple application which will make use of the HTTP Cookies. ```sh $ cat _examples/cookies/basic/main.go @@ -795,9 +603,7 @@ func main() { * `ctx.Request().Cookie(name)` is also available, it's the `net/http` approach * Learn more about path parameter's types by clicking [here](_examples/routing/dynamic-path/main.go#L31). -### httptest - -Next, is the critical part of this section, the **HTTP Testing**. +### Testing Iris offers an incredible support for the [httpexpect](github.com/iris-contrib/httpexpect), a Testing Framework for web applications. However, you are able to use the standard Go's `net/http/httptest` package as well but in this example we will use the `kataras/iris/httptest`.