mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-05 16:06:55 +08:00
166 lines
4.2 KiB
Go
166 lines
4.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/oarkflow/json"
|
|
"github.com/oarkflow/mq"
|
|
"github.com/oarkflow/mq/dag"
|
|
)
|
|
|
|
// HTTPAPINodeHandler handles HTTP API calls to external services
|
|
type HTTPAPINodeHandler struct {
|
|
dag.Operation
|
|
client *http.Client
|
|
}
|
|
|
|
func NewHTTPAPINodeHandler(id string) *HTTPAPINodeHandler {
|
|
handler := &HTTPAPINodeHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "http_api",
|
|
Type: dag.HTTPAPI,
|
|
Tags: []string{"external", "http", "api"},
|
|
},
|
|
client: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
handler.Payload = dag.Payload{Data: make(map[string]any)}
|
|
handler.RequiredFields = []string{"url"}
|
|
return handler
|
|
}
|
|
|
|
func (h *HTTPAPINodeHandler) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
data, err := dag.UnmarshalPayload[map[string]any](ctx, task.Payload)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to unmarshal task payload: %w", err), Ctx: ctx}
|
|
}
|
|
|
|
// Extract HTTP configuration from payload data
|
|
method, ok := h.Payload.Data["method"].(string)
|
|
if !ok {
|
|
method = "GET" // default to GET
|
|
}
|
|
|
|
url, ok := h.Payload.Data["url"].(string)
|
|
if !ok {
|
|
return mq.Result{Error: fmt.Errorf("HTTP URL not specified"), Ctx: ctx}
|
|
}
|
|
|
|
// Prepare request body
|
|
var reqBody io.Reader
|
|
if method == "POST" || method == "PUT" || method == "PATCH" {
|
|
if body, exists := data["body"]; exists {
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to marshal request body: %w", err), Ctx: ctx}
|
|
}
|
|
reqBody = bytes.NewBuffer(bodyBytes)
|
|
} else if body, exists := h.Payload.Data["body"]; exists {
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to marshal request body: %w", err), Ctx: ctx}
|
|
}
|
|
reqBody = bytes.NewBuffer(bodyBytes)
|
|
}
|
|
}
|
|
|
|
// Make the HTTP call
|
|
result, err := h.makeHTTPCall(ctx, method, url, reqBody, data)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("HTTP call failed: %w", err), Ctx: ctx}
|
|
}
|
|
|
|
// Prepare response
|
|
responseData := map[string]any{
|
|
"http_response": result,
|
|
"original_data": data,
|
|
}
|
|
|
|
generated := h.GeneratedFields
|
|
if len(generated) == 0 {
|
|
generated = append(generated, h.ID)
|
|
}
|
|
for _, g := range generated {
|
|
data[g] = responseData
|
|
}
|
|
|
|
responsePayload, err := json.Marshal(responseData)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to marshal response: %w", err), Ctx: ctx}
|
|
}
|
|
|
|
return mq.Result{Payload: responsePayload, Ctx: ctx}
|
|
}
|
|
|
|
func (h *HTTPAPINodeHandler) makeHTTPCall(ctx context.Context, method, url string, body io.Reader, data map[string]any) (map[string]any, error) {
|
|
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
|
}
|
|
|
|
// Set headers
|
|
// req.Header.Set("Content-Type", "application/json")
|
|
// req.Header.Set("Accept", "application/json")
|
|
|
|
// Add custom headers from configuration
|
|
if headers, ok := h.Payload.Data["headers"].(map[string]any); ok {
|
|
for k, v := range headers {
|
|
if strVal, ok := v.(string); ok {
|
|
req.Header.Set(k, strVal)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add query parameters from data
|
|
if queryParams, ok := data["query"].(map[string]any); ok {
|
|
q := req.URL.Query()
|
|
for k, v := range queryParams {
|
|
if strVal, ok := v.(string); ok {
|
|
q.Add(k, strVal)
|
|
}
|
|
}
|
|
req.URL.RawQuery = q.Encode()
|
|
}
|
|
|
|
resp, err := h.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("HTTP request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
result := map[string]any{
|
|
"status_code": resp.StatusCode,
|
|
"status": resp.Status,
|
|
"headers": make(map[string]string),
|
|
}
|
|
|
|
// Convert headers to map
|
|
for k, v := range resp.Header {
|
|
result["headers"].(map[string]string)[k] = strings.Join(v, ", ")
|
|
}
|
|
|
|
// Try to parse response body as JSON
|
|
var jsonBody interface{}
|
|
if err := json.Unmarshal(respBody, &jsonBody); err != nil {
|
|
// If not JSON, store as string
|
|
result["body"] = string(respBody)
|
|
} else {
|
|
result["body"] = jsonBody
|
|
}
|
|
|
|
return result, nil
|
|
}
|