Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / server_adapter_go.go
diff --git a/src/foundation/api/revel/server_adapter_go.go b/src/foundation/api/revel/server_adapter_go.go
new file mode 100644 (file)
index 0000000..bacaf91
--- /dev/null
@@ -0,0 +1,647 @@
+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)
+}