在Go中嘲笑HTTPS响应 [英] Mocking HTTPS responses in Go
问题描述
我正在尝试编写对Web服务发出请求的包的测试。我遇到的问题可能是由于我对TLS缺乏了解。
目前我的测试看起来像这样:
func TestSimple(){
server:= httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,r * http.Request){$ b $设置(Content-Type,application / json)
w.WriteHeader(200)
fmt.Fprintf(w,`{fake:json data这里})
)))
transport:=& http.Transport {
Proxy:func(req * http.Request)(* url.URL,error){
return url.Parse(server.URL)
},
}
//客户端是我的包中发出请求的类型
client:= Client {
c:http.Client {Transport:transport},
}
client.DoRequest()// ...
}
我的包有一个包变量(我希望它是一个常量..),用于查询Web服务的基地址。它是一个https网址。我上面创建的测试服务器是普通的HTTP,没有TLS。
默认情况下,我的测试失败,出现错误tls:first记录看起来不像TLS握手。
为了得到这个结果,我的测试在进行查询之前将包变量更改为普通的http URL而不是https。
有没有办法解决这个问题?我可以使包变量成为一个常量(https),并且可以将 http.Transport
设置为降级为未加密的HTTP,或者使用 httptest .NewTLSServer()
改为?
(当我尝试使用 NewTLSServer()
时,我从127.0.0.1获得http:TLS握手错误: 45678:tls:oversized record received with length 20037)
大部分行为在 net / http
可以被模拟,扩展或改变。虽然 http.Client
是一个实现HTTP客户端语义的具体类型,它的所有字段都被导出并可以定制。
Client.Transport
字段可能会被替换,以使客户端执行任何操作自定义协议(如ftp://或file://)直接连接到本地处理程序(不生成HTTP协议字节或通过网络发送任何内容)。
客户端功能,例如 < a href =http://play.golang.org/p/afljO086iB =noreferrer> http://play.golang.org/p/afljO086iB 包含一个自定义的 上面链接的代码也包含在下面: 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: 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 (When I try to use Most of the behavior in The The client functions, such as http://play.golang.org/p/afljO086iB contains a custom The above-linked code is included below as well:
这篇关于在Go中嘲笑HTTPS响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! http.Get
,所有这些都使用导出的 http.DefaultClient
包变量(您可以修改),所以利用这些便利函数的代码不会 ,例如,必须更改为调用自定义客户端变量上的方法。请注意,虽然修改公共库中的全局行为是不合理的,但在应用程序和测试(包括库测试)中这样做是非常有用的。
http.RoundTripper
重写请求URL,以便将其路由到本地托管的 httptest.Server
,另一个示例直接通过请求一个 http.Handler
以及一个自定义 http.ResponseWriter
实现,以创建一个 http.Response
。第二种方法并不像第一种方式那样勤勉(它没有在响应值中填写尽可能多的字段),但是效率更高,并且应该足够兼容,以便与大多数处理程序和客户端调用程序一起工作。
package main
进口(
fmt
io
记录
净/ http
净/ http / httptest
net / url
os
path
strings
)
func Handler(w http.ResponseWriter,r * http.Request){
fmt.Fprintf(w,hello%s \ n,path.Base(r.URL.Path))
}
func main(){
s:= httptest.NewServer(http.HandlerFunc(Handler))
u,err:= url.Parse(s.URL)
if err!= nil {
log.Fatalln(未能解析httptest.Server URL:,err)
}
http.DefaultClient.Transport = RewriteTransport {URL:u}
resp,err:= http。获取(https://google.com/path-on e)
如果err!= nil {
log.Fatalln(未能发送第一个请求:,err)
}
fmt.Println([First Response] )
resp.Write(os.Stdout)
fmt.Print(\\\
,strings.Repeat( - ,80),\\\
\\\
)
http.DefaultClient.Transport = HandlerTransport {http.HandlerFunc(Handler)}
resp,err = http.Get(https://google.com/path-two )
if err!= nil {
log.Fatalln(未能发送第二个请求:,err)
}
fmt.Println([Second Response])
resp.Write(os.Stdout)
}
// RewriteTransport是一个http.RoundTripper,它使用提供的URL的Scheme和Host重写请求
// //,和它的路径作为前缀。
//不透明字段不变。
//如果Transport为零,则使用http.DefaultTransport
type RewriteTransport struct {
Transport http.RoundTripper
URL * url.URL
}
func(t RewriteTransport)RoundTrip(req * http.Request)(* http.Response,error){
//注意url.URL.ResolveReference在这里不起作用
//因为tu是绝对URL
req.URL.Scheme = t.URL.Scheme
req.URL.Host = t.URL.Host
req.URL.Path = path.Join(如果rt == nil {
rt = http.DefaultTransport
}
返回rt.RoundTrip(req)
}
类型HandlerTransport结构{h http.Handler}
func(t HandlerTransport)RoundTrip(req * http.Request) (* http.Response,error){
r,w:= io.Pipe()
resp:=& http.Response {
Proto:HTTP / 1.1,
ProtoMajor:1,
ProtoMinor:1,
标题:make(http.Header),
正文:r,
Request:req,
}
ready:= make(chan struct {})
prw:=& pipeResponseWriter {r,w,resp,ready}
go func(){
defer w.Close()
thServeHTTP(prw,req)
}()
<-ready
return resp,nil
}
类型pipeResponseWriter结构{$ b $ * io.PipeReader
w * io.PipeWriter
resp * http.Response
ready chan< - struct {}
}
func(w * pipeResponseWriter)Header()http.Header {
return w.resp.Header
}
func(w * pipeResponseWriter)Write(p [] byte)(int,error){
if w.ready!= nil {
w.WriteHeader(http.StatusOK)
返回wwWrite(p)
}
func(w * pipeResponseWriter)WriteHeader(status int){
if w.ready == nil {
//已经叫做
返回
}
w.resp.StatusCode =状态
w.resp.Status = fmt。 Sprintf(%d%s,status,http.StatusText(status))
close(w.ready)
w.ready = nil
}
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() // ...
}
http.Transport
that "downgrades" to unencrypted HTTP, or use httptest.NewTLSServer()
instead? NewTLSServer()
I get "http: TLS handshake error from 127.0.0.1:45678: tls: oversized record received with length 20037")net/http
can be mocked, extended, or altered. Although http.Client
is a concrete type that implements HTTP client semantics, all of its fields are exported and may be customized.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).http.Get
, all utilize the exported http.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.RoundTripper
that rewrites the request URL so that it'll be routed to a locally hosted httptest.Server
, and another example that directly passes the request to an http.Handler
, along with a custom http.ResponseWriter
implementation, in order to create an http.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.package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path"
"strings"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello %s\n", path.Base(r.URL.Path))
}
func main() {
s := httptest.NewServer(http.HandlerFunc(Handler))
u, err := url.Parse(s.URL)
if err != nil {
log.Fatalln("failed to parse httptest.Server URL:", err)
}
http.DefaultClient.Transport = RewriteTransport{URL: u}
resp, err := http.Get("https://google.com/path-one")
if err != nil {
log.Fatalln("failed to send first request:", err)
}
fmt.Println("[First Response]")
resp.Write(os.Stdout)
fmt.Print("\n", strings.Repeat("-", 80), "\n\n")
http.DefaultClient.Transport = HandlerTransport{http.HandlerFunc(Handler)}
resp, err = http.Get("https://google.com/path-two")
if err != nil {
log.Fatalln("failed to send second request:", err)
}
fmt.Println("[Second Response]")
resp.Write(os.Stdout)
}
// RewriteTransport is an http.RoundTripper that rewrites requests
// using the provided URL's Scheme and Host, and its Path as a prefix.
// The Opaque field is untouched.
// If Transport is nil, http.DefaultTransport is used
type RewriteTransport struct {
Transport http.RoundTripper
URL *url.URL
}
func (t RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// note that url.URL.ResolveReference doesn't work here
// since t.u is an absolute url
req.URL.Scheme = t.URL.Scheme
req.URL.Host = t.URL.Host
req.URL.Path = path.Join(t.URL.Path, req.URL.Path)
rt := t.Transport
if rt == nil {
rt = http.DefaultTransport
}
return rt.RoundTrip(req)
}
type HandlerTransport struct{ h http.Handler }
func (t HandlerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
r, w := io.Pipe()
resp := &http.Response{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Body: r,
Request: req,
}
ready := make(chan struct{})
prw := &pipeResponseWriter{r, w, resp, ready}
go func() {
defer w.Close()
t.h.ServeHTTP(prw, req)
}()
<-ready
return resp, nil
}
type pipeResponseWriter struct {
r *io.PipeReader
w *io.PipeWriter
resp *http.Response
ready chan<- struct{}
}
func (w *pipeResponseWriter) Header() http.Header {
return w.resp.Header
}
func (w *pipeResponseWriter) Write(p []byte) (int, error) {
if w.ready != nil {
w.WriteHeader(http.StatusOK)
}
return w.w.Write(p)
}
func (w *pipeResponseWriter) WriteHeader(status int) {
if w.ready == nil {
// already called
return
}
w.resp.StatusCode = status
w.resp.Status = fmt.Sprintf("%d %s", status, http.StatusText(status))
close(w.ready)
w.ready = nil
}