I'm trying to write tests for a package that makes requests to a web service. I'm running into issues probably due to my lack of understanding of TLS.
Currently my test looks something like this:
func TestSimple() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
fmt.Fprintf(w, `{ "fake" : "json data here" }`)
}))
transport := &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
return url.Parse(server.URL)
},
}
// Client is the type in my package that makes requests
client := Client{
c: http.Client{Transport: transport},
}
client.DoRequest() // ...
}
My package has a package variable (I'd like for it to be a constant..) for the base address of the web service to query. It is an https URL. The test server I created above is plain HTTP, no TLS.
By default, my test fails with the error "tls: first record does not look like a TLS handshake."
To get this to work, my tests change the package variable to a plain http URL instead of https before making the query.
Is there any way around this? Can I make the package variable a constant (https), and either set up a http.Transport
that "downgrades" to unencrypted HTTP, or use httptest.NewTLSServer()
instead?
(When I try to use NewTLSServer()
I get "http: TLS handshake error from 127.0.0.1:45678: tls: oversized record received with length 20037")
Most of the behavior in
net/http
can be mocked, extended, or altered. Althoughhttp.Client
is a concrete type that implements HTTP client semantics, all of its fields are exported and may be customized.The
Client.Transport
field, in particular, may be replaced to make the Client do anything from using custom protocols (such as ftp:// or file://) to connecting directly to local handlers (without generating HTTP protocol bytes or sending anything over the network).The client functions, such as
http.Get
, all utilize the exportedhttp.DefaultClient
package variable (which you may modify), so code that utilizes these convenience functions does not, for example, have to be changed to call methods on a custom Client variable. Note that while it would be unreasonable to modify global behavior in a publicly-available library, it's very useful to do so in applications and tests (including library tests).http://play.golang.org/p/afljO086iB contains a custom
http.RoundTripper
that rewrites the request URL so that it'll be routed to a locally hostedhttptest.Server
, and another example that directly passes the request to anhttp.Handler
, along with a customhttp.ResponseWriter
implementation, in order to create anhttp.Response
. The second approach isn't as diligent as the first (it doesn't fill out as many fields in the Response value) but is more efficient, and should be compatible enough to work with most handlers and client callers.The above-linked code is included below as well:
The reason you're getting the error
http: TLS handshake error from 127.0.0.1:45678: tls: oversized record received with length 20037
is because https requires a domain name (not an IP Address). Domain names are SSL certificates are assigned to.Start the httptest server in TLS mode with your own certs
To provide a valid SSL certificate for a connection are the options of:
No Cert
If you don't supply your own cert, then an
example.com
cert is loaded as default.Self-Signed Cert
To create a testing cert can use the included self-signed cert generator at
$GOROOT/src/crypto/tls/generate_cert.go --host "*.domain.name"
You'll get
x509: certificate signed by unknown authority
warnings because it's self-signed so you'll need to have your client skip those warnings, by adding the following to your http.Transport field:Valid Real Cert
Finally, if you're going to use a real cert, then save the valid cert and key where they can be loaded.
The key here is to use
server.URL = https://sub.domain.com
to supply your own domain.