Files
mq/handlers/http_api_handler.go
2025-09-26 09:22:10 +05:45

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
}