Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.17.1 #2

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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.
Expand Down
84 changes: 67 additions & 17 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -127,15 +131,14 @@ 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.
// This address must be prefixed with the network type followed by a colon,
// 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]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still this...

localAddress = parts[1]
} else {
localAddress = address + ":" + strconv.Itoa(port)
Expand All @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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)
}