From 45170bc53e9b7de071b5a740a102051e9f4b139a Mon Sep 17 00:00:00 2001 From: John Date: Thu, 30 Apr 2020 20:37:09 +0800 Subject: [PATCH] add ClientMaxBodySize configuration for ghttp.Server --- .../net/ghttp/client/upload-batch/server.go | 2 +- .example/net/ghttp/client/upload/server.go | 2 +- .example/other/test.go | 4 - net/ghttp/ghttp_server.go | 2 +- net/ghttp/ghttp_server_config.go | 267 ++++++++++++++---- net/ghttp/ghttp_server_config_mess.go | 4 + net/ghttp/ghttp_server_graceful.go | 42 ++- net/ghttp/ghttp_server_handler.go | 4 + net/ghttp/ghttp_unit_config_test.go | 98 +++++++ os/gfile/gfile_size.go | 2 +- os/gview/gview_config.go | 1 + 11 files changed, 355 insertions(+), 73 deletions(-) diff --git a/.example/net/ghttp/client/upload-batch/server.go b/.example/net/ghttp/client/upload-batch/server.go index 7fc13f8e9..482e1a7d6 100644 --- a/.example/net/ghttp/client/upload-batch/server.go +++ b/.example/net/ghttp/client/upload-batch/server.go @@ -9,7 +9,7 @@ import ( func Upload(r *ghttp.Request) { saveDirPath := "/tmp/" files := r.GetUploadFiles("upload-file") - if err := files.Save(saveDirPath); err != nil { + if _, err := files.Save(saveDirPath); err != nil { r.Response.WriteExit(err) } r.Response.WriteExit("upload successfully") diff --git a/.example/net/ghttp/client/upload/server.go b/.example/net/ghttp/client/upload/server.go index 7fc13f8e9..482e1a7d6 100644 --- a/.example/net/ghttp/client/upload/server.go +++ b/.example/net/ghttp/client/upload/server.go @@ -9,7 +9,7 @@ import ( func Upload(r *ghttp.Request) { saveDirPath := "/tmp/" files := r.GetUploadFiles("upload-file") - if err := files.Save(saveDirPath); err != nil { + if _, err := files.Save(saveDirPath); err != nil { r.Response.WriteExit(err) } r.Response.WriteExit("upload successfully") diff --git a/.example/other/test.go b/.example/other/test.go index 942332f4c..151284a4c 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" ) @@ -10,10 +9,7 @@ func main() { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { group.ALL("/", func(r *ghttp.Request) { - fmt.Println(r.GetBodyString()) - fmt.Println(r.Header) r.Response.Write(r.GetBodyString()) - r.Response.Write(r.Header) }) }) s.SetPort(8199) diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 91c0f6330..cb567596a 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -598,7 +598,7 @@ func (s *Server) getListenerFdMap() map[string]string { "http": "", } for _, v := range s.servers { - str := v.itemFunc + "#" + gconv.String(v.Fd()) + "," + str := v.address + "#" + gconv.String(v.Fd()) + "," if v.isHttps { if len(m["https"]) > 0 { m["https"] += "," diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 773d93063..0fdb9962c 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -10,6 +10,7 @@ import ( "crypto/tls" "fmt" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/util/gutil" "net/http" "strconv" "time" @@ -33,55 +34,206 @@ const ( URI_TYPE_CAMEL = 3 // Method name to URI converting type, which converts name to its camel case. ) -// HTTP Server configuration. +// ServerConfig is the HTTP Server configuration manager. type ServerConfig struct { - Address string // Basic: Server listening address like ":port", multiple addresses joined using ','. - HTTPSAddr string // Basic: HTTPS addresses, multiple addresses joined using char ','. - HTTPSCertPath string // Basic: HTTPS certification file path. - HTTPSKeyPath string // Basic: HTTPS key file path. - TLSConfig *tls.Config // Basic: TLS configuration for use by ServeTLS and ListenAndServeTLS. - Handler http.Handler // Basic: Request handler. - ReadTimeout time.Duration // Basic: Maximum duration for reading the entire request, including the body. - WriteTimeout time.Duration // Basic: Maximum duration before timing out writes of the response. - IdleTimeout time.Duration // Basic: Maximum amount of time to wait for the next request when keep-alive is enabled. - MaxHeaderBytes int // Basic: Maximum number of bytes the server will read parsing the request header's keys and values, including the request line. - KeepAlive bool // Basic: Enable HTTP keep-alive. - ServerAgent string // Basic: Server agent information. - View *gview.View // Basic: View object for the server. - Rewrites map[string]string // Static: URI rewrite rules map. - IndexFiles []string // Static: The index files for static folder. - IndexFolder bool // Static: List sub-files when requesting folder; server responses HTTP status code 403 if false. - ServerRoot string // Static: The root directory for static service. - SearchPaths []string // Static: Additional searching directories for static service. - StaticPaths []staticPathItem // Static: URI to directory mapping array. - FileServerEnabled bool // Static: Switch for static service. - CookieMaxAge time.Duration // Cookie: Max TTL for cookie items. - CookiePath string // Cookie: Cookie Path(also affects the default storage for session id). - CookieDomain string // Cookie: Cookie Domain(also affects the default storage for session id). - SessionMaxAge time.Duration // Session: Max TTL for session items. - SessionIdName string // Session: Session id name. - SessionPath string // Session: Session Storage directory path for storing session files. - SessionStorage gsession.Storage // Session: Session Storage implementer. - Logger *glog.Logger // Logging: Logger for server. - LogPath string // Logging: Directory for storing logging files. - LogStdout bool // Logging: Printing logging content to stdout. - ErrorStack bool // Logging: Logging stack information when error. - ErrorLogEnabled bool // Logging: Enable error logging files. - ErrorLogPattern string // Logging: Error log file pattern like: error-{Ymd}.log - AccessLogEnabled bool // Logging: Enable access logging files. - AccessLogPattern string // Logging: Error log file pattern like: access-{Ymd}.log - PProfEnabled bool // PProf: Enable PProf feature. - PProfPattern string // PProf: PProf service pattern for router. - FormParsingMemory int64 // Other: Max memory in bytes which can be used for parsing multimedia form. - NameToUriType int // Other: Type for converting struct method name to URI when registering routes. - RouteOverWrite bool // Other: Allow overwrite the route if duplicated. - DumpRouterMap bool // Other: Whether automatically dump router map when server starts. - Graceful bool // Other: Enable graceful reload feature for all servers of the process. + // ================================== + // Basic. + // ================================== + + // Address specifies the server listening address like "port" or ":port", + // multiple addresses joined using ','. + Address string + + // HTTPSAddr specifies the HTTPS addresses, multiple addresses joined using char ','. + HTTPSAddr string + + // HTTPSCertPath specifies certification file path for HTTPS service. + HTTPSCertPath string + + // HTTPSKeyPath specifies the key file path for HTTPS service. + HTTPSKeyPath string + + // TLSConfig optionally provides a TLS configuration for use + // by ServeTLS and ListenAndServeTLS. Note that this value is + // cloned by ServeTLS and ListenAndServeTLS, so it's not + // possible to modify the configuration with methods like + // tls.Config.SetSessionTicketKeys. To use + // SetSessionTicketKeys, use Server.Serve with a TLS Listener + // instead. + TLSConfig *tls.Config + + // Handler the handler for HTTP request. + Handler http.Handler + + // ReadTimeout is the maximum duration for reading the entire + // request, including the body. + // + // Because ReadTimeout does not let Handlers make per-request + // decisions on each request body's acceptable deadline or + // upload rate, most users will prefer to use + // ReadHeaderTimeout. It is valid to use them both. + ReadTimeout time.Duration + + // WriteTimeout is the maximum duration before timing out + // writes of the response. It is reset whenever a new + // request's header is read. Like ReadTimeout, it does not + // let Handlers make decisions on a per-request basis. + WriteTimeout time.Duration + + // IdleTimeout is the maximum amount of time to wait for the + // next request when keep-alives are enabled. If IdleTimeout + // is zero, the value of ReadTimeout is used. If both are + // zero, there is no timeout. + IdleTimeout time.Duration + + // MaxHeaderBytes controls the maximum number of bytes the + // server will read parsing the request header's keys and + // values, including the request line. It does not limit the + // size of the request body. + // + // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. + // It's 1024 bytes in default. + MaxHeaderBytes int + + // KeepAlive enables HTTP keep-alive. + KeepAlive bool + + // ServerAgent specifies the server agent information, which is wrote to + // HTTP response header as "Server". + ServerAgent string + + // View specifies the default template view object for the server. + View *gview.View + + // ================================== + // Static. + // ================================== + + // Rewrites specifies the URI rewrite rules map. + Rewrites map[string]string + + // IndexFiles specifies the index files for static folder. + IndexFiles []string + + // IndexFolder specifies if listing sub-files when requesting folder. + // The server responses HTTP status code 403 if it is false. + IndexFolder bool + + // ServerRoot specifies the root directory for static service. + ServerRoot string + + // SearchPaths specifies additional searching directories for static service. + SearchPaths []string + + // StaticPaths specifies URI to directory mapping array. + StaticPaths []staticPathItem + + // FileServerEnabled is the global switch for static service. + // It is automatically set enabled if any static path is set. + FileServerEnabled bool + + // ================================== + // Cookie. + // ================================== + + // CookieMaxAge specifies the max TTL for cookie items. + CookieMaxAge time.Duration + + // CookiePath specifies cookie path. + // It also affects the default storage for session id. + CookiePath string + + // CookieDomain specifies cookie domain. + // It also affects the default storage for session id. + CookieDomain string + + // ================================== + // Session. + // ================================== + + // SessionMaxAge specifies max TTL for session items. + SessionMaxAge time.Duration + + // SessionIdName specifies the session id name. + SessionIdName string + + // SessionPath specifies the session storage directory path for storing session files. + // It only makes sense if the session storage is type of file storage. + SessionPath string + + // SessionStorage specifies the session storage. + SessionStorage gsession.Storage + + // ================================== + // Logging. + // ================================== + + // Logger specifies the logger for server. + Logger *glog.Logger + + // LogPath specifies the directory for storing logging files. + LogPath string + + // LogStdout specifies whether printing logging content to stdout. + LogStdout bool + + // ErrorStack specifies whether logging stack information when error. + ErrorStack bool + + // ErrorLogEnabled enables error logging content to files. + ErrorLogEnabled bool + + // ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + ErrorLogPattern string + + // AccessLogEnabled enables access logging content to files. + AccessLogEnabled bool + + // AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log + AccessLogPattern string + + // ================================== + // PProf. + // ================================== + + // PProfEnabled enables PProf feature. + PProfEnabled bool + + // PProfPattern specifies the PProf service pattern for router. + PProfPattern string + + // ================================== + // Other. + // ================================== + + // ClientMaxBodySize specifies the max body size limit in bytes for client request. + // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. + // It's 8MB in default. + ClientMaxBodySize int64 + + // FormParsingMemory specifies max memory buffer size in bytes which can be used for + // parsing multimedia form. + // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. + // It's 1MB in default. + FormParsingMemory int64 + + // NameToUriType specifies the type for converting struct method name to URI when + // registering routes. + NameToUriType int + + // RouteOverWrite allows overwrite the route if duplicated. + RouteOverWrite bool + + // DumpRouterMap specifies whether automatically dumps router map when server starts. + DumpRouterMap bool + + // Graceful enables graceful reload feature for all servers of the process. + Graceful bool } -// Config returns the default ServerConfig object. -// Note that, do not define this default configuration to local variable, -// as there're some pointer attributes that may be shared in different servers. +// Config creates and returns a ServerConfig object with default configurations. +// Note that, do not define this default configuration to local package variable, as there're +// some pointer attributes that may be shared in different servers. func Config() ServerConfig { return ServerConfig{ Address: "", @@ -112,7 +264,8 @@ func Config() ServerConfig { AccessLogEnabled: false, AccessLogPattern: "access-{Ymd}.log", DumpRouterMap: true, - FormParsingMemory: 100 * 1024 * 1024, // 100MB + ClientMaxBodySize: 8 * 1024 * 1024, // 8MB + FormParsingMemory: 1024 * 1024, // 1MB Rewrites: make(map[string]string), Graceful: true, } @@ -130,6 +283,21 @@ func ConfigFromMap(m map[string]interface{}) (ServerConfig, error) { // SetConfigWithMap sets the configuration for the server using map. func (s *Server) SetConfigWithMap(m map[string]interface{}) error { + // The m now is a shallow copy of m. + // Any changes to m does not affect the original one. + // A little tricky, isn't it? + m = gutil.MapCopy(m) + // Allow setting the size configuration items using string size like: + // 1m, 100mb, 512kb, etc. + if k, v := gutil.MapPossibleItemByKey(m, "MaxHeaderBytes"); k != "" { + m[k] = gfile.StrToSize(gconv.String(v)) + } + if k, v := gutil.MapPossibleItemByKey(m, "ClientMaxBodySize"); k != "" { + m[k] = gfile.StrToSize(gconv.String(v)) + } + if k, v := gutil.MapPossibleItemByKey(m, "FormParsingMemory"); k != "" { + m[k] = gfile.StrToSize(gconv.String(v)) + } // Update the current configuration object. if err := gconv.Struct(m, &s.config); err != nil { return err @@ -156,7 +324,6 @@ func (s *Server) SetConfig(c ServerConfig) error { s.EnableHTTPS(c.HTTPSCertPath, c.HTTPSKeyPath) } SetGraceful(c.Graceful) - intlog.Printf("SetConfig: %+v", s.config) return nil } @@ -168,7 +335,7 @@ func (s *Server) SetAddr(address string) { } // SetPort sets the listening ports for the server. -// The listening ports can be multiple. +// The listening ports can be multiple like: SetPort(80, 8080). func (s *Server) SetPort(port ...int) { if len(port) > 0 { s.config.Address = "" @@ -187,7 +354,7 @@ func (s *Server) SetHTTPSAddr(address string) { } // SetHTTPSPort sets the HTTPS listening ports for the server. -// The listening ports can be multiple. +// The listening ports can be multiple like: SetHTTPSPort(443, 500). func (s *Server) SetHTTPSPort(port ...int) { if len(port) > 0 { s.config.HTTPSAddr = "" diff --git a/net/ghttp/ghttp_server_config_mess.go b/net/ghttp/ghttp_server_config_mess.go index 505a2d4ad..1208cc5fc 100644 --- a/net/ghttp/ghttp_server_config_mess.go +++ b/net/ghttp/ghttp_server_config_mess.go @@ -14,6 +14,10 @@ func (s *Server) SetDumpRouterMap(enabled bool) { s.config.DumpRouterMap = enabled } +func (s *Server) SetClientMaxBodySize(maxSize int64) { + s.config.ClientMaxBodySize = maxSize +} + func (s *Server) SetFormParsingMemory(maxMemory int64) { s.config.FormParsingMemory = maxMemory } diff --git a/net/ghttp/ghttp_server_graceful.go b/net/ghttp/ghttp_server_graceful.go index ac7b4305b..da48a4c11 100644 --- a/net/ghttp/ghttp_server_graceful.go +++ b/net/ghttp/ghttp_server_graceful.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "github.com/gogf/gf/os/gproc" + "github.com/gogf/gf/text/gstr" "log" "net" "net/http" @@ -22,7 +23,7 @@ import ( type gracefulServer struct { server *Server // Belonged server. fd uintptr // 热重启时传递的socket监听文件句柄 - itemFunc string // 监听地址信息 + address string // 监听地址信息 httpServer *http.Server // 底层http.Server rawListener net.Listener // 原始listener listener net.Listener // 接口化封装的listener @@ -31,11 +32,15 @@ type gracefulServer struct { } // 创建一个优雅的Http Server -func (s *Server) newGracefulServer(itemFunc string, fd ...int) *gracefulServer { +func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer { + // Change port to address like: 80 -> :80 + if gstr.IsNumeric(address) { + address = ":" + address + } gs := &gracefulServer{ server: s, - itemFunc: itemFunc, - httpServer: s.newHttpServer(itemFunc), + address: address, + httpServer: s.newHttpServer(address), } // 是否有继承的文件描述符 if len(fd) > 0 && fd[0] > 0 { @@ -45,9 +50,9 @@ func (s *Server) newGracefulServer(itemFunc string, fd ...int) *gracefulServer { } // 生成一个底层的Web Server对象 -func (s *Server) newHttpServer(itemFunc string) *http.Server { +func (s *Server) newHttpServer(address string) *http.Server { server := &http.Server{ - Addr: itemFunc, + Addr: address, Handler: s.config.Handler, ReadTimeout: s.config.ReadTimeout, WriteTimeout: s.config.WriteTimeout, @@ -61,8 +66,7 @@ func (s *Server) newHttpServer(itemFunc string) *http.Server { // 执行HTTP监听 func (s *gracefulServer) ListenAndServe() error { - itemFunc := s.httpServer.Addr - ln, err := s.getNetListener(itemFunc) + ln, err := s.getNetListener() if err != nil { return err } @@ -89,7 +93,6 @@ func (s *gracefulServer) setFd(fd int) { // 执行HTTPS监听 func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig ...*tls.Config) error { - itemFunc := s.httpServer.Addr var config *tls.Config if len(tlsConfig) > 0 && tlsConfig[0] != nil { config = tlsConfig[0] @@ -109,7 +112,7 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig . if err != nil { return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error())) } - ln, err := s.getNetListener(itemFunc) + ln, err := s.getNetListener() if err != nil { return err } @@ -134,7 +137,10 @@ func (s *gracefulServer) doServe() error { if s.fd != 0 { action = "reloaded" } - s.server.Logger().Printf("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.itemFunc) + s.server.Logger().Printf( + "%d: %s server %s listening on [%s]", + gproc.Pid(), s.getProto(), action, s.address, + ) s.status = SERVER_STATUS_RUNNING err := s.httpServer.Serve(s.listener) s.status = SERVER_STATUS_STOPPED @@ -142,7 +148,7 @@ func (s *gracefulServer) doServe() error { } // 自定义的net.Listener -func (s *gracefulServer) getNetListener(itemFunc string) (net.Listener, error) { +func (s *gracefulServer) getNetListener() (net.Listener, error) { var ln net.Listener var err error if s.fd > 0 { @@ -153,7 +159,7 @@ func (s *gracefulServer) getNetListener(itemFunc string) (net.Listener, error) { return nil, err } } else { - ln, err = net.Listen("tcp", itemFunc) + ln, err = net.Listen("tcp", s.httpServer.Addr) if err != nil { err = fmt.Errorf("%d: net.Listen error: %v", gproc.Pid(), err) } @@ -167,7 +173,10 @@ func (s *gracefulServer) shutdown() { return } if err := s.httpServer.Shutdown(context.Background()); err != nil { - s.server.Logger().Errorf("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.itemFunc, err) + s.server.Logger().Errorf( + "%d: %s server [%s] shutdown error: %v", + gproc.Pid(), s.getProto(), s.address, err, + ) } } @@ -177,6 +186,9 @@ func (s *gracefulServer) close() { return } if err := s.httpServer.Close(); err != nil { - s.server.Logger().Errorf("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.itemFunc, err) + s.server.Logger().Errorf( + "%d: %s server [%s] closed error: %v", + gproc.Pid(), s.getProto(), s.address, err, + ) } } diff --git a/net/ghttp/ghttp_server_handler.go b/net/ghttp/ghttp_server_handler.go index 6a788fe41..362797e73 100644 --- a/net/ghttp/ghttp_server_handler.go +++ b/net/ghttp/ghttp_server_handler.go @@ -26,6 +26,10 @@ import ( // 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Max body size limit. + if s.config.ClientMaxBodySize > 0 { + r.Body = http.MaxBytesReader(w, r.Body, s.config.ClientMaxBodySize) + } // 重写规则判断 if len(s.config.Rewrites) > 0 { if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok { diff --git a/net/ghttp/ghttp_unit_config_test.go b/net/ghttp/ghttp_unit_config_test.go index e00de18e1..45a049cfd 100644 --- a/net/ghttp/ghttp_unit_config_test.go +++ b/net/ghttp/ghttp_unit_config_test.go @@ -7,6 +7,10 @@ package ghttp_test import ( + "fmt" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/text/gstr" "testing" "time" @@ -58,3 +62,97 @@ func Test_SetConfigWithMap(t *testing.T) { t.Assert(err, nil) }) } + +func Test_ClientMaxBodySize(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.POST("/", func(r *ghttp.Request) { + r.Response.Write(r.GetBodyString()) + }) + }) + m := g.Map{ + "Address": p, + "ClientMaxBodySize": "1k", + } + gtest.Assert(s.SetConfigWithMap(m), nil) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + data := make([]byte, 1056) + for i := 0; i < 1056; i++ { + data[i] = 'a' + } + t.Assert( + gstr.Trim(c.PostContent("/", data)), + data[:1024], + ) + }) +} + +func Test_ClientMaxBodySize_File(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.POST("/", func(r *ghttp.Request) { + r.GetUploadFile("file") + r.Response.Write("ok") + }) + }) + m := g.Map{ + "Address": p, + "ErrorLogEnabled": false, + "ClientMaxBodySize": "1k", + } + gtest.Assert(s.SetConfigWithMap(m), nil) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + + // ok + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + path := gfile.TempDir(gtime.TimestampNanoStr()) + data := make([]byte, 512) + for i := 0; i < 512; i++ { + data[i] = 'a' + } + t.Assert(gfile.PutBytes(path, data), nil) + defer gfile.Remove(path) + t.Assert( + gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)), + "ok", + ) + }) + + // too large + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + path := gfile.TempDir(gtime.TimestampNanoStr()) + data := make([]byte, 1056) + for i := 0; i < 1056; i++ { + data[i] = 'a' + } + t.Assert(gfile.PutBytes(path, data), nil) + defer gfile.Remove(path) + t.Assert( + gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)), + "http: request body too large", + ) + }) +} diff --git a/os/gfile/gfile_size.go b/os/gfile/gfile_size.go index 402ddbb0c..37d08e947 100644 --- a/os/gfile/gfile_size.go +++ b/os/gfile/gfile_size.go @@ -27,7 +27,7 @@ func ReadableSize(path string) string { return FormatSize(Size(path)) } -// StrToSize converts formatted size string to its size in bytes from Bytes to BrontoByte. +// StrToSize converts formatted size string to its size in bytes. func StrToSize(sizeStr string) int64 { i := 0 for ; i < len(sizeStr); i++ { diff --git a/os/gview/gview_config.go b/os/gview/gview_config.go index b589fc35c..2404c2f56 100644 --- a/os/gview/gview_config.go +++ b/os/gview/gview_config.go @@ -77,6 +77,7 @@ func (view *View) SetConfigWithMap(m map[string]interface{}) error { return errors.New("configuration cannot be empty") } // The m now is a shallow copy of m. + // Any changes to m does not affect the original one. // A little tricky, isn't it? m = gutil.MapCopy(m) // Most common used configuration support for single view path.