minor improvements

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-31 09:40:27 +03:00
parent 5700690dea
commit 8d99b46801
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
6 changed files with 66 additions and 49 deletions

View File

@ -599,6 +599,7 @@ New Package-level Variables:
New Context Methods: New Context Methods:
- `Context.SaveFormFile(fh *multipart.FileHeader, dest string) (int64, error)` previously unexported. Accepts a result file of `Context.FormFile` and saves it to the disk.
- `Context.URLParamSlice(name string) []string` is a a shortcut of `ctx.Request().URL.Query()[name]`. Like `URLParam` but it returns all values as a string slice instead of a single string separated by commas. - `Context.URLParamSlice(name string) []string` is a a shortcut of `ctx.Request().URL.Query()[name]`. Like `URLParam` but it returns all values as a string slice instead of a single string separated by commas.
- `Context.PostValueMany(name string) (string, error)` returns the post data of a given key. The returned value is a single string separated by commas on multiple values. It also reports whether the form was empty or when the "name" does not exist or whether the available values are empty. It strips any empty key-values from the slice before return. See `ErrEmptyForm`, `ErrNotFound` and `ErrEmptyFormField` respectfully. The `PostValueInt`, `PostValueInt64`, `PostValueFloat64` and `PostValueBool` now respect the above errors too (the `PostValues` method now returns a second output argument of `error` too, see breaking changes below). - `Context.PostValueMany(name string) (string, error)` returns the post data of a given key. The returned value is a single string separated by commas on multiple values. It also reports whether the form was empty or when the "name" does not exist or whether the available values are empty. It strips any empty key-values from the slice before return. See `ErrEmptyForm`, `ErrNotFound` and `ErrEmptyFormField` respectfully. The `PostValueInt`, `PostValueInt64`, `PostValueFloat64` and `PostValueBool` now respect the above errors too (the `PostValues` method now returns a second output argument of `error` too, see breaking changes below).
- `Context.URLParamsSorted() []memstore.StringEntry` returns a sorted (by key) slice of key-value entries of the URL Query parameters. - `Context.URLParamsSorted() []memstore.StringEntry` returns a sorted (by key) slice of key-value entries of the URL Query parameters.
@ -637,6 +638,7 @@ New Context Methods:
Breaking Changes: Breaking Changes:
- `ContextUploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader) bool) (uploaded []*multipart.FileHeader, n int64, err error)` now returns the total files uploaded too (as its first parameter) and the "before" variadic option should return a boolean, if false then the specific file is skipped.
- `Context.PostValues(name string) ([]string, error)` now returns a second output argument of `error` type too, which reports `ErrEmptyForm` or `ErrNotFound` or `ErrEmptyFormField`. The single post value getters now returns the **last value** if multiple was given instead of the first one (this allows clients to append values on flow updates). - `Context.PostValues(name string) ([]string, error)` now returns a second output argument of `error` type too, which reports `ErrEmptyForm` or `ErrNotFound` or `ErrEmptyFormField`. The single post value getters now returns the **last value** if multiple was given instead of the first one (this allows clients to append values on flow updates).
- `Party.GetReporter()` **removed**. The `Application.Build` returns the first error now and the API's errors are logged, this allows the server to run even if some of the routes are invalid but not fatal to the entire application (it was a request from a company). - `Party.GetReporter()` **removed**. The `Application.Build` returns the first error now and the API's errors are logged, this allows the server to run even if some of the routes are invalid but not fatal to the entire application (it was a request from a company).
- `versioning.NewGroup(string)` now accepts a `Party` as its first input argument: `NewGroup(Party, string)`. - `versioning.NewGroup(string)` now accepts a `Party` as its first input argument: `NewGroup(Party, string)`.

View File

@ -102,7 +102,7 @@ func uploadView(ctx iris.Context) {
func upload(ctx iris.Context) { func upload(ctx iris.Context) {
ctx.SetMaxRequestBodySize(maxSize) ctx.SetMaxRequestBodySize(maxSize)
_, err := ctx.UploadFormFiles(uploadDir, beforeSave) _, _, err := ctx.UploadFormFiles(uploadDir, beforeSave)
if err != nil { if err != nil {
ctx.StopWithError(iris.StatusPayloadTooRage, err) ctx.StopWithError(iris.StatusPayloadTooRage, err)
return return
@ -111,12 +111,13 @@ func upload(ctx iris.Context) {
ctx.Redirect("/files") ctx.Redirect("/files")
} }
func beforeSave(ctx iris.Context, file *multipart.FileHeader) { func beforeSave(ctx iris.Context, file *multipart.FileHeader) bool {
ip := ctx.RemoteAddr() ip := ctx.RemoteAddr()
ip = strings.ReplaceAll(ip, ".", "_") ip = strings.ReplaceAll(ip, ".", "_")
ip = strings.ReplaceAll(ip, ":", "_") ip = strings.ReplaceAll(ip, ":", "_")
file.Filename = ip + "-" + file.Filename file.Filename = ip + "-" + file.Filename
return true
} }
func deleteFile(ctx iris.Context) { func deleteFile(ctx iris.Context) {

View File

@ -5,8 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -66,7 +64,7 @@ func newApp() *iris.Application {
files := form.File["files[]"] files := form.File["files[]"]
failures := 0 failures := 0
for _, file := range files { for _, file := range files {
_, err = saveUploadedFile(file, "./uploads") _, err = ctx.SaveFormFile(file, "./uploads/"+file.Filename)
if err != nil { if err != nil {
failures++ failures++
ctx.Writef("failed to upload: %s\n", file.Filename) ctx.Writef("failed to upload: %s\n", file.Filename)
@ -78,24 +76,7 @@ func newApp() *iris.Application {
return app return app
} }
func saveUploadedFile(fh *multipart.FileHeader, destDirectory string) (int64, error) { func beforeSave(ctx iris.Context, file *multipart.FileHeader) bool {
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)
}
func beforeSave(ctx iris.Context, file *multipart.FileHeader) {
ip := ctx.RemoteAddr() ip := ctx.RemoteAddr()
// make sure you format the ip in a way // make sure you format the ip in a way
// that can be used for a file name (simple case): // that can be used for a file name (simple case):
@ -109,8 +90,9 @@ func beforeSave(ctx iris.Context, file *multipart.FileHeader) {
// no need for more actions, internal uploader will use this // no need for more actions, internal uploader will use this
// name to save the file into the "./uploads" folder. // name to save the file into the "./uploads" folder.
if ip == "" { if ip == "" {
return return true // don't change the file but continue saving it.
} }
file.Filename = ip + "-" + file.Filename file.Filename = ip + "-" + file.Filename
return true
} }

View File

@ -30,7 +30,7 @@ func readBody(ctx iris.Context) {
var p payload var p payload
// Bind request body to "p" depending on the content-type that client sends the data, // Bind request body to "p" depending on the content-type that client sends the data,
// e.g. JSON, XML, YAML, MessagePack, Form, URL Query. // e.g. JSON, XML, YAML, MessagePack, Protobuf, Form and URL Query.
err := ctx.ReadBody(&p) err := ctx.ReadBody(&p)
if err != nil { if err != nil {
ctx.StopWithProblem(iris.StatusBadRequest, ctx.StopWithProblem(iris.StatusBadRequest,

View File

@ -1925,7 +1925,7 @@ func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader,
// to the system physical location "destDirectory". // to the system physical location "destDirectory".
// //
// The second optional argument "before" gives caller the chance to // The second optional argument "before" gives caller the chance to
// modify the *miltipart.FileHeader before saving to the disk, // modify or cancel the *miltipart.FileHeader before saving to the disk,
// it can be used to change a file's name based on the current request, // it can be used to change a file's name based on the current request,
// all FileHeader's options can be changed. You can ignore it if // all FileHeader's options can be changed. You can ignore it if
// you don't need to use this capability before saving a file to the disk. // you don't need to use this capability before saving a file to the disk.
@ -1938,52 +1938,60 @@ func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader,
// http.ErrMissingFile if no file received. // http.ErrMissingFile if no file received.
// //
// If you want to receive & accept files and manage them manually you can use the `context#FormFile` // If you want to receive & accept files and manage them manually you can use the `context#FormFile`
// instead and create a copy function that suits your needs, the below is for generic usage. // instead and create a copy function that suits your needs or use the `SaveFormFile` method,
// the below is for generic usage.
// //
// The default form's memory maximum size is 32MB, it can be changed by the // The default form's memory maximum size is 32MB, it can be changed by
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. // the `WithPostMaxMemory` configurator or by `SetMaxRequestBodySize` or
// by the `LimitRequestBodySize` middleware (depends the use case).
// //
// See `FormFile` to a more controlled to receive a file. // See `FormFile` to a more controlled way to receive a file.
// //
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files
func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader)) (n int64, err error) { func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader) bool) (uploaded []*multipart.FileHeader, n int64, err error) {
err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()) err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory())
if err != nil { if err != nil {
return 0, err return nil, 0, err
} }
if ctx.request.MultipartForm != nil { if ctx.request.MultipartForm != nil {
if fhs := ctx.request.MultipartForm.File; fhs != nil { if fhs := ctx.request.MultipartForm.File; fhs != nil {
for _, files := range fhs { for _, files := range fhs {
innerLoop:
for _, file := range files { for _, file := range files {
for _, b := range before { for _, b := range before {
b(ctx, file) if !b(ctx, file) {
continue innerLoop
}
} }
n0, err0 := uploadTo(file, destDirectory) n0, err0 := ctx.SaveFormFile(file, filepath.Join(destDirectory, file.Filename))
if err0 != nil { if err0 != nil {
return 0, err0 return nil, 0, err0
} }
n += n0 n += n0
uploaded = append(uploaded, file)
} }
} }
return n, nil return uploaded, n, nil
} }
} }
return 0, http.ErrMissingFile return nil, 0, http.ErrMissingFile
} }
func uploadTo(fh *multipart.FileHeader, destDirectory string) (int64, error) { // SaveFormFile saves a result of `FormFile` to the "dest" disk full path (directory + filename).
// See `FormFile` and `UploadFormFiles` too.
func (ctx *Context) SaveFormFile(fh *multipart.FileHeader, dest string) (int64, error) {
src, err := fh.Open() src, err := fh.Open()
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer src.Close() defer src.Close()
out, err := os.OpenFile(filepath.Join(destDirectory, fh.Filename), out, err := os.Create(dest)
os.O_WRONLY|os.O_CREATE, os.FileMode(0666))
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -2204,6 +2212,35 @@ var (
// Usage: errors.Is(err, ErrEmptyFormField) // Usage: errors.Is(err, ErrEmptyFormField)
// See postValue method. It's only returned on parsed post value methods. // See postValue method. It's only returned on parsed post value methods.
ErrEmptyFormField = errors.New("empty form field") ErrEmptyFormField = errors.New("empty form field")
// ConnectionCloseErrorSubstr if at least one of the given
// substrings are found in a net.OpError:os.SyscallError error type
// on `IsErrConnectionReset` then the function will report true.
ConnectionCloseErrorSubstr = []string{
"broken pipe",
"connection reset by peer",
}
// IsErrConnectionClosed reports whether the given "err"
// is caused because of a broken connection.
IsErrConnectionClosed = func(err error) bool {
if err == nil {
return false
}
if opErr, ok := err.(*net.OpError); ok {
if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {
errStr := strings.ToLower(syscallErr.Error())
for _, s := range ConnectionCloseErrorSubstr {
if strings.Contains(errStr, s) {
return true
}
}
}
}
return false
}
) )
// ReadForm binds the request body of a form to the "formObject". // ReadForm binds the request body of a form to the "formObject".

View File

@ -3,8 +3,8 @@ package recover
import ( import (
"fmt" "fmt"
"net/http/httputil"
"runtime" "runtime"
"strconv"
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
) )
@ -14,13 +14,8 @@ func init() {
} }
func getRequestLogs(ctx *context.Context) string { func getRequestLogs(ctx *context.Context) string {
var status, ip, method, path string rawReq, _ := httputil.DumpRequest(ctx.Request(), false)
status = strconv.Itoa(ctx.GetStatusCode()) return string(rawReq)
path = ctx.Path()
method = ctx.Method()
ip = ctx.RemoteAddr()
// the date should be logged by iris' Logger, so we skip them
return fmt.Sprintf("%v %s %s %s", status, path, method, ip)
} }
// New returns a new recover middleware, // New returns a new recover middleware,
@ -30,7 +25,7 @@ func New() context.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
if ctx.IsStopped() { if ctx.IsStopped() { // handled by other middleware.
return return
} }