github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/web/server.go (about) 1 // Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package web 6 7 import ( 8 "bytes" 9 "code.google.com/p/go.net/websocket" 10 "crypto/tls" 11 "fmt" 12 "log" 13 "net" 14 "net/http" 15 "net/http/pprof" 16 "os" 17 "path" 18 "reflect" 19 "regexp" 20 "runtime" 21 "strconv" 22 "strings" 23 "time" 24 ) 25 26 // ServerConfig is configuration for server objects. 27 type ServerConfig struct { 28 StaticDir string 29 StaticFilesServer bool // FilesServerRoot: StaticDir + "/files" 30 Addr string 31 Port int 32 CookieSecret string 33 RecoverPanic bool 34 Profiler bool 35 } 36 37 // Server represents a web.go server. 38 type Server struct { 39 Config *ServerConfig 40 routes []route 41 Logger *log.Logger 42 Env map[string]interface{} 43 //save the listener so it can be closed 44 l net.Listener 45 } 46 47 func NewServer() *Server { 48 return &Server{ 49 Config: Config, 50 Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime), 51 Env: map[string]interface{}{}, 52 } 53 } 54 55 func (s *Server) initServer() { 56 if s.Config == nil { 57 s.Config = &ServerConfig{} 58 } 59 60 if s.Logger == nil { 61 s.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime) 62 } 63 } 64 65 type route struct { 66 r string 67 cr *regexp.Regexp 68 method string 69 handler reflect.Value 70 httpHandler http.Handler 71 } 72 73 func (s *Server) addRoute(r string, method string, handler interface{}) { 74 cr, err := regexp.Compile(r) 75 if err != nil { 76 s.Logger.Printf("Error in route regex %q\n", r) 77 return 78 } 79 80 switch handler.(type) { 81 case http.Handler: 82 s.routes = append(s.routes, route{r: r, cr: cr, method: method, httpHandler: handler.(http.Handler)}) 83 case reflect.Value: 84 fv := handler.(reflect.Value) 85 s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv}) 86 default: 87 fv := reflect.ValueOf(handler) 88 s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv}) 89 } 90 } 91 92 // ServeHTTP is the interface method for Go's http server package 93 func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) { 94 s.Process(c, req) 95 } 96 97 // Process invokes the routing system for server s 98 func (s *Server) Process(c http.ResponseWriter, req *http.Request) { 99 route := s.routeHandler(req, c) 100 if route != nil { 101 route.httpHandler.ServeHTTP(c, req) 102 } 103 } 104 105 // Get adds a handler for the 'GET' http method for server s. 106 func (s *Server) Get(route string, handler interface{}) { 107 s.addRoute(route, "GET", handler) 108 } 109 110 // Post adds a handler for the 'POST' http method for server s. 111 func (s *Server) Post(route string, handler interface{}) { 112 s.addRoute(route, "POST", handler) 113 } 114 115 // Put adds a handler for the 'PUT' http method for server s. 116 func (s *Server) Put(route string, handler interface{}) { 117 s.addRoute(route, "PUT", handler) 118 } 119 120 // Delete adds a handler for the 'DELETE' http method for server s. 121 func (s *Server) Delete(route string, handler interface{}) { 122 s.addRoute(route, "DELETE", handler) 123 } 124 125 // Match adds a handler for an arbitrary http method for server s. 126 func (s *Server) Match(method string, route string, handler interface{}) { 127 s.addRoute(route, method, handler) 128 } 129 130 //Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI. 131 func (s *Server) Handler(route string, method string, httpHandler http.Handler) { 132 s.addRoute(route, method, httpHandler) 133 } 134 135 //Adds a handler for websockets. Only for webserver mode. Will have no effect when running as FCGI or SCGI. 136 func (s *Server) Websocket(route string, httpHandler websocket.Handler) { 137 s.addRoute(route, "GET", httpHandler) 138 } 139 140 // Run starts the web application and serves HTTP requests for s 141 func (s *Server) Run(addr string) { 142 s.initServer() 143 144 mux := http.NewServeMux() 145 if s.Config.StaticFilesServer && s.Config.StaticDir != "" { 146 mux.Handle("/static/files/", http.StripPrefix("/static/files/", 147 http.FileServer(http.Dir(s.Config.StaticDir+"/files")), 148 )) 149 } 150 if s.Config.Profiler { 151 mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 152 mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 153 mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) 154 mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 155 } 156 mux.Handle("/", s) 157 158 s.Logger.Printf("web.go serving %s\n", addr) 159 160 l, err := net.Listen("tcp", addr) 161 if err != nil { 162 log.Fatal("ListenAndServe:", err) 163 } 164 s.l = l 165 err = http.Serve(s.l, mux) 166 s.l.Close() 167 } 168 169 // RunFcgi starts the web application and serves FastCGI requests for s. 170 func (s *Server) RunFcgi(addr string) { 171 s.initServer() 172 s.Logger.Printf("web.go serving fcgi %s\n", addr) 173 s.listenAndServeFcgi(addr) 174 } 175 176 // RunScgi starts the web application and serves SCGI requests for s. 177 func (s *Server) RunScgi(addr string) { 178 s.initServer() 179 s.Logger.Printf("web.go serving scgi %s\n", addr) 180 s.listenAndServeScgi(addr) 181 } 182 183 // RunTLS starts the web application and serves HTTPS requests for s. 184 func (s *Server) RunTLS(addr string, config *tls.Config) error { 185 s.initServer() 186 mux := http.NewServeMux() 187 mux.Handle("/", s) 188 l, err := tls.Listen("tcp", addr, config) 189 if err != nil { 190 log.Fatal("Listen:", err) 191 return err 192 } 193 194 s.l = l 195 return http.Serve(s.l, mux) 196 } 197 198 // Close stops server s. 199 func (s *Server) Close() { 200 if s.l != nil { 201 s.l.Close() 202 } 203 } 204 205 // safelyCall invokes `function` in recover block 206 func (s *Server) safelyCall(function reflect.Value, args []reflect.Value) (resp []reflect.Value, e interface{}) { 207 defer func() { 208 if err := recover(); err != nil { 209 if !s.Config.RecoverPanic { 210 // go back to panic 211 panic(err) 212 } else { 213 e = err 214 resp = nil 215 s.Logger.Println("Handler crashed with error", err) 216 for i := 1; ; i += 1 { 217 _, file, line, ok := runtime.Caller(i) 218 if !ok { 219 break 220 } 221 s.Logger.Println(file, line) 222 } 223 } 224 } 225 }() 226 return function.Call(args), nil 227 } 228 229 // requiresContext determines whether 'handlerType' contains 230 // an argument to 'web.Ctx' as its first argument 231 func requiresContext(handlerType reflect.Type) bool { 232 //if the method doesn't take arguments, no 233 if handlerType.NumIn() == 0 { 234 return false 235 } 236 237 //if the first argument is not a pointer, no 238 a0 := handlerType.In(0) 239 if a0.Kind() != reflect.Ptr { 240 return false 241 } 242 //if the first argument is a context, yes 243 if a0.Elem() == contextType { 244 return true 245 } 246 247 return false 248 } 249 250 // tryServingFile attempts to serve a static file, and returns 251 // whether or not the operation is successful. 252 // It checks the following directories for the file, in order: 253 // 1) Config.StaticDir 254 // 2) The 'static' directory in the parent directory of the executable. 255 // 3) The 'static' directory in the current working directory 256 func (s *Server) tryServingFile(name string, req *http.Request, w http.ResponseWriter) bool { 257 //try to serve a static file 258 if s.Config.StaticDir != "" { 259 staticFile := path.Join(s.Config.StaticDir, name) 260 if fileExists(staticFile) { 261 http.ServeFile(w, req, staticFile) 262 return true 263 } 264 } else { 265 for _, staticDir := range defaultStaticDirs { 266 staticFile := path.Join(staticDir, name) 267 if fileExists(staticFile) { 268 http.ServeFile(w, req, staticFile) 269 return true 270 } 271 } 272 } 273 return false 274 } 275 276 func (s *Server) logRequest(ctx Context, sTime time.Time) { 277 //log the request 278 var logEntry bytes.Buffer 279 req := ctx.Request 280 requestPath := req.URL.Path 281 282 duration := time.Now().Sub(sTime) 283 var client string 284 285 // We suppose RemoteAddr is of the form Ip:Port as specified in the Request 286 // documentation at http://golang.org/pkg/net/http/#Request 287 pos := strings.LastIndex(req.RemoteAddr, ":") 288 if pos > 0 { 289 client = req.RemoteAddr[0:pos] 290 } else { 291 client = req.RemoteAddr 292 } 293 294 fmt.Fprintf(&logEntry, "%s - %s %s - %v", client, req.Method, requestPath, duration) 295 296 if len(ctx.Params) > 0 { 297 fmt.Fprintf(&logEntry, " - Params: %v\n", ctx.Params) 298 } 299 300 ctx.Server.Logger.Print(logEntry.String()) 301 302 } 303 304 // the main route handler in web.go 305 // Tries to handle the given request. 306 // Finds the route matching the request, and execute the callback associated 307 // with it. In case of custom http handlers, this function returns an "unused" 308 // route. The caller is then responsible for calling the httpHandler associated 309 // with the returned route. 310 func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) (unused *route) { 311 requestPath := req.URL.Path 312 ctx := Context{req, map[string]string{}, s, w} 313 314 //set some default headers 315 ctx.SetHeader("Server", "webgo", true) 316 tm := time.Now().UTC() 317 318 //ignore errors from ParseForm because it's usually harmless. 319 req.ParseForm() 320 if len(req.Form) > 0 { 321 for k, v := range req.Form { 322 ctx.Params[k] = v[0] 323 } 324 } 325 326 defer s.logRequest(ctx, tm) 327 328 ctx.SetHeader("Date", webTime(tm), true) 329 330 if req.Method == "GET" || req.Method == "HEAD" { 331 if s.tryServingFile(requestPath, req, w) { 332 return 333 } 334 } 335 336 //Set the default content-type 337 ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true) 338 339 for i := 0; i < len(s.routes); i++ { 340 route := s.routes[i] 341 cr := route.cr 342 //if the methods don't match, skip this handler (except HEAD can be used in place of GET) 343 if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") { 344 continue 345 } 346 347 if !cr.MatchString(requestPath) { 348 continue 349 } 350 match := cr.FindStringSubmatch(requestPath) 351 352 if len(match[0]) != len(requestPath) { 353 continue 354 } 355 356 if route.httpHandler != nil { 357 unused = &route 358 // We can not handle custom http handlers here, give back to the caller. 359 return 360 } 361 362 var args []reflect.Value 363 handlerType := route.handler.Type() 364 if requiresContext(handlerType) { 365 args = append(args, reflect.ValueOf(&ctx)) 366 } 367 for _, arg := range match[1:] { 368 args = append(args, reflect.ValueOf(arg)) 369 } 370 371 ret, err := s.safelyCall(route.handler, args) 372 if err != nil { 373 //there was an error or panic while calling the handler 374 ctx.Abort(500, "Server Error") 375 } 376 if len(ret) == 0 { 377 return 378 } 379 380 sval := ret[0] 381 382 var content []byte 383 384 if sval.Kind() == reflect.String { 385 content = []byte(sval.String()) 386 } else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 { 387 content = sval.Interface().([]byte) 388 } 389 ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true) 390 _, err = ctx.ResponseWriter.Write(content) 391 if err != nil { 392 ctx.Server.Logger.Println("Error during write: ", err) 393 } 394 return 395 } 396 397 // try serving index.html or index.htm 398 if req.Method == "GET" || req.Method == "HEAD" { 399 if s.tryServingFile(path.Join(requestPath, "index.html"), req, w) { 400 return 401 } else if s.tryServingFile(path.Join(requestPath, "index.htm"), req, w) { 402 return 403 } 404 } 405 ctx.Abort(404, "Page not found") 406 return 407 } 408 409 // SetLogger sets the logger for server s 410 func (s *Server) SetLogger(logger *log.Logger) { 411 s.Logger = logger 412 }