Search code examples
c++httpchunked-encoding

Error: Illegal or missing hexadecimal sequence in chunked-encoding


I'm writing my own http server and right now I'm trying to tmplement chunked transfer encoding. My attempt looks like this:

void HttpServer::SendRespons(HttpServer::Connection &socket_) noexcept {
  int sent = 0;
  int counter = 0;
  int index = 0;
  std::size_t response_size_;
  std::ifstream t(this->conf_.GetRootPath() + socket_.data);
  socket_.respons = {(std::istreambuf_iterator<char>(t)),
                     std::istreambuf_iterator<char>()};
  t.clear();
  response_size_ = socket_.respons.size();
  std::vector<std::string> strVec;
  strVec.reserve(socket_.respons.size() / BUFSIZE + 2);
  strVec.push_back(
      "HTTP/1.1 200 OK\r\nServer: http\r\nContent-Type: "
      "text/html\r\nTransfer-Encoding: "
      "chunked\r\nConnection: Keep-Alive\r\n\r\n");
  while (index + 16374 < static_cast<int>(response_size_)) {
    strVec.push_back("3ff6\r\n" + socket_.respons.substr(index, 16374) +
                     "\r\n\r\n");
    index += 16374;
  }
  std::stringstream stream;
  stream << std::hex
         << std::atoi(std::to_string(response_size_ - index).c_str());
  strVec.push_back(stream.str() + "\r\n" +
                   socket_.respons.substr(index, response_size_ - index) +
                   "\r\n\r\n");
  strVec.push_back("0\r\n\r\n");
  index = 0;
  response_size_ += (strVec[0].size() + strVec[strVec.size() - 1].size());

  std::lock_guard<std::mutex> lg(this->connected_clients_mux_);
  while (index < static_cast<int>(strVec.size())) {
    int len =
        write(socket_.socket, (char *)&strVec[index][0], strVec[index].size());

    if (len < 0) {
      //...
      } else if ((errno == EPIPE) || (errno == ECONNRESET)) {
        std::cout << __FILE__ << " " << __func__
                  << " sent stop (epipe || econreset), len= " << len << " "
                  << "error #" << errno << std::endl;
        break;
      //...
    }
    ++index;
    sent += len;
    std::cout << __FILE__ << " " << __func__ << " sent " << len << " "
              << "packet #" << ++counter << std::endl;
  }
  std::cout << std::endl
            << __FILE__ << " " << __func__
            << " assumed length:" << response_size_
            << " sent:" << sent << " to: " << socket_.socket << std::endl;
}

The above function should read the file, split it into parts and pass them one by one. As far as I can tell, the splits are going fine, but when trying to send a file:

curl -v http://localhost:8080/filename

i am getting:

*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /filename HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: http
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: Keep-Alive
< 
//.... here is first chunk
* Illegal or missing hexadecimal sequence in chunked-encoding
* Closing connection 0
curl: (56) Illegal or missing hexadecimal sequence in chunked-encoding

as you can see, the client receives the first part of the response (headers) and processes them. After - it expects the body of the message. Having received the first part of it, the connection is closed. I understand that the error is as written in the message somewhere in the format of my own chunk, but I myself cannot figure out exactly where. I followed instruntions from https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Format but obv made a mistake. Since the client does not output anything for the hexadecimal sequence it expects, nothing for the hexadecimal sequence it received and considered in error i ask about help.


Solution

  • I think only one \r\n is needed at the end of this portion of code.

        strVec.push_back("3ff6\r\n" + socket_.respons.substr(index, 16374) +
                         "\r\n\r\n");
    

    You should not insert an empty line between the previous chunk and the hexadecimal size of the next chunk. (and the same goes for the last chunk)

    The wikipedia page you provide inserts empty lines between chunks in the example just in order to increase readability, but these empty lines are not actually sent.