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  }