C# LibCurl.Net отправка multipart/form-data

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Kusko, 10 Oct 2016.

  1. Kusko

    Kusko New Member

    Joined:
    9 Oct 2016
    Messages:
    3
    Likes Received:
    0
    Reputations:
    0
    Уважаемые форумчане, подскажите, как правильно отправить 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($chCURLOPT_URL$endpoint);
            
    curl_setopt($chCURLOPT_USERAGENT$this->userAgent);
            
    curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
            
    curl_setopt($chCURLOPT_FOLLOWLOCATIONtrue);
            
    curl_setopt($chCURLOPT_HEADERtrue);
            
    curl_setopt($chCURLOPT_VERBOSE$this->parent->debug);
            
    curl_setopt($chCURLOPT_SSL_VERIFYPEER$this->verifyPeer);
            
    curl_setopt($chCURLOPT_SSL_VERIFYHOST$this->verifyHost);
            
    curl_setopt($chCURLOPT_HTTPHEADER$headers);
            
    curl_setopt($chCURLOPT_COOKIEFILE$this->parent->IGDataPath.$this->parent->username.'-cookies.dat');
            
    curl_setopt($chCURLOPT_COOKIEJAR$this->parent->IGDataPath.$this->parent->username.'-cookies.dat');
            
    curl_setopt($chCURLOPT_POSTtrue);
            
    curl_setopt($chCURLOPT_POSTFIELDS$data);

            if (
    $this->parent->proxy) {
                
    curl_setopt($chCURLOPT_PROXY$this->parent->proxyHost);
                if (
    $this->parent->proxyAuth) {
                    
    curl_setopt($chCURLOPT_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;
        }
    Тело запроса в снифере выглядит так:
    начало
    [​IMG]
    конец:
    [​IMG]

    А вот аналогичный код на 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, результат один и он не меняется:

    [​IMG]

    И это все тело. Все что я вижу в сниффере.
    Сервер в свою очередь выдает ошибку 408.

    По скольку на PHP все работает, я полагаю что как-то не правильно использую Libcurl.
    Неделя гугла не дала результатов(( На сайте либкурла есть примеры на C++ но в C# там все подругому.

    Подскажите, кто юзает curl на net, как мне добиться отправки такого же запроса как на PHP ?
     
  2. seosimf

    seosimf Member

    Joined:
    3 Mar 2011
    Messages:
    271
    Likes Received:
    44
    Reputations:
    6
    Во-первых: зачем считывать бинарные данные как string?
    Во-вторых: во-вторых file_get_contents? File.ReadAllText
    В-третьих: зачем тебе курл в C#? Чем неустраивает WebRequest? Если нужна поддержка Socks4/5 проксей, то есть к примеру сторонние библиотеки: xNet, ViKing.Engine
     
  3. Kusko

    Kusko New Member

    Joined:
    9 Oct 2016
    Messages:
    3
    Likes Received:
    0
    Reputations:
    0
    Конечно можно через вебреквест, но там нужно разбираться по какому принципу куки записывать и доставать из файла. Я затестил, через вебреквест тот же текст отправляется полностью. Кажись в либкурл я не так организовал оотправку, в частности в POSTFIELDS. Потому все же интересует отправка либкурлом. Если это возможно. МБ есть какие особенности в либе для отправки подобного?

     
  4. Kusko

    Kusko New Member

    Joined:
    9 Oct 2016
    Messages:
    3
    Likes Received:
    0
    Reputations:
    0
    Ибо параметр POSTFIELD массив байт не шлет( в теле запроса пусто) К тому же нужно ж еще строки разделители приписывать в начале и в конце тела
     
  5. seosimf

    seosimf Member

    Joined:
    3 Mar 2011
    Messages:
    271
    Likes Received:
    44
    Reputations:
    6
    Ты берешь массив байтов, затем переводишь его напрямую в строку, а затем опять переводишь его в массив байт - это плохой решения и в C#, в отличии от пхп так не делают.
    Используй MemoryStream - в него можешь записывать строки и массив-байт изображения а затем вызвать MemoryStream.ToArray().
    Твоя проблема в том, что ты записываешь изображения, массив байтов, в строку, а потом назад. В итоге у тебя нет закрывающего boundary, может там добавляется 0x00 в конец "изображения"-строки и твой boundary отрезается нафиг, и поэтому твой запрос не завершается корректно - сервер его ждет(boundary), и после определенного таймаута, не дождавшись его, выкидывает тебе 408 статус код. Еще раз - используй MemoryStream и byte[] а не занимайся перегоном байтов в строки и назад.
    Мой совет - используй WebRequest/WebResponse или HttpClient, в котором уже есть готовые для прямой работы с HTTP методы, с Cookie там работать проще простого, правда есть свои нюансы.
     
    Kusko likes this.