diff --git a/include/CPVFramework/Http/HttpConstantStrings.hpp b/include/CPVFramework/Http/HttpConstantStrings.hpp index eeee959..2b4a8bf 100644 --- a/include/CPVFramework/Http/HttpConstantStrings.hpp +++ b/include/CPVFramework/Http/HttpConstantStrings.hpp @@ -101,7 +101,7 @@ namespace cpv::constants { static const constexpr char GatewayTimeout[] = "Gateway Timeout"; static const constexpr char HttpVersionNotSupported[] = "HTTP Version Not Supported"; - // standard request fields + // standard request header fields static const constexpr char AIM[] = "A-IM"; static const constexpr char Accept[] = "Accept"; static const constexpr char AcceptCharset[] = "Accept-Charset"; @@ -130,13 +130,13 @@ namespace cpv::constants { static const constexpr char TE[] = "TE"; static const constexpr char UserAgent[] = "User-Agent"; - // non-standard request fields + // non-standard request header fields static const constexpr char UpgradeInsecureRequests[] = "Upgrade-Insecure-Requests"; static const constexpr char XRequestedWith[] = "X-Requested-With"; static const constexpr char DNT[] = "DNT"; static const constexpr char XCsrfToken[] = "X-Csrf-Token"; - // standard response fields + // standard response header fields static const constexpr char AccessControlAllowOrigin[] = "Access-Control-Allow-Origin"; static const constexpr char AccessControlAllowCredentials[] = "Access-Control-Allow-Credentials"; static const constexpr char AccessControlExposeHeaders[] = "Access-Control-Expose-Headers"; @@ -184,14 +184,42 @@ namespace cpv::constants { static const constexpr char WWWAuthenticate[] = "WWW-Authenticate"; static const constexpr char XFrameOptions[] = "X-Frame-Options"; - // non-standard response fields + // non-standard response header fields static const constexpr char Refresh[] = "Refresh"; - // standard response values + // standard response header values static const constexpr char Chunked[] = "chunked"; static const constexpr char Keepalive[] = "keep-alive"; static const constexpr char Close[] = "close"; static const constexpr char TextPlainUtf8[] = "text/plain;charset=utf-8"; static const constexpr char ApplicationJsonUtf8[] = "application/json;charset=utf-8"; + + // reduce fragments for common headers + namespace with_crlf_colonspace { + // request header fields + static const constexpr char Host[] = "\r\nHost: "; + static const constexpr char ContentType[] = "\r\nContent-Type: "; + static const constexpr char ContentLength[] = "\r\nContent-Length: "; + static const constexpr char Connection[] = "\r\nConnection: "; + static const constexpr char Pragma[] = "\r\nPragma: "; + static const constexpr char UpgradeInsecureRequests[] = "\r\nUpgrade-Insecure-Requests: "; + static const constexpr char DNT[] = "\r\nDNT: "; + static const constexpr char UserAgent[] = "\r\nUser-Agent: "; + static const constexpr char Accept[] = "\r\nAccept: "; + static const constexpr char AcceptEncoding[] = "\r\nAccept-Encoding: "; + static const constexpr char AcceptLanguage[] = "\r\nAccept-Language: "; + static const constexpr char Cookie[] = "\r\nCookie: "; + static const constexpr char XRequestedWith[] = "\r\nX-Requested-With: "; + // response header fields + static const constexpr char Date[] = "\r\nDate: "; + static const constexpr char ContentEncoding[] = "\r\nContent-Encoding: "; + static const constexpr char TransferEncoding[] = "\r\nTransfer-Encoding: "; + static const constexpr char Server[] = "\r\nServer: "; + static const constexpr char Vary[] = "\r\nVary: "; + static const constexpr char ETag[] = "\r\nETag: "; + static const constexpr char CacheControl[] = "\r\nCache-Control: "; + static const constexpr char Expires[] = "\r\nExpires: "; + static const constexpr char LastModified[] = "\r\nLast-Modified: "; + } } diff --git a/include/CPVFramework/Http/HttpRequestHeaders.hpp b/include/CPVFramework/Http/HttpRequestHeaders.hpp index 3906d46..5d6ee99 100644 --- a/include/CPVFramework/Http/HttpRequestHeaders.hpp +++ b/include/CPVFramework/Http/HttpRequestHeaders.hpp @@ -1,6 +1,7 @@ #pragma once #include "../Allocators/StackAllocator.hpp" #include "../Utility/SharedString.hpp" +#include "../Utility/Packet.hpp" #include "./HttpConstantStrings.hpp" namespace cpv { @@ -56,6 +57,12 @@ namespace cpv { } } + /** + * Append all headers to packet fragments for http 1. + * notice it will append crlf to begin but not to end. + */ + void appendToHttp1Packet(Packet::MultipleFragments& fragments); + /** Set header value */ void setHeader(SharedString&& key, SharedString&& value); diff --git a/include/CPVFramework/Http/HttpResponseHeaders.hpp b/include/CPVFramework/Http/HttpResponseHeaders.hpp index 5d98fa1..51c2161 100644 --- a/include/CPVFramework/Http/HttpResponseHeaders.hpp +++ b/include/CPVFramework/Http/HttpResponseHeaders.hpp @@ -2,6 +2,7 @@ #include #include "../Allocators/StackAllocator.hpp" #include "../Utility/SharedString.hpp" +#include "../Utility/Packet.hpp" #include "./HttpConstantStrings.hpp" namespace cpv { @@ -60,6 +61,12 @@ namespace cpv { } } + /** + * Append all headers to packet fragments for http 1. + * notice it will append crlf to begin but not to end. + */ + void appendToHttp1Packet(Packet::MultipleFragments& fragments); + /** Set header value */ void setHeader(SharedString&& key, SharedString&& value); diff --git a/include/CPVFramework/Utility/ConstantStrings.hpp b/include/CPVFramework/Utility/ConstantStrings.hpp index a56823d..f90ab9d 100644 --- a/include/CPVFramework/Utility/ConstantStrings.hpp +++ b/include/CPVFramework/Utility/ConstantStrings.hpp @@ -30,6 +30,7 @@ namespace cpv::constants { static const constexpr char ColonSlashSlash[] = "://"; static const constexpr char LF[] = "\n"; static const constexpr char CRLF[] = "\r\n"; + static const constexpr char CRLFCRLF[] = "\r\n\r\n"; static const constexpr char True[] = "true"; static const constexpr char False[] = "false"; static const constexpr char Null[] = "null"; diff --git a/src/Http/HttpRequestHeaders.cpp b/src/Http/HttpRequestHeaders.cpp index 9a7e696..d995a58 100644 --- a/src/Http/HttpRequestHeaders.cpp +++ b/src/Http/HttpRequestHeaders.cpp @@ -35,6 +35,69 @@ namespace cpv { } } + /** Append all headers to packet fragments for http 1 */ + void HttpRequestHeaders::appendToHttp1Packet(Packet::MultipleFragments& fragments) { + namespace cs = constants::with_crlf_colonspace; + if (!host_.empty()) { + fragments.append(cs::Host); + fragments.append(host_.share()); + } + if (!contentType_.empty()) { + fragments.append(cs::ContentType); + fragments.append(contentType_.share()); + } + if (!contentLength_.empty()) { + fragments.append(cs::ContentLength); + fragments.append(contentLength_.share()); + } + if (!connection_.empty()) { + fragments.append(cs::Connection); + fragments.append(connection_.share()); + } + if (!pragma_.empty()) { + fragments.append(cs::Pragma); + fragments.append(pragma_.share()); + } + if (!upgradeInsecureRequests_.empty()) { + fragments.append(cs::UpgradeInsecureRequests); + fragments.append(upgradeInsecureRequests_.share()); + } + if (!dnt_.empty()) { + fragments.append(cs::DNT); + fragments.append(dnt_.share()); + } + if (!userAgent_.empty()) { + fragments.append(cs::UserAgent); + fragments.append(userAgent_.share()); + } + if (!accept_.empty()) { + fragments.append(cs::Accept); + fragments.append(accept_.share()); + } + if (!acceptEncoding_.empty()) { + fragments.append(cs::AcceptEncoding); + fragments.append(acceptEncoding_.share()); + } + if (!acceptLanguage_.empty()) { + fragments.append(cs::AcceptLanguage); + fragments.append(acceptLanguage_.share()); + } + if (!cookie_.empty()) { + fragments.append(cs::Cookie); + fragments.append(cookie_.share()); + } + if (!xRequestedWith_.empty()) { + fragments.append(cs::XRequestedWith); + fragments.append(xRequestedWith_.share()); + } + for (auto& pair : remainHeaders_) { + fragments.append(constants::CRLF); + fragments.append(pair.first.share()); + fragments.append(constants::ColonSpace); + fragments.append(pair.second.share()); + } + } + /** Get header value */ SharedString HttpRequestHeaders::getHeader(const SharedString& key) const { auto it = Internal::FixedMembers.find(key); diff --git a/src/Http/HttpResponseHeaders.cpp b/src/Http/HttpResponseHeaders.cpp index 722287e..d5c083e 100644 --- a/src/Http/HttpResponseHeaders.cpp +++ b/src/Http/HttpResponseHeaders.cpp @@ -24,6 +24,71 @@ namespace cpv { { constants::LastModified, &HttpResponseHeaders::lastModified_ }, }); + /** Append all headers to packet fragments for http 1 */ + void HttpResponseHeaders::appendToHttp1Packet(Packet::MultipleFragments& fragments) { + namespace cs = constants::with_crlf_colonspace; + if (!date_.empty()) { + fragments.append(cs::Date); + fragments.append(date_.share()); + } + if (!contentType_.empty()) { + fragments.append(cs::ContentType); + fragments.append(contentType_.share()); + } + if (!contentLength_.empty()) { + fragments.append(cs::ContentLength); + fragments.append(contentLength_.share()); + } + if (!contentEncoding_.empty()) { + fragments.append(cs::ContentEncoding); + fragments.append(contentEncoding_.share()); + } + if (!transferEncoding_.empty()) { + fragments.append(cs::TransferEncoding); + fragments.append(transferEncoding_.share()); + } + if (!connection_.empty()) { + fragments.append(cs::Connection); + fragments.append(connection_.share()); + } + if (!server_.empty()) { + fragments.append(cs::Server); + fragments.append(server_.share()); + } + if (!vary_.empty()) { + fragments.append(cs::Vary); + fragments.append(vary_.share()); + } + if (!etag_.empty()) { + fragments.append(cs::ETag); + fragments.append(etag_.share()); + } + if (!cacheControl_.empty()) { + fragments.append(cs::CacheControl); + fragments.append(cacheControl_.share()); + } + if (!expires_.empty()) { + fragments.append(cs::Expires); + fragments.append(expires_.share()); + } + if (!lastModified_.empty()) { + fragments.append(cs::LastModified); + fragments.append(lastModified_.share()); + } + for (auto& pair : remainHeaders_) { + fragments.append(constants::CRLF); + fragments.append(pair.first.share()); + fragments.append(constants::ColonSpace); + fragments.append(pair.second.share()); + } + for (auto& pair : additionHeaders_) { + fragments.append(constants::CRLF); + fragments.append(pair.first.share()); + fragments.append(constants::ColonSpace); + fragments.append(pair.second.share()); + } + } + /** Set header value */ void HttpResponseHeaders::setHeader(SharedString&& key, SharedString&& value) { auto it = Internal::FixedMembers.find(key); diff --git a/src/HttpServer/Connections/Http11ServerConnection.cpp b/src/HttpServer/Connections/Http11ServerConnection.cpp index fdb63dd..e6f6ff4 100644 --- a/src/HttpServer/Connections/Http11ServerConnection.cpp +++ b/src/HttpServer/Connections/Http11ServerConnection.cpp @@ -424,14 +424,12 @@ namespace cpv { /** (for reply loop) Get maximum fragments count for response headers */ std::size_t Http11ServerConnection::getResponseHeadersFragmentsCount() const { // calculate fragments count - // +6: version, space, status code, space, status message, crlf - // +4: date header, colon + space, header value, crlf - // +4: server header, colon + space, header value, crlf - // +4: connection header, colon + space, header value, crlf + // +5: version, space, status code, space, status message // + headers count * 4 - // +1: crlf + // +1: crlfcrlf + // +1: reserved auto& response = processingContext_.getResponse(); - return 19 + response.getHeaders().maxSize() * 4; + return 7 + response.getHeaders().maxSize() * 4; } /** (for reply loop) Append response headers to packet, please check responseHeadersAppended first */ @@ -481,7 +479,6 @@ namespace cpv { } } // append response headers to packet - // manipulate fragments vector directly to avoid variant and boundary checks auto& fragments = packet.getOrConvertToMultiple(); fragments.reserveAddition(getResponseHeadersFragmentsCount()); fragments.append(response.getVersion().share()); @@ -489,15 +486,8 @@ namespace cpv { fragments.append(response.getStatusCode().share()); fragments.append(constants::Space); fragments.append(response.getStatusMessage().share()); - fragments.append(constants::CRLF); - responseHeaders.foreach([&fragments] - (const SharedString& key, const SharedString& value) { - fragments.append(key.share()); - fragments.append(constants::ColonSpace); - fragments.append(value.share()); - fragments.append(constants::CRLF); - }); - fragments.append(constants::CRLF); + responseHeaders.appendToHttp1Packet(fragments); + fragments.append(constants::CRLFCRLF); } /** (for reply loop) Determine whether keep connection or not by checking connection header */ diff --git a/tests/Cases/Http/TestHttpRequest.cpp b/tests/Cases/Http/TestHttpRequest.cpp index 9c8affc..6146a9d 100644 --- a/tests/Cases/Http/TestHttpRequest.cpp +++ b/tests/Cases/Http/TestHttpRequest.cpp @@ -120,6 +120,46 @@ TEST(HttpRequest, headersForeach) { "AdditionC: TestAdditionC\r\n"); } +TEST(HttpRequest, headersAppendToHttp1Packet) { + cpv::HttpRequest request; + auto& headers = request.getHeaders(); + headers.setHost("TestHost"); + headers.setContentType("TestContentType"); + headers.setContentLength("TestContentLength"); + headers.setConnection("TestConnection"); + headers.setPragma("TestPragma"); + headers.setUpgradeInsecureRequests("TestUpgradeInsecureRequests"); + headers.setDNT("TestDNT"); + headers.setUserAgent("TestUserAgent"); + headers.setAccept("TestAccept"); + headers.setAcceptEncoding("TestAcceptEncoding"); + headers.setAcceptLanguage("TestAcceptLanguage"); + headers.setCookie("TestCookie"); + headers.setXRequestedWith("TestXRequestedWith"); + headers.setHeader("AdditionA", "TestAdditionA"); + headers.setHeader("AdditionB", "TestAdditionB"); + headers.setHeader("AdditionC", "TestAdditionC"); + cpv::Packet packet; + headers.appendToHttp1Packet(packet.getOrConvertToMultiple()); + ASSERT_EQ(packet.toString(), + "\r\nHost: TestHost\r\n" + "Content-Type: TestContentType\r\n" + "Content-Length: TestContentLength\r\n" + "Connection: TestConnection\r\n" + "Pragma: TestPragma\r\n" + "Upgrade-Insecure-Requests: TestUpgradeInsecureRequests\r\n" + "DNT: TestDNT\r\n" + "User-Agent: TestUserAgent\r\n" + "Accept: TestAccept\r\n" + "Accept-Encoding: TestAcceptEncoding\r\n" + "Accept-Language: TestAcceptLanguage\r\n" + "Cookie: TestCookie\r\n" + "X-Requested-With: TestXRequestedWith\r\n" + "AdditionA: TestAdditionA\r\n" + "AdditionB: TestAdditionB\r\n" + "AdditionC: TestAdditionC"); +} + TEST(HttpRequest, headersNotConstructible) { ASSERT_FALSE(std::is_constructible_v); ASSERT_FALSE(std::is_copy_constructible_v); diff --git a/tests/Cases/Http/TestHttpResponse.cpp b/tests/Cases/Http/TestHttpResponse.cpp index 3a06228..3077ee2 100644 --- a/tests/Cases/Http/TestHttpResponse.cpp +++ b/tests/Cases/Http/TestHttpResponse.cpp @@ -115,6 +115,44 @@ TEST(HttpResponse, headersForeach) { "AdditionC: TestAdditionC\r\n"); } +TEST(HttpResponse, headersAppendToHttp1Packet) { + cpv::HttpResponse response; + auto& headers = response.getHeaders(); + headers.setDate("TestDate"); + headers.setContentType("TestContentType"); + headers.setContentLength("TestContentLength"); + headers.setContentEncoding("TestContentEncoding"); + headers.setTransferEncoding("TestTransferEncoding"); + headers.setConnection("TestConnection"); + headers.setServer("TestServer"); + headers.setVary("TestVary"); + headers.setETag("TestETag"); + headers.setCacheControl("TestCacheControl"); + headers.setExpires("TestExpires"); + headers.setLastModified("TestLastModified"); + headers.setHeader("AdditionA", "TestAdditionA"); + headers.setHeader("AdditionB", "TestAdditionB"); + headers.setHeader("AdditionC", "TestAdditionC"); + cpv::Packet packet; + headers.appendToHttp1Packet(packet.getOrConvertToMultiple()); + ASSERT_EQ(packet.toString(), + "\r\nDate: TestDate\r\n" + "Content-Type: TestContentType\r\n" + "Content-Length: TestContentLength\r\n" + "Content-Encoding: TestContentEncoding\r\n" + "Transfer-Encoding: TestTransferEncoding\r\n" + "Connection: TestConnection\r\n" + "Server: TestServer\r\n" + "Vary: TestVary\r\n" + "ETag: TestETag\r\n" + "Cache-Control: TestCacheControl\r\n" + "Expires: TestExpires\r\n" + "Last-Modified: TestLastModified\r\n" + "AdditionA: TestAdditionA\r\n" + "AdditionB: TestAdditionB\r\n" + "AdditionC: TestAdditionC"); +} + TEST(HttpResponse, additionHeaders) { cpv::HttpResponse response; auto& headers = response.getHeaders();