将文件从 .NET 的 HttpClient 发布到 CPPCMS returns 400 错误请求,服务器上没有错误记录
POSTing file from .NET's HttpClient to CPPCMS returns a 400 Bad Request with no error logging on server
我有一个 CPPCMS (V1.1.0) 服务器(下面的示例代码)和一个 C# (.NET 4.0) 客户端(下面的示例代码),我正在尝试 POST 来自 C# 的文件客户端到 CPPCMS 服务器。
如果我从 PaleMoon 或 CURL (V7.56.1) 客户端(下面的示例代码)POST 访问服务器,服务器可以正常工作,所以我认为问题不在服务器代码中。
如果我 POST 从它到 http://posttestserver.com,客户端工作,所以我认为问题不在客户端代码中。
但是,当我针对 "working" C# 客户端尝试 "working" 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 中的代码手动复制到此 post 中,因此我在下面的示例中可能有错误
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
我已经将问题追溯到 CPPCMS 中的一个错误。当它解析附件的 headers 时,如果 name/value 对中的值未被引号括起来,则它不会将解析器正确定位在该值之后。这导致它失败,因为它期望 semi-colon 或 end-of-line,而是获取前一个值的开头。我已经向 CPPCMS 提交了 bug report。
如果引用该值,没有问题,但我无法让 C# 的 HttpClient 引用 Content-Disposition 的所有值——可以引用 "name" 和 "filename" 部分(我已经看到另一个 Whosebug post 表明这是问题的根源),但是,HttpClient 还添加了一个 "filename*" 部分,而且似乎没有办法引用这个(因为 HttpClient 将文字引号替换为 %22).
如果其他人遇到此问题并需要自己更新 CPPCMS 代码,则错误在 parse_pair
函数中的 private/multipart_parser.h
中。在该函数的末尾有一个 if(*p=='"') ... else ...
语句。在 true 条件下(工作引用解析器),块以 p=tmp;
结束,但在 false 条件下(有问题的未引用解析器),块以 tmp=p;
结束。将其更新为 p=tmp;
可解决问题。
我有一个 CPPCMS (V1.1.0) 服务器(下面的示例代码)和一个 C# (.NET 4.0) 客户端(下面的示例代码),我正在尝试 POST 来自 C# 的文件客户端到 CPPCMS 服务器。
如果我从 PaleMoon 或 CURL (V7.56.1) 客户端(下面的示例代码)POST 访问服务器,服务器可以正常工作,所以我认为问题不在服务器代码中。
如果我 POST 从它到 http://posttestserver.com,客户端工作,所以我认为问题不在客户端代码中。
但是,当我针对 "working" C# 客户端尝试 "working" 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 中的代码手动复制到此 post 中,因此我在下面的示例中可能有错误
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
我已经将问题追溯到 CPPCMS 中的一个错误。当它解析附件的 headers 时,如果 name/value 对中的值未被引号括起来,则它不会将解析器正确定位在该值之后。这导致它失败,因为它期望 semi-colon 或 end-of-line,而是获取前一个值的开头。我已经向 CPPCMS 提交了 bug report。
如果引用该值,没有问题,但我无法让 C# 的 HttpClient 引用 Content-Disposition 的所有值——可以引用 "name" 和 "filename" 部分(我已经看到另一个 Whosebug post 表明这是问题的根源),但是,HttpClient 还添加了一个 "filename*" 部分,而且似乎没有办法引用这个(因为 HttpClient 将文字引号替换为 %22).
如果其他人遇到此问题并需要自己更新 CPPCMS 代码,则错误在 parse_pair
函数中的 private/multipart_parser.h
中。在该函数的末尾有一个 if(*p=='"') ... else ...
语句。在 true 条件下(工作引用解析器),块以 p=tmp;
结束,但在 false 条件下(有问题的未引用解析器),块以 tmp=p;
结束。将其更新为 p=tmp;
可解决问题。