package webdevice import ( "errors" "io" "net/http" "net/http/httputil" "net/url" "strconv" "strings" ) var ( ErrInvalidTargetIP = errors.New("invalid target ip") ErrTargetNotAllowed = errors.New("target ip not allowed") ErrInvalidProxyURL = errors.New("invalid proxy target") ) func (s *Service) ProxyHTTP(w http.ResponseWriter, r *http.Request, targetIP, proxyPath string) error { if !IsPrivateIPv4Literal(targetIP) { return ErrInvalidTargetIP } if !s.IsAllowed(targetIP) { return ErrTargetNotAllowed } rawTarget := s.ProxyTargetURL(targetIP) targetURL, err := url.Parse(rawTarget) if err != nil || targetURL.Scheme == "" || targetURL.Host == "" { return ErrInvalidProxyURL } if proxyPath == "" { proxyPath = "/" } rawQuery := r.URL.RawQuery proxy := &httputil.ReverseProxy{ Director: func(req *http.Request) { req.URL.Scheme = targetURL.Scheme req.URL.Host = targetURL.Host req.URL.Path = JoinProxyTargetPath(targetURL.Path, proxyPath) req.URL.RawPath = "" req.URL.RawQuery = rawQuery req.Host = targetURL.Host req.Header.Del("Accept-Encoding") }, ModifyResponse: func(resp *http.Response) error { proxyPrefix := "/proxy/web/" + targetIP if location := resp.Header.Get("Location"); location != "" { resp.Header.Set("Location", RewriteLocation(targetIP, targetURL, location)) } if cookies := resp.Header.Values("Set-Cookie"); len(cookies) > 0 { resp.Header.Del("Set-Cookie") for _, cookie := range cookies { resp.Header.Add("Set-Cookie", RewriteSetCookie(cookie, proxyPrefix)) } } contentType := resp.Header.Get("Content-Type") if ShouldRewriteBody(contentType) { body, err := io.ReadAll(resp.Body) if err != nil { return err } _ = resp.Body.Close() rewritten := RewriteText(string(body), proxyPrefix, targetURL, contentType) rewrittenBytes := []byte(rewritten) resp.Body = io.NopCloser(strings.NewReader(rewritten)) resp.ContentLength = int64(len(rewrittenBytes)) resp.Header.Set("Content-Length", strconv.Itoa(len(rewrittenBytes))) resp.Header.Del("Content-Encoding") resp.Header.Del("Content-MD5") resp.Header.Del("Etag") } return nil }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { http.Error(w, "代理访问失败: "+err.Error(), http.StatusBadGateway) }, } proxy.ServeHTTP(closeNotifyWriter{ResponseWriter: w}, r) return nil } type closeNotifyWriter struct { http.ResponseWriter } func (w closeNotifyWriter) CloseNotify() <-chan bool { ch := make(chan bool, 1) return ch } func (w closeNotifyWriter) Flush() { if flusher, ok := w.ResponseWriter.(http.Flusher); ok { flusher.Flush() } }