github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/handler_error.go (about)

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