// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "errors" "io" "mime/multipart" "net/url" "strings" "time" ) const ( /* Minimum Engine Type Values */ _ = iota ENGINE_RESPONSE_STATUS ENGINE_WRITER ENGINE_PARAMETERS ENGINE_PATH ENGINE_REQUEST ENGINE_RESPONSE ) const ( /* HTTP Engine Type Values Starts at 1000 */ HTTP_QUERY = ENGINE_PARAMETERS HTTP_PATH = ENGINE_PATH HTTP_BODY = iota + 1000 HTTP_FORM = iota + 1000 HTTP_MULTIPART_FORM = iota + 1000 HTTP_METHOD = iota + 1000 HTTP_REQUEST_URI = iota + 1000 HTTP_REQUEST_CONTEXT = iota + 1000 HTTP_REMOTE_ADDR = iota + 1000 HTTP_HOST = iota + 1000 HTTP_URL = iota + 1000 HTTP_SERVER_HEADER = iota + 1000 HTTP_STREAM_WRITER = iota + 1000 HTTP_WRITER = ENGINE_WRITER ) type ( ServerContext interface { GetRequest() ServerRequest GetResponse() ServerResponse } // Callback ServerRequest type ServerRequest interface { GetRaw() interface{} Get(theType int) (interface{}, error) Set(theType int, theValue interface{}) bool } // Callback ServerResponse type ServerResponse interface { ServerRequest } // Callback WebSocket type ServerWebSocket interface { ServerResponse MessageSendJSON(v interface{}) error MessageReceiveJSON(v interface{}) error MessageSend(v interface{}) error MessageReceive(v interface{}) error } // Expected response for HTTP_SERVER_HEADER type (if implemented) ServerHeader interface { SetCookie(cookie string) // Sets the cookie GetCookie(key string) (value ServerCookie, err error) // Gets the cookie Set(key string, value string) Add(key string, value string) Del(key string) Get(key string) (value []string) GetKeys() (headerKeys []string) SetStatus(statusCode int) } // Expected response for FROM_HTTP_COOKIE type (if implemented) ServerCookie interface { GetValue() string } // Expected response for HTTP_MULTIPART_FORM ServerMultipartForm interface { GetFiles() map[string][]*multipart.FileHeader GetValues() url.Values RemoveAll() error } StreamWriter interface { WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error } ServerEngine interface { // Initialize the server (non blocking) Init(init *EngineInit) // Starts the server. This will block until server is stopped Start() // Fires a new event to the server Event(event Event, args interface{}) EventResponse // Returns the engine instance for specific calls Engine() interface{} // Returns the engine Name Name() string // Returns any stats Stats() map[string]interface{} } // The initialization structure passed into the engine EngineInit struct { Address, // The address Network string // The network Port int // The port HTTPMuxList ServerMuxList // The HTTPMux Callback func(ServerContext) // The ServerContext callback endpoint } // An empty server engine ServerEngineEmpty struct { } // The route handler structure ServerMux struct { PathPrefix string // The path prefix Callback interface{} // The callback interface as appropriate to the server } // A list of handlers used for adding special route functions ServerMuxList []ServerMux ) // Sorting function func (r ServerMuxList) Len() int { return len(r) } // Sorting function func (r ServerMuxList) Less(i, j int) bool { return len(r[i].PathPrefix) > len(r[j].PathPrefix) } // Sorting function func (r ServerMuxList) Swap(i, j int) { r[i], r[j] = r[j], r[i] } // Search function, returns the largest path matching this func (r ServerMuxList) Find(path string) (interface{}, bool) { for _, p := range r { if p.PathPrefix == path || strings.HasPrefix(path, p.PathPrefix) { return p.Callback, true } } return nil, false } // Adds this routehandler to the route table. It will be called (if the path prefix matches) // before the Revel mux, this can only be called after the ENGINE_BEFORE_INITIALIZED event func AddHTTPMux(path string, callback interface{}) { ServerEngineInit.HTTPMuxList = append(ServerEngineInit.HTTPMuxList, ServerMux{PathPrefix: path, Callback: callback}) } // Callback point for the server to handle the func handleInternal(ctx ServerContext) { start := time.Now() var c *Controller if RevelConfig.Controller.Reuse { c = RevelConfig.Controller.Stack.Pop().(*Controller) defer func() { RevelConfig.Controller.Stack.Push(c) }() } else { c = NewControllerEmpty() } var ( req, resp = c.Request, c.Response ) c.SetController(ctx) req.WebSocket, _ = ctx.GetResponse().(ServerWebSocket) clientIP := ClientIP(req) // Once finished in the internal, we can return these to the stack c.ClientIP = clientIP c.Log = AppLog.New("ip", clientIP, "path", req.GetPath(), "method", req.Method) // Call the first filter, this will process the request Filters[0](c, Filters[1:]) if c.Result != nil { c.Result.Apply(req, resp) } else if c.Response.Status != 0 { c.Response.SetStatus(c.Response.Status) } // Close the Writer if we can if w, ok := resp.GetWriter().(io.Closer); ok { _ = w.Close() } // Revel request access log format // RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath // Sample format: terminal format // INFO 2017/08/02 22:31:41 server-engine.go:168: Request Stats ip=::1 path=/public/img/favicon.png method=GET action=Static.Serve namespace=static\\ start=2017/08/02 22:31:41 status=200 duration_seconds=0.0007656 // Recommended storing format to json code which looks like // {"action":"Static.Serve","caller":"server-engine.go:168","duration_seconds":0.00058336,"ip":"::1","lvl":3, // "method":"GET","msg":"Request Stats","namespace":"static\\","path":"/public/img/favicon.png", // "start":"2017-08-02T22:34:08-0700","status":200,"t":"2017-08-02T22:34:08.303112145-07:00"} c.Log.Info("Request Stats", "start", start, "status", c.Response.Status, "duration_seconds", time.Since(start).Seconds(), "section", "requestlog", ) } var ( ENGINE_UNKNOWN_GET = errors.New("Server Engine Invalid Get") ) func (e *ServerEngineEmpty) Get(_ string) interface{} { return nil } func (e *ServerEngineEmpty) Set(_ string, _ interface{}) bool { return false }