| 513 | } |
| 514 | |
| 515 | void HTTPRequest::WriteReply(HTTPStatusCode status, std::span<const std::byte> reply_body) |
| 516 | { |
| 517 | HTTPResponse res; |
| 518 | |
| 519 | // Some response headers are determined in advance and stored in the request |
| 520 | res.m_headers = std::move(m_response_headers); |
| 521 | |
| 522 | // Response version matches request version |
| 523 | res.m_version = m_version; |
| 524 | |
| 525 | // Add response code |
| 526 | res.m_status = status; |
| 527 | |
| 528 | // See libevent evhttp_response_needs_body() |
| 529 | // Response headers are different if no body is needed |
| 530 | bool needs_body{status != HTTP_NO_CONTENT && (status < 100 || status >= 200)}; |
| 531 | bool needs_content_length{false}; |
| 532 | |
| 533 | bool keep_alive{false}; |
| 534 | |
| 535 | // See libevent evhttp_make_header_response() |
| 536 | // Expected response headers depend on protocol version |
| 537 | if (m_version.major == 1) { |
| 538 | // HTTP/1.0 |
| 539 | if (m_version.minor == 0) { |
| 540 | auto connection_header{m_headers.FindFirst("Connection")}; |
| 541 | if (connection_header && ToLower(connection_header.value()) == "keep-alive") { |
| 542 | res.m_headers.Write("Connection", "keep-alive"); |
| 543 | keep_alive = true; |
| 544 | // HTTP/1.0 connections are closed by default so EOF is sufficient |
| 545 | // to indicate end of the body. Adding Content-Length a special case. |
| 546 | if (needs_body) needs_content_length = true; |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | // HTTP/1.1 |
| 551 | if (m_version.minor >= 1) { |
| 552 | const int64_t now_seconds{TicksSinceEpoch<std::chrono::seconds>(NodeClock::now())}; |
| 553 | res.m_headers.Write("Date", FormatRFC1123DateTime(now_seconds)); |
| 554 | |
| 555 | // HTTP/1.1 connections are kept alive by default and always require Content-Length. |
| 556 | if (needs_body) needs_content_length = true; |
| 557 | |
| 558 | // Default for HTTP/1.1 |
| 559 | keep_alive = true; |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | if (needs_content_length) { |
| 564 | res.m_headers.Write("Content-Length", util::ToString(reply_body.size())); |
| 565 | } |
| 566 | |
| 567 | if (needs_body && !res.m_headers.FindFirst("Content-Type")) { |
| 568 | // Default type from libevent evhttp_new_object() |
| 569 | res.m_headers.Write("Content-Type", "text/html; charset=ISO-8859-1"); |
| 570 | } |
| 571 | |
| 572 | auto connection_header{m_headers.FindFirst("Connection")}; |