-
Notifications
You must be signed in to change notification settings - Fork 241
/
Copy pathmain.go
129 lines (118 loc) · 4 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Command proxy is a chromedp example demonstrating how to authenticate a proxy
// server which requires authentication.
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"net/http/httputil"
"github.com/chromedp/cdproto/fetch"
"github.com/chromedp/chromedp"
)
func main() {
// create a simple proxy that requires authentication
p := httptest.NewServer(newProxy())
defer p.Close()
// create a web server
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, "test")
}))
defer s.Close()
opts := append(chromedp.DefaultExecAllocatorOptions[:],
// 1) specify the proxy server.
// Note that the username/password is not provided here.
// Check the link below for the description of the proxy settings:
// https://www.chromium.org/developers/design-documents/network-settings
chromedp.ProxyServer(p.URL),
// By default, Chrome will bypass localhost.
// The test server is bound to localhost, so we should add the
// following flag to use the proxy for localhost URLs.
chromedp.Flag("proxy-bypass-list", "<-loopback>"),
)
ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
// log the protocol messages to understand how it works.
ctx, cancel = chromedp.NewContext(ctx, chromedp.WithDebugf(log.Printf))
defer cancel()
// 3) handle the Fetch.AuthRequired event and provide the username/password to the proxy
// We will disable the fetch domain and cancel the event handler once the proxy is
// authenticated to reduce the overhead. If your project needs the fetch domain to be enabled,
// then you should change the code accordingly.
lctx, lcancel := context.WithCancel(ctx)
chromedp.ListenTarget(lctx, func(ev interface{}) {
switch ev := ev.(type) {
case *fetch.EventRequestPaused:
go func() {
_ = chromedp.Run(ctx, fetch.ContinueRequest(ev.RequestID))
}()
case *fetch.EventAuthRequired:
if ev.AuthChallenge.Source == fetch.AuthChallengeSourceProxy {
go func() {
_ = chromedp.Run(ctx,
fetch.ContinueWithAuth(ev.RequestID, &fetch.AuthChallengeResponse{
Response: fetch.AuthChallengeResponseResponseProvideCredentials,
Username: "u",
Password: "p",
}),
// Chrome will remember the credential for the current instance,
// so we can disable the fetch domain once credential is provided.
// Please file an issue if Chrome does not work in this way.
fetch.Disable(),
)
// and cancel the event handler too.
lcancel()
}()
}
}
})
if err := chromedp.Run(ctx,
// 2) enable the fetch domain to handle the Fetch.AuthRequired event
fetch.Enable().WithHandleAuthRequests(true),
chromedp.Navigate(s.URL),
); err != nil {
log.Fatal(err)
}
// to show that further requests (even in new tabs) are authenticated.
tctx, cancel := chromedp.NewContext(ctx)
defer cancel()
if err := chromedp.Run(tctx,
chromedp.Navigate(s.URL+"/tab"),
); err != nil {
log.Fatal(err)
}
}
// newProxy creates a proxy that requires authentication.
func newProxy() *httputil.ReverseProxy {
return &httputil.ReverseProxy{
Director: func(r *http.Request) {
if dump, err := httputil.DumpRequest(r, true); err == nil {
log.Printf("%s", dump)
}
// hardcode username/password "u:p" (base64 encoded: dTpw ) to make it simple
if auth := r.Header.Get("Proxy-Authorization"); auth != "Basic dTpw" {
r.Header.Set("X-Failed", "407")
}
},
Transport: &transport{http.DefaultTransport},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
if err.Error() == "407" {
log.Println("proxy: not authorized")
w.Header().Add("Proxy-Authenticate", `Basic realm="Proxy Authorization"`)
w.WriteHeader(407)
} else {
w.WriteHeader(http.StatusBadGateway)
}
},
}
}
type transport struct {
http.RoundTripper
}
func (t *transport) RoundTrip(r *http.Request) (*http.Response, error) {
if h := r.Header.Get("X-Failed"); h != "" {
return nil, fmt.Errorf(h)
}
return t.RoundTripper.RoundTrip(r)
}