martes, 23 de julio de 2013

HTTP, C#: URI no válido: la cadena URI es demasiado larga

El error se produce al crear una nueva instancia de

System.Net.Http.FormUrlEncodedContent

pasándole una lista de clave-valor cuando superamos un límite de caracteres… (es fácil de superar si enviamos binarios codificados en base 64). Cabe decir que la petición no llega ni a salir, al intentar generar una instancia de esta clase de .NET ya genera una excepción quejándose que la URI es demasiado larga:

URI no válido: la cadena URI es demasiado larga

->

Invalid URI: The Uri string is too long



Al crear un cliente

REST

para peticiones con verbo POST esta es la codificación por defecto que se suele usar para enviar parámetros en el request al servidor. El error se produce al superar un límite de caracteres en la URL, tendríamos que mirar de cambiar el tipo de codificación en el que enviamos el contenido del request (siempre que el servidor entienda igual el mensaje con otro tipo de codificación) o bien buscar alguna trampa para poder saltarnos la limitación de caracteres con la que trabaja esta clase.

En mi caso el servidor de destino de la petición es un Apache y testeando con un cliente

REST

no .NET me deja enviar la petición con binarios pesados y el servidor la acepta sin problemas. Me gustaría remarcar que la solución más óptima a nivel de performance es recurrir a MultiPartFormDataContent como codificación si vamos a enviar ficheros en el request. Aún así si necesitáis forzar un envío desde .NET con binarios codificado como

formUrlEncoded

también es posible a continuación os paso una altenativa en c#.

Yo estoy usando el cliente

REST

genérico que desarrollé para areaTIC, a medida que lo voy usando se han ido añadiendo sobrecargas para contemplar otros tipos de encoding y para enviar en el POST objetos complejos con sub-listas, ya publicaré un post con el código más adelante cuando lo tenga 100 % pulido.

En el artículo de hace unos meses planteaba un método POST que usa

FormUrlEncodedContent

por defecto siempre para enviar el request y es aquí donde se origina la expcepción:
List<KeyValuePair<string, string>> oListTest = new AreaTICJavaScriptSerializer().GetKeyValuePair(pValue, serialiceListAsParamArray);
FormUrlEncodedContent contentencoded = new FormUrlEncodedContent(oListTest);
Os paso ahora el código alternativo para enviar el contenido en formato

FormUrlEncodedContent

evitando usar la instancia que genera la excepción:
List<KeyValuePair<string, string>> oListTest = new AreaTICJavaScriptSerializer().GetKeyValuePair(pValue, serialiceListAsParamArray);
string pURLEncodedContent = "";

foreach (KeyValuePair<string, string> item in oListTest)
   pURLEncodedContent += string.Format("{0}={1}&", System.Web.HttpUtility.UrlEncode(item.Key), System.Web.HttpUtility.UrlEncode(item.Value));

pURLEncodedContent = pURLEncodedContent.Substring(0, pURLEncodedContent.Length - 1);

ByteArrayContent content = new ByteArrayContent(Generic.Utiles.UtilesFichero.ConvertStringToByteArray(pURLEncodedContent));
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = _Client.PostAsync(_ServiceName, content).Result;
Os paso también el código de las funciones ConvertStringToByteArray que es una librería propia (no tiene ningún misterio pero os evito buscar por internet).
public static byte[] ConvertStringToByteArray(string psCad)
{
    Encoding oEnc = Encoding.GetEncoding("ISO-8859-15");
    return oEnc.GetBytes(psCad);
}
El “truco” está en generar un string codificado para URL, convertirlo a un array de Bytes, modificar el Header contentType y enviar al PostAsync una instancia de ByteArrayContent. Como en PostAsync se espera alguna clase derivada de HttpContent y ambas tanto

FormUrlEncodedContent

como ByteArrayContent derivan de HttpContent deja enviar y en mi caso el servidor la procesa ok.

Espero os sea de ayuda, como siempre vuestras dudas, comentarios y/o aportaciones son bienvenidas… no os cortéis un pelo. Por otro lado recordar que podéis seguir

areaTIC

en las principales redes sociales.

1 comentario:

Unknown dijo...

Hola tengo un problema parecido al tuyo no se si me puedas dar una mano. este es mi código.

private static async void AsyncPost()
{
byte[] pdfBytes = File.ReadAllBytes("C:\\prueba\\prueba.pdf");
string pdfBase64 = Convert.ToBase64String(pdfBytes);
string newJson = "{\"encabezado\": { \"numero_factura\":\"FA001100x04\",\"nit\":\"10001\",\"resolucion\":\"RES001 2015\",\"tipo_documento\":\"CC\",\"numero_documento\":\"910178812\",\"cliente\":\"Smartbill Test User\",\"fecha\":\"2015 -08-22 09.00.00\"},\"items\":[ {\"producto\":\"Sliders Ninja 300\",\"precio\":300000.0,\"cantidad\":1,\"valorTotal\":300000.0,\"impuesto\":0.0},{\"producto\":\"Cargador para moto\",\"precio\":50000.0,\"cantidad\":1,\"valorTotal\":50000.0,\"impuesto\":0.0} ],\"pie\": { \"cantidad\":2,\"valorTotal\":350000.0,\"impuesto\":0.0}}";
string token = "3cb1ec9a9f3ac06f29f4bb05f4df58d5efeb073b";
var valores = new Dictionary();
valores.Add("json", newJson);
valores.Add("token", token);
valores.Add("pdf", pdfBase64);

//StringContent content = new StringContent(stringToSend, Encoding.UTF8, "application/x-www-form-urlencoded");
var content = new FormUrlEncodedContent(valores);
string elias = content.ToString();


using (var client = new HttpClient())
{
try
{
var httpResponseMessage = await client.PostAsync("http://www.smartbill.co:332/SmartBill2/rest/factura/addandpdf", content);

if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine("ok");
Console.WriteLine(httpResponseMessage.Content.ReadAsStringAsync().Result);
}

else
{
Console.WriteLine(httpResponseMessage.StatusCode);
}
}
catch (OperationCanceledException) {
Console.WriteLine("error");
}
}

}

Publicar un comentario