Отправка файла из HttpClient .NET в CPPCMS возвращает 400 Bad Request без регистрации ошибок на сервере.

У меня есть сервер CPPCMS (V1.1.0) (пример кода ниже) и клиент C# (.NET 4.0) (пример кода ниже), и я пытаюсь отправить файл с клиента C# на сервер CPPCMS.

Сервер работает, если я отправляю на него POST из PaleMoon или клиента CURL (V7.56.1) (пример кода ниже), поэтому я не думаю, что проблема в коде сервера.

Клиент работает, если я отправляю от него POST на http://posttestserver.com, поэтому я не думаю, что проблема в клиентском коде.

Однако, когда я пробую свой «рабочий» сервер CPPCMS против своего «рабочего» клиента C#, я получаю ответ 400 Bad Request. Ответ генерируется CPPCMS (мой первый журнал сервера никогда не записывается, поскольку application::main не вызывается), хотя CPPCMS не показывает и не выдает никаких ошибок на сервере, даже при включенном ведении журнала отладки.

Итак, у меня есть клиент и сервер, которые работают сами по себе, но не вместе. Глядя на трассировку сети в Microsoft Network Monitor 3.4 и TCPDUMP, я не вижу функциональной разницы между рабочим запросом и неудачным. Трассировка предполагает, что CPPCMS дает сбой на самом первом пакете POST (многопакетного запроса POST), хотя это может быть проблема времени в трассировке, я не знаю.

Заметки

  • Сервер CPPCMS работает на Ubuntu 16.04.
  • Клиент C# работает в Windows 7.
  • Рабочий тест CURL был выполнен с серверной машины.
  • Рабочий тест PaleMoon был выполнен с клиентской машины.
  • Я скопировал код из Ubuntu в этот пост вручную, так что у меня могут быть ошибки в их примерах ниже

Веб-сервер CPPCMS: main.cpp

#include <cppcms/service.h>
#include <cppcms/applications_pool.h>
#include <cppcms/application.h>
#include <cppcms/http_request.h>
#include <iostream>
#include <string>
class Application : public cppcms::application
{  public:
   Application
   (  cppcms::service & service
   ): cppcms::application(service)
   { }
   virtual void main
   (  std::string url
   ){ std::cout
      << "Request for " << url << std::endl
      << "  Uploaded file count "
      << request().files().size()
      << std::endl;
};
int main(int argc, char * * args)
{ cppcms::service service(argc, args);
  service.applications_pool().mount
  ( cppcms::applications_factory<Application>()
  );
  service.run();
  return 0;
}
// g++ -o test main.cpp -lcppcms -lbooster -std=c++11
// ./test -c config.js

Веб-сервер CPPCMS: config.js

{ "service":
  { "api": "http"
  , "port": 8080
  , "ip": "10.0.0.4"
  }
, "http": { "script": "/" }
, "logging": { "level": "debug" }
}

Клиент C#: main.cs

using System;
using System.Net.Http;
namespace CPPCMSTest
{  class Program
   {  static void Main(string[] args)
      {  using(var client = new HttpClient())
         using(var content = new MultipartFormDataContent())
         {  content.Add(new StringContent("test"), "name", "filename");
            Console.WriteLine
            (  client
               .  PostAsync("http://10.0.0.4:8080", content)
               .  Result
               .  Content
               .  ReadAsStringAsync()
               .  Result
            );
         }
      }
   }
}
// Output is:
// <html>
// <body>
// <h1>400 Bad Request</h1>
// </body>
// </html>
//
// CPPCMS output is:
// 2018-01-19 12:34:56; cppcms_http, info: POST / (http_api.cpp:249)
//
// That is, CPPCMS doesn't show an error and doesn't reach the application::main

Клиент CURL: main.cpp

#include <curl/curl.h>
#include <iostream>
int main(int argc, char * * args)
{  curl_global_init(CURL_GLOBAL_DEFAULT);
   CURL * curl(curl_easy_init());
   curl_mime * mime(curl_mime_init(curl));
   curl_mime_filedata(curl_mime_addpart(mime), "main.cpp");
   curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
   curl_easy_setopt(curl, CURLOPT_URL, "http://10.0.0.4:8080");
   curl_easy_perform(curl);
   curl_mime_free(mime);
   long responseCode;
   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
   curl_easy_cleanup(curl);
   std::cout << "Response code is " << responseCode << std::endl;
   return 0;
}
// g++ -o test main.cpp -lcurl -std=c++11
// ./test
// Response code is 200
// 
// CPPCMS output is:
// 2018-01-19 12:34:56; cppcms_http, info: POST / (http_api.cpp:249)
// Request for
//   Uploaded file count 1

person Eliott    schedule 19.01.2018    source источник


Ответы (1)


Я проследил проблему до ошибки в CPPCMS. Когда он анализирует заголовки вложения, если значение в паре имя/значение не заключено в кавычки, он неправильно размещает синтаксический анализатор после значения. Это приводит к сбою, потому что он ожидает точку с запятой или конец строки, а вместо этого получает начало предыдущего значения. Я отправил отчет об ошибке в CPPCMS.

Если значение заключено в кавычки, проблем нет, но я не могу заставить C# HttpClient заключать в кавычки все значения для Content-Disposition — можно заключать в кавычки части «имя» и «имя файла» (и я видел другой сообщение StackOverflow, которое показывает это как источник проблем), однако HttpClient также добавляет часть "имя файла*", и там похоже, это невозможно процитировать (поскольку HttpClient заменяет буквальные кавычки на% 22).

Если у кого-то еще есть эта проблема и ему нужно обновить код CPPCMS самостоятельно, ошибка находится в private/multipart_parser.h в функции parse_pair. В конце этой функции есть оператор if(*p=='"') ... else .... При истинном условии (рабочий парсер в кавычках) блок заканчивается на p=tmp;, а при ложном условии (глючный парсер без кавычек) блок заканчивается на tmp=p;. Обновление до p=tmp; устраняет проблему.

person Eliott    schedule 20.01.2018