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  }