266 lines
7.3 KiB
Go
266 lines
7.3 KiB
Go
package testweb
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io"
|
|
"io/fs"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Deps testweb 依赖。
|
|
type Deps struct {
|
|
SuperviseTarget string
|
|
FileTarget string
|
|
APILogRoot string
|
|
}
|
|
|
|
// Register 注册 /t 页面与 API。
|
|
func Register(mux *http.ServeMux, deps Deps) {
|
|
s := &server{deps: deps}
|
|
sub, _ := fs.Sub(webFS, "web")
|
|
static := http.FileServer(http.FS(sub))
|
|
|
|
mux.HandleFunc("/t", s.handlePage)
|
|
mux.HandleFunc("/t/", func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/t/static/") {
|
|
http.StripPrefix("/t/static/", static).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if r.URL.Path == "/t" || r.URL.Path == "/t/" {
|
|
s.handlePage(w, r)
|
|
return
|
|
}
|
|
if r.URL.Path == "/t/prescription" || r.URL.Path == "/t/prescription/" {
|
|
s.handlePrescriptionPage(w, r)
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
})
|
|
|
|
mux.HandleFunc("/t/api/meta", s.handleMeta)
|
|
mux.HandleFunc("/t/api/logs", s.handleLogs)
|
|
mux.HandleFunc("/t/api/logs/detail", s.handleLogDetail)
|
|
mux.HandleFunc("/t/api/logs/export", s.handleLogExport)
|
|
mux.HandleFunc("/t/api/prescription/records", s.handlePrescriptionRecords)
|
|
mux.HandleFunc("/t/api/prescription/rebuild", s.handlePrescriptionRebuild)
|
|
mux.HandleFunc("/t/api/test/connect", s.handleTestConnect)
|
|
mux.HandleFunc("/t/api/test/send", s.handleTestSend)
|
|
}
|
|
|
|
type server struct {
|
|
deps Deps
|
|
}
|
|
|
|
func (s *server) handlePage(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/t" && r.URL.Path != "/t/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
b, err := webFS.ReadFile("web/index.html")
|
|
if err != nil {
|
|
http.Error(w, "page not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
_, _ = w.Write(b)
|
|
}
|
|
|
|
func (s *server) handleMeta(w http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(w, map[string]any{
|
|
"superviseTarget": s.deps.SuperviseTarget,
|
|
"fileTarget": s.deps.FileTarget,
|
|
"apiLogRoot": s.deps.APILogRoot,
|
|
})
|
|
}
|
|
|
|
func (s *server) handleLogs(w http.ResponseWriter, _ *http.Request) {
|
|
list, err := ListFileLogs(s.deps.APILogRoot)
|
|
if err != nil {
|
|
writeErr(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if list == nil {
|
|
list = []FileLogEntry{}
|
|
}
|
|
writeJSON(w, list)
|
|
}
|
|
|
|
func (s *server) handleLogDetail(w http.ResponseWriter, r *http.Request) {
|
|
rel := r.URL.Query().Get("rel")
|
|
parsed, err := ParseAPILogFile(s.deps.APILogRoot, rel)
|
|
if err != nil {
|
|
writeErr(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
out := toDetailDTO(parsed)
|
|
writeJSON(w, out)
|
|
}
|
|
|
|
type logDetailJSON struct {
|
|
Rel string `json:"rel"`
|
|
Meta map[string]string `json:"meta"`
|
|
Inbound sectionDTO `json:"inbound"`
|
|
Outbound sectionDTO `json:"outbound"`
|
|
Response ResponseSection `json:"response"`
|
|
ParseWarnings []string `json:"parseWarnings,omitempty"`
|
|
OutboundAbsent bool `json:"outboundAbsent"`
|
|
}
|
|
|
|
type sectionDTO struct {
|
|
URL string `json:"url,omitempty"`
|
|
Method string `json:"method,omitempty"`
|
|
Headers map[string]string `json:"headers"`
|
|
BodyRaw string `json:"bodyRaw,omitempty"`
|
|
BodyLen int `json:"bodyLen"`
|
|
BodyBase64 string `json:"bodyBase64,omitempty"`
|
|
BodyPlain bool `json:"bodyPlain"`
|
|
}
|
|
|
|
func toDetailDTO(p *ParsedAPILog) logDetailJSON {
|
|
return logDetailJSON{
|
|
Rel: p.Rel,
|
|
Meta: p.Meta,
|
|
Inbound: sectionDTOFrom(p.Inbound),
|
|
Outbound: sectionDTOFrom(p.Outbound),
|
|
Response: p.Response,
|
|
ParseWarnings: p.ParseWarnings,
|
|
OutboundAbsent: p.OutboundAbsent,
|
|
}
|
|
}
|
|
|
|
func sectionDTOFrom(sec RequestSection) sectionDTO {
|
|
d := sectionDTO{
|
|
URL: sec.URL,
|
|
Method: sec.Method,
|
|
Headers: sec.Headers,
|
|
BodyRaw: sec.BodyRaw,
|
|
BodyLen: sec.BodyLen,
|
|
BodyBase64: sec.BodyBase64,
|
|
BodyPlain: sec.BodyPlain,
|
|
}
|
|
if len(sec.BodyBytes) > 0 && !sec.BodyPlain {
|
|
d.BodyBase64 = base64.StdEncoding.EncodeToString(sec.BodyBytes)
|
|
}
|
|
return d
|
|
}
|
|
|
|
func (s *server) handleLogExport(w http.ResponseWriter, r *http.Request) {
|
|
rel := r.URL.Query().Get("rel")
|
|
useInbound := r.URL.Query().Get("source") == "inbound"
|
|
parsed, err := ParseAPILogFile(s.deps.APILogRoot, rel)
|
|
if err != nil {
|
|
writeErr(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
data, err := ToPostmanCollection(parsed, useInbound)
|
|
if err != nil {
|
|
writeErr(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
name := "forward-file-" + strings.ReplaceAll(rel, "/", "_") + ".postman_collection.json"
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
|
|
_, _ = w.Write(data)
|
|
}
|
|
|
|
func (s *server) handleTestConnect(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
var req struct {
|
|
Channel string `json:"channel"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeErr(w, "invalid json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ch := strings.ToLower(strings.TrimSpace(req.Channel))
|
|
var results []ConnectResult
|
|
switch ch {
|
|
case "supervise", "28212":
|
|
results = append(results, TestConnectivity("supervise", s.deps.SuperviseTarget))
|
|
case "file", "28211":
|
|
results = append(results, TestConnectivity("file", s.deps.FileTarget))
|
|
default:
|
|
results = append(results,
|
|
TestConnectivity("supervise", s.deps.SuperviseTarget),
|
|
TestConnectivity("file", s.deps.FileTarget),
|
|
)
|
|
}
|
|
writeJSON(w, map[string]any{"results": results})
|
|
}
|
|
|
|
func (s *server) handleTestSend(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
body, _ := io.ReadAll(r.Body)
|
|
var req struct {
|
|
Rel string `json:"rel"`
|
|
UseOutbound bool `json:"useOutbound"`
|
|
URL string `json:"url"`
|
|
Headers map[string]string `json:"headers"`
|
|
BodyBase64 string `json:"bodyBase64"`
|
|
}
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
writeErr(w, "invalid json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
url := strings.TrimSpace(req.URL)
|
|
headers := req.Headers
|
|
var payload []byte
|
|
|
|
if req.Rel != "" {
|
|
parsed, err := ParseAPILogFile(s.deps.APILogRoot, req.Rel)
|
|
if err != nil {
|
|
writeErr(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
sec := parsed.Outbound
|
|
if !req.UseOutbound {
|
|
sec = parsed.Inbound
|
|
}
|
|
if url == "" {
|
|
url = sec.URL
|
|
}
|
|
if len(headers) == 0 {
|
|
headers = sec.Headers
|
|
}
|
|
payload = sec.BodyBytes
|
|
}
|
|
if req.BodyBase64 != "" {
|
|
dec, err := base64.StdEncoding.DecodeString(req.BodyBase64)
|
|
if err != nil {
|
|
writeErr(w, "invalid bodyBase64", http.StatusBadRequest)
|
|
return
|
|
}
|
|
payload = dec
|
|
}
|
|
if url == "" {
|
|
writeErr(w, "url required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if headers == nil {
|
|
headers = map[string]string{}
|
|
}
|
|
|
|
result := ReplayRequest(url, headers, payload)
|
|
writeJSON(w, result)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, v any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func writeErr(w http.ResponseWriter, msg string, code int) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(code)
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"error": msg})
|
|
}
|