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  }