Уважаемые форумчане, подскажите, как правильно отправить multipart/form-data с помощью libcurl ? Я переписываю код PHP на C#. Задача - отправить картинку строкой в теле POST запроса. Запросы снифаю. На PHP все работает, запрос отправляется целиком. PHP: $upload_id = number_format(round(microtime(true) * 1000), 0, '', ''); $fileToUpload = file_get_contents($photo); //читаю jpg картинку, в переменной крякозябры)) $bodies = [ [ 'type' => 'form-data', 'name' => 'upload_id', 'data' => $upload_id, ], [ 'type' => 'form-data', 'name' => '_uuid', 'data' => $this->parent->uuid, ], [ 'type' => 'form-data', 'name' => '_csrftoken', 'data' => $this->parent->token, ], [ 'type' => 'form-data', 'name' => 'image_compression', 'data' => '{"lib_name":"jt","lib_version":"1.3.0","quality":"70"}', ], [ 'type' => 'form-data', 'name' => 'photo', 'data' => $fileToUpload, 'filename' => 'pending_media_'.number_format(round(microtime(true) * 1000), 0, '', '').'.jpg', 'headers' => [ 'Content-Transfer-Encoding: binary', 'Content-type: application/octet-stream', ], ], ]; $data = $this->buildBody($bodies, $boundary); $headers = [ 'Connection: close', 'Accept: */*', 'Content-type: multipart/form-data; boundary='.$boundary, 'Content-Length: '.strlen($data), 'Cookie2: $Version=1', 'Accept-Language: en-US', 'Accept-Encoding: gzip', ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $endpoint); curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_VERBOSE, $this->parent->debug); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verifyPeer); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->verifyHost); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_COOKIEFILE, $this->parent->IGDataPath.$this->parent->username.'-cookies.dat'); curl_setopt($ch, CURLOPT_COOKIEJAR, $this->parent->IGDataPath.$this->parent->username.'-cookies.dat'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); if ($this->parent->proxy) { curl_setopt($ch, CURLOPT_PROXY, $this->parent->proxyHost); if ($this->parent->proxyAuth) { curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->parent->proxyAuth); } } $resp = curl_exec($ch); Функция build_body формирует тело запроса. PHP: protected function buildBody($bodies, $boundary) { $body = ''; foreach ($bodies as $b) { $body .= '--'.$boundary."\r\n"; $body .= 'Content-Disposition: '.$b['type'].'; name="'.$b['name'].'"'; if (isset($b['filename'])) { $ext = pathinfo($b['filename'], PATHINFO_EXTENSION); $body .= '; filename="'.'pending_media_'.number_format(round(microtime(true) * 1000), 0, '', '').'.'.$ext.'"'; } if (isset($b['headers']) && is_array($b['headers'])) { foreach ($b['headers'] as $header) { $body .= "\r\n".$header; } } $body .= "\r\n\r\n".$b['data']."\r\n"; } $body .= '--'.$boundary.'--'; return $body; } Тело запроса в снифере выглядит так: начало конец: А вот аналогичный код на C# libcurl, который работает, но не правильно: Code: upload_id = Convert.ToString((long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalMilliseconds); fileToUpload = Extra.file_get_contents(photo); List<Dictionary<string, string>> bodies = new List<Dictionary<string, string>>(); Dictionary<string, string> a = new Dictionary<string, string>(); a["type"] = "form-data"; a["name"] = "upload_id"; a["data"] = upload_id; bodies.Add(a); Dictionary<string, string> b = new Dictionary<string, string>(); b["type"] = "form-data"; b["name"] = "_uuid"; b["data"] = this.parent.uuid; bodies.Add(b); Dictionary<string, string> c = new Dictionary<string, string>(); c["type"] = "form-data"; c["name"] = "_csrftoken"; c["data"] = this.parent.token; bodies.Add(c); Dictionary<string, string> d = new Dictionary<string, string>(); d["type"] = "form-data"; d["name"] = "image_compression"; d["data"] = "{\"lib_name\":\"jt\",\"lib_version\":\"1.3.0\",\"quality\":\"70\"}"; bodies.Add(d); Dictionary<string, string> e = new Dictionary<string, string>(); e["type"] = "form-data"; e["name"] = "photo"; e["data"] = fileToUpload; e["filename"] = "pending_media_" + Convert.ToString((long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalMilliseconds) + ".jpg"; e["headers"] = "Content-Transfer-Encoding: binary;Content-type: application/octet-stream"; bodies.Add(e); string data = this.buildBody(bodies, boundary); byte[] sentData = Encoding.Default.GetBytes(data); Curl.GlobalInit((int)CURLinitFlag.CURL_GLOBAL_ALL); ch2 = new Easy(); resp = ""; Easy.WriteFunction wf = new Easy.WriteFunction(OnWriteData); Slist headers = new Slist(); headers.Append("Connection: close"); headers.Append("Accept: */*"); headers.Append("Content-type: multipart/form-data; boundary=" + boundary); headers.Append("Content-Length: " + data.Length.ToString()); headers.Append("Cookie2: $Version=1"); headers.Append("Accept-Language: en-US"); headers.Append("Accept-Encoding: gzip"); Console.WriteLine(data.Length.ToString()); ch2.SetOpt(CURLoption.CURLOPT_URL, endpoint); ch2.SetOpt(CURLoption.CURLOPT_USERAGENT, this.userAgent); ch2.SetOpt(CURLoption.CURLOPT_FOLLOWLOCATION, true); ch2.SetOpt(CURLoption.CURLOPT_HEADER, true); ch2.SetOpt(CURLoption.CURLOPT_VERBOSE, false); ch2.SetOpt(CURLoption.CURLOPT_SSL_VERIFYPEER, this.verifyPeer); ch2.SetOpt(CURLoption.CURLOPT_SSL_VERIFYHOST, this.verifyHost); ch2.SetOpt(CURLoption.CURLOPT_HTTPHEADER, headers); ch2.SetOpt(CURLoption.CURLOPT_COOKIEFILE, this.parent.IGDataPath + this.parent.username + "-cookies.dat"); ch2.SetOpt(CURLoption.CURLOPT_COOKIEJAR, this.parent.IGDataPath + this.parent.username + "-cookies.dat"); ch2.SetOpt(CURLoption.CURLOPT_POST, true); ch2.SetOpt(CURLoption.CURLOPT_POSTFIELDS, data); if (this.parent.proxy != null) { ch.SetOpt(CURLoption.CURLOPT_PROXY, this.parent.proxyHost); if (this.parent.proxyAuth != null) { ch.SetOpt(CURLoption.CURLOPT_PROXYUSERPWD, this.parent.proxyAuth); } } ch2.SetOpt(CURLoption.CURLOPT_WRITEFUNCTION, wf); ch2.Perform(); ch2.Cleanup(); Функция file_get_contents Code: public static string file_get_contents(string fileName) { //StreamReader sr = new StreamReader(fileName); //string sContentsa = sr.ReadToEnd(); //sr.Close(); byte[] b = null; using (FileStream f = new FileStream(fileName, FileMode.Open)) { b = new byte[f.Length]; f.Read(b, 0, b.Length); } string sContentsa = Encoding.Default.GetString(b); return sContentsa; } Функция build_body Code: protected string buildBody(List<Dictionary<string, string>> bodies, string boundary) { string body = ""; foreach (Dictionary<string, string> b in bodies) { body += "--" + boundary + "\r\n"; body += "Content-Disposition: " + b["type"] + "; name=\"" + b["name"] + "\""; if (b.ContainsKey("filename")) { string ext = Path.GetExtension(b["filename"]); body += "; filename=\"" + "pending_media_" + Convert.ToString((long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalMilliseconds) + ext + "\""; } if (b.ContainsKey("headers")) { if (b["headers"].Contains(";")) { string[] str = b["headers"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string head in str) { body += "\r\n" + head; } } } body += "\r\n\r\n" + b["data"] + "\r\n"; } body += "--" + boundary + "--"; return body; } При выполнении, на функции ch2.Perform(); прога стоит секунд на 40. Как только я не игрался с различными кодировками в функции file_get_contents, результат один и он не меняется: И это все тело. Все что я вижу в сниффере. Сервер в свою очередь выдает ошибку 408. По скольку на PHP все работает, я полагаю что как-то не правильно использую Libcurl. Неделя гугла не дала результатов(( На сайте либкурла есть примеры на C++ но в C# там все подругому. Подскажите, кто юзает curl на net, как мне добиться отправки такого же запроса как на PHP ?
Во-первых: зачем считывать бинарные данные как string? Во-вторых: во-вторых file_get_contents? File.ReadAllText В-третьих: зачем тебе курл в C#? Чем неустраивает WebRequest? Если нужна поддержка Socks4/5 проксей, то есть к примеру сторонние библиотеки: xNet, ViKing.Engine
Конечно можно через вебреквест, но там нужно разбираться по какому принципу куки записывать и доставать из файла. Я затестил, через вебреквест тот же текст отправляется полностью. Кажись в либкурл я не так организовал оотправку, в частности в POSTFIELDS. Потому все же интересует отправка либкурлом. Если это возможно. МБ есть какие особенности в либе для отправки подобного?
Ибо параметр POSTFIELD массив байт не шлет( в теле запроса пусто) К тому же нужно ж еще строки разделители приписывать в начале и в конце тела
Ты берешь массив байтов, затем переводишь его напрямую в строку, а затем опять переводишь его в массив байт - это плохой решения и в C#, в отличии от пхп так не делают. Используй MemoryStream - в него можешь записывать строки и массив-байт изображения а затем вызвать MemoryStream.ToArray(). Твоя проблема в том, что ты записываешь изображения, массив байтов, в строку, а потом назад. В итоге у тебя нет закрывающего boundary, может там добавляется 0x00 в конец "изображения"-строки и твой boundary отрезается нафиг, и поэтому твой запрос не завершается корректно - сервер его ждет(boundary), и после определенного таймаута, не дождавшись его, выкидывает тебе 408 статус код. Еще раз - используй MemoryStream и byte[] а не занимайся перегоном байтов в строки и назад. Мой совет - используй WebRequest/WebResponse или HttpClient, в котором уже есть готовые для прямой работы с HTTP методы, с Cookie там работать проще простого, правда есть свои нюансы.