PDA

Ver la versión completa : C# Http según C#, y la liada de las peticiones



Drumpi
09/01/2024, 19:04
Buenas a todos:

Necesito que me echéis un cable, aunque sea al cuello.
Antecedentes
Estoy cambiando el motor de una WebApi, que antes usaba una API a través de la típica dll, con sus funciones, variables... todo muy C.
Pero los tiempos cambian y ahora nos "obligan" a utilizar una API web, en que las peticiones se hacen mediante HTTP y comandos GET, POST o PU... PATCH.
Los comandos más sencillos (el 80% de la API) no he tenido problemas: tengo una instancia HttpClient, que contiene los datos de logeo en el sistema, y es lo que uso, junto a JsonConvert.Serialize (para convertir clases en datos y datos en clases) para hacer mis peticiones.

Pero ahora resulta que tengo que anexar documentos, y la API trae una función sencilla para hacerlo de forma local, y un galimatías para hacerlo de forma remota.
Con la forma sencilla creo un objeto "attachment" en la BBDD que puedo anexar a cualquier documento, y funciona aunque la API web esté en otra máquina... pero el fichero no se copia a la carpeta donde debería guardarse de forma automática, junto a todos los anexos.

El problema
Según la documentación, para poder hacer el anexo correctamente, debo hacer una petición multiparte, que tenga el siguiente aspecto:

POST /b1s/v1/Attachments2 HTTP/1.1
Content-Type: multipart/form-data; boundary=myBoundary
--myBoundary
Content-Disposition: form-data; name="files"; filename="image1.jpg"
Content-Type: image/jpeg

<image binary data>
--myBoundary
Content-Disposition: form-data; name="files"; filename="image2.jpg"
Content-Type: image/jpeg

<image binary data>
--myBoundary--

El problema es que no sé traducir todo eso a comandos de la librería .Net.Http. Todos mis intentos me han producido una respuesta 405 (Method not allowed), pero si profundizo en el error, hay un error "250: Bad post content" por ahí dentro. Lo he intentado incluso con ChatGPT, pero hasta él se lía mezclando el HttpContent con un MultipartContent.
Soy tan inútil, que no sé qué significa "<image binary data>"... Sí, sé que es el contenido del fichero, pero no sé a qué lo tengo que convertir para ponerlo ahí, ni cómo incrustarlo.
Lo más limpio que tengo hecho sería esto:


public static async Task<HttpResponseMessage> AddNetAttachmentList (HttpClient client, List<string> fileList)
{
HttpRequestMessage request;
HttpResponseMessage response = null;
MultipartFormDataContent mpContent;
StringContent content;
string boundary;
string tempString;
byte[] fileContent;

JsonSerializerSettings serializeSettings = new Newtonsoft.Json.JsonSerializerSettings()
{
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
//DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore,
//Culture = new System.Globalization.CultureInfo("en-US")
};

//Creando petición multipart
boundary = "Attachments2_AddList";
mpContent = new MultipartFormDataContent(boundary);

for (int i = 0; i < fileList.Count; i++)
{
fileContent = File.ReadAllBytes(fileList[i]);
//var fileContentStream = new MemoryStream(fileContent);
//var fileContentStreamContent = new StreamContent(fileContentStream);
var fileContentStreamContent = new ByteArrayContent(fileContent);

switch (Path.GetExtension(fileList[i]))
{
case ".jpg":
fileContentStreamContent.Headers.Add("Content-Type","image/jpeg");
mpContent.Add(fileContentStreamContent, "files", Path.GetFileName(fileList[i]));
break;
case ".pdf":
//fileContent = File.ReadAllBytes(fileList[i]);
//content = new ByteArrayContent(fileContent);
//content.Headers.ContentDisposition = new ContentDispositionHeaderValue(string.Format("form-data; name=\"files\"; filename=\"{0}\"", fileList[i]));
//content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
break;
default:
fileContentStreamContent.Headers.Add("Content-Type", "application/octet-stream");
mpContent.Add(fileContentStreamContent, "files", Path.GetFileName(fileList[i]));
break;
}
}


response = client.PostAsync("Attachments2", mpContent).Result;

return response;



Obviamente he quitado mucha paja por en medio (gestión de errores, líneas de debug, etc...), y lo he intentado de un par de formas más, pero no consigo el resultado esperado.
¿Alguien me puede ayudar? Si no puede ser en C#, en algo que sea lo más parecido posible.

Gracias.

josepzin
09/01/2024, 19:47
Content-Type: image/jpeg

<image binary data>

Mira en C# cómo convertir una imagen JPG a datos binarios, seguro es algo muy común y fácil de hacer.

-----Actualizado-----

En PHP me suena haber hecho esto algunas veces.

Por lo que veo, primero se pone el "mime" (image/png...) y después la imagen convertida a datos base64.

Queda algo asi: data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3/..............

Drumpi
10/01/2024, 11:12
A ver, es posible que el problema que tengo sea de conversión del fichero a algo que se pueda adjuntar (voy a hacer una prueba con un texto plano puesto a pelo, que de eso sí tengo ejemplo), pero lo que me preocupa, en realidad, es todo lo demás.
Si por mi fuera, monto el primer código en una string y se lo largo al método que sea, pero no sé ni hacer eso, aparte de que estoy seguro de que es la forma más burda e incorrecta de hacer las cosas.
Apenas he tocado programación Web, y casi nunca he hecho peticiones en red (o han sido GEt, o POST con JSON serializados, una y otra vez), y esto me supera :llorosa:

josepzin
10/01/2024, 12:39
No me suena nunca haber usado eso de PATCH, sí lo de GET y POST.

Yo te diría que primero te asegures que funciona la comunicación con el ejemplo mas simple posible y luego le vas agregando cosas.

Yo de C# ni idea, de peticiones, llevo unos días haciendo cosas AJAX con Javascript, que la verdad funciona muy bien ahora sin ninguna librería extra, lo que sería "Javascript vainilla"

pakoito
10/01/2024, 19:07
Buscate otra libreria con soporte explicito de multi-parte. Meterse en estos berenjenales no merece la pena.

EDIT: Nuevas versiones de .NET tienen soporte explicito: https://stackoverflow.com/a/18233515 o https://learn.microsoft.com/en-us/dotnet/api/system.net.http.multipartformdatacontent?view=net-8.0 o https://makolyte.com/csharp-how-to-send-a-file-with-httpclient/

Drumpi
12/01/2024, 14:25
Al final lo consulté al experto contratado por la empresa. Normalmente tarda días en responder, pero tenía el día tonto y nos pusimos al ajo.

A ver, las peticiones simples las hago sin problemas, y por lo que respecta a montar una petición multiparte, no andaba demasiado lejos en una de las implementaciones que hice... y aún así, al experto no le funcionaba tampoco.
Tras unas cuantas pruebas, "bajamos a los infiernos", y empezamos a montar la petición "a mano"... Usando las librerías .Net.Http, pero escribiendo boundaries, content-types y demás a base de cadenas de texto. Usar Postman tampoco ayudaba, porque ahí sí funcionaba.

Tras 2 horas, el problema venía porque en la cabecera había una serie de valores que necesitaban comillas dobles, y porque los "boundaries" se estaban enviando con comillas cuando debían ir sin ellas. Y eso lo sacó porque existe una librería que (en teoría) simplifica las cosas, y leyó este problema en los comentarios del código fuente.
Total, que si a un experto le costó eso, yo no lo hubiera sacado en la vida.

Y todavía habrá quien pregunte que por qué no me gusta la programación web :D

josepzin
12/01/2024, 15:19
Y todavía habrá quien pregunte que por qué no me gusta la programación web :D

Es que estás en el lenguaje equivocado :P

Drumpi
12/01/2024, 17:34
¿Perdona?
¿Me estás diciendo que, en un ecosistema, que se basa en la interpretación de comandos de texto plano, en el que puedes definir una variable que puede contener, literalmente, cualquier cosa, y que te tienes que fiar de que lo va a interpretar como el tipo que realmente quieres que sea, y que, de pronto, por un "quítame de ahí esas comillas, o te hundo", el problema está en el lenguaje que estoy usando? :awesome:

Yo me vuelvo a los infiernos del código de bajo nivel. Programar en ASM del 68K era más fácil que esto :lol:

josepzin
12/01/2024, 18:35
Donde esté PHP que se quiten mierda5 modernas o de bajo nivel.

Drumpi
06/02/2024, 11:27
Voy a aprovechar el hilo para hacer una consulta, y así no lleno el foro con mis neuras :D

He seguido avanzando en el proyecto, y ahora resulta que, el problema que tenía con la API binaria, y que nos ha obligado a cambiar la forma de comunicarnos con el ERP, ha vuelto... pero en forma de chapa.
Con la API, bajo ciertas circunstancias (que aún no tengo muy claras), genera una excepción que, ejecutándose dentro de IIS como una WebApi, provoca la caída de su "grupo de aplicaciones", y nos obligaba a reiniciar la web manualmente. En ocasiones, el error era tan catastrófico que incluso echaba abajo el servicio W3WP de Windows.

Ahora, usando las peticiones web, bajo unas circunstancias similares, llega un punto en que cualquier petición que le hagamos al HttpClient nos devuelve un error 500 (InternalServerError). Lo que hacemos es que, cuando el usuario se loguea, creamos un HttpClient, y le incrustamos la cookie de autorización, y a partir de ahí lo usamos a lo largo de lo que dure su sesión (por defecto, 30 segundos, aunque para depuración lo tengo a 3 minutos). A diferencia de la otra API, si dejamos que la sesión caduque, y que el código se encargue de reconectar, o si descartamos el HttpClient y creamos otro, todo vuelve a funcionar con normalidad (y de momento, no se ha caído ni el grupo de aplicaciones ni nada).

Sabiendo esto, había pensado hacer que, cada petición del usuario genere un HttpClient, que se use para todas las tareas que deben ejecutarse en dicha petición. Supuestamente, habrá pocos usuarios de la web, y las peticiones entrará una cada 5 a 15 minutos en lugares tranquilos, o 2 por minuto en entornos con más actividad (por ahora, sólo usamos esta API web para peticiones POST y PUT, para el GET seguimos con el sistema antiguo).
Lo que desconozco son las consecuencias que puede acarrear esto. He leído que aunque se deje de usar el HttpClient, su puerto de comunicación sigue en uso durante unos 4 minutos, y puede provocar... no sé si he entendido que se queda sin puertos, o que se le somete a mucho estrés al sistema que los asigna.

Por si acaso, de momento, estoy intentando detectar cuándo entra en "modo negación", y cuando pille un error 500, que desloguée al usuario y reconecte con un HttpClient "fresco". El problema es que tengo que hacer eso con cada petición (o al menos, con las más problemáticas).

josepzin
06/02/2024, 13:04
Madremia las cosas que te pasan. Con razón la tasa de suicidio entre algunos desarrolladores es tan alta :P

hardyx
29/11/2024, 10:08
Buscate otra libreria con soporte explicito de multi-parte. Meterse en estos berenjenales no merece la pena.

EDIT: Nuevas versiones de .NET tienen soporte explicito: https://stackoverflow.com/a/18233515 o https://learn.microsoft.com/en-us/dotnet/api/system.net.http.multipartformdatacontent?view=net-8.0 o https://makolyte.com/csharp-how-to-send-a-file-with-httpclient/

Pienso lo mismo, romperse la cabeza de esa manera para enviar ficheros adjuntos es una pérdida de tiempo, a no ser que te conozcas el protocolo de al dedillo y te guste hacerlo a mano.

-----Actualizado-----


Voy a aprovechar el hilo para hacer una consulta, y así no lleno el foro con mis neuras :D

He seguido avanzando en el proyecto, y ahora resulta que, el problema que tenía con la API binaria, y que nos ha obligado a cambiar la forma de comunicarnos con el ERP, ha vuelto... pero en forma de chapa.
Con la API, bajo ciertas circunstancias (que aún no tengo muy claras), genera una excepción que, ejecutándose dentro de IIS como una WebApi, provoca la caída de su "grupo de aplicaciones", y nos obligaba a reiniciar la web manualmente. En ocasiones, el error era tan catastrófico que incluso echaba abajo el servicio W3WP de Windows.

Ahora, usando las peticiones web, bajo unas circunstancias similares, llega un punto en que cualquier petición que le hagamos al HttpClient nos devuelve un error 500 (InternalServerError). Lo que hacemos es que, cuando el usuario se loguea, creamos un HttpClient, y le incrustamos la cookie de autorización, y a partir de ahí lo usamos a lo largo de lo que dure su sesión (por defecto, 30 segundos, aunque para depuración lo tengo a 3 minutos). A diferencia de la otra API, si dejamos que la sesión caduque, y que el código se encargue de reconectar, o si descartamos el HttpClient y creamos otro, todo vuelve a funcionar con normalidad (y de momento, no se ha caído ni el grupo de aplicaciones ni nada).

Sabiendo esto, había pensado hacer que, cada petición del usuario genere un HttpClient, que se use para todas las tareas que deben ejecutarse en dicha petición. Supuestamente, habrá pocos usuarios de la web, y las peticiones entrará una cada 5 a 15 minutos en lugares tranquilos, o 2 por minuto en entornos con más actividad (por ahora, sólo usamos esta API web para peticiones POST y PUT, para el GET seguimos con el sistema antiguo).
Lo que desconozco son las consecuencias que puede acarrear esto. He leído que aunque se deje de usar el HttpClient, su puerto de comunicación sigue en uso durante unos 4 minutos, y puede provocar... no sé si he entendido que se queda sin puertos, o que se le somete a mucho estrés al sistema que los asigna.

Por si acaso, de momento, estoy intentando detectar cuándo entra en "modo negación", y cuando pille un error 500, que desloguée al usuario y reconecte con un HttpClient "fresco". El problema es que tengo que hacer eso con cada petición (o al menos, con las más problemáticas).

Si lo he entendido bien, usáis un servidor que bajo ciertas condiciones o número de peticiones se cae, pues vaya chapucilla. Eso puede ser por mala programación, falta de recursos en el propio servidor, o que usen una versión del año de la tana del IIS. En resumen, servidores con Windows + IIS = caca de vaca, lo mejor y más usado es usar Linux y Java. Pero bueno, muchas veces en la empresas las tecnologías vienen impuestas. Lo que dices de las conexiones a puertos que se mantienen, puede ser normal por la caché pero también puede ser que no esté bien configurado, vamos que la culpa no es del cliente si el servidor se cae. Tampoco dices que se manden 1000 peticiones por segundo, por lo tanto con un tráfico normal debería de aguantar, te lo digo en general sin saber mucho de ese servidor. Yo he trabajado con servidores (Java) que recibían cientos o miles de peticiones por segundo y si los configura una persona con conocimientos eso es más duro que una roca y no se cae.

P.D. Me he dado cuenta que el tema es de hace meses, espero que lo hayáis podido solucionar.