mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-15 21:03:41 +00:00
refactor: remove proxy API (#1596)
This commit is contained in:
98 files changed
+5958
-1225
No files matched your search
@@ -1,9 +1,7 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
@@ -337,43 +335,6 @@ func UpdateExtension(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
||||
target := r.Header.Get("X-Target-Uri")
|
||||
if target == "" {
|
||||
writeError(w, "param invalid: X-Target-Uri")
|
||||
return
|
||||
}
|
||||
targetUrl, err := url.Parse(target)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
r.RequestURI = ""
|
||||
r.URL = targetUrl
|
||||
r.Host = targetUrl.Host
|
||||
r.Header.Del("Cookie")
|
||||
r.Header.Del("X-Api-Token")
|
||||
r.Header.Del("X-Target-Uri")
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
for k, vv := range resp.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func GetStats(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
taskId := vars["id"]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type MirrorCandidatesReq struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type MirrorCandidatesResp struct {
|
||||
URLs []string `json:"urls"`
|
||||
}
|
||||
+31
-28
@@ -216,7 +216,6 @@ func buildServer(startCfg *model.StartConfig, continueOnStartup bool) (*http.Ser
|
||||
WriteStatusJson(w, response.StatusCode, response.Body)
|
||||
})
|
||||
}
|
||||
r.Path("/api/web/proxy").HandlerFunc(DoProxy)
|
||||
if startCfg.MCPEnable {
|
||||
r.Path("/mcp").Handler(mcpserver.NewHandler(Downloader))
|
||||
} else {
|
||||
@@ -231,35 +230,39 @@ func buildServer(startCfg *model.StartConfig, continueOnStartup bool) (*http.Ser
|
||||
if enableWebAuth {
|
||||
webSessions = newWebSessionStore()
|
||||
}
|
||||
if startCfg.WebEnable {
|
||||
if enableWebAuth {
|
||||
r.Methods(http.MethodPost).Path("/api/web/login").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var loginReq model.WebAuth
|
||||
if ReadJson(r, w, &loginReq) {
|
||||
if loginReq.Username == startCfg.WebAuth.Username && loginReq.Password == startCfg.WebAuth.Password {
|
||||
sessionID, expiresAt, err := webSessions.create()
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: webAuthCookieName,
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
MaxAge: int(webAuthSessionTTL.Seconds()),
|
||||
Expires: expiresAt,
|
||||
HttpOnly: true,
|
||||
Secure: requestUsesHTTPS(r),
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
WriteJson(w, model.NewNilResult())
|
||||
if enableWebAuth {
|
||||
r.Methods(http.MethodPost).Path("/api/web/login").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var loginReq model.WebAuth
|
||||
if ReadJson(r, w, &loginReq) {
|
||||
if loginReq.Username == startCfg.WebAuth.Username && loginReq.Password == startCfg.WebAuth.Password {
|
||||
sessionID, expiresAt, err := webSessions.create()
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: webAuthCookieName,
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
MaxAge: int(webAuthSessionTTL.Seconds()),
|
||||
Expires: expiresAt,
|
||||
HttpOnly: true,
|
||||
Secure: requestUsesHTTPS(r),
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
WriteJson(w, model.NewNilResult())
|
||||
return
|
||||
}
|
||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||
})
|
||||
}
|
||||
}
|
||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||
})
|
||||
}
|
||||
// Reserve the API namespace before the web file handler, including unknown
|
||||
// endpoints and unsupported methods on otherwise known API paths.
|
||||
r.Path("/api").Handler(http.NotFoundHandler())
|
||||
r.PathPrefix("/api/").Handler(http.NotFoundHandler())
|
||||
if startCfg.WebEnable {
|
||||
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
||||
r.PathPrefix("/").Handler(gzipMiddleware(http.FileServer(newEmbedCacheFileSystem(http.FS(startCfg.WebFS)))))
|
||||
@@ -325,7 +328,7 @@ func buildServer(startCfg *model.StartConfig, continueOnStartup bool) (*http.Ser
|
||||
})
|
||||
|
||||
server := &http.Server{Handler: handlers.CORS(
|
||||
handlers.AllowedHeaders([]string{"Accept", "Content-Type", "Authorization", "X-Api-Token", "X-Target-Uri", "Mcp-Method", "Mcp-Name", "Mcp-Protocol-Version", "Mcp-Session-Id", "Last-Event-ID"}),
|
||||
handlers.AllowedHeaders([]string{"Accept", "Content-Type", "Authorization", "X-Api-Token", "Mcp-Method", "Mcp-Name", "Mcp-Protocol-Version", "Mcp-Session-Id", "Last-Event-ID"}),
|
||||
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||
handlers.AllowedOrigins([]string{"*"}),
|
||||
handlers.ExposedHeaders([]string{"Mcp-Session-Id"}),
|
||||
|
||||
+44
-24
@@ -2,7 +2,6 @@ package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/internal/test"
|
||||
@@ -669,29 +669,49 @@ func TestWebFsEnhance(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDoProxy(t *testing.T) {
|
||||
doTest(func() {
|
||||
code, respBody := doHttpRequest0(http.MethodGet, "/api/web/proxy", map[string]string{
|
||||
"X-Target-Uri": "https://github.com/GopeedLab/gopeed/raw/695da7ea87d2b455552b709d3cb4d7879484d4d1/README.md",
|
||||
}, nil)
|
||||
if code != http.StatusOK {
|
||||
t.Errorf("DoProxy() got = %v, want %v", code, http.StatusOK)
|
||||
}
|
||||
want := "4ee193b676f1ebb2ad810e016350d52a"
|
||||
got := fmt.Sprintf("%x", md5.Sum(respBody))
|
||||
if got != want {
|
||||
t.Errorf("DoProxy() got = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
doTest(func() {
|
||||
code, _ := doHttpRequest0(http.MethodGet, "/api/web/proxy", map[string]string{
|
||||
"X-Target-Uri": "https://github.com/GopeedLab/gopeed/raw/695da7ea87d2b455552b709d3cb4d7879484d4d1/NOT_FOUND",
|
||||
}, nil)
|
||||
if code != http.StatusNotFound {
|
||||
t.Errorf("DoProxy() got = %v, want %v", code, http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
func TestUnknownAPIPathsReturn404(t *testing.T) {
|
||||
for _, webEnabled := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("web=%t", webEnabled), func(t *testing.T) {
|
||||
doTest0(func(cfg *model.StartConfig) {
|
||||
cfg.WebEnable = webEnabled
|
||||
cfg.WebFS = fstest.MapFS{
|
||||
"index.html": &fstest.MapFile{Data: []byte("<html>app</html>")},
|
||||
"api/static.txt": &fstest.MapFile{Data: []byte("must not serve API paths as files")},
|
||||
"apiary": &fstest.MapFile{Data: []byte("namespace boundary")},
|
||||
}
|
||||
}, func() {
|
||||
paths := []string{"/api", "/api/", "/api/missing", "/api/v1/missing", "/api/web/missing", "/api/web/login", "/api/static.txt"}
|
||||
for _, path := range paths {
|
||||
for _, method := range []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodHead} {
|
||||
code, body := doHttpRequest0(method, path, nil, nil)
|
||||
if code != http.StatusNotFound {
|
||||
t.Fatalf("%s %s status %d, want 404: %s", method, path, code, body)
|
||||
}
|
||||
if strings.Contains(string(body), "<html>app</html>") {
|
||||
t.Fatalf("%s %s fell through to web files", method, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Known API routes still run, while unsupported methods use the API fallback.
|
||||
code, _ := doHttpRequest0(http.MethodGet, "/api/v1/info", nil, nil)
|
||||
if code != http.StatusOK {
|
||||
t.Fatalf("known API status %d, want 200", code)
|
||||
}
|
||||
code, _ = doHttpRequest0(http.MethodPost, "/api/v1/info", nil, nil)
|
||||
if code != http.StatusNotFound {
|
||||
t.Fatalf("unsupported API method status %d, want 404", code)
|
||||
}
|
||||
if webEnabled {
|
||||
for path, want := range map[string]string{"/": "<html>app</html>", "/apiary": "namespace boundary"} {
|
||||
code, body := doHttpRequest0(http.MethodGet, path, map[string]string{"Accept-Encoding": "identity"}, nil)
|
||||
if code != http.StatusOK || !strings.Contains(string(body), want) {
|
||||
t.Fatalf("web file %s status %d body %s", path, code, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestWebhook(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user