github.com/bazelbuild/rules_webtesting@v0.2.0/go/httphelper/http_helper.go (about)

     1  // Copyright 2016 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package httphelper provides simple wrappers for working with HTTP.
    16  package httphelper
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"crypto/tls"
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"sort"
    29  	"strings"
    30  	"time"
    31  )
    32  
    33  var client = &http.Client{
    34  	Transport: &http.Transport{
    35  		TLSClientConfig: &tls.Config{
    36  			InsecureSkipVerify: true,
    37  		},
    38  		Proxy: http.ProxyFromEnvironment,
    39  		DialContext: (&net.Dialer{
    40  			Timeout:   30 * time.Second,
    41  			KeepAlive: 30 * time.Second,
    42  			DualStack: true,
    43  		}).DialContext,
    44  		MaxIdleConns:          100,
    45  		IdleConnTimeout:       90 * time.Second,
    46  		TLSHandshakeTimeout:   10 * time.Second,
    47  		ExpectContinueTimeout: 1 * time.Second,
    48  	},
    49  }
    50  
    51  // Forward forwards r to host and writes the response from host to w.
    52  func Forward(ctx context.Context, host, trimPrefix string, w http.ResponseWriter, r *http.Request) error {
    53  	url, err := constructURL(host, r.URL.Path, trimPrefix)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	buffer := &bytes.Buffer{}
    59  
    60  	if r.ContentLength > 0 {
    61  		buffer.Grow(int(r.ContentLength))
    62  	}
    63  
    64  	length, err := io.Copy(buffer, r.Body)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// Construct request based on Method, URL Path, and Body from r
    70  	request, err := http.NewRequest(r.Method, url.String(), buffer)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	request = request.WithContext(ctx)
    75  	request.ContentLength = length
    76  
    77  	request.Header["Content-Type"] = r.Header["Content-Type"]
    78  	request.Header["Accept"] = r.Header["Accept"]
    79  	request.Header["Accept-Encoding"] = r.Header["Accept-Encoding"]
    80  
    81  	resp, err := client.Do(request)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer resp.Body.Close()
    86  
    87  	// Copy response headers from resp to w
    88  	for k, vs := range resp.Header {
    89  		w.Header().Del(k)
    90  		for _, v := range vs {
    91  			w.Header().Add(k, v)
    92  		}
    93  	}
    94  
    95  	SetDefaultResponseHeaders(w.Header())
    96  
    97  	// Copy status code from resp to w
    98  	w.WriteHeader(resp.StatusCode)
    99  	_, err = io.Copy(w, resp.Body)
   100  	return err
   101  }
   102  
   103  // Get returns the contents located at url.
   104  func Get(ctx context.Context, url string) (*http.Response, error) {
   105  	request, err := http.NewRequest(http.MethodGet, url, nil)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	request = request.WithContext(ctx)
   110  	return client.Do(request)
   111  }
   112  
   113  func constructURL(base, path, prefix string) (*url.URL, error) {
   114  	u, err := url.Parse(base)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	if !strings.HasPrefix(prefix, "/") {
   120  		prefix = "/" + prefix
   121  	}
   122  
   123  	if !strings.HasPrefix(path, prefix) {
   124  		return nil, fmt.Errorf("%q does not have expected prefix %q", path, prefix)
   125  	}
   126  
   127  	ref, err := url.Parse(strings.TrimPrefix(path, prefix))
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	return u.ResolveReference(ref), err
   133  }
   134  
   135  type longestToShortest []string
   136  
   137  func (s longestToShortest) Len() int {
   138  	return len(s)
   139  }
   140  
   141  func (s longestToShortest) Swap(i, j int) {
   142  	s[i], s[j] = s[j], s[i]
   143  }
   144  
   145  func (s longestToShortest) Less(i, j int) bool {
   146  	return len(s[i]) > len(s[j])
   147  }
   148  
   149  // FQDN returns the fully-qualified domain name (or localhost if lookup
   150  // according to the hostname fails).
   151  func FQDN() (string, error) {
   152  	hostname, err := os.Hostname()
   153  	if err != nil {
   154  		// Fail if the kernel fails to report a hostname.
   155  		return "", err
   156  	}
   157  
   158  	addrs, err := net.LookupHost(hostname)
   159  	if err != nil {
   160  		return "localhost", nil
   161  	}
   162  
   163  	for _, addr := range addrs {
   164  		if names, err := net.LookupAddr(addr); err == nil && len(names) > 0 {
   165  			sort.Sort(longestToShortest(names))
   166  			for _, name := range names {
   167  				name = strings.TrimRight(name, ".")
   168  				if strings.HasPrefix(name, hostname) {
   169  					return name, nil
   170  				}
   171  			}
   172  			return names[0], nil
   173  		}
   174  	}
   175  
   176  	return "localhost", nil
   177  }
   178  
   179  // SetDefaultResponseHeaders sets headers that appear in all WebDriver responses.
   180  func SetDefaultResponseHeaders(h http.Header) {
   181  	h.Set("Access-Control-Allow-Origin", "*")
   182  	h.Set("Access-Control-Allow-Methods", "CONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE")
   183  	// Adding here in case any other allow headers are already set.
   184  	h.Add("Access-Control-Allow-Headers", "Accept,Content-Type")
   185  	h.Set("Cache-Control", "no-cache")
   186  }