diff --git a/params.go b/params.go index 73f6168ed..ee2abb534 100644 --- a/params.go +++ b/params.go @@ -6,12 +6,13 @@ package revel import ( "encoding/json" + "errors" + "io" "io/ioutil" "mime/multipart" "net/url" "os" "reflect" - "errors" ) // Params provides a unified view of the request params. @@ -32,6 +33,7 @@ type Params struct { Query url.Values // Parameters from the query string, e.g. /index?limit=10 Form url.Values // Parameters from the request body. + Body io.ReadCloser Files map[string][]*multipart.FileHeader // Files uploaded in a multipart form tmpFiles []*os.File // Temp files used during the request. JSON []byte // JSON data from request body @@ -40,6 +42,7 @@ type Params struct { // ParseParams parses the `http.Request` params into `revel.Controller.Params` func ParseParams(params *Params, req *Request) { params.Query = req.URL.Query() + params.Body = req.Body // Parse the body depending on the content type. switch req.ContentType { @@ -60,24 +63,19 @@ func ParseParams(params *Params, req *Request) { params.Form = req.MultipartForm.Value params.Files = req.MultipartForm.File } - case "application/json": - fallthrough - case "text/json": - if req.Body != nil { - if content, err := ioutil.ReadAll(req.Body); err == nil { - // We wont bind it until we determine what we are binding too - params.JSON = content - } else { - ERROR.Println("Failed to ready request body bytes", err) - } - } else { - INFO.Println("Json post received with empty body") - } } params.Values = params.calcValues() } +func (p *Params) GetBody() (bytes []byte, err error) { + if p.Body == nil { + return nil, errors.New("Nil request body") + } + bytes, err = ioutil.ReadAll(p.Body) + return +} + // Bind looks for the named parameter, converts it to the requested type, and // writes it into "dest", which must be settable. If the value can not be // parsed, "dest" is set to the zero value. @@ -101,18 +99,24 @@ func (p *Params) Bind(dest interface{}, name string) { p.JSON = jsonData } -// Bind binds the JSON data to the dest. +// BindJSON binds the JSON data to the dest. func (p *Params) BindJSON(dest interface{}) error { value := reflect.ValueOf(dest) if value.Kind() != reflect.Ptr { - WARN.Println("BindJSON not a pointer") return errors.New("BindJSON not a pointer") } - if err := json.Unmarshal(p.JSON, dest); err != nil { - WARN.Println("W: bindMap: Unable to unmarshal request:", err) + if p.Body == nil { + return errors.New("Empty body") + } + if p.JSON != nil { + return json.Unmarshal(p.JSON, dest) + } + content, err := ioutil.ReadAll(p.Body) + if err != nil { return err } - return nil + p.JSON = content + return json.Unmarshal(content, dest) } // calcValues returns a unified view of the component param maps. diff --git a/server.go b/server.go index 1195a0f37..b140034fd 100644 --- a/server.go +++ b/server.go @@ -7,13 +7,14 @@ package revel import ( "fmt" "io" - "net" "net/http" "sort" "strconv" "strings" + "sync" "time" + "github.com/facebookgo/grace/gracehttp" "golang.org/x/net/websocket" ) @@ -23,6 +24,7 @@ var ( MainTemplateLoader *TemplateLoader MainWatcher *Watcher Server *http.Server + wg sync.WaitGroup ) // This method handles all requests. It dispatches to handleInternal after @@ -56,6 +58,8 @@ func handleInternal(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) start := time.Now() clientIP := ClientIP(r) + wg.Add(1) + defer wg.Done() var ( req = NewRequest(r) resp = NewResponse(w) @@ -127,7 +131,6 @@ func Run(port int) { port = HTTPPort } - var network = "tcp" var localAddress string // If the port is zero, treat the address as a fully qualified local address. @@ -135,7 +138,7 @@ func Run(port int) { // e.g. unix:/tmp/app.socket or tcp6:::1 (equivalent to tcp6:0:0:0:0:0:0:0:1) if port == 0 { parts := strings.SplitN(address, ":", 2) - network = parts[0] + // network = parts[0] localAddress = parts[1] } else { localAddress = address + ":" + strconv.Itoa(port) @@ -150,26 +153,27 @@ func Run(port int) { InitServer() + // Crazy Harness needs this output for "revel run" to work. go func() { time.Sleep(100 * time.Millisecond) fmt.Printf("Listening on %s...\n", Server.Addr) }() - if HTTPSsl { - if network != "tcp" { - // This limitation is just to reduce complexity, since it is standard - // to terminate SSL upstream when using unix domain sockets. - ERROR.Fatalln("SSL is only supported for TCP sockets. Specify a port to listen on.") - } - ERROR.Fatalln("Failed to listen:", - Server.ListenAndServeTLS(HTTPSslCert, HTTPSslKey)) - } else { - listener, err := net.Listen(network, Server.Addr) - if err != nil { - ERROR.Fatalln("Failed to listen:", err) - } - ERROR.Fatalln("Failed to serve:", Server.Serve(listener)) + Server = &http.Server{ + Addr: localAddress, + Handler: http.HandlerFunc(handle), + } + + if err := gracehttp.Serve(Server); err != nil { + ERROR.Fatalln("Failed to serve:", err) } + + INFO.Println("Waiting for handlers to complete.") + wg.Wait() + INFO.Println("Running Shutdown Hooks.") + runShutdownHooks() + + INFO.Println("Exit.") } func runStartupHooks() { @@ -184,6 +188,14 @@ type StartupHook struct { f func() } +func runShutdownHooks() { + for _, hook := range shutdownHooks { + hook() + } +} + +var shutdownHooks []func() + type StartupHooks []StartupHook var startupHooks StartupHooks @@ -243,3 +255,41 @@ func OnAppStart(f func(), order ...int) { } startupHooks = append(startupHooks, StartupHook{order: o, f: f}) } + +// Register a function to be run at app shutdown. +// +// The order you register the functions will be the order they are run. +// You can think of it as a FIFO queue. +// This process will happen after the server has received a shutdown signal, +// and after the server has stopped listening for connections. +// +// If your application spawns it's own goroutines it will also be responsible +// for the graceful cleanup of those. Otherwise they will terminate with the main thread. +// +// Example: +// +// // from: yourapp/app/controllers/somefile.go +// func ShutdownBackgroundDBProcess() { +// // waits for background process to complete, +// // or sends a signal on a termination channel. +// } +// +// func CloseDB() { +// // do DB cleanup stuff here +// } +// +// // from: yourapp/app/init.go +// func init() { +// // set up filters... +// +// // register startup functions +// revel.OnAppShutdown(ShutdownBackgroundDBProcess) +// revel.OnAppShutdown(CloseDB) +// } +// +// This can be useful when you need to close client connections, files, +// shutdown or terminate monitors and other goroutines. +// +func OnAppShutdown(f func()) { + shutdownHooks = append(shutdownHooks, f) +} diff --git a/watcher.go b/watcher.go index d29567b1e..e71af85b6 100644 --- a/watcher.go +++ b/watcher.go @@ -10,7 +10,7 @@ import ( "strings" "sync" - "gopkg.in/fsnotify.v1" + "gopkg.in/fsnotify/fsnotify.v1" ) // Listener is an interface for receivers of filesystem events.