201 lines
4.6 KiB
Go
201 lines
4.6 KiB
Go
package soap
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/xml"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Client es la interfaz para un cliente SOAP
|
|
type Client interface {
|
|
Call(ctx context.Context, operation string, request interface{}, response interface{}) error
|
|
}
|
|
|
|
// ClientOptions contiene opciones para el cliente SOAP
|
|
type ClientOptions struct {
|
|
Endpoint string
|
|
Username string
|
|
Password string
|
|
Timeout time.Duration
|
|
Headers map[string]string
|
|
}
|
|
|
|
// RequestIDKey es la clave para el ID de solicitud en el contexto
|
|
type RequestIDKey struct{}
|
|
|
|
// client implementa la interfaz Client
|
|
//type client struct {
|
|
// endpoint string
|
|
// httpClient *http.Client
|
|
// credentials *credentials
|
|
// headers map[string]string
|
|
//}
|
|
|
|
// credentials contiene las credenciales para autenticación
|
|
type credentials struct {
|
|
username string
|
|
password string
|
|
}
|
|
|
|
// Añadir este método al final del archivo pkg/soap/soap.go
|
|
// En la definición del cliente:
|
|
type client struct {
|
|
endpoint string
|
|
httpClient *http.Client
|
|
credentials *credentials
|
|
headers map[string]string
|
|
}
|
|
|
|
// Endpoint retorna el endpoint actual del cliente
|
|
func (c *client) Endpoint() string {
|
|
return c.endpoint
|
|
}
|
|
|
|
// NewClient crea un nuevo cliente SOAP
|
|
func NewClient(options ClientOptions) Client {
|
|
httpClient := &http.Client{
|
|
Timeout: options.Timeout,
|
|
}
|
|
|
|
var creds *credentials
|
|
if options.Username != "" && options.Password != "" {
|
|
creds = &credentials{
|
|
username: options.Username,
|
|
password: options.Password,
|
|
}
|
|
}
|
|
|
|
return &client{
|
|
endpoint: options.Endpoint,
|
|
httpClient: httpClient,
|
|
credentials: creds,
|
|
headers: options.Headers,
|
|
}
|
|
}
|
|
|
|
// Envelope es la estructura para el envelope SOAP
|
|
type Envelope struct {
|
|
XMLName xml.Name `xml:"Envelope"`
|
|
XMLNS string `xml:"xmlns,attr"`
|
|
Header interface{} `xml:"Header,omitempty"`
|
|
Body Body `xml:"Body"`
|
|
}
|
|
|
|
// Body es la estructura para el cuerpo SOAP
|
|
type Body struct {
|
|
XMLName xml.Name `xml:"Body"`
|
|
Content interface{} `xml:",omitempty"`
|
|
Fault *Fault `xml:"Fault,omitempty"`
|
|
}
|
|
|
|
// Fault es la estructura para errores SOAP
|
|
type Fault struct {
|
|
XMLName xml.Name `xml:"Fault"`
|
|
FaultCode string `xml:"faultcode,omitempty"`
|
|
FaultString string `xml:"faultstring,omitempty"`
|
|
Detail string `xml:"detail,omitempty"`
|
|
}
|
|
|
|
// Call realiza una llamada SOAP
|
|
func (c *client) Call(ctx context.Context, operation string, request interface{}, response interface{}) error {
|
|
// Obtener ID de solicitud del contexto o generar uno nuevo
|
|
var requestID string
|
|
if id, ok := ctx.Value(RequestIDKey{}).(string); ok {
|
|
requestID = id
|
|
} else {
|
|
requestID = uuid.New().String()
|
|
}
|
|
|
|
// Crear envelope SOAP
|
|
envelope := Envelope{
|
|
XMLNS: "http://schemas.xmlsoap.org/soap/envelope/",
|
|
Body: Body{
|
|
Content: request,
|
|
},
|
|
}
|
|
|
|
// Serializar envelope a XML
|
|
requestBuffer := &bytes.Buffer{}
|
|
enc := xml.NewEncoder(requestBuffer)
|
|
enc.Indent("", " ")
|
|
if err := enc.Encode(envelope); err != nil {
|
|
return errors.Wrap(err, "error encoding SOAP request")
|
|
}
|
|
|
|
// Crear solicitud HTTP
|
|
httpReq, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
c.endpoint,
|
|
requestBuffer,
|
|
)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error creating HTTP request")
|
|
}
|
|
|
|
// Establecer cabeceras HTTP
|
|
httpReq.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
|
httpReq.Header.Set("SOAPAction", operation)
|
|
httpReq.Header.Set("X-Request-ID", requestID)
|
|
|
|
// Agregar cabeceras adicionales
|
|
for key, value := range c.headers {
|
|
httpReq.Header.Set(key, value)
|
|
}
|
|
|
|
// Agregar autenticación si es necesario
|
|
if c.credentials != nil {
|
|
httpReq.SetBasicAuth(c.credentials.username, c.credentials.password)
|
|
}
|
|
|
|
// Realizar solicitud HTTP
|
|
httpResp, err := c.httpClient.Do(httpReq)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error sending HTTP request")
|
|
}
|
|
defer httpResp.Body.Close()
|
|
|
|
// Leer respuesta
|
|
respBody, err := io.ReadAll(httpResp.Body)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error reading HTTP response")
|
|
}
|
|
|
|
// Verificar código de estado HTTP
|
|
if httpResp.StatusCode != http.StatusOK {
|
|
return errors.Errorf("HTTP error: %d - %s, body: %s",
|
|
httpResp.StatusCode,
|
|
httpResp.Status,
|
|
string(respBody),
|
|
)
|
|
}
|
|
|
|
// Deserializar respuesta
|
|
respEnvelope := &Envelope{
|
|
Body: Body{
|
|
Content: response,
|
|
},
|
|
}
|
|
|
|
if err := xml.Unmarshal(respBody, respEnvelope); err != nil {
|
|
return errors.Wrap(err, "error unmarshalling SOAP response")
|
|
}
|
|
|
|
// Verificar si hay un SOAP Fault
|
|
if respEnvelope.Body.Fault != nil {
|
|
return errors.Errorf("SOAP Fault: %s - %s, detail: %s",
|
|
respEnvelope.Body.Fault.FaultCode,
|
|
respEnvelope.Body.Fault.FaultString,
|
|
respEnvelope.Body.Fault.Detail,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|