|
2 | 2 | package bartender
|
3 | 3 |
|
4 | 4 | import (
|
| 5 | + "context" |
5 | 6 | "log"
|
6 | 7 | "net/http"
|
7 | 8 | "net/http/httputil"
|
8 | 9 | "net/url"
|
| 10 | + "strings" |
9 | 11 |
|
10 | 12 | "github.com/go-rod/rod"
|
11 | 13 | "github.com/mileusna/useragent"
|
@@ -53,27 +55,80 @@ func (b *Bartender) BypassUserAgentNames(list map[string]bool) {
|
53 | 55 |
|
54 | 56 | func (b *Bartender) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
55 | 57 | ua := useragent.Parse(r.Header.Get("User-Agent"))
|
56 |
| - if b.bypassList[ua.Name] { |
| 58 | + if r.Method != http.MethodGet || b.bypassList[ua.Name] { |
57 | 59 | b.proxy.ServeHTTP(w, r)
|
58 | 60 |
|
59 | 61 | return
|
60 | 62 | }
|
61 | 63 |
|
62 |
| - b.RenderPage(w, r) |
| 64 | + if b.RenderPage(w, r) { |
| 65 | + return |
| 66 | + } |
| 67 | + |
| 68 | + b.proxy.ServeHTTP(w, r) |
63 | 69 | }
|
64 | 70 |
|
65 |
| -func (b *Bartender) RenderPage(w http.ResponseWriter, r *http.Request) { |
66 |
| - log.Println("headless render:", r.URL.String()) |
| 71 | +// RenderPage returns true if the page is rendered by the headless browser. |
| 72 | +func (b *Bartender) RenderPage(w http.ResponseWriter, r *http.Request) bool { |
| 73 | + u := b.getTargetURL(r.URL) |
| 74 | + |
| 75 | + statusCode, resHeader := getHeader(r.Context(), u) |
| 76 | + |
| 77 | + if !htmlContentType(resHeader) { |
| 78 | + return false |
| 79 | + } |
| 80 | + |
| 81 | + log.Println("headless render:", u) |
| 82 | + |
| 83 | + page := b.pool.Get(func() *rod.Page { return rod.New().MustConnect().MustPage() }) |
| 84 | + defer b.pool.Put(page) |
| 85 | + |
| 86 | + page.MustNavigate(u).MustWaitStable() |
| 87 | + |
| 88 | + for k, vs := range resHeader { |
| 89 | + if k == "Content-Length" { |
| 90 | + continue |
| 91 | + } |
| 92 | + |
| 93 | + for _, v := range vs { |
| 94 | + w.Header().Add(k, v) |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + w.WriteHeader(statusCode) |
67 | 99 |
|
68 |
| - u := *r.URL |
| 100 | + _, err := w.Write([]byte(page.MustHTML())) |
| 101 | + if err != nil { |
| 102 | + panic(err) |
| 103 | + } |
| 104 | + |
| 105 | + return true |
| 106 | +} |
| 107 | + |
| 108 | +func (b *Bartender) getTargetURL(reqURL *url.URL) string { |
| 109 | + u := *reqURL |
69 | 110 | u.Scheme = b.target.Scheme
|
70 | 111 | u.Host = b.target.Host
|
71 | 112 |
|
72 |
| - page := b.pool.Get(func() *rod.Page { return rod.New().MustConnect().MustPage() }) |
| 113 | + return u.String() |
| 114 | +} |
73 | 115 |
|
74 |
| - page.MustNavigate(u.String()).MustWaitStable() |
| 116 | +func getHeader(ctx context.Context, u string) (int, http.Header) { |
| 117 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) |
| 118 | + if err != nil { |
| 119 | + panic(err) |
| 120 | + } |
75 | 121 |
|
76 |
| - w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| 122 | + res, err := http.DefaultClient.Do(req) |
| 123 | + if err != nil { |
| 124 | + panic(err) |
| 125 | + } |
| 126 | + |
| 127 | + _ = res.Body.Close() |
| 128 | + |
| 129 | + return res.StatusCode, res.Header |
| 130 | +} |
77 | 131 |
|
78 |
| - _, _ = w.Write([]byte(page.MustHTML())) |
| 132 | +func htmlContentType(h http.Header) bool { |
| 133 | + return strings.Contains(h.Get("Content-Type"), "text/html") |
79 | 134 | }
|
0 commit comments