github.com/aarzilli/tools@v0.0.0-20151123112009-0d27094f75e0/net/http/loghttp/adapter.go (about) 1 // Package loghttp helps logging and or printing to the http response, branching for GAE. 2 package loghttp 3 4 import ( 5 "fmt" 6 "log" 7 "net/http" 8 "net/url" 9 "path" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strings" 14 "time" 15 16 "github.com/pbberlin/tools/appengine/util_appengine" 17 "github.com/pbberlin/tools/dsu/distributed_unancestored" 18 "golang.org/x/net/context" 19 "google.golang.org/appengine" 20 aelog "google.golang.org/appengine/log" 21 ) 22 23 // added @ for /_ah/mail 24 var validRequestPath = regexp.MustCompile(`^([a-zA-Z0-9\.\-\_\/@]*)$`) 25 26 // works like an interface - functions just have to fit in the signature 27 type ExtendedHandler func(http.ResponseWriter, *http.Request, map[string]interface{}) 28 29 // mjibson.appengine handler 30 type AppengineHandler func(context.Context, http.ResponseWriter, *http.Request) 31 32 /* 33 34 This an adapter for adding a map to each handlerFunc 35 36 http://golang.org/doc/articles/wiki/ 37 38 1.) requi(a1) 39 2.) given(a1,a2) 40 => 3.) requi(a1) = adapter( given(a1,a2) ) 41 42 func adapter( given func( t1, t2) ){ 43 return func( a1 ) { // signature of requi 44 a2 := something // set second argument 45 given( a1, a2) 46 } 47 } 48 49 No chance for closure context variables. 50 They can not flow into given(), 51 because given() is not anonymous. 52 53 54 Adding a map to each handlerFunc 55 Precomputing keys to the directory and the base of the URL 56 /aaa/bbbb/ccc.html => aaa/bbb ; ccc.html 57 Can be enhanced with more precomputed stuff 58 59 Validation: Checking path for legal chars 60 61 Introducing a "global defer" func 62 Catching panics and writing shortened stacktrace 63 64 */ 65 66 var C context.Context 67 68 // Adapter() checks the path, takes the time, precomputes values into a map 69 // provides a global panic catcher 70 // The typed signature is cleaner than the long version: 71 // func Adapter(given func(http.ResponseWriter, *http.Request, map[string]interface{})) http.HandlerFunc { 72 func Adapter(given ExtendedHandler) http.HandlerFunc { 73 74 return func(w http.ResponseWriter, r *http.Request) { 75 76 start := time.Now() 77 78 c, _ := util_appengine.SafelyExtractGaeCtxError(r) 79 lgi := log.Printf 80 lge := log.Fatalf 81 if c != nil { 82 defer logServerTime(c, start) 83 // lgi = c.Infof 84 lgi = func(format string, v ...interface{}) { 85 aelog.Infof(c, format, v...) 86 } 87 88 // lge = c.Errorf 89 lge = func(format string, v ...interface{}) { 90 aelog.Errorf(c, format, v...) 91 } 92 93 C = c 94 } 95 96 if !authenticate(w, r) { 97 return 98 } 99 100 //check_against := r.URL.String() 101 check_against := r.URL.Path 102 matches := validRequestPath.FindStringSubmatch(check_against) 103 if matches == nil { 104 s := "illegal chars in path: " + check_against 105 lgi(s) 106 http.Error(w, s, http.StatusInternalServerError) 107 return 108 } 109 110 s, err := url.Parse(r.URL.String()) 111 if err != nil { 112 panic("Could not url.Parse current url") 113 } 114 mp := map[string]interface{}{ 115 "dir": path.Dir(s.Path), 116 "base": path.Base(s.Path), 117 } 118 119 defer func() { 120 // note: Println works even in panic 121 122 panicSignal := recover() 123 if panicSignal != nil { 124 miniStacktrace := "" 125 for i := 1; i < 11; i++ { 126 _, file, line, _ := runtime.Caller(i) 127 if strings.Index(file, `/src/pkg/runtime/`) > -1 { 128 miniStacktrace += fmt.Sprintf("<br>\n\t\t %s:%d ", file[len(file)-20:], line) 129 } else { 130 dir := filepath.Dir(file) 131 dirLast := filepath.Base(dir) 132 file = filepath.Join(dirLast, filepath.Base(file)) 133 134 // we cannot determine, whether html is already sent 135 // we cannot determine, whether we are in plaintext or html context 136 // thus we need the <br> 137 miniStacktrace += fmt.Sprintf("<br>\n\t\t /%s:%d ", file, line) 138 } 139 } 140 141 // headers := w.Header() 142 // for k, v := range headers { 143 // miniStacktrace += fmt.Sprintf("%#v %#v<br>\n", k, v) 144 // } 145 if panicSignal == "abort_handler_processing" { 146 s := fmt.Sprint("\thttp processing aborted\n", miniStacktrace) 147 lge(s) 148 w.Write([]byte(s)) 149 } else if panicSignal != nil { 150 s := fmt.Sprintf("\tPANIC caught by util_err.Adapter: %v %s\n", panicSignal, miniStacktrace) 151 lge(s) 152 w.Write([]byte(s)) 153 } 154 155 } 156 }() 157 158 r.Header.Set("adapter_01", "a string set by adapter") 159 160 if c == nil { 161 given(w, r, mp) 162 } else { 163 var given1 AppengineHandler 164 given1 = func(c context.Context, w http.ResponseWriter, r *http.Request) { 165 166 given(w, r, mp) 167 168 // automatically set on appengine live, but not on appengine dev 169 if r.Header.Get("Content-Type") == "" { 170 w.Header().Set("Content-Type", "text/html; charset=utf-8") 171 } 172 173 if r.Header.Get("X-Custom-Header-Counter") != "nocounter" { 174 cntr := 0 175 if true { 176 // This seems to cause problems with new applications 177 // possible because of missing indize 178 distributed_unancestored.Increment(c, mp["dir"].(string)+mp["base"].(string)) 179 cntr, _ = distributed_unancestored.Count(c, mp["dir"].(string)+mp["base"].(string)) 180 } 181 fmt.Fprintf(w, "<br>\n%v Views<br>\n", cntr) 182 } 183 } 184 185 if true || appengine.IsDevAppServer() { 186 given1(c, w, r) 187 } else { 188 // wrapped := appstats.NewHandler(given1) // mjibson 189 // wrapped.ServeHTTP(w, r) 190 } 191 192 } 193 194 } 195 } 196 197 func authenticate(w http.ResponseWriter, r *http.Request) bool { 198 return true 199 } 200 201 func logServerTime(c context.Context, start time.Time) { 202 age := time.Now().Sub(start) 203 if age.Seconds() < 0.01 { 204 aelog.Infof(c, " request took %v nano secs", age.Nanoseconds()) 205 } else { 206 aelog.Infof(c, " request took %2.2v secs", age.Seconds()) 207 } 208 }