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) }