--- /dev/null
+package revel
+
+import (
+ "net"
+ "net/http"
+ "time"
+ "context"
+ "golang.org/x/net/websocket"
+ "io"
+ "mime/multipart"
+ "net/url"
+ "os"
+ "os/signal"
+ "path"
+ "sort"
+ "strconv"
+ "strings"
+ "github.com/revel/revel/utils"
+)
+
+// Register the GoHttpServer engine
+func init() {
+ AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+ if typeOf == REVEL_BEFORE_MODULES_LOADED {
+ RegisterServerEngine(GO_NATIVE_SERVER_ENGINE, func() ServerEngine { return &GoHttpServer{} })
+ }
+ return
+ })
+}
+
+// The Go HTTP server
+type GoHttpServer struct {
+ Server *http.Server // The server instance
+ ServerInit *EngineInit // The server engine initialization
+ MaxMultipartSize int64 // The largest size of file to accept
+ goContextStack *utils.SimpleLockStack // The context stack Set via server.context.stack, server.context.maxstack
+ goMultipartFormStack *utils.SimpleLockStack // The multipart form stack set via server.form.stack, server.form.maxstack
+ HttpMuxList ServerMuxList
+ HasAppMux bool
+ signalChan chan os.Signal
+}
+
+// Called to initialize the server with this EngineInit
+func (g *GoHttpServer) Init(init *EngineInit) {
+ g.MaxMultipartSize = int64(Config.IntDefault("server.request.max.multipart.filesize", 32)) << 20 /* 32 MB */
+ g.goContextStack = utils.NewStackLock(Config.IntDefault("server.context.stack", 100),
+ Config.IntDefault("server.context.maxstack", 200),
+ func() interface{} {
+ return NewGoContext(g)
+ })
+ g.goMultipartFormStack = utils.NewStackLock(Config.IntDefault("server.form.stack", 100),
+ Config.IntDefault("server.form.maxstack", 200),
+ func() interface{} { return &GoMultipartForm{} })
+ g.ServerInit = init
+
+ revelHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+ g.Handle(writer, request)
+ })
+
+ // Adds the mux list
+ g.HttpMuxList = init.HTTPMuxList
+ sort.Sort(g.HttpMuxList)
+ g.HasAppMux = len(g.HttpMuxList) > 0
+ g.signalChan = make(chan os.Signal)
+
+ g.Server = &http.Server{
+ Addr: init.Address,
+ Handler: revelHandler,
+ ReadTimeout: time.Duration(Config.IntDefault("http.timeout.read", 0)) * time.Second,
+ WriteTimeout: time.Duration(Config.IntDefault("http.timeout.write", 0)) * time.Second,
+ }
+
+}
+
+// Handler is assigned in the Init
+func (g *GoHttpServer) Start() {
+ go func() {
+ time.Sleep(100 * time.Millisecond)
+ serverLogger.Debugf("Start: Listening on %s...", g.Server.Addr)
+ }()
+ if HTTPSsl {
+ if g.ServerInit.Network != "tcp" {
+ // This limitation is just to reduce complexity, since it is standard
+ // to terminate SSL upstream when using unix domain sockets.
+ serverLogger.Fatal("SSL is only supported for TCP sockets. Specify a port to listen on.")
+ }
+ serverLogger.Fatal("Failed to listen:", "error",
+ g.Server.ListenAndServeTLS(HTTPSslCert, HTTPSslKey))
+ } else {
+ listener, err := net.Listen(g.ServerInit.Network, g.Server.Addr)
+ if err != nil {
+ serverLogger.Fatal("Failed to listen:", "error", err)
+ }
+ serverLogger.Warn("Server exiting:", "error", g.Server.Serve(listener))
+ }
+}
+
+// Handle the request and response for the server
+func (g *GoHttpServer) Handle(w http.ResponseWriter, r *http.Request) {
+ // This section is called if the developer has added custom mux to the app
+ if g.HasAppMux && g.handleAppMux(w, r) {
+ return
+ }
+ g.handleMux(w, r)
+}
+
+// Handle the request and response for the servers mux
+func (g *GoHttpServer) handleAppMux(w http.ResponseWriter, r *http.Request) bool {
+ // Check the prefix and split them
+ requestPath := path.Clean(r.URL.Path)
+ if handler, hasHandler := g.HttpMuxList.Find(requestPath); hasHandler {
+ clientIP := HttpClientIP(r)
+ localLog := AppLog.New("ip", clientIP,
+ "path", r.URL.Path, "method", r.Method)
+ defer func() {
+ if err := recover(); err != nil {
+ localLog.Error("An error was caught using the handler", "path", requestPath, "error", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ }()
+ start := time.Now()
+ handler.(http.HandlerFunc)(w, r)
+ localLog.Info("Request Stats",
+ "start", start,
+ "duration_seconds", time.Since(start).Seconds(), "section", "requestlog",
+ )
+ return true
+ }
+ return false
+}
+
+// Passes the server request to Revel
+func (g *GoHttpServer) handleMux(w http.ResponseWriter, r *http.Request) {
+ if maxRequestSize := int64(Config.IntDefault("http.maxrequestsize", 0)); maxRequestSize > 0 {
+ r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
+ }
+
+ upgrade := r.Header.Get("Upgrade")
+ context := g.goContextStack.Pop().(*GoContext)
+ defer func() {
+ g.goContextStack.Push(context)
+ }()
+ context.Request.SetRequest(r)
+ context.Response.SetResponse(w)
+
+ if upgrade == "websocket" || upgrade == "Websocket" {
+ websocket.Handler(func(ws *websocket.Conn) {
+ //Override default Read/Write timeout with sane value for a web socket request
+ if err := ws.SetDeadline(time.Now().Add(time.Hour * 24)); err != nil {
+ serverLogger.Error("SetDeadLine failed:", err)
+ }
+ r.Method = "WS"
+ context.Request.WebSocket = ws
+ context.WebSocket = &GoWebSocket{Conn: ws, GoResponse: *context.Response}
+ g.ServerInit.Callback(context)
+ }).ServeHTTP(w, r)
+ } else {
+ g.ServerInit.Callback(context)
+ }
+}
+
+// ClientIP method returns client IP address from HTTP request.
+//
+// Note: Set property "app.behind.proxy" to true only if Revel is running
+// behind proxy like nginx, haproxy, apache, etc. Otherwise
+// you may get inaccurate Client IP address. Revel parses the
+// IP address in the order of X-Forwarded-For, X-Real-IP.
+//
+// By default revel will get http.Request's RemoteAddr
+func HttpClientIP(r *http.Request) string {
+ if Config.BoolDefault("app.behind.proxy", false) {
+ // Header X-Forwarded-For
+ if fwdFor := strings.TrimSpace(r.Header.Get(HdrForwardedFor)); fwdFor != "" {
+ index := strings.Index(fwdFor, ",")
+ if index == -1 {
+ return fwdFor
+ }
+ return fwdFor[:index]
+ }
+
+ // Header X-Real-Ip
+ if realIP := strings.TrimSpace(r.Header.Get(HdrRealIP)); realIP != "" {
+ return realIP
+ }
+ }
+
+ if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+ return remoteAddr
+ }
+
+ return ""
+}
+
+// The server key name
+const GO_NATIVE_SERVER_ENGINE = "go"
+
+// Returns the name of this engine
+func (g *GoHttpServer) Name() string {
+ return GO_NATIVE_SERVER_ENGINE
+}
+
+// Returns stats for this engine
+func (g *GoHttpServer) Stats() map[string]interface{} {
+ return map[string]interface{}{
+ "Go Engine Context": g.goContextStack.String(),
+ "Go Engine Forms": g.goMultipartFormStack.String(),
+ }
+}
+
+// Return the engine instance
+func (g *GoHttpServer) Engine() interface{} {
+ return g.Server
+}
+
+// Handles an event from Revel
+func (g *GoHttpServer) Event(event Event, args interface{}) (r EventResponse) {
+ switch event {
+ case ENGINE_STARTED:
+ signal.Notify(g.signalChan, os.Interrupt, os.Kill)
+ go func() {
+ _ = <-g.signalChan
+ serverLogger.Info("Received quit singal Please wait ... ")
+ RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil)
+ }()
+ case ENGINE_SHUTDOWN_REQUEST:
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(Config.IntDefault("app.cancel.timeout", 60)))
+ defer cancel()
+ g.Server.Shutdown(ctx)
+ default:
+
+ }
+
+ return
+}
+
+type (
+ // The go context
+ GoContext struct {
+ Request *GoRequest // The request
+ Response *GoResponse // The response
+ WebSocket *GoWebSocket // The websocket
+ }
+
+ // The go request
+ GoRequest struct {
+ Original *http.Request // The original
+ FormParsed bool // True if form parsed
+ MultiFormParsed bool // True if multipart form parsed
+ WebSocket *websocket.Conn // The websocket
+ ParsedForm *GoMultipartForm // The parsed form data
+ Goheader *GoHeader // The header
+ Engine *GoHttpServer // THe server
+ }
+
+ // The response
+ GoResponse struct {
+ Original http.ResponseWriter // The original writer
+ Goheader *GoHeader // The header
+ Writer io.Writer // The writer
+ Request *GoRequest // The request
+ Engine *GoHttpServer // The engine
+ }
+
+ // The multipart form
+ GoMultipartForm struct {
+ Form *multipart.Form // The form
+ }
+
+ // The go header
+ GoHeader struct {
+ Source interface{} // The source
+ isResponse bool // True if response header
+ }
+
+ // The websocket
+ GoWebSocket struct {
+ Conn *websocket.Conn // The connection
+ GoResponse // The response
+ }
+
+ // The cookie
+ GoCookie http.Cookie
+)
+
+// Create a new go context
+func NewGoContext(instance *GoHttpServer) *GoContext {
+ // This bit in here is for the test cases, which pass in a nil value
+ if instance == nil {
+ instance = &GoHttpServer{MaxMultipartSize: 32 << 20}
+ instance.goContextStack = utils.NewStackLock(100, 200,
+ func() interface{} {
+ return NewGoContext(instance)
+ })
+ instance.goMultipartFormStack = utils.NewStackLock(100, 200,
+ func() interface{} { return &GoMultipartForm{} })
+ }
+ c := &GoContext{Request: &GoRequest{Goheader: &GoHeader{}, Engine: instance}}
+ c.Response = &GoResponse{Goheader: &GoHeader{}, Request: c.Request, Engine: instance}
+ return c
+}
+
+// get the request
+func (c *GoContext) GetRequest() ServerRequest {
+ return c.Request
+}
+
+// Get the response
+func (c *GoContext) GetResponse() ServerResponse {
+ if c.WebSocket != nil {
+ return c.WebSocket
+ }
+ return c.Response
+}
+
+// Destroy the context
+func (c *GoContext) Destroy() {
+ c.Response.Destroy()
+ c.Request.Destroy()
+ if c.WebSocket != nil {
+ c.WebSocket.Destroy()
+ c.WebSocket = nil
+ }
+}
+
+// Communicate with the server engine to exchange data
+func (r *GoRequest) Get(key int) (value interface{}, err error) {
+ switch key {
+ case HTTP_SERVER_HEADER:
+ value = r.GetHeader()
+ case HTTP_MULTIPART_FORM:
+ value, err = r.GetMultipartForm()
+ case HTTP_QUERY:
+ value = r.Original.URL.Query()
+ case HTTP_FORM:
+ value, err = r.GetForm()
+ case HTTP_REQUEST_URI:
+ value = r.Original.URL.String()
+ case HTTP_REQUEST_CONTEXT:
+ value = r.Original.Context()
+ case HTTP_REMOTE_ADDR:
+ value = r.Original.RemoteAddr
+ case HTTP_METHOD:
+ value = r.Original.Method
+ case HTTP_PATH:
+ value = r.Original.URL.Path
+ case HTTP_HOST:
+ value = r.Original.Host
+ case HTTP_URL:
+ value = r.Original.URL
+ case HTTP_BODY:
+ value = r.Original.Body
+ default:
+ err = ENGINE_UNKNOWN_GET
+ }
+
+ return
+}
+
+// Sets the request key with value
+func (r *GoRequest) Set(key int, value interface{}) bool {
+ return false
+}
+
+// Returns the form
+func (r *GoRequest) GetForm() (url.Values, error) {
+ if !r.FormParsed {
+ if e := r.Original.ParseForm(); e != nil {
+ return nil, e
+ }
+ r.FormParsed = true
+ }
+
+ return r.Original.Form, nil
+}
+
+// Returns the form
+func (r *GoRequest) GetMultipartForm() (ServerMultipartForm, error) {
+ if !r.MultiFormParsed {
+ if e := r.Original.ParseMultipartForm(r.Engine.MaxMultipartSize); e != nil {
+ return nil, e
+ }
+ r.ParsedForm = r.Engine.goMultipartFormStack.Pop().(*GoMultipartForm)
+ r.ParsedForm.Form = r.Original.MultipartForm
+ }
+
+ return r.ParsedForm, nil
+}
+
+// Returns the header
+func (r *GoRequest) GetHeader() ServerHeader {
+ return r.Goheader
+}
+
+// Returns the raw value
+func (r *GoRequest) GetRaw() interface{} {
+ return r.Original
+}
+
+// Sets the request
+func (r *GoRequest) SetRequest(req *http.Request) {
+ r.Original = req
+ r.Goheader.Source = r
+ r.Goheader.isResponse = false
+
+}
+
+// Destroy the request
+func (r *GoRequest) Destroy() {
+ r.Goheader.Source = nil
+ r.Original = nil
+ r.FormParsed = false
+ r.MultiFormParsed = false
+ r.ParsedForm = nil
+}
+
+// Gets the key from the response
+func (r *GoResponse) Get(key int) (value interface{}, err error) {
+ switch key {
+ case HTTP_SERVER_HEADER:
+ value = r.Header()
+ case HTTP_STREAM_WRITER:
+ value = r
+ case HTTP_WRITER:
+ value = r.Writer
+ default:
+ err = ENGINE_UNKNOWN_GET
+ }
+ return
+}
+
+// Sets the key with the value
+func (r *GoResponse) Set(key int, value interface{}) (set bool) {
+ switch key {
+ case ENGINE_RESPONSE_STATUS:
+ r.Header().SetStatus(value.(int))
+ set = true
+ case HTTP_WRITER:
+ r.SetWriter(value.(io.Writer))
+ set = true
+ }
+ return
+}
+
+// Sets the header
+func (r *GoResponse) Header() ServerHeader {
+ return r.Goheader
+}
+
+// Gets the original response
+func (r *GoResponse) GetRaw() interface{} {
+ return r.Original
+}
+
+// Sets the writer
+func (r *GoResponse) SetWriter(writer io.Writer) {
+ r.Writer = writer
+}
+
+// Write output to stream
+func (r *GoResponse) WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error {
+ // Check to see if the output stream is modified, if not send it using the
+ // Native writer
+ written := false
+ if _, ok := r.Writer.(http.ResponseWriter); ok {
+ if rs, ok := reader.(io.ReadSeeker); ok {
+ http.ServeContent(r.Original, r.Request.Original, name, modtime, rs)
+ written = true
+ }
+ }
+ if !written {
+ // Else, do a simple io.Copy.
+ ius := r.Request.Original.Header.Get("If-Unmodified-Since")
+ if t, err := http.ParseTime(ius); err == nil && !modtime.IsZero() {
+ // The Date-Modified header truncates sub-second precision, so
+ // use mtime < t+1s instead of mtime <= t to check for unmodified.
+ if modtime.Before(t.Add(1 * time.Second)) {
+ h := r.Original.Header()
+ delete(h, "Content-Type")
+ delete(h, "Content-Length")
+ if h.Get("Etag") != "" {
+ delete(h, "Last-Modified")
+ }
+ r.Original.WriteHeader(http.StatusNotModified)
+ return nil
+ }
+ }
+
+ if contentlen != -1 {
+ header := ServerHeader(r.Goheader)
+ if writer, found := r.Writer.(*CompressResponseWriter); found {
+ header = ServerHeader(writer.Header)
+ }
+ header.Set("Content-Length", strconv.FormatInt(contentlen, 10))
+ }
+ if _, err := io.Copy(r.Writer, reader); err != nil {
+ r.Original.WriteHeader(http.StatusInternalServerError)
+ return err
+ } else {
+ r.Original.WriteHeader(http.StatusOK)
+ }
+ }
+ return nil
+}
+
+// Frees response
+func (r *GoResponse) Destroy() {
+ if c, ok := r.Writer.(io.Closer); ok {
+ c.Close()
+ }
+ r.Goheader.Source = nil
+ r.Original = nil
+ r.Writer = nil
+}
+
+// Sets the response
+func (r *GoResponse) SetResponse(w http.ResponseWriter) {
+ r.Original = w
+ r.Writer = w
+ r.Goheader.Source = r
+ r.Goheader.isResponse = true
+
+}
+
+// Sets the cookie
+func (r *GoHeader) SetCookie(cookie string) {
+ if r.isResponse {
+ r.Source.(*GoResponse).Original.Header().Add("Set-Cookie", cookie)
+ }
+}
+
+// Gets the cookie
+func (r *GoHeader) GetCookie(key string) (value ServerCookie, err error) {
+ if !r.isResponse {
+ var cookie *http.Cookie
+ if cookie, err = r.Source.(*GoRequest).Original.Cookie(key); err == nil {
+ value = GoCookie(*cookie)
+
+ }
+
+ }
+ return
+}
+
+// Sets (replaces) header key
+func (r *GoHeader) Set(key string, value string) {
+ if r.isResponse {
+ r.Source.(*GoResponse).Original.Header().Set(key, value)
+ }
+}
+
+// Adds the header key
+func (r *GoHeader) Add(key string, value string) {
+ if r.isResponse {
+ r.Source.(*GoResponse).Original.Header().Add(key, value)
+ }
+}
+
+// Deletes the header key
+func (r *GoHeader) Del(key string) {
+ if r.isResponse {
+ r.Source.(*GoResponse).Original.Header().Del(key)
+ }
+}
+
+// Gets the header key
+func (r *GoHeader) Get(key string) (value []string) {
+ if !r.isResponse {
+ value = r.Source.(*GoRequest).Original.Header[key]
+ if len(value) == 0 {
+ if ihead := r.Source.(*GoRequest).Original.Header.Get(key); ihead != "" {
+ value = append(value, ihead)
+ }
+ }
+ } else {
+ value = r.Source.(*GoResponse).Original.Header()[key]
+ }
+ return
+}
+
+// Returns list of header keys
+func (r *GoHeader) GetKeys() (value []string) {
+ if !r.isResponse {
+ for key := range r.Source.(*GoRequest).Original.Header {
+ value = append(value, key)
+ }
+ } else {
+ for key := range r.Source.(*GoResponse).Original.Header() {
+ value = append(value, key)
+ }
+ }
+ return
+}
+
+// Sets the status of the header
+func (r *GoHeader) SetStatus(statusCode int) {
+ if r.isResponse {
+ r.Source.(*GoResponse).Original.WriteHeader(statusCode)
+ }
+}
+
+// Return cookies value
+func (r GoCookie) GetValue() string {
+ return r.Value
+}
+
+// Return files from the form
+func (f *GoMultipartForm) GetFiles() map[string][]*multipart.FileHeader {
+ return f.Form.File
+}
+
+// Return values from the form
+func (f *GoMultipartForm) GetValues() url.Values {
+ return url.Values(f.Form.Value)
+}
+
+// Remove all values from the form freeing memory
+func (f *GoMultipartForm) RemoveAll() error {
+ return f.Form.RemoveAll()
+}
+
+/**
+ * Message send JSON
+ */
+func (g *GoWebSocket) MessageSendJSON(v interface{}) error {
+ return websocket.JSON.Send(g.Conn, v)
+}
+
+/**
+ * Message receive JSON
+ */
+func (g *GoWebSocket) MessageReceiveJSON(v interface{}) error {
+ return websocket.JSON.Receive(g.Conn, v)
+}
+
+/**
+ * Message Send
+ */
+func (g *GoWebSocket) MessageSend(v interface{}) error {
+ return websocket.Message.Send(g.Conn, v)
+}
+
+/**
+ * Message receive
+ */
+func (g *GoWebSocket) MessageReceive(v interface{}) error {
+ return websocket.Message.Receive(g.Conn, v)
+}