@@ -12687,3 +12687,164 @@ TEST(ErrorHandlingTest, SSLStreamConnectionClosed) {
1268712687 t.join ();
1268812688}
1268912689#endif
12690+
12691+ TEST (ETagTest, StaticFileETagAndIfNoneMatch) {
12692+ using namespace httplib ;
12693+
12694+ // Create a test file
12695+ const char *fname = " etag_testfile.txt" ;
12696+ const char *content = " etag-content" ;
12697+ {
12698+ std::ofstream ofs (fname);
12699+ ofs << content;
12700+ }
12701+
12702+ Server svr;
12703+ svr.set_mount_point (" /static" , " ." );
12704+ auto t = std::thread ([&]() { svr.listen (" localhost" , 8087 ); });
12705+ svr.wait_until_ready ();
12706+
12707+ Client cli (" localhost" , 8087 );
12708+
12709+ // First request: should get 200 with ETag header
12710+ auto res1 = cli.Get (" /static/etag_testfile.txt" );
12711+ ASSERT_TRUE (res1);
12712+ ASSERT_EQ (200 , res1->status );
12713+ ASSERT_TRUE (res1->has_header (" ETag" ));
12714+ std::string etag = res1->get_header_value (" ETag" );
12715+ EXPECT_FALSE (etag.empty ());
12716+
12717+ // Verify ETag format: W/"hex-hex"
12718+ EXPECT_EQ (' W' , etag[0 ]);
12719+ EXPECT_EQ (' /' , etag[1 ]);
12720+ EXPECT_EQ (' "' , etag[2 ]);
12721+
12722+ // Exact match: expect 304 Not Modified
12723+ Headers h2 = {{" If-None-Match" , etag}};
12724+ auto res2 = cli.Get (" /static/etag_testfile.txt" , h2);
12725+ ASSERT_TRUE (res2);
12726+ EXPECT_EQ (304 , res2->status );
12727+
12728+ // Wildcard match: expect 304 Not Modified
12729+ Headers h3 = {{" If-None-Match" , " *" }};
12730+ auto res3 = cli.Get (" /static/etag_testfile.txt" , h3);
12731+ ASSERT_TRUE (res3);
12732+ EXPECT_EQ (304 , res3->status );
12733+
12734+ // Non-matching ETag: expect 200
12735+ Headers h4 = {{" If-None-Match" , " W/\" deadbeef\" " }};
12736+ auto res4 = cli.Get (" /static/etag_testfile.txt" , h4);
12737+ ASSERT_TRUE (res4);
12738+ EXPECT_EQ (200 , res4->status );
12739+
12740+ // Multiple ETags with one matching: expect 304
12741+ Headers h5 = {{" If-None-Match" , " W/\" other\" , " + etag + " , W/\" another\" " }};
12742+ auto res5 = cli.Get (" /static/etag_testfile.txt" , h5);
12743+ ASSERT_TRUE (res5);
12744+ EXPECT_EQ (304 , res5->status );
12745+
12746+ svr.stop ();
12747+ t.join ();
12748+ std::remove (fname);
12749+ }
12750+
12751+ TEST (ETagTest, LastModifiedAndIfModifiedSince) {
12752+ using namespace httplib ;
12753+
12754+ // Create a test file
12755+ const char *fname = " ims_testfile.txt" ;
12756+ const char *content = " if-modified-since-test" ;
12757+ {
12758+ std::ofstream ofs (fname);
12759+ ofs << content;
12760+ }
12761+
12762+ Server svr;
12763+ svr.set_mount_point (" /static" , " ." );
12764+ auto t = std::thread ([&]() { svr.listen (" localhost" , 8088 ); });
12765+ svr.wait_until_ready ();
12766+
12767+ Client cli (" localhost" , 8088 );
12768+
12769+ // First request: should get 200 with Last-Modified header
12770+ auto res1 = cli.Get (" /static/ims_testfile.txt" );
12771+ ASSERT_TRUE (res1);
12772+ ASSERT_EQ (200 , res1->status );
12773+ ASSERT_TRUE (res1->has_header (" Last-Modified" ));
12774+ std::string last_modified = res1->get_header_value (" Last-Modified" );
12775+ EXPECT_FALSE (last_modified.empty ());
12776+
12777+ // If-Modified-Since with same time: expect 304
12778+ Headers h2 = {{" If-Modified-Since" , last_modified}};
12779+ auto res2 = cli.Get (" /static/ims_testfile.txt" , h2);
12780+ ASSERT_TRUE (res2);
12781+ EXPECT_EQ (304 , res2->status );
12782+
12783+ // If-Modified-Since with future time: expect 304
12784+ Headers h3 = {{" If-Modified-Since" , " Sun, 01 Jan 2099 00:00:00 GMT" }};
12785+ auto res3 = cli.Get (" /static/ims_testfile.txt" , h3);
12786+ ASSERT_TRUE (res3);
12787+ EXPECT_EQ (304 , res3->status );
12788+
12789+ // If-Modified-Since with past time: expect 200
12790+ Headers h4 = {{" If-Modified-Since" , " Sun, 01 Jan 2000 00:00:00 GMT" }};
12791+ auto res4 = cli.Get (" /static/ims_testfile.txt" , h4);
12792+ ASSERT_TRUE (res4);
12793+ EXPECT_EQ (200 , res4->status );
12794+
12795+ // If-None-Match takes precedence over If-Modified-Since
12796+ // (send matching ETag with old If-Modified-Since -> should still be 304)
12797+ ASSERT_TRUE (res1->has_header (" ETag" ));
12798+ std::string etag = res1->get_header_value (" ETag" );
12799+ Headers h5 = {{" If-None-Match" , etag},
12800+ {" If-Modified-Since" , " Sun, 01 Jan 2000 00:00:00 GMT" }};
12801+ auto res5 = cli.Get (" /static/ims_testfile.txt" , h5);
12802+ ASSERT_TRUE (res5);
12803+ EXPECT_EQ (304 , res5->status );
12804+
12805+ svr.stop ();
12806+ t.join ();
12807+ std::remove (fname);
12808+ }
12809+
12810+ TEST (ETagTest, VaryAcceptEncodingWithCompression) {
12811+ using namespace httplib ;
12812+
12813+ Server svr;
12814+
12815+ // Endpoint that returns compressible content
12816+ svr.Get (" /compressible" , [](const Request &, Response &res) {
12817+ // Return a large enough body to trigger compression
12818+ std::string body (1000 , ' a' );
12819+ res.set_content (body, " text/plain" );
12820+ });
12821+
12822+ auto t = std::thread ([&]() { svr.listen (" localhost" , 8089 ); });
12823+ svr.wait_until_ready ();
12824+
12825+ Client cli (" localhost" , 8089 );
12826+
12827+ // Request with gzip support: should get Vary header when compressed
12828+ cli.set_compress (true );
12829+ auto res1 = cli.Get (" /compressible" );
12830+ ASSERT_TRUE (res1);
12831+ EXPECT_EQ (200 , res1->status );
12832+
12833+ // If Content-Encoding is set, Vary should also be set
12834+ if (res1->has_header (" Content-Encoding" )) {
12835+ EXPECT_TRUE (res1->has_header (" Vary" ));
12836+ EXPECT_EQ (" Accept-Encoding" , res1->get_header_value (" Vary" ));
12837+ }
12838+
12839+ // Request without Accept-Encoding header: should not have compression
12840+ Headers h_no_compress;
12841+ auto res2 = cli.Get (" /compressible" , h_no_compress);
12842+ ASSERT_TRUE (res2);
12843+ EXPECT_EQ (200 , res2->status );
12844+
12845+ // Verify Vary header is present when compression is applied
12846+ // (the exact behavior depends on server configuration)
12847+
12848+ svr.stop ();
12849+ t.join ();
12850+ }
0 commit comments