github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/web/web.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/hmac"
    11  	"crypto/sha1"
    12  	"crypto/tls"
    13  	"encoding/base64"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"log"
    17  	"mime"
    18  	"net/http"
    19  	"os"
    20  	"path"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  )
    26  
    27  // A Context object is created for every incoming HTTP request, and is
    28  // passed to handlers as an optional first argument. It provides information
    29  // about the request, including the http.Request object, the GET and POST params,
    30  // and acts as a Writer for the response.
    31  type Context struct {
    32  	Request *http.Request
    33  	Params  map[string]string
    34  	Server  *Server
    35  	http.ResponseWriter
    36  }
    37  
    38  // WriteString writes string data into the response object.
    39  func (ctx *Context) WriteString(content string) {
    40  	ctx.ResponseWriter.Write([]byte(content))
    41  }
    42  
    43  // Abort is a helper method that sends an HTTP header and an optional
    44  // body. It is useful for returning 4xx or 5xx errors.
    45  // Once it has been called, any return value from the handler will
    46  // not be written to the response.
    47  func (ctx *Context) Abort(status int, body string) {
    48  	ctx.ResponseWriter.WriteHeader(status)
    49  	ctx.ResponseWriter.Write([]byte(body))
    50  }
    51  
    52  // Redirect is a helper method for 3xx redirects.
    53  func (ctx *Context) Redirect(status int, url_ string) {
    54  	ctx.ResponseWriter.Header().Set("Location", url_)
    55  	ctx.ResponseWriter.WriteHeader(status)
    56  	ctx.ResponseWriter.Write([]byte("Redirecting to: " + url_))
    57  }
    58  
    59  // Notmodified writes a 304 HTTP response
    60  func (ctx *Context) NotModified() {
    61  	ctx.ResponseWriter.WriteHeader(304)
    62  }
    63  
    64  // NotFound writes a 404 HTTP response
    65  func (ctx *Context) NotFound(message string) {
    66  	ctx.ResponseWriter.WriteHeader(404)
    67  	ctx.ResponseWriter.Write([]byte(message))
    68  }
    69  
    70  //Unauthorized writes a 401 HTTP response
    71  func (ctx *Context) Unauthorized() {
    72  	ctx.ResponseWriter.WriteHeader(401)
    73  }
    74  
    75  //Forbidden writes a 403 HTTP response
    76  func (ctx *Context) Forbidden() {
    77  	ctx.ResponseWriter.WriteHeader(403)
    78  }
    79  
    80  // ContentType sets the Content-Type header for an HTTP response.
    81  // For example, ctx.ContentType("json") sets the content-type to "application/json"
    82  // If the supplied value contains a slash (/) it is set as the Content-Type
    83  // verbatim. The return value is the content type as it was
    84  // set, or an empty string if none was found.
    85  func (ctx *Context) ContentType(val string) string {
    86  	var ctype string
    87  	if strings.ContainsRune(val, '/') {
    88  		ctype = val
    89  	} else {
    90  		if !strings.HasPrefix(val, ".") {
    91  			val = "." + val
    92  		}
    93  		ctype = mime.TypeByExtension(val)
    94  	}
    95  	if ctype != "" {
    96  		ctx.Header().Set("Content-Type", ctype)
    97  	}
    98  	return ctype
    99  }
   100  
   101  // SetHeader sets a response header. If `unique` is true, the current value
   102  // of that header will be overwritten . If false, it will be appended.
   103  func (ctx *Context) SetHeader(hdr string, val string, unique bool) {
   104  	if unique {
   105  		ctx.Header().Set(hdr, val)
   106  	} else {
   107  		ctx.Header().Add(hdr, val)
   108  	}
   109  }
   110  
   111  // SetCookie adds a cookie header to the response.
   112  func (ctx *Context) SetCookie(cookie *http.Cookie) {
   113  	ctx.SetHeader("Set-Cookie", cookie.String(), false)
   114  }
   115  
   116  func getCookieSig(key string, val []byte, timestamp string) string {
   117  	hm := hmac.New(sha1.New, []byte(key))
   118  
   119  	hm.Write(val)
   120  	hm.Write([]byte(timestamp))
   121  
   122  	hex := fmt.Sprintf("%02x", hm.Sum(nil))
   123  	return hex
   124  }
   125  
   126  func (ctx *Context) SetSecureCookie(name string, val string, age int64) {
   127  	//base64 encode the val
   128  	if len(ctx.Server.Config.CookieSecret) == 0 {
   129  		ctx.Server.Logger.Println("Secret Key for secure cookies has not been set. Please assign a cookie secret to web.Config.CookieSecret.")
   130  		return
   131  	}
   132  	var buf bytes.Buffer
   133  	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
   134  	encoder.Write([]byte(val))
   135  	encoder.Close()
   136  	vs := buf.String()
   137  	vb := buf.Bytes()
   138  	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
   139  	sig := getCookieSig(ctx.Server.Config.CookieSecret, vb, timestamp)
   140  	cookie := strings.Join([]string{vs, timestamp, sig}, "|")
   141  	ctx.SetCookie(NewCookie(name, cookie, age))
   142  }
   143  
   144  func (ctx *Context) GetSecureCookie(name string) (string, bool) {
   145  	for _, cookie := range ctx.Request.Cookies() {
   146  		if cookie.Name != name {
   147  			continue
   148  		}
   149  
   150  		parts := strings.SplitN(cookie.Value, "|", 3)
   151  
   152  		val := parts[0]
   153  		timestamp := parts[1]
   154  		sig := parts[2]
   155  
   156  		if getCookieSig(ctx.Server.Config.CookieSecret, []byte(val), timestamp) != sig {
   157  			return "", false
   158  		}
   159  
   160  		ts, _ := strconv.ParseInt(timestamp, 0, 64)
   161  
   162  		if time.Now().Unix()-31*86400 > ts {
   163  			return "", false
   164  		}
   165  
   166  		buf := bytes.NewBufferString(val)
   167  		encoder := base64.NewDecoder(base64.StdEncoding, buf)
   168  
   169  		res, _ := ioutil.ReadAll(encoder)
   170  		return string(res), true
   171  	}
   172  	return "", false
   173  }
   174  
   175  // small optimization: cache the context type instead of repeteadly calling reflect.Typeof
   176  var contextType reflect.Type
   177  
   178  var defaultStaticDirs []string
   179  
   180  func init() {
   181  	contextType = reflect.TypeOf(Context{})
   182  	//find the location of the exe file
   183  	wd, _ := os.Getwd()
   184  	arg0 := path.Clean(os.Args[0])
   185  	var exeFile string
   186  	if strings.HasPrefix(arg0, "/") {
   187  		exeFile = arg0
   188  	} else {
   189  		//TODO for robustness, search each directory in $PATH
   190  		exeFile = path.Join(wd, arg0)
   191  	}
   192  	parent, _ := path.Split(exeFile)
   193  	defaultStaticDirs = append(defaultStaticDirs, path.Join(parent, "static"))
   194  	defaultStaticDirs = append(defaultStaticDirs, path.Join(wd, "static"))
   195  	return
   196  }
   197  
   198  // Process invokes the main server's routing system.
   199  func Process(c http.ResponseWriter, req *http.Request) {
   200  	mainServer.Process(c, req)
   201  }
   202  
   203  // Run starts the web application and serves HTTP requests for the main server.
   204  func Run(addr string) {
   205  	mainServer.Run(addr)
   206  }
   207  
   208  // RunTLS starts the web application and serves HTTPS requests for the main server.
   209  func RunTLS(addr string, config *tls.Config) {
   210  	mainServer.RunTLS(addr, config)
   211  }
   212  
   213  // RunScgi starts the web application and serves SCGI requests for the main server.
   214  func RunScgi(addr string) {
   215  	mainServer.RunScgi(addr)
   216  }
   217  
   218  // RunFcgi starts the web application and serves FastCGI requests for the main server.
   219  func RunFcgi(addr string) {
   220  	mainServer.RunFcgi(addr)
   221  }
   222  
   223  // Close stops the main server.
   224  func Close() {
   225  	mainServer.Close()
   226  }
   227  
   228  // Get adds a handler for the 'GET' http method in the main server.
   229  func Get(route string, handler interface{}) {
   230  	mainServer.Get(route, handler)
   231  }
   232  
   233  // Post adds a handler for the 'POST' http method in the main server.
   234  func Post(route string, handler interface{}) {
   235  	mainServer.addRoute(route, "POST", handler)
   236  }
   237  
   238  // Put adds a handler for the 'PUT' http method in the main server.
   239  func Put(route string, handler interface{}) {
   240  	mainServer.addRoute(route, "PUT", handler)
   241  }
   242  
   243  // Delete adds a handler for the 'DELETE' http method in the main server.
   244  func Delete(route string, handler interface{}) {
   245  	mainServer.addRoute(route, "DELETE", handler)
   246  }
   247  
   248  // Match adds a handler for an arbitrary http method in the main server.
   249  func Match(method string, route string, handler interface{}) {
   250  	mainServer.addRoute(route, method, handler)
   251  }
   252  
   253  //Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
   254  func Handler(route string, method string, httpHandler http.Handler) {
   255  	mainServer.Handler(route, method, httpHandler)
   256  }
   257  
   258  //Adds a handler for websockets. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
   259  func Websocket(route string, httpHandler websocket.Handler) {
   260  	mainServer.Websocket(route, httpHandler)
   261  }
   262  
   263  // SetLogger sets the logger for the main server.
   264  func SetLogger(logger *log.Logger) {
   265  	mainServer.Logger = logger
   266  }
   267  
   268  // Config is the configuration of the main server.
   269  var Config = &ServerConfig{
   270  	RecoverPanic: true,
   271  }
   272  
   273  var mainServer = NewServer()