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 }