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