如何使用 C++ 和 Boost Asio 从 HTTP post 请求中获取键值

How to get key values from HTTP post request using C++ and Boost Asio

我接到了一项作业,其中涉及使用 C++ 和 Boost Asio 库编写 Web 服务器。

我已经建立了工作服务器,它可以使用一本名为 "Boost.Asio C++ Network Programming Cookbook" 的书将 html 文件发送回客户端浏览器,但我正在努力处理来自客户端的 POST 请求。

当客户端连接到服务器时,他们会得到一个简单的 HTML 表单,其中包含用于登录服务器的用户名和密码字段,然后使用 POST 将其发送到服务器请求。

我已经把接收到的POST请求的内容输出到控制台,可以看到所有的header信息,但是看不到表单数据。我已经使用 Wireshark 检查数据包,数据正在通过网络发送。

服务器正在接收数据作为 Boost Asio streambuf,我正在解析它以获取请求的 HTML 文件,方法是将其读入向量,然后获取相关元素,例如方法或目标。

对于在哪里可以找到有关如何解析表单数据的教程,有人有什么建议吗?

下面的代码是解析 POST 请求并根据请求内容处理响应的 cpp 文件的一部分。 '&request' 参数是 Boost Asio streambuf

我在网络编程方面的经验很少,不胜感激任何建议!

解析请求的代码

// Prepare and return the response message.
// Parse the request from the client to find requested document 
std::istream buffer(&request);
std::vector<std::string> parsed((std::istream_iterator<std::string>(buffer)), std::istream_iterator<std::string>() );   

处理 POST 请求


else if (parsed.size() >= 3 && parsed[0] == "POST") {

            htmlFile = "/files.html";

            // Retrieve files from server file system. The second element in 'parsed' vector is file name
            std::ifstream fileStream(".\directory" + htmlFile);

            // If the file exists then iterate it and assign the value to the content string variable, else return 404.
            if (fileStream.good()) {
                std::string fileContents((std::istreambuf_iterator<char>(fileStream)), std::istreambuf_iterator<char>());
                content = fileContents;
                code = "200 ok";
            }
            else {
                std::ifstream fileStream(".\directory\404.html");
                std::string fileContents((std::istreambuf_iterator<char>(fileStream)), std::istreambuf_iterator<char>());
                content = fileContents;
                code = "404";
            }// End of nested if-else statement 

        }// End of else-if statement
        else {
            std::ifstream fileStream(".\directory\401.html");
            std::string fileContents((std::istreambuf_iterator<char>(fileStream)), std::istreambuf_iterator<char>());
            content = fileContents;
            code = "401";
            // Write bad request to log file for security audits if not "GET" request
            logging.logAction("Illegal request by client IP " + m_sock->remote_endpoint().address().to_string());

        }//End of if-else statement

        std::ostringstream oss;
        oss << "GET HTTP/1.1 " << code << " \r\n";
        oss << "Cache-Control: no-cache, private" << "\r\n";
        oss << "Content-Type: text/html" << "\r\n";
        oss << "Content-Length: " << content.size() << "\r\n";
        oss << "\r\n\r\n";
        oss << content;

        response = oss.str().c_str();

HTTP 是一个线性协议。样本:https://www.tutorialspoint.com/http/http_requests.htm

POST /cgi-bin/process.cgi HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.tutorialspoint.com
Content-Type: application/x-www-form-urlencoded
Content-Length: length
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

licenseID=string&content=string&/paramsXML=string

您需要更具体地进行解析,而不是将每个空格分隔 "word" 放入向量中。

从这样的事情开始:

Live On Coliru

#include <iostream>
#include <iomanip>
#include <boost/asio.hpp>

int main() {
    boost::asio::streambuf request;
    {
        std::ostream sample(&request);
        sample <<
            "POST /cgi-bin/process.cgi HTTP/1.1\r\n"
            "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)\r\n"
            "Host: www.tutorialspoint.com\r\n"
            "Content-Type: application/x-www-form-urlencoded\r\n"
            "Content-Length: 49\r\n"
            "Accept-Language: en-us\r\n"
            "Accept-Encoding: gzip, deflate\r\n"
            "Connection: Keep-Alive\r\n"
            "\r\n"
            "licenseID=string&content=string&/paramsXML=string"
            ;
    }

    std::istream buffer(&request);
    std::string line;

    // parsing the headers
    while (getline(buffer, line, '\n')) {
        if (line.empty() || line == "\r") {
            break; // end of headers reached
        }
        if (line.back() == '\r') {
            line.resize(line.size()-1);
        }
        // simply ignoring headers for now
        std::cout << "Ignore header: " << std::quoted(line) << "\n";
    }

    std::string const body(std::istreambuf_iterator<char>{buffer}, {});

    std::cout << "Parsed content: " << std::quoted(body) << "\n";
}

打印

Ignore header: "POST /cgi-bin/process.cgi HTTP/1.1"
Ignore header: "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)"
Ignore header: "Host: www.tutorialspoint.com"
Ignore header: "Content-Type: application/x-www-form-urlencoded"
Ignore header: "Content-Length: 49"
Ignore header: "Accept-Language: en-us"
Ignore header: "Accept-Encoding: gzip, deflate"
Ignore header: "Connection: Keep-Alive"
Parsed content: "licenseID=string&content=string&/paramsXML=string"

读取“Content-Length:”的简单 ASIO HTTP 解析器,然后继续使用它来读取非 header 部分的其余部分(POST 消息)

int http::parse(asio::ip::tcp::socket& sock, http_msg_t& http)
{
  std::string line;
  asio::streambuf buf;
  std::stringstream ss;
  asio::error_code error;
  size_t content_size = 0;
  size_t read_left = 0;
  try
  {
    //read until end of HTTP header
    //Note:: after a successful read_until operation, the streambuf may contain additional data 
    //beyond the delimiter. An application will typically leave that data in the streambuf for a subsequent 
    //read_until operation to examine.

    asio::read_until(sock, buf, "\r\n\r\n");
    std::istream stream(&buf);
    while (std::getline(stream, line) && line != "\r")
    {
      http.header.push_back(line);
    }

    //store method and url
    line = http.header.at(0);
    http.method = http::http_get_method(line);
    http.url = http::http_get_url(line);

    //find 'Content-Length'
    for (int idx = 0; idx < http.header.size(); idx++)
    {
      line = http.header.at(idx);
      if (line.find("Content-Length: ") != std::string::npos)
      {
        size_t start = line.find(":");
        start += 2; //space
        size_t end = line.find("\r");
        std::string s = line.substr(start, end - 1);
        try
        {
          content_size = std::atoi(s.c_str());
        }
        catch (std::invalid_argument&)
        {
          events::log("invalid Content-Length");
          return -1;
        }
        http.content_size = content_size;
      }
    }

    if (http.content_size == 0)
    {
      //nothing to read; not a POST; must be an acknowledgement response 
      return 0;
    }

    //read end of message left
    //dump whatever content we already have
    if (buf.size() > 0)
    {
      ss << &buf;
      std::string s = ss.str();
      read_left = content_size - s.size();
    }
    else
    {
      read_left = content_size;
    }

    //asio::read reads exact number of bytes
    size_t recv = asio::read(sock, buf, asio::transfer_exactly(read_left));
    ss << &buf;
  }
  catch (std::exception& e)
  {
    events::log(e.what());
    return -1;
  }
  http.msg = ss.str();
  return 0;
}