github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/handler_error.go (about)

     1  package gateway
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"html/template"
     7  	"net/http"
     8  	"runtime/pprof"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/TykTechnologies/tyk/headers"
    14  	"github.com/TykTechnologies/tyk/request"
    15  )
    16  
    17  const (
    18  	defaultTemplateName   = "error"
    19  	defaultTemplateFormat = "json"
    20  	defaultContentType    = headers.ApplicationJSON
    21  )
    22  
    23  // APIError is generic error object returned if there is something wrong with the request
    24  type APIError struct {
    25  	Message template.HTML
    26  }
    27  
    28  // ErrorHandler is invoked whenever there is an issue with a proxied request, most middleware will invoke
    29  // the ErrorHandler if something is wrong with the request and halt the request processing through the chain
    30  type ErrorHandler struct {
    31  	BaseMiddleware
    32  }
    33  
    34  // HandleError is the actual error handler and will store the error details in analytics if analytics processing is enabled.
    35  func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMsg string, errCode int, writeResponse bool) {
    36  	defer e.Base().UpdateRequestSession(r)
    37  
    38  	if writeResponse {
    39  		var templateExtension string
    40  		var contentType string
    41  
    42  		switch r.Header.Get(headers.ContentType) {
    43  		case headers.ApplicationXML:
    44  			templateExtension = "xml"
    45  			contentType = headers.ApplicationXML
    46  		default:
    47  			templateExtension = "json"
    48  			contentType = headers.ApplicationJSON
    49  		}
    50  
    51  		w.Header().Set(headers.ContentType, contentType)
    52  
    53  		templateName := "error_" + strconv.Itoa(errCode) + "." + templateExtension
    54  
    55  		// Try to use an error template that matches the HTTP error code and the content type: 500.json, 400.xml, etc.
    56  		tmpl := templates.Lookup(templateName)
    57  
    58  		// Fallback to a generic error template, but match the content type: error.json, error.xml, etc.
    59  		if tmpl == nil {
    60  			templateName = defaultTemplateName + "." + templateExtension
    61  			tmpl = templates.Lookup(templateName)
    62  		}
    63  
    64  		// If no template is available for this content type, fallback to "error.json".
    65  		if tmpl == nil {
    66  			templateName = defaultTemplateName + "." + defaultTemplateFormat
    67  			tmpl = templates.Lookup(templateName)
    68  			w.Header().Set(headers.ContentType, defaultContentType)
    69  		}
    70  
    71  		//If the config option is not set or is false, add the header
    72  		if !e.Spec.GlobalConfig.HideGeneratorHeader {
    73  			w.Header().Add(headers.XGenerator, "tyk.io")
    74  		}
    75  
    76  		// Close connections
    77  		if e.Spec.GlobalConfig.CloseConnections {
    78  			w.Header().Add(headers.Connection, "close")
    79  		}
    80  
    81  		// Need to return the correct error code!
    82  		w.WriteHeader(errCode)
    83  		apiError := APIError{template.HTML(template.JSEscapeString(errMsg))}
    84  		tmpl.Execute(w, &apiError)
    85  	}
    86  
    87  	if memProfFile != nil {
    88  		pprof.WriteHeapProfile(memProfFile)
    89  	}
    90  
    91  	if e.Spec.DoNotTrack {
    92  		return
    93  	}
    94  
    95  	// Track the key ID if it exists
    96  	token := ctxGetAuthToken(r)
    97  	var alias string
    98  
    99  	ip := request.RealIP(r)
   100  	if e.Spec.GlobalConfig.StoreAnalytics(ip) {
   101  		t := time.Now()
   102  
   103  		addVersionHeader(w, r, e.Spec.GlobalConfig)
   104  
   105  		version := e.Spec.getVersionFromRequest(r)
   106  		if version == "" {
   107  			version = "Non Versioned"
   108  		}
   109  
   110  		if e.Spec.Proxy.StripListenPath {
   111  			r.URL.Path = strings.Replace(r.URL.Path, e.Spec.Proxy.ListenPath, "", 1)
   112  		}
   113  
   114  		// This is an odd bugfix, will need further testing
   115  		r.URL.Path = "/" + r.URL.Path
   116  		if strings.HasPrefix(r.URL.Path, "//") {
   117  			r.URL.Path = strings.TrimPrefix(r.URL.Path, "/")
   118  		}
   119  
   120  		oauthClientID := ""
   121  		session := ctxGetSession(r)
   122  		tags := make([]string, 0, estimateTagsCapacity(session, e.Spec))
   123  		if session != nil {
   124  			oauthClientID = session.OauthClientID
   125  			alias = session.Alias
   126  			tags = append(tags, getSessionTags(session)...)
   127  		}
   128  
   129  		if len(e.Spec.TagHeaders) > 0 {
   130  			tags = tagHeaders(r, e.Spec.TagHeaders, tags)
   131  		}
   132  
   133  		rawRequest := ""
   134  		rawResponse := ""
   135  		if recordDetail(r, e.Spec.GlobalConfig) {
   136  			// Get the wire format representation
   137  			var wireFormatReq bytes.Buffer
   138  			r.Write(&wireFormatReq)
   139  			rawRequest = base64.StdEncoding.EncodeToString(wireFormatReq.Bytes())
   140  		}
   141  
   142  		trackEP := false
   143  		trackedPath := r.URL.Path
   144  		if p := ctxGetTrackedPath(r); p != "" && !ctxGetDoNotTrack(r) {
   145  			trackEP = true
   146  			trackedPath = p
   147  		}
   148  
   149  		host := r.URL.Host
   150  		if host == "" && e.Spec.target != nil {
   151  			host = e.Spec.target.Host
   152  		}
   153  
   154  		record := AnalyticsRecord{
   155  			r.Method,
   156  			host,
   157  			trackedPath,
   158  			r.URL.Path,
   159  			r.ContentLength,
   160  			r.Header.Get(headers.UserAgent),
   161  			t.Day(),
   162  			t.Month(),
   163  			t.Year(),
   164  			t.Hour(),
   165  			errCode,
   166  			token,
   167  			t,
   168  			version,
   169  			e.Spec.Name,
   170  			e.Spec.APIID,
   171  			e.Spec.OrgID,
   172  			oauthClientID,
   173  			0,
   174  			rawRequest,
   175  			rawResponse,
   176  			ip,
   177  			GeoData{},
   178  			tags,
   179  			alias,
   180  			trackEP,
   181  			t,
   182  		}
   183  
   184  		if e.Spec.GlobalConfig.AnalyticsConfig.EnableGeoIP {
   185  			record.GetGeo(ip)
   186  		}
   187  
   188  		expiresAfter := e.Spec.ExpireAnalyticsAfter
   189  		if e.Spec.GlobalConfig.EnforceOrgDataAge {
   190  			orgExpireDataTime := e.OrgSessionExpiry(e.Spec.OrgID)
   191  
   192  			if orgExpireDataTime > 0 {
   193  				expiresAfter = orgExpireDataTime
   194  			}
   195  
   196  		}
   197  
   198  		record.SetExpiry(expiresAfter)
   199  		if e.Spec.GlobalConfig.AnalyticsConfig.NormaliseUrls.Enabled {
   200  			record.NormalisePath(&e.Spec.GlobalConfig)
   201  		}
   202  
   203  		analytics.RecordHit(&record)
   204  	}
   205  
   206  	// Report in health check
   207  	reportHealthValue(e.Spec, BlockedRequestLog, "-1")
   208  
   209  	if memProfFile != nil {
   210  		pprof.WriteHeapProfile(memProfFile)
   211  	}
   212  }