Golang net/http/transport proxy CONNECT method header supporting - go

Golang's package net/http/transport can automatic setup Proxy-Authorization header in
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error)
like
proxyURL, _ := url.Parse("http://username:password#example.com")
client := http.Client{Transport: &http.Transport{Proxy:http.ProxyURL(proxyURL)}}
But I need submit X-Header to proxy server. How Can I custom transport CONNECT method request header?
net/http/transport

how about this:
// ...
request, err := http.NewRequest("GET", "https://www.google.com", nil)
if err != nil {
// do something
}
// add header here.
request.Header.Add("X-Header", "xxx")
response, err := client.Do(request)
if err != nil {
// do something
}
// ...

http.Transport has a function which allows you to set some additional headers which will be sent during CONNECT.
Example:
var client http.Client
client.Transport = &http.Transport{
Proxy: http.ProxyURL(myProxy),
GetProxyConnectHeader: func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) {
return http.Header{"My-Custom-Header": []string{"My-Custom-Value"}}, nil
},
}

Related

How can I orginize code in the right way in my case? Golang

There are almost two identical functions that do approximately the same thing. What would be the right way to organize the code to avoid repetition in this case? The httpGetter() function accesses cloud platform API and gets JSON response, which I then parsed in another function and based on it I form terraform manifest from the template. The getToken() function does almost the same thing, just gets a token, which is then used in the httpGetter() function.
var accessToken = getToken()
func httpGetter(method, url string) (*http.Response, []byte) {
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add("Accept", "application/json;version=35.0")
req.Header.Add("Authorization", "Bearer "+accessToken)
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}
return res, body
}
func getToken() string {
url := "https://cloud-platform-api.com/api/sessions"
method := "POST"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add("Accept", "application/*+xml;version=35.0")
req.Header.Add("Authorization", "Basic <auth-hash>")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
if err != nil {
fmt.Println(err)
}
accessToken := res.Header.Get("x-vmware-vcloud-access-token")
return accessToken
}
First thing first if you know the method is a getter then you don't need to pass the method param so the signature would become something like below. Also, you are already returning a *http.Response back to the caller now it will be the callers decision on what to do with the response + the caller should decide what to do in case of if the HTTP call fails so return error and let the caller decide.
func HttpGet(url string) (*http.Response, error)
Now you also want POST method with body (in some cases) so have another function
func HttpPost(URL string, body []byte) (*http.Response, error)
Now to manage both signature and have a common code you could have a private method that will be just used in this file or you could also expose that method (it is up to you)
type Headers map[string]string
func http(method, URL string, body []byte, headers Headers) (*http.Response, error) { // we pass headers here so that the caller can pass custom headers for request
client := &http.Client{}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json;version=35.0") // common static header you can keep as it is
for key, value := range headers {
req.Header.Add(key, value)
}
return client.Do(req)
}
Using this your call from the two methods would look like
func HttpGet(url string, headers Headers) (*http.Response, error) {
return http(http.MethodGet, URL, nil, headers)
}
func HttpPost(url string, body []byte, headers Headers) (*http.Response, error) {
return http(http.MethodPost, url, body, headers)
}
Now you can use this to pass the auth token from the caller like:
func getToken() {
res, err := httpPost("https://cloud-platform-api.com/api/sessions", nil,
map[string]string{
"Authorization": "Basic <auth-hash>",
}
if err != nil {
// do something with error
}
if res.StatusCode == http.StatusCreated {
// do what you want with the success response like unmarshalling to JSON
}
}
and for cases where you don't need to pass header, you can do
res, err := httpGet("some-url", nil) // you pass header as nil

Transform http.Response into array of bytes

I am trying to develop a tcp proxy, in this tcp proxy I will have to manipulate both http and tcp requests.
At the moment for the incoming request I detect if it is an http or tcp request, if it is an http then I parse it into an http.Request:
func (s *TcpProxy) OnMessage(c *connection.Connection, ctx interface{}, data []byte) interface{} {
reader := bytes.NewReader(data)
newReader := bufio.NewReader(reader)
req, err := http.ReadRequest(newReader)
// This is an http request
}
Now I manipulate the request conveniently since I can use the methods exposed from that interface, then eventually I will send the response back from my proxy to the service that received the inbound request.
func (s *TcpProxy) OnMessage(c *connection.Connection, ctx interface{}, data []byte) interface{} {
reader := bytes.NewReader(data)
newReader := bufio.NewReader(reader)
req, err := http.ReadRequest(newReader)
// Manipulate http request
// ...
// Proxy the request
proxyReq, err := http.NewRequest(req.Method, proxyUrl, req.Body)
// Capture the duration while making a request to the destination service.
res, err := httpClient.Do(proxyReq)
buf := res.ToBuffer() // <= How can I achieve this
c.Send(buf)
c.Close()
return nil
}
However I cannot find a way to convert back the response into an array of bytes or string, am I missing something?
An http.Request object has a Write method:
func (r *Request) Write(w io.Writer) error
Write writes an HTTP/1.1 request, which is the header and body, in wire format.
You can use this to write the bytes into a buffer object. For example:
package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
var buf bytes.Buffer
req, err := http.NewRequest("GET", "http://google.com", nil)
if err != nil {
panic(err)
}
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
if err := res.Write(&buf); err != nil {
panic(err)
}
// ...do whatever you want with the buffer here...
fmt.Println(buf.String())
}
A Buffer object has a Bytes method that will return a byte array, if that's what you want.

Proxy gateway send HTTP response back

I am looking to make a proxy gateway in Go.
Almost done ! One thing is still missing : send the entire client response to the server request.
I've got my own HTTP handler :
func (f HttpHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if rurl, err := getOriginurl(r.RequestURI); err == nil {
[...]
client := &Http.Client{}
r.URL = rurl
r.RequestURI = ""
resp, err := client.Do(r)
if err == nil {
for k, vs := range resp.Header {
for _, v := range vs {
w.Header().Set(k, v)
}
}
w.WriteHeader(resp.StatusCode)
if responseData,err := ioutil.ReadAll(resp.Body); err == nil {
w.Write(responseData)
}
}
}
}
func getOriginurl(request string) *url.URL {
{...}
// Would return an *url.URL with : http://127.0.0.1:8080/{requestURI}
}
I am looking for a way to optimize the way to parse Client response to ResponseWriter.
Actually my question would be : How to parse Response type to ResponseWriter exhaustively ?
You can use httputil.NewSingleHostReverseProxy instead of your own HTTP client logic.
httputil.NewSingleHostReverseProxy(rurl).ServeHTTP(w, r)

How to get x509 Certificate from http Client in Go

I've been over the docs at https://golang.org/pkg/ but can't make this connection.
I am creating a client and request like so (error handling removed):
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req, reqErr := http.NewRequest(requestMethod, requestUrl, nil)
resp, clientErr := client.Do(req)
I need to get a x509.Certificate to read details of the cert returned from the server, but still need the http.Repsonse as well.
How can I get a x509.Certificate instance and an http.Response while only making a single request?
The response has a TLS *tls.ConnectionState field, which in turn has:
type ConnectionState struct {
// other fields
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
}
so you can just do:
resp, clientErr := client.Do(req)
if clientErr != nil {
panic(clientErr)
}
if resp.TLS != nil {
certificates := resp.TLS.PeerCertificates
if len(certificates) > 0 {
// you probably want certificates[0]
cert := certificates[0]
}
}

Transport options to ensure net/http client connect via TLS 1.2

I have a go service that makes REST requests to an HTTP server that I don't control. A customer asked my to "confirm" that my service is connecting via TLS 1.2. Is that something that I can do in code?
Current code looks something like this:
request, _ := http.NewRequest("PUT",
"https://example.com/path/to/endpoint",
bytes.NewReader(json))
client := &http.Client{}
response, _ := client.Do(request)
defer response.Body.Close()
str, err := ioutil.ReadAll(response.Body)
Based on a quick read of the docs I believe I need to use a Transport and build my client using that transport. Something like this:
tr := &http.Transport{
... some options here ...
}
client := &http.Client{Transport: tr}
But I'm not sure what options I should set.
At the time of writing, Go will speak TLS 1.2 automatically if the server supports it.
tls.ConnectionState reports various negotiated TLS parameters of a connection, including the protocol version.
To get the underlying TLS connection for an HTTP client it is easiest to set the DialTLS field of the Transport to a function that establishes and remembers the connection. Once the response arrived (but before you close the response body!), call tls.Conn.ConnectionState:
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
)
func main() {
var (
conn *tls.Conn
err error
)
tlsConfig := http.DefaultTransport.(*http.Transport).TLSClientConfig
c := &http.Client{
Transport: &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
conn, err = tls.Dial(network, addr, tlsConfig)
return conn, err
},
},
}
res, err := c.Get("https://example.com")
if err != nil {
log.Fatal(err)
}
versions := map[uint16]string{
tls.VersionSSL30: "SSL",
tls.VersionTLS10: "TLS 1.0",
tls.VersionTLS11: "TLS 1.1",
tls.VersionTLS12: "TLS 1.2",
}
fmt.Println(res.Request.URL)
fmt.Println(res.Status)
v := conn.ConnectionState().Version
fmt.Println(versions[v])
res.Body.Close()
}
// Output:
// https://example.com
// 200 OK
// TLS 1.2
From the docs
Package tls partially implements TLS 1.2, as specified in RFC 5246.
That beeing said I keep this function as a snippet to create the necessary configuration:
func NewTLSConfig(clientCertFile, clientKeyFile, caCertFile string) (*tls.Config, error) {
tlsConfig := tls.Config{}
// Load client cert
cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
if err != nil {
return &tlsConfig, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
// Load CA cert
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
return &tlsConfig, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caCertPool
tlsConfig.BuildNameToCertificate()
return &tlsConfig, err
}
After that you just need to initialize the transport:
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}

Resources