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 }