github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/apmhttputil/url.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apmhttputil 19 20 import ( 21 "net" 22 "net/http" 23 "strings" 24 25 "github.com/waldiirawan/apm-agent-go/v2/internal/apmstrings" 26 "github.com/waldiirawan/apm-agent-go/v2/model" 27 ) 28 29 // RequestURL returns a model.URL for req. 30 // 31 // If req contains an absolute URI, the values will be split and 32 // sanitized, but no further processing performed. For all other 33 // requests (i.e. most server-side requests), we reconstruct the 34 // URL based on various proxy forwarding headers and other request 35 // attributes. 36 func RequestURL(req *http.Request) model.URL { 37 out := model.URL{ 38 Path: truncateString(req.URL.Path), 39 Search: truncateString(req.URL.RawQuery), 40 Hash: truncateString(req.URL.Fragment), 41 } 42 if req.URL.Host != "" { 43 // Absolute URI: client-side or proxy request, so ignore the 44 // headers. 45 hostname, port := splitHost(req.URL.Host) 46 out.Hostname = truncateString(hostname) 47 out.Port = truncateString(port) 48 out.Protocol = truncateString(req.URL.Scheme) 49 return out 50 } 51 52 // This is a server-side request URI, which contains only the path. 53 // We synthesize the full URL by extracting the host and protocol 54 // from headers, or inferring from other properties. 55 var fullHost string 56 forwarded := ParseForwarded(req.Header.Get("Forwarded")) 57 if forwarded.Host != "" { 58 fullHost = forwarded.Host 59 out.Protocol = truncateString(forwarded.Proto) 60 } else if xfh := req.Header.Get("X-Forwarded-Host"); xfh != "" { 61 fullHost = xfh 62 } else { 63 fullHost = req.Host 64 } 65 hostname, port := splitHost(fullHost) 66 out.Hostname = truncateString(hostname) 67 out.Port = truncateString(port) 68 69 // Protocol might be extracted from the Forwarded header. If it's not, 70 // look for various other headers. 71 if out.Protocol == "" { 72 if proto := req.Header.Get("X-Forwarded-Proto"); proto != "" { 73 out.Protocol = truncateString(proto) 74 } else if proto := req.Header.Get("X-Forwarded-Protocol"); proto != "" { 75 out.Protocol = truncateString(proto) 76 } else if proto := req.Header.Get("X-Url-Scheme"); proto != "" { 77 out.Protocol = truncateString(proto) 78 } else if req.Header.Get("Front-End-Https") == "on" { 79 out.Protocol = "https" 80 } else if req.Header.Get("X-Forwarded-Ssl") == "on" { 81 out.Protocol = "https" 82 } else if req.TLS != nil { 83 out.Protocol = "https" 84 } else { 85 // Assume http otherwise. 86 out.Protocol = "http" 87 } 88 } 89 return out 90 } 91 92 func splitHost(in string) (host, port string) { 93 if strings.LastIndexByte(in, ':') == -1 { 94 // In the common (relative to other "errors") case that 95 // there is no colon, we can avoid allocations by not 96 // calling SplitHostPort. 97 return in, "" 98 } 99 host, port, err := net.SplitHostPort(in) 100 if err != nil { 101 if n := len(in); n > 1 && in[0] == '[' && in[n-1] == ']' { 102 in = in[1 : n-1] 103 } 104 return in, "" 105 } 106 return host, port 107 } 108 109 func truncateString(s string) string { 110 // At the time of writing, all length limits are 1024. 111 s, _ = apmstrings.Truncate(s, 1024) 112 return s 113 }