Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/bin
/bin64
/_build*
/build_test
temp

# Emacs
Expand Down
128 changes: 128 additions & 0 deletions IMPLEMENTATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Format String Support Implementation Summary

## Overview
This implementation adds optional support for modern C++ formatting (std::format or fmtlib) to the http_io examples, addressing issue: "need fmtlib or std::format or both".

## Solution Architecture

### Conditional Compilation Strategy
The solution uses a tiered approach:
1. **Preferred**: C++20 `std::format` (via `std::vformat` for runtime format strings)
2. **Fallback**: fmtlib if available (auto-detected via `__has_include(<fmt/core.h>)`)
3. **Legacy**: Original `std::stringstream` concatenation for maximum compatibility

### Key Benefits
- **Zero Breaking Changes**: Existing code continues to work unchanged
- **No Hard Dependencies**: fmtlib is completely optional
- **Automatic Detection**: Uses preprocessor to detect available features
- **Backward Compatible**: Works with C++11, C++17, and C++20
- **Performance**: Modern formatting is more efficient than stringstream

## Implementation Details

### Files Modified

1. **example/server/format.hpp** (new)
- Conditional compilation header
- Detects std::format or fmtlib availability
- Provides `detail::format()` wrapper function

2. **example/server/logger.hpp**
- Added format string support when available
- Maintains legacy variadic template API for fallback
- Comprehensive inline documentation

3. **example/server/CMakeLists.txt**
- Optional fmtlib detection via `find_package(fmt QUIET)`
- Links fmtlib only if found
- Status message when fmtlib is used

4. **example/client/burl/utils.cpp**
- Updated `format_size()` to use format library
- Resolved existing TODO comment
- Same tiered approach as server example

5. **example/client/burl/CMakeLists.txt**
- Optional fmtlib detection (same as server)

### Documentation Added

1. **example/server/README.md**
- Complete usage guide
- Build instructions with and without format support
- Examples of both API styles

2. **example/server/logger_example.cpp**
- Demonstrates both modern and legacy APIs
- Shows conditional compilation in practice
- Useful reference for users

3. **README.adoc**
- Added Optional Dependencies section
- Installation instructions for fmtlib
- Clear explanation of auto-detection

## Usage Examples

### Modern Format String API (C++20 or with fmtlib)
```cpp
section log;
LOG_INF(log, "Request from {} for {}", client_ip, path);
LOG_DBG(log, "Response time: {:.2f}ms", response_time);
```

### Legacy Variadic API (C++11 fallback)
```cpp
section log;
LOG_INF(log, "Request from ", client_ip, " for ", path);
LOG_DBG(log, "Response time: ", response_time, "ms");
```

## Build Configurations

### With C++20 (auto-detects std::format)
```bash
cmake -DCMAKE_CXX_STANDARD=20 ..
make
```

### With fmtlib (any C++ standard)
```bash
# Using vcpkg
vcpkg install fmt
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg/scripts/buildsystems/vcpkg.cmake ..
make
```

### Without format support (C++11 compatible)
```bash
cmake ..
make
```

## Testing

All configurations tested successfully:
- ✅ C++11 without format library (stringstream fallback)
- ✅ C++20 with std::format (automatic detection)
- ✅ Backward compatibility (existing code works unchanged)

## Future Enhancements

Potential improvements (out of scope for this PR):
- Example demonstrating advanced format string features
- Performance benchmarks comparing stringstream vs format
- Integration with structured logging frameworks

## Technical Notes

### Why std::vformat instead of std::format?
`std::format` requires compile-time constant format strings in C++20. Since our logger needs to accept runtime format strings, we use `std::vformat` which accepts runtime strings.

### Why not Boost.Format?
While Boost.Format is available, it:
- Uses different syntax (`%1%` instead of `{}`)
- Is slower than modern alternatives
- Doesn't match the industry direction (std::format/fmtlib)

The chosen approach aligns with modern C++ best practices and industry standards.
19 changes: 19 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@

== boost.http_io

=== Optional Dependencies

The examples can optionally use https://github.com/fmtlib/fmt[fmtlib] for improved formatting support:

* When C++20 is available: Uses `std::format` automatically
* When fmtlib is available: Uses `fmt::format` as fallback
* Otherwise: Uses `std::stringstream` for formatting

To enable fmtlib support:

```bash
# With vcpkg
vcpkg install fmt
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg/scripts/buildsystems/vcpkg.cmake ..

# Or let CMake find system-installed fmtlib
cmake ..
```

=== Visual Studio Solution

```cpp
Expand Down
7 changes: 7 additions & 0 deletions example/client/burl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,11 @@ if (CMAKE_CXX_STANDARD EQUAL 20)
target_compile_definitions(http_io_example_client_burl PRIVATE BURL_HAS_LIBPSL)
endif ()

# Optional: Try to find and link fmtlib for better formatting support
find_package(fmt QUIET)
if (fmt_FOUND)
target_link_libraries(http_io_example_client_burl fmt::fmt)
message(STATUS "burl example: Using fmtlib for formatting")
endif()

endif()
6 changes: 6 additions & 0 deletions example/client/burl/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import ../../../../config/checks/config : requires ;
using openssl ;
import ac ;

# Optional fmtlib library support
# If fmtlib is available, link against it for better formatting
lib fmt : : <name>fmt <search>/usr/local/lib <search>/usr/lib ;
explicit fmt ;

project
: requirements
[ requires
Expand All @@ -20,6 +25,7 @@ project
<library>/boost/http_io//boost_http_io
[ ac.check-library /boost/rts//boost_rts_zlib : <library>/boost/rts//boost_rts_zlib : ]
[ ac.check-library /boost/rts//boost_rts_brotli : <library>/boost/rts//boost_rts_brotli : ]
[ ac.check-library fmt : <library>fmt : ]
<library>/boost/url//boost_url
<library>/boost/program_options//boost_program_options
<library>/boost/scope//boost_scope
Expand Down
17 changes: 16 additions & 1 deletion example/client/burl/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
#include <iomanip>
#include <sstream>

// Check for C++20 std::format support
#if __cplusplus >= 202002L && __has_include(<format>)
#include <format>
#define BURL_HAS_STD_FORMAT
#elif __has_include(<fmt/core.h>)
#include <fmt/core.h>
#define BURL_HAS_FMT
#endif

namespace grammar = boost::urls::grammar;
namespace variant2 = boost::variant2;

Expand Down Expand Up @@ -164,9 +173,15 @@ format_size(std::uint64_t size, int width)
auto ints = static_cast<int>(std::log10(scaled)) + 1;
auto fracs = std::max(width - ints - 1, 0);

// TODO: replace ostringstream
#if defined(BURL_HAS_STD_FORMAT)
return std::format("{:.{}f} {}", scaled, fracs, units[order]);
#elif defined(BURL_HAS_FMT)
return fmt::format("{:.{}f} {}", scaled, fracs, units[order]);
#else
// Fallback to ostringstream
std::ostringstream os;
os << std::fixed << std::setprecision(fracs) << scaled << " "
<< units[order];
return os.str();
#endif
}
7 changes: 7 additions & 0 deletions example/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ set_property(TARGET http_io_server_example
target_link_libraries(http_io_server_example
Boost::http_io
Boost::url)

# Optional: Try to find and link fmtlib for better formatting support
find_package(fmt QUIET)
if (fmt_FOUND)
target_link_libraries(http_io_server_example fmt::fmt)
message(STATUS "Server example: Using fmtlib for formatting")
endif()
8 changes: 8 additions & 0 deletions example/server/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@
# Official repository: https://github.com/CPPAlliance/http_proto
#

import ac ;

# Optional fmtlib library support
# If fmtlib is available, link against it for better formatting
lib fmt : : <name>fmt <search>/usr/local/lib <search>/usr/lib ;
explicit fmt ;

project
: requirements
<library>/boost/http_io//boost_http_io
<library>/boost/url//boost_url
[ ac.check-library fmt : <library>fmt : ]
<include>.
;

Expand Down
72 changes: 72 additions & 0 deletions example/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# HTTP Server Example

This example demonstrates a basic HTTP server using Boost.Http.IO.

## Features

- Asynchronous request handling with Boost.Asio
- File serving with MIME type detection
- Custom logging system with format string support

## Format String Support

The logger supports modern C++ formatting when available:

### Using std::format (C++20)
When compiled with C++20, the logger automatically uses `std::format`:
```cpp
section log;
LOG_INF(log, "Received request from {} for {}", client_ip, path);
```

### Using fmtlib
If C++20 is not available but [fmtlib](https://github.com/fmtlib/fmt) is installed:
```bash
# Install fmtlib (example with vcpkg)
vcpkg install fmt

# Configure CMake to find fmtlib
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg/scripts/buildsystems/vcpkg.cmake ..
```

The logger will automatically detect and use fmtlib:
```cpp
section log;
LOG_INF(log, "Processing request {} of {}", count, total);
```

### Fallback (No Format Library)
If neither std::format nor fmtlib is available, the logger uses the legacy API:
```cpp
section log;
LOG_INF(log, "Processing request ", count, " of ", total);
```

## Building

### With Format Support (Recommended)
```bash
# Using C++20
cmake -DCMAKE_CXX_STANDARD=20 ..
make

# Or using fmtlib with C++11/17
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg/scripts/buildsystems/vcpkg.cmake ..
make
```

### Without Format Support
```bash
cmake ..
make
```

The server example will compile successfully with or without format library support.

## Usage

```bash
./http_io_server_example [document_root]
```

Where `document_root` is the directory to serve files from (defaults to current directory).
58 changes: 58 additions & 0 deletions example/server/format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/http_io
//

#ifndef BOOST_HTTP_IO_EXAMPLE_SERVER_FORMAT_HPP
#define BOOST_HTTP_IO_EXAMPLE_SERVER_FORMAT_HPP

//
// Format string support utility for server example.
//
// This header provides conditional compilation support for modern C++ formatting:
// - Uses std::format (via std::vformat) when C++20 is available
// - Falls back to fmtlib if <fmt/core.h> is available
// - Provides no formatting if neither is available (uses legacy stringstream)
//
// This allows the server example to benefit from format string support
// when available without requiring it as a hard dependency.
//

#include <string>

// Check for C++20 std::format support
#if __cplusplus >= 202002L && __has_include(<format>)
#include <format>
#define BOOST_HTTP_IO_HAS_STD_FORMAT
#elif __has_include(<fmt/core.h>)
#include <fmt/core.h>
#define BOOST_HTTP_IO_HAS_FMT
#endif

#if defined(BOOST_HTTP_IO_HAS_STD_FORMAT) || defined(BOOST_HTTP_IO_HAS_FMT)

namespace detail {

#if defined(BOOST_HTTP_IO_HAS_STD_FORMAT)
template<typename... Args>
inline std::string format(std::string_view fmt, Args&&... args)
{
return std::vformat(fmt, std::make_format_args(args...));
}
#elif defined(BOOST_HTTP_IO_HAS_FMT)
template<typename... Args>
inline std::string format(std::string_view fmt, Args&&... args)
{
return fmt::format(fmt, std::forward<Args>(args)...);
}
#endif

} // detail

#endif // format support available

#endif
Loading