1

我有一个 CPPCMS (V1.1.0) 服务器(下面的示例代码)和一个 C# (.NET 4.0) 客户端(下面的示例代码),我正在尝试将文件从 C# 客户端发布到 CPPCMS 服务器。

如果我从 PaleMoon 或 CURL (V7.56.1) 客户端(下面的示例代码)发布到服务器,服务器就可以工作,所以我认为问题不在于服务器代码。

如果我从客户端 POST 到http://posttestserver.com,客户端就可以工作,所以我认为问题不在于客户端代码。

但是,当我针对我的“工作”C# 客户端尝试我的“工作”CPPCMS 服务器时,我得到了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
4

1 回答 1

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;解决问题。

于 2018-01-20T05:47:52.827 回答