diff --git a/appservice/appservice.go b/appservice/appservice.go index 68e548d2..10b295d7 100644 --- a/appservice/appservice.go +++ b/appservice/appservice.go @@ -166,6 +166,7 @@ func (as *AppService) PrepareWebsocket() { defer as.websocketHandlersLock.Unlock() if as.websocketHandlers == nil { as.websocketHandlers = make(map[string]WebsocketHandler, 32) + as.websocketHandlers[WebsocketCommandHTTPProxy] = as.WebsocketHTTPProxy as.websocketRequests = make(map[int]chan<- *WebsocketCommand) } } diff --git a/appservice/wshttp.go b/appservice/wshttp.go new file mode 100644 index 00000000..40ceda9d --- /dev/null +++ b/appservice/wshttp.go @@ -0,0 +1,83 @@ +package appservice + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +const WebsocketCommandHTTPProxy = "http_proxy" + +type HTTPProxyRequest struct { + Method string `json:"method"` + Path string `json:"path"` + Query string `json:"query"` + Headers http.Header `json:"headers"` + Body json.RawMessage `json:"body"` +} + +type HTTPProxyResponse struct { + Status int `json:"status"` + Headers http.Header `json:"headers"` + Body json.RawMessage `json:"body"` + + bodyBuf bytes.Buffer +} + +func (p *HTTPProxyResponse) Header() http.Header { + return p.Headers +} + +func (p *HTTPProxyResponse) Write(bytes []byte) (int, error) { + if p.Status == 0 { + p.Status = http.StatusOK + } + return p.bodyBuf.Write(bytes) +} + +func (p *HTTPProxyResponse) WriteHeader(statusCode int) { + p.Status = statusCode +} + +func (as *AppService) WebsocketHTTPProxy(cmd WebsocketCommand) (bool, interface{}) { + var req HTTPProxyRequest + if err := json.Unmarshal(cmd.Data, &req); err != nil { + return false, fmt.Errorf("failed to parse proxy request: %w", err) + } + if cmd.Ctx == nil { + cmd.Ctx = context.Background() + } + reqURL := (&url.URL{ + Scheme: "http", + Host: "localhost", + Path: req.Path, + RawQuery: req.Query, + }).String() + httpReq, err := http.NewRequestWithContext(cmd.Ctx, req.Method, reqURL, bytes.NewReader(req.Body)) + if err != nil { + return false, fmt.Errorf("failed to create fake HTTP request: %w", err) + } + httpReq.Header = req.Headers + + var resp HTTPProxyResponse + resp.Headers = make(http.Header) + + as.Router.ServeHTTP(&resp, httpReq) + + if resp.bodyBuf.Len() > 0 { + bodyData := resp.bodyBuf.Bytes() + if json.Valid(bodyData) { + resp.Body = bodyData + } else { + resp.Body = make([]byte, 2+base64.RawStdEncoding.EncodedLen(len(bodyData))) + resp.Body[0] = '"' + base64.RawStdEncoding.Encode(resp.Body[1:], bodyData) + resp.Body[len(resp.Body)-1] = '"' + } + } + return true, &resp +}