First
This commit is contained in:
commit
3b41f26f2f
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
2
.idea/Xenith.iml
generated
Normal file
2
.idea/Xenith.iml
generated
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||
254
.idea/editor.xml
generated
Normal file
254
.idea/editor.xml
generated
Normal file
@ -0,0 +1,254 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BackendCodeEditorSettings">
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstexprIfDiscardedBranch/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReferenceToOverriddenVirtualFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseDesignatedInitializers/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseInternalLinkage/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakePythonSetting">
|
||||
<option name="pythonIntegrationState" value="YES" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Xenith.iml" filepath="$PROJECT_DIR$/.idea/Xenith.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
44
CMakeLists.txt
Normal file
44
CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(Xenith)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17) # В Makefile указан c++17
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Опции компиляции из твоего Makefile
|
||||
# Для Debug сборки CLion обычно сам добавляет -O0 -g, но можно форсировать
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g -fsanitize=address,undefined")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-fsanitize=address,undefined")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native -ffast-math -funroll-loops")
|
||||
endif()
|
||||
|
||||
# Добавляем предупреждения
|
||||
add_compile_options(-Wall -Wextra -Wno-unused-parameter)
|
||||
|
||||
# Список исходных файлов из твоего Makefile
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
xenith/core/model.cpp
|
||||
xenith/core/backward.cpp
|
||||
xenith/core/trainer.cpp
|
||||
xenith/core/checkpoint.cpp
|
||||
xenith/core/generate.cpp
|
||||
xenith/core/gradcheck.cpp
|
||||
# Файлы, которые уже были в твоем старом CMakeLists.txt
|
||||
xenith/preprocessing/tokenizer.cpp
|
||||
ollama_api/test.cpp
|
||||
xenith/converter.cpp
|
||||
)
|
||||
|
||||
# Заголовочные файлы (для удобства в IDE)
|
||||
file(GLOB_RECURSE HEADER_FILES "*.h" "*.hpp")
|
||||
|
||||
add_executable(Xenith ${SOURCES} ${HEADER_FILES})
|
||||
|
||||
# Настройка include директорий
|
||||
target_include_directories(Xenith PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Потоки (pthread)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(Xenith PRIVATE Threads::Threads)
|
||||
7
KVCacheArch
Normal file
7
KVCacheArch
Normal file
@ -0,0 +1,7 @@
|
||||
// [token][layer][kv][head][dim]
|
||||
struct TokenKV {
|
||||
float k[num_heads][head_dim]; // Ключи всех голов этого слоя
|
||||
float v[num_heads][head_dim]; // Значения всех голов этого слоя
|
||||
};
|
||||
|
||||
TokenKV kv_cache[MAX_CTX][NUM_LAYERS];
|
||||
47
Makefile
Normal file
47
Makefile
Normal file
@ -0,0 +1,47 @@
|
||||
CXX ?= g++
|
||||
CXXFLAGS ?= -O3 -march=native -ffast-math -funroll-loops -std=c++17 -Wall -Wextra -Wno-unused-parameter
|
||||
LDFLAGS ?= -pthread
|
||||
|
||||
SRC := main.cpp \
|
||||
xenith/core/model.cpp \
|
||||
xenith/core/backward.cpp \
|
||||
xenith/core/trainer.cpp \
|
||||
xenith/core/checkpoint.cpp \
|
||||
xenith/core/generate.cpp \
|
||||
xenith/core/gradcheck.cpp
|
||||
|
||||
OBJ := $(SRC:.cpp=.o)
|
||||
DEP := $(OBJ:.o=.d)
|
||||
BIN := bin/xenith
|
||||
|
||||
# Без -include .d make считает ПЕРВУЮ цель в make-файле целью по умолчанию.
|
||||
# Если первой стоит "-include $(DEP)", сборка молча превращается в no-op:
|
||||
# ни один .o не пересобирается, и правки исходников не доходят до бинарника.
|
||||
# Именно так мы потеряли час: gradcheck показывал одни и те же числа
|
||||
# после нескольких исправлений. Поэтому первая цель здесь — всегда all.
|
||||
ALL_OBJ := $(OBJ)
|
||||
|
||||
all: $(BIN)
|
||||
.PHONY: all
|
||||
|
||||
bin:
|
||||
mkdir -p bin
|
||||
|
||||
$(BIN): $(ALL_OBJ)
|
||||
@mkdir -p bin
|
||||
$(CXX) $(ALL_OBJ) -o $@ $(LDFLAGS)
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
debug:
|
||||
$(MAKE) clean
|
||||
$(MAKE) CXXFLAGS="-O1 -g -std=c++17 -Wall -Wextra -Wno-unused-parameter -fsanitize=address,undefined" \
|
||||
LDFLAGS="-pthread -fsanitize=address,undefined"
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) $(DEP) $(BIN)
|
||||
|
||||
.PHONY: debug clean
|
||||
|
||||
-include $(DEP)
|
||||
136
README.md
Normal file
136
README.md
Normal file
@ -0,0 +1,136 @@
|
||||
# Xenith
|
||||
|
||||
Трансформер на чистом C++: обучение и генерация текста на CPU, без внешних
|
||||
зависимостей. Всё в одном бинарнике.
|
||||
|
||||
```
|
||||
make # собрать -> bin/xenith
|
||||
./bin/xenith --help
|
||||
```
|
||||
|
||||
## Что внутри
|
||||
|
||||
Архитектура — как у LLaMA: pre-norm, RMSNorm, RoPE, SwiGLU, связанные
|
||||
эмбеддинги (tied weights), каузальное внимание. Обучение — вручную
|
||||
написанный обратный проход + AdamW. Никаких фреймворков.
|
||||
|
||||
| файл | что делает |
|
||||
|---|---|
|
||||
| `xenith/core/tensor.h` | матрицы, три формы GEMM, RMSNorm, RNG, пул потоков |
|
||||
| `xenith/core/model.cpp` | конфигурация, инициализация, прямой проход, RoPE, Adam |
|
||||
| `xenith/core/backward.cpp` | обратный проход (ручной, без автомиффов) |
|
||||
| `xenith/core/trainer.cpp` | цикл обучения, расписание lr, чекпоинты, валидация |
|
||||
| `xenith/core/generate.cpp` | генерация с KV-кэшем, temperature / top-k / top-p |
|
||||
| `xenith/core/checkpoint.cpp` | формат `.xnh`, токенизатор, сохранение/загрузка |
|
||||
| `xenith/core/gradcheck.cpp` | численная проверка градиентов + бенчмарк |
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
# 1) собрать модель из своего текста
|
||||
./bin/xenith new models/my.xnh --corpus data.txt \
|
||||
--vocab 512 --embd 128 --layers 6 --heads 8 --ctx 256
|
||||
|
||||
# 2) обучить
|
||||
./bin/xenith train models/my.xnh --corpus data.txt \
|
||||
--out models/my_trained.xnh --steps 5000 --batch 16 --lr 3e-4
|
||||
|
||||
# 3) сгенерировать
|
||||
./bin/xenith gen models/my_trained.xnh --prompt "Привет" --n 200 --temp 0.8
|
||||
```
|
||||
|
||||
## Команды
|
||||
|
||||
### new — создать модель
|
||||
|
||||
Читает корпус, строит по нему словарь, инициализирует веса случайно.
|
||||
|
||||
```
|
||||
--corpus PATH текст (обязательно)
|
||||
--out PATH файл модели (по умолчанию model.xnh)
|
||||
--vocab N размер словаря (512)
|
||||
--embd N ширина эмбеддинга (128)
|
||||
--layers N число слоёв (4)
|
||||
--heads N число голов (4). embd должен делиться на heads
|
||||
--ctx N максимальный контекст (128)
|
||||
--ffn N ширина FFN (0 = 4*embd)
|
||||
--rope N база RoPE (10000)
|
||||
--seed N зерно инициализации (1337)
|
||||
--untied отдельная матрица выхода вместо связанной с эмбеддингом
|
||||
```
|
||||
|
||||
Словарь двухуровневый: частые слова берутся целиком, остальное режется на
|
||||
символы. Так словарь остаётся маленьким, а любой текст кодируется без потерь —
|
||||
модель может учить и слова, и буквы.
|
||||
|
||||
### train — обучить
|
||||
|
||||
```
|
||||
--corpus PATH текст (обязательно)
|
||||
--out PATH куда сохранить (по умолчанию перезаписывает входной файл)
|
||||
--steps N шагов (1000)
|
||||
--batch N батч (8)
|
||||
--block N длина окна (64)
|
||||
--lr F скорость (3e-4)
|
||||
--wd F weight decay (0.01)
|
||||
--clip F клип нормы градиента (1.0)
|
||||
--warmup N прогрев (100)
|
||||
--seed N зерно (1337)
|
||||
--log-every N как часто печатать (50)
|
||||
--ckpt-every N промежуточный чекпоинт каждые N шагов (0 = нет)
|
||||
--val-every N валидация каждые N шагов (0 = нет)
|
||||
--val-tokens N размер валидации (20000)
|
||||
--threads N потоков (0 = все ядра)
|
||||
--resume продолжить с сохранённого step
|
||||
```
|
||||
|
||||
Прогрев + косинусное затухание до `lr * 0.1`. Промежуточные чекпоинты
|
||||
пишутся как `models/my.xnh.step1500` — можно откатиться, если loss
|
||||
развалился.
|
||||
|
||||
### gen — сгенерировать
|
||||
|
||||
```
|
||||
--prompt STR стартовый текст
|
||||
--n N сколько токенов (200)
|
||||
--temp F температура; 0 = жадный выбор (0.8)
|
||||
--top-k N top-k (40), 0 = выкл
|
||||
--top-p F top-p (0.95), 1.0 = выкл
|
||||
--seed N зерно
|
||||
--no-stream не печатать в процессе
|
||||
--batch "a;b;c" несколько промптов через ;
|
||||
--show-tokens показать id токенов
|
||||
```
|
||||
|
||||
### info / gradcheck / bench
|
||||
|
||||
```bash
|
||||
./bin/xenith info models/my.xnh # конфигурация и словарь
|
||||
./bin/xenith gradcheck # градиенты против численных
|
||||
./bin/xenith bench models/my.xnh # скорость
|
||||
```
|
||||
|
||||
`gradcheck` стоит запускать после любой правки в `model.cpp` / `backward.cpp` —
|
||||
он ловит ошибку в обратном проходе за секунды.
|
||||
|
||||
## Формат модели
|
||||
|
||||
Один файл `.xnh` содержит и словарь, и веса, и состояние оптимизатора.
|
||||
При загрузке проверяется каждая размерность тензора, так что модель,
|
||||
собранная с другими настройками, отвергается с понятной ошибкой, а не
|
||||
молча портит вывод.
|
||||
|
||||
Совместимость с `models/test_model.bfr` (старый формат) **не** поддерживается:
|
||||
там была другая раскладка весов.
|
||||
|
||||
## Что учесть
|
||||
|
||||
- **Скорость.** ~7600 ток/с на 4 ядрах для 4 слоёв × 64 измерения. С ростом
|
||||
модели скорость падает квадратично по `embd` и линейно по слоям.
|
||||
Замеряйте своим `bench`.
|
||||
- **Форма входа.** `text`, `code`, `html` — чем однороднее корпус, тем
|
||||
меньше словарь и тем быстрее обучение.
|
||||
- **Количество данных.** Модель на 0.25 млн параметров хочет хотя бы
|
||||
несколько мегабайт текста, иначе будет только запоминать.
|
||||
- **Токенизатор** — слова + символы, не BPE. Для морфологически богатых
|
||||
языков это проще, но экономнее BPE приходится платить длиной последовательности.
|
||||
BIN
bin/xenith
Executable file
BIN
bin/xenith
Executable file
Binary file not shown.
2
config.yaml
Normal file
2
config.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
models:
|
||||
BiPy: models/bipy.bif
|
||||
4000
corpus/demo.txt
Normal file
4000
corpus/demo.txt
Normal file
File diff suppressed because it is too large
Load Diff
2
libs/cpp-httplib-0.57.1/.gitattributes
vendored
Normal file
2
libs/cpp-httplib-0.57.1/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/test/www*/dir/*.html text eol=lf
|
||||
/test/www*/dir/*.txt text eol=lf
|
||||
70
libs/cpp-httplib-0.57.1/.github/workflows/abidiff.yaml
vendored
Normal file
70
libs/cpp-httplib-0.57.1/.github/workflows/abidiff.yaml
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# SPDX-FileCopyrightText: 2025 Andrea Pappacoda <andrea@pappacoda.it>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: abidiff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
|
||||
jobs:
|
||||
abi:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
container:
|
||||
image: debian:testing
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apt -y --update install --no-install-recommends
|
||||
abigail-tools
|
||||
ca-certificates
|
||||
g++
|
||||
git
|
||||
libbrotli-dev
|
||||
libssl-dev
|
||||
libzstd-dev
|
||||
meson
|
||||
pkg-config
|
||||
python3
|
||||
zlib1g-dev
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: current
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: previous
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
git switch master
|
||||
git describe --tags --abbrev=0 master | xargs git checkout
|
||||
|
||||
- name: Build current
|
||||
working-directory: current
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Build previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Run abidiff
|
||||
run: abidiff
|
||||
--headers-dir1 previous/build
|
||||
--headers-dir2 current/build
|
||||
previous/build/libcpp-httplib.so
|
||||
current/build/libcpp-httplib.so
|
||||
62
libs/cpp-httplib-0.57.1/.github/workflows/benchmark_ab.yaml
vendored
Normal file
62
libs/cpp-httplib-0.57.1/.github/workflows/benchmark_ab.yaml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
name: benchmark-ab
|
||||
|
||||
# Manual A/B throughput comparison between two refs.
|
||||
#
|
||||
# This is a measurement, not a test: it never fails the build on a slow result.
|
||||
# Absolute req/s from a shared runner is meaningless on its own, so both refs
|
||||
# are built and measured alternately in the same job and only the ratio of the
|
||||
# medians is reported, with a permutation test to say whether the difference
|
||||
# stands out from the run-to-run noise.
|
||||
#
|
||||
# Non-SSL and Linux only for now.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base:
|
||||
description: "Baseline ref"
|
||||
required: false
|
||||
default: "origin/master"
|
||||
head:
|
||||
description: "Ref to compare (defaults to the ref this run was started on)"
|
||||
required: false
|
||||
default: ""
|
||||
rounds:
|
||||
description: "Measurement rounds per ref (9+ recommended; below 4 the test can never reach significance)"
|
||||
required: false
|
||||
default: "9"
|
||||
duration:
|
||||
description: "Load duration per measurement"
|
||||
required: false
|
||||
default: "5s"
|
||||
connections:
|
||||
description: "Concurrent connections"
|
||||
required: false
|
||||
default: "10"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: install bombardier
|
||||
run: go install github.com/codesenberg/bombardier@latest
|
||||
|
||||
- name: run A/B benchmark
|
||||
run: |
|
||||
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||
HEAD_REF="${{ inputs.head }}"
|
||||
if [ -z "$HEAD_REF" ]; then HEAD_REF="${{ github.sha }}"; fi
|
||||
./benchmark/ab.sh \
|
||||
--base "${{ inputs.base }}" \
|
||||
--head "$HEAD_REF" \
|
||||
--rounds "${{ inputs.rounds }}" \
|
||||
--duration "${{ inputs.duration }}" \
|
||||
--connections "${{ inputs.connections }}"
|
||||
106
libs/cpp-httplib-0.57.1/.github/workflows/benchmark_run.yaml
vendored
Normal file
106
libs/cpp-httplib-0.57.1/.github/workflows/benchmark_run.yaml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name: benchmark-run
|
||||
|
||||
# Runs the committed benchmark (`just bench`) and records the numbers.
|
||||
#
|
||||
# This is a measurement, not a test: nothing here fails the build. Unlike
|
||||
# benchmark-ab, which compares two refs inside one job, this just reports the
|
||||
# absolute throughput of the current ref alongside Crow for reference.
|
||||
#
|
||||
# Absolute req/s is only meaningful against other runs on the same runner type,
|
||||
# so compare like with like when reading the history.
|
||||
#
|
||||
# Non-SSL only. Windows is excluded: benchmark/Makefile depends on `nc`, `&`
|
||||
# and `kill`, so it would need a PowerShell rewrite first.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
duration:
|
||||
description: "Load duration per server"
|
||||
required: false
|
||||
default: "5s"
|
||||
connections:
|
||||
description: "Concurrent connections"
|
||||
required: false
|
||||
default: "10"
|
||||
crow:
|
||||
description: "Also benchmark Crow v1.3.1 for reference"
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
bench:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# macos runners ship without Go.
|
||||
- name: setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: install bombardier
|
||||
run: go install github.com/codesenberg/bombardier@latest
|
||||
|
||||
# crow_all.h includes <asio.hpp>, which no runner has out of the box.
|
||||
- name: install asio
|
||||
if: ${{ inputs.crow }}
|
||||
run: |
|
||||
if [ "$RUNNER_OS" = "Linux" ]; then
|
||||
sudo apt-get update && sudo apt-get install -y libasio-dev
|
||||
else
|
||||
brew install asio
|
||||
fi
|
||||
|
||||
- name: run benchmark
|
||||
run: |
|
||||
# Without pipefail the `tee` below swallows a build failure and the
|
||||
# job reports success having measured nothing.
|
||||
set -o pipefail
|
||||
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||
CROW_FLAGS="-std=c++17"
|
||||
if [ "$RUNNER_OS" = "macOS" ]; then
|
||||
CROW_FLAGS="$CROW_FLAGS -I$(brew --prefix asio)/include"
|
||||
fi
|
||||
if [ "${{ inputs.crow }}" = "true" ]; then TARGET=bench-all; else TARGET=bench; fi
|
||||
make -C benchmark "$TARGET" \
|
||||
CROW_CXXFLAGS="$CROW_FLAGS" \
|
||||
BENCH="bombardier -c ${{ inputs.connections }} -d ${{ inputs.duration }} localhost:8080" \
|
||||
2>&1 | tee /tmp/bench.txt
|
||||
|
||||
# pipefail only catches a failed build. Each Makefile recipe ends in
|
||||
# `kill`, so a bombardier that never ran still leaves make happy — check
|
||||
# that the measurements are actually there.
|
||||
- name: check results were produced
|
||||
run: |
|
||||
expected=1
|
||||
if [ "${{ inputs.crow }}" = "true" ]; then expected=2; fi
|
||||
got=$(grep -c "Reqs/sec" /tmp/bench.txt || true)
|
||||
if [ "$got" -lt "$expected" ]; then
|
||||
echo "::error::expected $expected benchmark result(s), found $got"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: record results
|
||||
if: always()
|
||||
run: |
|
||||
{
|
||||
echo "## Benchmark (${{ matrix.os }})"
|
||||
echo ""
|
||||
echo "- ref: \`${{ github.ref_name }}\` (${{ github.sha }})"
|
||||
echo "- connections=${{ inputs.connections }} duration=${{ inputs.duration }}"
|
||||
echo ""
|
||||
echo '```'
|
||||
cat /tmp/bench.txt
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
42
libs/cpp-httplib-0.57.1/.github/workflows/cifuzz.yaml
vendored
Normal file
42
libs/cpp-httplib-0.57.1/.github/workflows/cifuzz.yaml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
name: CIFuzz
|
||||
|
||||
# The fuzzers only build httplib.h and the targets under test/fuzzing, so a
|
||||
# pull request that touches neither has nothing for CIFuzz to exercise. Fuzzing
|
||||
# is by far the longest job in CI (10 minutes of fuzzing on top of building the
|
||||
# OSS-Fuzz image), and skipping it for documentation-only changes keeps the
|
||||
# full 600 seconds for the pull requests that do reach the parsers.
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'httplib.h'
|
||||
- 'test/fuzzing/**'
|
||||
- '.github/workflows/cifuzz.yaml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cpp-httplib'
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cpp-httplib'
|
||||
fuzz-seconds: 600
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
37
libs/cpp-httplib-0.57.1/.github/workflows/docs.yml
vendored
Normal file
37
libs/cpp-httplib-0.57.1/.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: docs
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'docs-src/**'
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history so sitemap <lastmod> reflects each page's real last commit date
|
||||
fetch-depth: 0
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install docs-gen
|
||||
run: cargo install docs-gen
|
||||
- name: Build
|
||||
run: docs-gen build docs-src docs
|
||||
- uses: actions/configure-pages@v5
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
51
libs/cpp-httplib-0.57.1/.github/workflows/release-docker.yml
vendored
Normal file
51
libs/cpp-httplib-0.57.1/.github/workflows/release-docker.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
name: Release Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history and tags
|
||||
|
||||
- name: Extract tag (manual)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: set_tag_manual
|
||||
run: |
|
||||
# Checkout the latest tag and set output
|
||||
git fetch --tags
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0)
|
||||
git checkout $LATEST_TAG
|
||||
echo "tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract tag (release)
|
||||
if: github.event_name == 'release'
|
||||
id: set_tag_release
|
||||
run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64 # Build for both amd64 and arm64
|
||||
# Use extracted tag without leading 'v'
|
||||
tags: |
|
||||
yhirose4dockerhub/cpp-httplib-server:latest
|
||||
yhirose4dockerhub/cpp-httplib-server:${{ steps.set_tag_manual.outputs.tag || steps.set_tag_release.outputs.tag }}
|
||||
37
libs/cpp-httplib-0.57.1/.github/workflows/test-32bit.yml
vendored
Normal file
37
libs/cpp-httplib-0.57.1/.github/workflows/test-32bit.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: 32-bit Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-win32:
|
||||
name: Windows 32-bit (MSVC x86)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build (Win32)
|
||||
shell: cmd
|
||||
run: |
|
||||
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do set VSDIR=%%i
|
||||
call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86 || exit /b 1
|
||||
cl /std:c++14 /EHsc /W4 /WX /c /Fo:NUL test\test_32bit_build.cpp
|
||||
|
||||
test-arm32:
|
||||
name: ARM 32-bit (cross-compile)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install cross compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y g++-arm-linux-gnueabihf
|
||||
- name: Build (ARM 32-bit)
|
||||
run: arm-linux-gnueabihf-g++ -std=c++11 -Wall -Wextra -Wno-psabi -Werror -c -o /dev/null test/test_32bit_build.cpp
|
||||
629
libs/cpp-httplib-0.57.1/.github/workflows/test.yaml
vendored
Normal file
629
libs/cpp-httplib-0.57.1/.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,629 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gtest_filter:
|
||||
description: 'Google Test filter'
|
||||
test_linux:
|
||||
description: 'Test on Linux'
|
||||
type: boolean
|
||||
default: true
|
||||
test_macos:
|
||||
description: 'Test on MacOS'
|
||||
type: boolean
|
||||
default: true
|
||||
test_windows:
|
||||
description: 'Test on Windows'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Exclude *_Online tests by default — they hit external services and flake on
|
||||
# CI runners. Run with workflow_dispatch + a custom filter to include them.
|
||||
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '-*_Online' }}
|
||||
|
||||
jobs:
|
||||
style-check:
|
||||
# Uses the macOS runner's pre-installed Homebrew so clang-format tracks
|
||||
# whatever version `brew install clang-format` currently resolves to on
|
||||
# the maintainer's own Mac, instead of a version pinned in this file.
|
||||
runs-on: macos-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install clang-format
|
||||
run: |
|
||||
brew update
|
||||
brew install clang-format coreutils
|
||||
- name: run style check
|
||||
run: |
|
||||
clang-format --version
|
||||
cd test && make style_check
|
||||
|
||||
build-and-test-on-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- arch_flags: -m32
|
||||
arch_suffix: :i386
|
||||
name: (32-bit)
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install libraries
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
|
||||
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
|
||||
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
|
||||
libzstd-dev${{ matrix.config.arch_suffix }}
|
||||
- name: build and run tests
|
||||
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls, wolfssl]
|
||||
name: ubuntu (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: sudo apt-get install -y libmbedtls-dev
|
||||
- name: install wolfSSL
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: sudo apt-get install -y libwolfssl-dev
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||
- name: build and run tests (wolfSSL)
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||
- name: run fuzz test target
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make fuzz_test
|
||||
- name: build and run WebSocket heartbeat test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
||||
- name: build and run WebSocket TLS thread safety test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety
|
||||
- name: build and run ThreadPool test
|
||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||
|
||||
# Ubuntu 26.04's apt ships Mbed TLS 3.6, giving 3.x coverage that
|
||||
# ubuntu-latest (24.04 = 2.28) and macOS (Homebrew = 4.x) no longer provide.
|
||||
# Uses the 26.04 public-preview image; fold into the main ubuntu matrix once
|
||||
# ubuntu-latest moves to 26.04.
|
||||
ubuntu-2604-mbedtls:
|
||||
runs-on: ubuntu-26.04
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
name: ubuntu-26.04 (mbedtls 3.x)
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: install Mbed TLS
|
||||
run: sudo apt-get install -y libmbedtls-dev
|
||||
- name: build and run tests (Mbed TLS)
|
||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||
|
||||
# BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee
|
||||
# and is not packaged by distros, so we build it from source. cpp-httplib
|
||||
# treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL
|
||||
# macro (see httplib.h). This job is best-effort: continue-on-error keeps
|
||||
# upstream API drift from blocking PRs while still surfacing breakage.
|
||||
ubuntu-boringssl:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
continue-on-error: true
|
||||
name: ubuntu (boringssl, best-effort)
|
||||
env:
|
||||
# Tracking HEAD keeps us honest about upstream churn. If breakage
|
||||
# becomes routine, replace HEAD with a 40-char commit SHA; the
|
||||
# resolve step uses the SHA directly when it matches that shape.
|
||||
BORINGSSL_REF: HEAD
|
||||
BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: resolve BoringSSL commit
|
||||
id: boringssl-rev
|
||||
# Accept either a ref name (resolved via git ls-remote) or a full
|
||||
# 40-char SHA used directly. ls-remote does not list arbitrary
|
||||
# commit SHAs, so pinning requires the second path.
|
||||
run: |
|
||||
if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
sha="${BORINGSSL_REF}"
|
||||
echo "Using pinned BoringSSL SHA: ${sha}"
|
||||
else
|
||||
sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
|
||||
if [ -z "$sha" ]; then
|
||||
echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Resolved ${BORINGSSL_REF} -> ${sha}"
|
||||
fi
|
||||
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||
- name: cache BoringSSL build
|
||||
id: boringssl-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.BORINGSSL_PREFIX }}
|
||||
key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
|
||||
- name: build BoringSSL
|
||||
if: steps.boringssl-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -e
|
||||
git clone https://boringssl.googlesource.com/boringssl boringssl
|
||||
cd boringssl
|
||||
git checkout "${{ steps.boringssl-rev.outputs.sha }}"
|
||||
cmake -S . -B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
|
||||
cmake --build build -j"$(nproc)" --target install
|
||||
- name: build and run tests (BoringSSL)
|
||||
# Override OPENSSL_SUPPORT to point the existing OpenSSL Makefile path
|
||||
# at BoringSSL's prefix. BoringSSL defines OPENSSL_IS_BORINGSSL in
|
||||
# <openssl/base.h>, which httplib.h and test.cc use to switch on API
|
||||
# differences (e.g. SAN-only hostname verification, no CN fallback).
|
||||
#
|
||||
# BoringSSL's public headers (<openssl/stack.h>) use std::enable_if_t,
|
||||
# so consumers must compile with C++14 or later. cpp-httplib itself
|
||||
# supports C++11, but anyone pairing it with BoringSSL inherits this
|
||||
# constraint. EXTRA_CXXFLAGS appends after the Makefile's -std=c++11
|
||||
# and the later flag wins.
|
||||
run: |
|
||||
cd test
|
||||
BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -lpthread"
|
||||
make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||
make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
|
||||
# macOS counterpart of the BoringSSL job. Same best-effort posture; the
|
||||
# extra framework links cover the macOS Keychain integration that
|
||||
# httplib.h auto-enables for any TLS backend on macOS.
|
||||
macos-boringssl:
|
||||
runs-on: macos-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||
continue-on-error: true
|
||||
name: macos (boringssl, best-effort)
|
||||
env:
|
||||
BORINGSSL_REF: HEAD
|
||||
BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: resolve BoringSSL commit
|
||||
id: boringssl-rev
|
||||
# Accept either a ref name (resolved via git ls-remote) or a full
|
||||
# 40-char SHA used directly. ls-remote does not list arbitrary
|
||||
# commit SHAs, so pinning requires the second path.
|
||||
run: |
|
||||
if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
sha="${BORINGSSL_REF}"
|
||||
echo "Using pinned BoringSSL SHA: ${sha}"
|
||||
else
|
||||
sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
|
||||
if [ -z "$sha" ]; then
|
||||
echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Resolved ${BORINGSSL_REF} -> ${sha}"
|
||||
fi
|
||||
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||
- name: cache BoringSSL build
|
||||
id: boringssl-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.BORINGSSL_PREFIX }}
|
||||
key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
|
||||
- name: build BoringSSL
|
||||
if: steps.boringssl-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -e
|
||||
git clone https://boringssl.googlesource.com/boringssl boringssl
|
||||
cd boringssl
|
||||
git checkout "${{ steps.boringssl-rev.outputs.sha }}"
|
||||
cmake -S . -B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
|
||||
cmake --build build -j"$(sysctl -n hw.ncpu)" --target install
|
||||
- name: build and run tests (BoringSSL)
|
||||
run: |
|
||||
cd test
|
||||
# CoreFoundation/Security frameworks satisfy the Keychain integration
|
||||
# auto-enabled in httplib.h for macOS TLS builds.
|
||||
BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -framework CoreFoundation -framework Security"
|
||||
make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||
make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
|
||||
# Reproducer for https://github.com/yhirose/cpp-httplib/issues/2431.
|
||||
# On Linux/glibc, getaddrinfo_with_timeout() schedules an asynchronous
|
||||
# DNS lookup with getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb.
|
||||
# When gai_suspend() hits the connection timeout, gai_cancel() is called
|
||||
# but does not block; the resolver worker can later write back into the
|
||||
# destroyed stack frame. To make the worker actually reach that write,
|
||||
# the test job runs a loopback UDP responder (test/dns_test_fixture.py)
|
||||
# that delays its reply past the test's 1s timeout, and uses an iptables
|
||||
# NAT rule so glibc's lookups land on that fixture instead of a real
|
||||
# nameserver. With ASAN's detect_stack_use_after_return enabled, the
|
||||
# late write-back is reported as a stack-use-after-return.
|
||||
issue-2431-repro:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
name: issue-2431 repro (Linux + ASAN)
|
||||
# Bound the whole job in case anything in the test harness hangs
|
||||
# unexpectedly. With the fixture in place a normal run is well under
|
||||
# a minute either way (ASAN abort on broken HEAD, clean pass on fix).
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
DNS_FIXTURE_PORT: "15353"
|
||||
DNS_FIXTURE_DELAY: "3"
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev zlib1g-dev libbrotli-dev \
|
||||
libzstd-dev libcurl4-openssl-dev iptables util-linux iproute2
|
||||
- name: start loopback DNS test fixture
|
||||
run: |
|
||||
# Force glibc through its DNS code path: Ubuntu's default
|
||||
# nsswitch short-circuits to NOTFOUND through mdns4_minimal,
|
||||
# which would skip the buggy code entirely.
|
||||
sudo sed -i 's/^hosts:.*/hosts: dns/' /etc/nsswitch.conf
|
||||
# Run the loopback fixture (delayed UDP responder).
|
||||
python3 test/dns_test_fixture.py "$DNS_FIXTURE_PORT" "$DNS_FIXTURE_DELAY" \
|
||||
>/tmp/dns_fixture.log 2>&1 &
|
||||
echo $! | sudo tee /tmp/dns_fixture.pid >/dev/null
|
||||
# Wait for the fixture to start listening.
|
||||
for _ in $(seq 1 50); do
|
||||
if ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT"; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT" \
|
||||
|| { echo "fixture failed to start"; cat /tmp/dns_fixture.log; exit 1; }
|
||||
# Send the test process's DNS lookups to the loopback fixture.
|
||||
# NAT only the local OUTPUT chain; conntrack handles the reply path.
|
||||
sudo iptables -t nat -I OUTPUT -p udp --dport 53 \
|
||||
-j REDIRECT --to-port "$DNS_FIXTURE_PORT"
|
||||
# Sanity check: a query must take at least the fixture delay
|
||||
# and resolve to NXDOMAIN (proving traffic reaches the fixture).
|
||||
start=$(date +%s)
|
||||
getent hosts unresolvable-host.invalid >/dev/null 2>&1 || true
|
||||
elapsed=$(( $(date +%s) - start ))
|
||||
if [ "$elapsed" -lt 2 ]; then
|
||||
echo "ERROR: lookup returned in ${elapsed}s; fixture not in path" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[ok] DNS lookups are routed to the test fixture (took ${elapsed}s)"
|
||||
- name: build test binary
|
||||
run: cd test && make test
|
||||
- name: run GetAddrInfoAsyncCancelTest
|
||||
run: |
|
||||
cd test
|
||||
ARCH=$(uname -m)
|
||||
CPPHTTPLIB_TEST_ISSUE_2431=1 \
|
||||
ASAN_OPTIONS=detect_stack_use_after_return=1 \
|
||||
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
||||
setarch "$ARCH" -R \
|
||||
./test --gtest_filter='GetAddrInfoAsyncCancelTest.*'
|
||||
- name: tear down test fixture
|
||||
if: always()
|
||||
run: |
|
||||
sudo iptables -t nat -F OUTPUT || true
|
||||
if [ -f /tmp/dns_fixture.pid ]; then
|
||||
sudo kill "$(cat /tmp/dns_fixture.pid)" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls, wolfssl]
|
||||
name: macos (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: brew install mbedtls
|
||||
- name: install wolfSSL
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: brew install wolfssl
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||
- name: build and run tests (wolfSSL)
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||
- name: run fuzz test target
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make fuzz_test
|
||||
- name: build and run WebSocket heartbeat test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
||||
- name: build and run WebSocket TLS thread safety test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety
|
||||
- name: build and run ThreadPool test
|
||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||
|
||||
ios-parse-check:
|
||||
runs-on: macos-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||
name: ios header parse check (not officially supported)
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install OpenSSL headers
|
||||
run: brew install openssl@3
|
||||
- name: verify header parses on iOS target
|
||||
run: |
|
||||
IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
|
||||
OPENSSL_INC=$(brew --prefix openssl@3)/include
|
||||
echo "Using iOS SDK: $IOS_SDK"
|
||||
echo '#include "httplib.h"' | clang++ \
|
||||
-isysroot "$IOS_SDK" \
|
||||
-target arm64-apple-ios16.0 \
|
||||
-std=c++11 \
|
||||
-DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-I"$OPENSSL_INC" \
|
||||
-I. -Wall -Wextra \
|
||||
-fsyntax-only -x c++ -
|
||||
- name: verify CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is rejected on iOS
|
||||
run: |
|
||||
IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
|
||||
OPENSSL_INC=$(brew --prefix openssl@3)/include
|
||||
out=$(echo '#include "httplib.h"' | clang++ \
|
||||
-isysroot "$IOS_SDK" \
|
||||
-target arm64-apple-ios16.0 \
|
||||
-std=c++11 \
|
||||
-DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN \
|
||||
-I"$OPENSSL_INC" \
|
||||
-I. \
|
||||
-fsyntax-only -x c++ - 2>&1 || true)
|
||||
if echo "$out" | grep -q "only supported on macOS"; then
|
||||
echo "OK: #error fired as expected"
|
||||
else
|
||||
echo "FAIL: expected #error did not fire"
|
||||
echo "--- compiler output ---"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- with_ssl: false
|
||||
compiled: false
|
||||
run_tests: true
|
||||
name: without SSL
|
||||
- with_ssl: true
|
||||
compiled: false
|
||||
run_tests: true
|
||||
name: with SSL
|
||||
- with_ssl: false
|
||||
compiled: true
|
||||
run_tests: false
|
||||
name: compiled
|
||||
name: windows ${{ matrix.config.name }}
|
||||
steps:
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Setup msbuild on windows
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: Cache vcpkg packages
|
||||
id: vcpkg-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:/vcpkg/installed
|
||||
key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd
|
||||
- name: Install vcpkg dependencies
|
||||
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
||||
run: vcpkg install gtest curl zlib brotli zstd
|
||||
- name: Install OpenSSL
|
||||
if: ${{ matrix.config.with_ssl }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Chocolatey's openssl package hardcodes a versioned slproweb URL, and
|
||||
# slproweb keeps only the newest build of each branch. The package
|
||||
# therefore 404s on every OpenSSL release until someone respins it.
|
||||
# Read slproweb's own manifest instead: it is updated at the same time
|
||||
# as the downloads it points at, so the URL is always live.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest is slow with it
|
||||
|
||||
$manifest = 'https://raw.githubusercontent.com/slproweb/opensslhashes/master/win32_openssl_hashes.json'
|
||||
$entry = (Invoke-RestMethod $manifest).files.PSObject.Properties.Value |
|
||||
Where-Object {
|
||||
$_.bits -eq 64 -and $_.arch -eq 'INTEL' -and
|
||||
-not $_.light -and $_.installer -eq 'exe' -and $_.basever -like '4.*'
|
||||
} |
|
||||
Sort-Object { [version]$_.basever } | Select-Object -Last 1
|
||||
if (-not $entry) { throw 'No 64-bit OpenSSL 4.x installer found in the manifest' }
|
||||
Write-Host "Installing OpenSSL $($entry.basever) from $($entry.url)"
|
||||
|
||||
$installer = Join-Path $env:RUNNER_TEMP 'Win64OpenSSL.exe'
|
||||
Invoke-WebRequest $entry.url -OutFile $installer
|
||||
$actual = (Get-FileHash $installer -Algorithm SHA512).Hash.ToLower()
|
||||
if ($actual -ne $entry.sha512.ToLower()) {
|
||||
throw "SHA512 mismatch: expected $($entry.sha512), got $actual"
|
||||
}
|
||||
|
||||
# Same silent flags the Chocolatey package used. The installer is Inno
|
||||
# Setup, so /DIR pins the location CMake already looks in. The inner
|
||||
# quotes matter: ArgumentList joins on spaces, so an unquoted /DIR
|
||||
# would install to C:\Program and only fail later, at load time.
|
||||
$dir = 'C:\Program Files\OpenSSL'
|
||||
$proc = Start-Process $installer -Wait -PassThru -ArgumentList `
|
||||
'/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/SP-', "/DIR=`"$dir`""
|
||||
if ($proc.ExitCode -ne 0) { throw "Installer exited with $($proc.ExitCode)" }
|
||||
|
||||
# Catch a misplaced install here rather than at link or load time.
|
||||
if (-not (Test-Path "$dir\lib\VC\x64\MD\libcrypto.lib")) {
|
||||
throw "OpenSSL import libraries missing under $dir"
|
||||
}
|
||||
if (-not (Get-ChildItem "$dir\bin\libcrypto-*.dll" -ErrorAction SilentlyContinue)) {
|
||||
throw "OpenSSL runtime DLLs missing under $dir\bin"
|
||||
}
|
||||
|
||||
"$dir\bin" | Out-File $env:GITHUB_PATH -Append -Encoding utf8
|
||||
"OPENSSL_CONF=$dir\bin\openssl.cfg" | Out-File $env:GITHUB_ENV -Append -Encoding utf8
|
||||
- name: Configure CMake ${{ matrix.config.name }}
|
||||
run: >
|
||||
cmake -B build -S .
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
|
||||
-DHTTPLIB_TEST=ON
|
||||
-DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
|
||||
-DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
||||
-DHTTPLIB_REQUIRE_ZLIB=ON
|
||||
-DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
-DHTTPLIB_REQUIRE_ZSTD=ON
|
||||
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
||||
- name: Build ${{ matrix.config.name }}
|
||||
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
|
||||
- name: Run tests ${{ matrix.config.name }}
|
||||
if: ${{ matrix.config.run_tests }}
|
||||
shell: pwsh
|
||||
working-directory: build/test
|
||||
run: |
|
||||
$shards = 4
|
||||
$procs = @()
|
||||
for ($i = 0; $i -lt $shards; $i++) {
|
||||
$log = "shard_${i}.log"
|
||||
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
|
||||
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '-*_Online' }}" `
|
||||
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
|
||||
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
|
||||
}
|
||||
$procs | Wait-Process
|
||||
$failed = $false
|
||||
for ($i = 0; $i -lt $shards; $i++) {
|
||||
$log = "shard_${i}.log"
|
||||
$proc = $procs[$i]
|
||||
$hasPassed = Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet
|
||||
$hasFailed = Select-String -Path $log -Pattern "\[ FAILED \]" -Quiet
|
||||
if ($hasPassed -and -not $hasFailed -and $proc.ExitCode -eq 0) {
|
||||
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
|
||||
Write-Host "Shard ${i}: $passed"
|
||||
} else {
|
||||
Write-Host "=== Shard $i FAILED (exit=$($proc.ExitCode)) ==="
|
||||
Get-Content $log
|
||||
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
if ($failed) { exit 1 }
|
||||
Write-Host "All shards passed."
|
||||
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
79
libs/cpp-httplib-0.57.1/.github/workflows/test_benchmark.yaml
vendored
Normal file
79
libs/cpp-httplib-0.57.1/.github/workflows/test_benchmark.yaml
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
name: benchmark
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: build and run
|
||||
run: cd test && make test_benchmark && ./test_benchmark
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: build and run
|
||||
run: cd test && make test_benchmark && ./test_benchmark
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Cache vcpkg packages
|
||||
id: vcpkg-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:/vcpkg/installed
|
||||
key: vcpkg-installed-windows-gtest
|
||||
- name: Install vcpkg dependencies
|
||||
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
||||
run: vcpkg install gtest
|
||||
- name: Configure and build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$cmake_content = @"
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(httplib-benchmark CXX)
|
||||
find_package(GTest REQUIRED)
|
||||
add_executable(httplib-benchmark test/test_benchmark.cc)
|
||||
target_include_directories(httplib-benchmark PRIVATE .)
|
||||
target_link_libraries(httplib-benchmark PRIVATE GTest::gtest_main)
|
||||
target_compile_options(httplib-benchmark PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
"@
|
||||
New-Item -ItemType Directory -Force -Path build_bench/test | Out-Null
|
||||
Set-Content -Path build_bench/CMakeLists.txt -Value $cmake_content
|
||||
Copy-Item -Path httplib.h -Destination build_bench/
|
||||
Copy-Item -Path test/test_benchmark.cc -Destination build_bench/test/
|
||||
cmake -B build_bench/build -S build_bench `
|
||||
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
cmake --build build_bench/build --config Release
|
||||
- name: Run with retry
|
||||
run: ctest --output-on-failure --test-dir build_bench/build -C Release --repeat until-pass:5
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
20
libs/cpp-httplib-0.57.1/.github/workflows/test_no_exceptions.yaml
vendored
Normal file
20
libs/cpp-httplib-0.57.1/.github/workflows/test_no_exceptions.yaml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: No Exceptions Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-no-exceptions:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libssl-dev zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev
|
||||
|
||||
- name: Run tests with CPPHTTPLIB_NO_EXCEPTIONS
|
||||
run: |
|
||||
cd test && make test_split EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS" && make test_openssl_parallel EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS"
|
||||
62
libs/cpp-httplib-0.57.1/.github/workflows/test_offline.yaml
vendored
Normal file
62
libs/cpp-httplib-0.57.1/.github/workflows/test_offline.yaml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
name: test_offline
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test_linux:
|
||||
description: 'Test on Linux'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GTEST_FILTER: "-*.*_Online"
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, no-tls]
|
||||
name: ubuntu (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: disable network
|
||||
run: |
|
||||
sudo iptables -A OUTPUT -o lo -j ACCEPT
|
||||
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
sudo iptables -A OUTPUT -j REJECT
|
||||
sudo ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
sudo ip6tables -A OUTPUT -j REJECT
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (No TLS)
|
||||
if: matrix.tls_backend == 'no-tls'
|
||||
run: cd test && make test_no_tls_parallel
|
||||
- name: restore network
|
||||
if: always()
|
||||
run: |
|
||||
sudo iptables -F OUTPUT
|
||||
sudo ip6tables -F OUTPUT
|
||||
37
libs/cpp-httplib-0.57.1/.github/workflows/test_proxy.yaml
vendored
Normal file
37
libs/cpp-httplib-0.57.1/.github/workflows/test_proxy.yaml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: Proxy Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-proxy:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls]
|
||||
name: proxy (${{ matrix.tls_backend }})
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev netcat-openbsd
|
||||
- name: Install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: Install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: sudo apt-get install -y libmbedtls-dev
|
||||
|
||||
- name: Run proxy tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make proxy
|
||||
env:
|
||||
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
|
||||
- name: Run proxy tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make proxy_mbedtls
|
||||
env:
|
||||
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
|
||||
470
libs/cpp-httplib-0.57.1/CMakeLists.txt
Normal file
470
libs/cpp-httplib-0.57.1/CMakeLists.txt
Normal file
@ -0,0 +1,470 @@
|
||||
#[[
|
||||
Build options:
|
||||
* Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value.
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_WOLFSSL_IF_AVAILABLE (default off)
|
||||
* HTTPLIB_USE_MBEDTLS_IF_AVAILABLE (default off)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_BUILD_MODULES (default off)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_WOLFSSL (default off)
|
||||
* HTTPLIB_REQUIRE_MBEDTLS (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_REQUIRE_ZSTD (default off)
|
||||
* HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES (default off)
|
||||
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL wolfssl MbedTLS ZLIB Brotli zstd) is available.
|
||||
This creates a httplib::httplib target (if found and if listed components are supported).
|
||||
It can be linked like so:
|
||||
|
||||
target_link_libraries(your_exe httplib::httplib)
|
||||
|
||||
The following will build & install for later use.
|
||||
|
||||
Linux/macOS:
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
sudo cmake --build . --target install
|
||||
|
||||
Windows:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
runas /user:Administrator "cmake --build . --config Release --target install"
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
These variables are available after you run find_package(httplib)
|
||||
* HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h).
|
||||
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
|
||||
* HTTPLIB_IS_USING_WOLFSSL - a bool for if wolfSSL support is enabled.
|
||||
* HTTPLIB_IS_USING_MBEDTLS - a bool for if MbedTLS support is enabled.
|
||||
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
|
||||
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
|
||||
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
|
||||
* HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES - a bool for if support of loading system certs from the Apple Keychain is enabled.
|
||||
* HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled.
|
||||
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
|
||||
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
|
||||
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
|
||||
* httplib_VERSION or HTTPLIB_VERSION - the project's version string.
|
||||
* HTTPLIB_FOUND - a bool for if the target was found.
|
||||
|
||||
Want to use precompiled headers (Cmake feature since v3.16)?
|
||||
It's as simple as doing the following (before linking):
|
||||
|
||||
target_precompile_headers(httplib::httplib INTERFACE "${HTTPLIB_HEADER_PATH}")
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
|
||||
# Get the CPPHTTPLIB_VERSION value and use it as a version
|
||||
# This gets the string with the CPPHTTPLIB_VERSION value from the header.
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Extracts just the version string itself from the whole string contained in _raw_version_string
|
||||
# since _raw_version_string would contain the entire line of code where it found the version string
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib
|
||||
VERSION ${_httplib_version}
|
||||
LANGUAGES CXX
|
||||
DESCRIPTION "A C++ header-only HTTP/HTTPS server and client library."
|
||||
HOMEPAGE_URL "https://github.com/yhirose/cpp-httplib"
|
||||
)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_WOLFSSL "Requires wolfSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_MBEDTLS "Requires MbedTLS to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
|
||||
# Make these options so their automatic use can be specifically disabled (as needed)
|
||||
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
|
||||
option(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE "Uses wolfSSL (if available) to enable HTTPS support." OFF)
|
||||
option(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE "Uses MbedTLS (if available) to enable HTTPS support." OFF)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
option(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES "Disable loading system certs from the Apple Keychain on macOS." OFF)
|
||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||
# C++20 modules support requires CMake 3.28 or later
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||
option(HTTPLIB_BUILD_MODULES "Build httplib modules (requires HTTPLIB_COMPILE to be ON)." OFF)
|
||||
else()
|
||||
set(HTTPLIB_BUILD_MODULES OFF CACHE INTERNAL "Build httplib modules disabled (requires CMake 3.28+)" FORCE)
|
||||
if(DEFINED CACHE{HTTPLIB_BUILD_MODULES} AND HTTPLIB_BUILD_MODULES)
|
||||
message(WARNING "HTTPLIB_BUILD_MODULES requires CMake 3.28 or later. Current version is ${CMAKE_VERSION}. Modules support has been disabled.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Incompatibility between TLS libraries
|
||||
set(TLS_LIBRARIES_USED_TMP 0)
|
||||
|
||||
foreach(tls_library OPENSSL WOLFSSL MBEDTLS)
|
||||
set(TLS_REQUIRED ${HTTPLIB_REQUIRE_${tls_library}})
|
||||
set(TLS_IF_AVAILABLE ${HTTPLIB_USE_${tls_library}_IF_AVAILABLE})
|
||||
|
||||
if(TLS_REQUIRED OR TLS_IF_AVAILABLE)
|
||||
math(EXPR TLS_LIBRARIES_USED_TMP "${TLS_LIBRARIES_USED_TMP} + 1")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(TLS_LIBRARIES_USED_TMP GREATER 1)
|
||||
message(FATAL_ERROR "TLS libraries are mutually exclusive.")
|
||||
endif()
|
||||
|
||||
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
|
||||
include(CMakeDependentOption)
|
||||
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
|
||||
"${BUILD_SHARED_LIBS}" HTTPLIB_COMPILE OFF
|
||||
)
|
||||
if(HTTPLIB_SHARED)
|
||||
set(HTTPLIB_LIB_TYPE SHARED)
|
||||
if(WIN32)
|
||||
# Necessary for Windows if building shared libs
|
||||
# See https://stackoverflow.com/a/40743080
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
else()
|
||||
set(HTTPLIB_LIB_TYPE STATIC)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
if(CMAKE_SYSTEM_VERSION)
|
||||
if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
|
||||
message(WARNING "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_SYSTEM_VERSION "10.0.19041.0")
|
||||
message(WARNING "The target is Windows but CMAKE_SYSTEM_VERSION is not set, the default system version is set to Windows 10.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES TRUE)
|
||||
if(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES)
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES FALSE)
|
||||
endif()
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
# Avoid a rare circumstance of not finding all components but the end-user did their
|
||||
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
|
||||
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
|
||||
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_WOLFSSL)
|
||||
find_package(wolfssl REQUIRED)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL TRUE)
|
||||
elseif(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE)
|
||||
find_package(wolfssl QUIET)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL ${wolfssl_FOUND})
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_MBEDTLS)
|
||||
find_package(MbedTLS REQUIRED)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS TRUE)
|
||||
elseif(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE)
|
||||
find_package(MbedTLS QUIET)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS ${MbedTLS_FOUND})
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
# This is so we can use our custom FindBrotli.cmake
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
# NOTE:
|
||||
# zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`.
|
||||
# Older versions must be consumed via their pkg-config file.
|
||||
if(HTTPLIB_REQUIRE_ZSTD)
|
||||
if (NOT TARGET zstd::libzstd)
|
||||
find_package(zstd 1.5.6 CONFIG)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
endif()
|
||||
set(HTTPLIB_IS_USING_ZSTD TRUE)
|
||||
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
|
||||
if (TARGET zstd::libzstd)
|
||||
set(HTTPLIB_IS_USING_ZSTD TRUE)
|
||||
else()
|
||||
find_package(zstd 1.5.6 CONFIG QUIET)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd)
|
||||
|
||||
if(TARGET PkgConfig::zstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
# Both find_package and PkgConf set a XXX_FOUND var
|
||||
set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(HTTPLIB_COMPILE)
|
||||
# Put the split script into the build dir
|
||||
configure_file(split.py "${CMAKE_CURRENT_BINARY_DIR}/split.py"
|
||||
COPYONLY
|
||||
)
|
||||
# Needs to be in the same dir as the python script
|
||||
configure_file(httplib.h "${CMAKE_CURRENT_BINARY_DIR}/httplib.h"
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
# Used outside of this if-else
|
||||
set(_INTERFACE_OR_PUBLIC PUBLIC)
|
||||
# Brings in the Python3_EXECUTABLE path we can use.
|
||||
find_package(Python3 REQUIRED)
|
||||
# Actually split the file
|
||||
# Keeps the output in the build dir to not pollute the main dir
|
||||
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/split.py"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ERROR_VARIABLE _httplib_split_error
|
||||
)
|
||||
if(_httplib_split_error)
|
||||
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
endif()
|
||||
|
||||
# If building modules, also generate the module file
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
# Put the generate_module script into the build dir
|
||||
configure_file(generate_module.py "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||
COPYONLY
|
||||
)
|
||||
# Generate the module file
|
||||
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ERROR_VARIABLE _httplib_module_error
|
||||
)
|
||||
if(_httplib_module_error)
|
||||
message(FATAL_ERROR "Failed when trying to generate cpp-httplib module with the Python script.\n${_httplib_module_error}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# split.py puts output in "out"
|
||||
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
|
||||
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||
)
|
||||
|
||||
# Add C++20 module support if requested
|
||||
# Include from separate file to prevent parse errors on older CMake versions
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||
include(cmake/modules.cmake)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION ${${PROJECT_NAME}_VERSION}
|
||||
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
|
||||
OUTPUT_NAME cpp-httplib
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
set(_httplib_build_includedir "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endif()
|
||||
# Lets you address the target with httplib::httplib
|
||||
# Only useful if building in-tree, versus using it from an installation.
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Require C++11, or C++20 if modules are enabled
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_20)
|
||||
else()
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
# Always require threads
|
||||
Threads::Threads
|
||||
# Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered.
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
# Needed for API from MacOS Security framework
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:-framework CFNetwork -framework CoreFoundation -framework Security>"
|
||||
# Needed for non-blocking getaddrinfo on MacOS
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>>:-framework CFNetwork -framework CoreFoundation>"
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::decoder>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:ZLIB::ZLIB>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:zstd::libzstd>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::SSL>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:wolfssl::wolfssl>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:MbedTLS::mbedtls>
|
||||
)
|
||||
|
||||
# Set the definitions to enable optional features
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:CPPHTTPLIB_WOLFSSL_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:CPPHTTPLIB_MBEDTLS_SUPPORT>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES>
|
||||
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
|
||||
)
|
||||
|
||||
# CMake configuration files installation directory
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
# Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc.
|
||||
configure_package_config_file("cmake/${PROJECT_NAME}Config.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
|
||||
# Passes the includedir install path
|
||||
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
|
||||
)
|
||||
|
||||
if(HTTPLIB_COMPILE)
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
else()
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
# Tells Cmake that it's a header-only lib
|
||||
# Mildly useful for end-users :)
|
||||
ARCH_INDEPENDENT
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httplib/modules CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/httplib/modules)
|
||||
else()
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||
endif()
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Install it so it can be used later by the httplibConfig.cmake file.
|
||||
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# Install documentation & license
|
||||
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
|
||||
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_BUILD_MODULES AND NOT HTTPLIB_COMPILE)
|
||||
message(FATAL_ERROR "HTTPLIB_BUILD_MODULES requires HTTPLIB_COMPILE to be ON.")
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
13
libs/cpp-httplib-0.57.1/Dockerfile
Normal file
13
libs/cpp-httplib-0.57.1/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM yhirose4dockerhub/ubuntu-builder AS builder
|
||||
WORKDIR /build
|
||||
COPY httplib.h .
|
||||
COPY docker/main.cc .
|
||||
RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /build/server /server
|
||||
COPY docker/html/index.html /html/index.html
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/server"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"]
|
||||
22
libs/cpp-httplib-0.57.1/LICENSE
Normal file
22
libs/cpp-httplib-0.57.1/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 yhirose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
204
libs/cpp-httplib-0.57.1/README-sse.md
Normal file
204
libs/cpp-httplib-0.57.1/README-sse.md
Normal file
@ -0,0 +1,204 @@
|
||||
# SSEClient - Server-Sent Events Client
|
||||
|
||||
A simple, EventSource-like SSE client for C++11.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-reconnect**: Automatically reconnects on connection loss
|
||||
- **Last-Event-ID**: Sends last received ID on reconnect for resumption
|
||||
- **retry field**: Respects server's reconnect interval
|
||||
- **Event types**: Supports custom event types via `on_event()`
|
||||
- **Async support**: Run in background thread with `start_async()`
|
||||
- **C++11 compatible**: No C++14/17/20 features required
|
||||
|
||||
## Quick Start
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Event: " << msg.event << std::endl;
|
||||
std::cout << "Data: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start(); // Blocking, with auto-reconnect
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### SSEMessage
|
||||
|
||||
```cpp
|
||||
struct SSEMessage {
|
||||
std::string event; // Event type (default: "message")
|
||||
std::string data; // Event payload
|
||||
std::string id; // Event ID
|
||||
};
|
||||
```
|
||||
|
||||
### SSEClient
|
||||
|
||||
#### Constructor
|
||||
|
||||
```cpp
|
||||
// Basic
|
||||
SSEClient(Client &client, const std::string &path);
|
||||
|
||||
// With custom headers
|
||||
SSEClient(Client &client, const std::string &path, const Headers &headers);
|
||||
```
|
||||
|
||||
#### Event Handlers
|
||||
|
||||
```cpp
|
||||
// Called for all events (or events without a specific handler)
|
||||
sse.on_message([](const SSEMessage &msg) { });
|
||||
|
||||
// Called for specific event types
|
||||
sse.on_event("update", [](const SSEMessage &msg) { });
|
||||
sse.on_event("delete", [](const SSEMessage &msg) { });
|
||||
|
||||
// Called when connection is established
|
||||
sse.on_open([]() { });
|
||||
|
||||
// Called on connection errors
|
||||
sse.on_error([](httplib::Error err) { });
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
|
||||
```cpp
|
||||
// Set reconnect interval (default: 3000ms)
|
||||
sse.set_reconnect_interval(5000);
|
||||
|
||||
// Set max reconnect attempts (default: 0 = unlimited)
|
||||
sse.set_max_reconnect_attempts(10);
|
||||
|
||||
// Update headers at any time (thread-safe)
|
||||
sse.set_headers({{"Authorization", "Bearer new_token"}});
|
||||
```
|
||||
|
||||
#### Control
|
||||
|
||||
```cpp
|
||||
// Blocking start with auto-reconnect
|
||||
sse.start();
|
||||
|
||||
// Non-blocking start (runs in background thread)
|
||||
sse.start_async();
|
||||
|
||||
// Stop the client (thread-safe)
|
||||
sse.stop();
|
||||
```
|
||||
|
||||
#### State
|
||||
|
||||
```cpp
|
||||
bool connected = sse.is_connected();
|
||||
const std::string &id = sse.last_event_id();
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### With Custom Event Types
|
||||
|
||||
```cpp
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_event("notification", [](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Notification: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Update: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### Async with Stop
|
||||
|
||||
```cpp
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start_async(); // Returns immediately
|
||||
|
||||
// ... do other work ...
|
||||
|
||||
sse.stop(); // Stop when done
|
||||
```
|
||||
|
||||
### With Custom Headers (e.g., Authentication)
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
{"Authorization", "Bearer token123"}
|
||||
};
|
||||
|
||||
httplib::sse::SSEClient sse(cli, "/events", headers);
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### Refreshing Auth Token on Reconnect
|
||||
|
||||
```cpp
|
||||
httplib::sse::SSEClient sse(cli, "/events",
|
||||
{{"Authorization", "Bearer " + get_token()}});
|
||||
|
||||
// Preemptively refresh token on each successful connection
|
||||
sse.on_open([&sse]() {
|
||||
sse.set_headers({{"Authorization", "Bearer " + get_token()}});
|
||||
});
|
||||
|
||||
// Or reactively refresh on auth failure (401 triggers reconnect)
|
||||
sse.on_error([&sse](httplib::Error) {
|
||||
sse.set_headers({{"Authorization", "Bearer " + refresh_token()}});
|
||||
});
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```cpp
|
||||
sse.on_error([](httplib::Error err) {
|
||||
std::cerr << "Error: " << httplib::to_string(err) << std::endl;
|
||||
});
|
||||
|
||||
sse.set_reconnect_interval(1000);
|
||||
sse.set_max_reconnect_attempts(5);
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
## SSE Protocol
|
||||
|
||||
The client parses SSE format according to the [W3C specification](https://html.spec.whatwg.org/multipage/server-sent-events.html):
|
||||
|
||||
```
|
||||
event: custom-type
|
||||
id: 123
|
||||
data: {"message": "hello"}
|
||||
|
||||
data: simple message
|
||||
|
||||
: this is a comment (ignored)
|
||||
```
|
||||
317
libs/cpp-httplib-0.57.1/README-stream.md
Normal file
317
libs/cpp-httplib-0.57.1/README-stream.md
Normal file
@ -0,0 +1,317 @@
|
||||
# cpp-httplib Streaming API
|
||||
|
||||
This document describes the streaming extensions for cpp-httplib, providing an iterator-style API for handling HTTP responses incrementally with **true socket-level streaming**.
|
||||
|
||||
> **Important Notes**:
|
||||
>
|
||||
> - **No Keep-Alive**: Each `stream::Get()` call uses a dedicated connection that is closed after the response is fully read. For connection reuse, use `Client::Get()`.
|
||||
> - **Single iteration only**: The `next()` method can only iterate through the body once.
|
||||
> - **Result is not thread-safe**: While `stream::Get()` can be called from multiple threads simultaneously, the returned `stream::Result` must be used from a single thread only.
|
||||
|
||||
## Overview
|
||||
|
||||
The streaming API allows you to process HTTP response bodies chunk by chunk using an iterator-style pattern. Data is read directly from the network socket, enabling low-memory processing of large responses. This is particularly useful for:
|
||||
|
||||
- **LLM/AI streaming responses** (e.g., ChatGPT, Claude, Ollama)
|
||||
- **Server-Sent Events (SSE)**
|
||||
- **Large file downloads** with progress tracking
|
||||
- **Reverse proxy implementations**
|
||||
|
||||
## Quick Start
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
// Get streaming response
|
||||
auto result = httplib::stream::Get(cli, "/stream");
|
||||
|
||||
if (result) {
|
||||
// Process response body in chunks
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## API Layers
|
||||
|
||||
cpp-httplib provides multiple API layers for different use cases:
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ SSEClient │ ← SSE-specific, parsed events
|
||||
│ - on_message(), on_event() │
|
||||
│ - Auto-reconnect, Last-Event-ID │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ stream::Get() / stream::Result │ ← Iterator-based streaming
|
||||
│ - while (result.next()) { ... } │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ open_stream() / StreamHandle │ ← General-purpose streaming
|
||||
│ - handle.read(buf, len) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Client::Get() │ ← Traditional, full buffering
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Use Case | Recommended API |
|
||||
|----------|----------------|
|
||||
| SSE with auto-reconnect | `SSEClient` (see [README-sse.md](README-sse.md)) |
|
||||
| LLM streaming (JSON Lines) | `stream::Get()` |
|
||||
| Large file download | `stream::Get()` or `open_stream()` |
|
||||
| Reverse proxy | `open_stream()` |
|
||||
| Small responses with Keep-Alive | `Client::Get()` |
|
||||
|
||||
## API Reference
|
||||
|
||||
### Low-Level API: `StreamHandle`
|
||||
|
||||
The `StreamHandle` struct provides direct control over streaming responses. It takes ownership of the socket connection and reads data directly from the network.
|
||||
|
||||
> **Note:** When using `open_stream()`, the connection is dedicated to streaming and **Keep-Alive is not supported**. For Keep-Alive connections, use `client.Get()` instead.
|
||||
|
||||
```cpp
|
||||
// Open a stream (takes ownership of socket)
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto handle = cli.open_stream("GET", "/path");
|
||||
|
||||
// Check validity
|
||||
if (handle.is_valid()) {
|
||||
// Access response headers immediately
|
||||
int status = handle.response->status;
|
||||
auto content_type = handle.response->get_header_value("Content-Type");
|
||||
|
||||
// Read body incrementally
|
||||
char buf[4096];
|
||||
ssize_t n;
|
||||
while ((n = handle.read(buf, sizeof(buf))) > 0) {
|
||||
process(buf, n);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### StreamHandle Members
|
||||
|
||||
| Member | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `response` | `std::unique_ptr<Response>` | HTTP response with headers |
|
||||
| `error` | `Error` | Error code if request failed |
|
||||
| `is_valid()` | `bool` | Returns true if response is valid |
|
||||
| `read(buf, len)` | `ssize_t` | Read up to `len` bytes directly from socket |
|
||||
| `get_read_error()` | `Error` | Get the last read error |
|
||||
| `has_read_error()` | `bool` | Check if a read error occurred |
|
||||
|
||||
### High-Level API: `stream::Get()` and `stream::Result`
|
||||
|
||||
The `httplib.h` header provides a more ergonomic iterator-style API.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
cli.set_follow_location(true);
|
||||
...
|
||||
|
||||
// Simple GET
|
||||
auto result = httplib::stream::Get(cli, "/path");
|
||||
|
||||
// GET with custom headers
|
||||
httplib::Headers headers = {{"Authorization", "Bearer token"}};
|
||||
auto result = httplib::stream::Get(cli, "/path", headers);
|
||||
|
||||
// Process the response
|
||||
if (result) {
|
||||
while (result.next()) {
|
||||
process(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Or read entire body at once
|
||||
auto result2 = httplib::stream::Get(cli, "/path");
|
||||
if (result2) {
|
||||
std::string body = result2.read_all();
|
||||
}
|
||||
```
|
||||
|
||||
#### stream::Result Members
|
||||
|
||||
| Member | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `operator bool()` | `bool` | Returns true if response is valid |
|
||||
| `is_valid()` | `bool` | Same as `operator bool()` |
|
||||
| `status()` | `int` | HTTP status code |
|
||||
| `headers()` | `const Headers&` | Response headers |
|
||||
| `get_header_value(key, def)` | `std::string` | Get header value (with optional default) |
|
||||
| `has_header(key)` | `bool` | Check if header exists |
|
||||
| `next()` | `bool` | Read next chunk, returns false when done |
|
||||
| `data()` | `const char*` | Pointer to current chunk data |
|
||||
| `size()` | `size_t` | Size of current chunk |
|
||||
| `read_all()` | `std::string` | Read entire remaining body into string |
|
||||
| `error()` | `Error` | Get the connection/request error |
|
||||
| `read_error()` | `Error` | Get the last read error |
|
||||
| `has_read_error()` | `bool` | Check if a read error occurred |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: SSE (Server-Sent Events) Client
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:1234");
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/events");
|
||||
if (!result) { return 1; }
|
||||
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
For a complete SSE client with auto-reconnection and event parsing, see `example/ssecli-stream.cc`.
|
||||
|
||||
### Example 2: LLM Streaming Response
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:11434"); // Ollama
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/api/generate");
|
||||
|
||||
if (result && result.status() == 200) {
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
std::cout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for connection errors
|
||||
if (result.read_error() != httplib::Error::Success) {
|
||||
std::cerr << "Connection lost\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Large File Download with Progress
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://example.com");
|
||||
auto result = httplib::stream::Get(cli, "/large-file.zip");
|
||||
|
||||
if (!result || result.status() != 200) {
|
||||
std::cerr << "Download failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream file("download.zip", std::ios::binary);
|
||||
size_t total = 0;
|
||||
|
||||
while (result.next()) {
|
||||
file.write(result.data(), result.size());
|
||||
total += result.size();
|
||||
std::cout << "\rDownloaded: " << (total / 1024) << " KB" << std::flush;
|
||||
}
|
||||
|
||||
std::cout << "\nComplete!\n";
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Reverse Proxy Streaming
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/proxy/(.*)", [](const httplib::Request& req, httplib::Response& res) {
|
||||
httplib::Client upstream("http://backend:8080");
|
||||
auto handle = upstream.open_stream("/" + req.matches[1].str());
|
||||
|
||||
if (!handle.is_valid()) {
|
||||
res.status = 502;
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = handle.response->status;
|
||||
res.set_chunked_content_provider(
|
||||
handle.response->get_header_value("Content-Type"),
|
||||
[handle = std::move(handle)](size_t, httplib::DataSink& sink) mutable {
|
||||
char buf[8192];
|
||||
auto n = handle.read(buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
sink.write(buf, static_cast<size_t>(n));
|
||||
return true;
|
||||
}
|
||||
sink.done();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 3000);
|
||||
```
|
||||
|
||||
## Comparison with Existing APIs
|
||||
|
||||
| Feature | `Client::Get()` | `open_stream()` | `stream::Get()` |
|
||||
|---------|----------------|-----------------|----------------|
|
||||
| Headers available | After complete | Immediately | Immediately |
|
||||
| Body reading | All at once | Direct from socket | Iterator-based |
|
||||
| Memory usage | Full body in RAM | Minimal (controlled) | Minimal (controlled) |
|
||||
| Keep-Alive support | ✅ Yes | ❌ No | ❌ No |
|
||||
| Compression | Auto-handled | Auto-handled | Auto-handled |
|
||||
| Best for | Small responses, Keep-Alive | Low-level streaming | Easy streaming |
|
||||
|
||||
## Features
|
||||
|
||||
- **True socket-level streaming**: Data is read directly from the network socket
|
||||
- **Low memory footprint**: Only the current chunk is held in memory
|
||||
- **Compression support**: Automatic decompression for gzip, brotli, and zstd
|
||||
- **Chunked transfer**: Full support for chunked transfer encoding
|
||||
- **SSL/TLS support**: Works with HTTPS connections
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Keep-Alive Behavior
|
||||
|
||||
The streaming API (`stream::Get()` / `open_stream()`) takes ownership of the socket connection for the duration of the stream. This means:
|
||||
|
||||
- **Keep-Alive is not supported** for streaming connections
|
||||
- The socket is closed when `StreamHandle` is destroyed
|
||||
- For Keep-Alive scenarios, use the standard `client.Get()` API instead
|
||||
|
||||
```cpp
|
||||
// Use for streaming (no Keep-Alive)
|
||||
auto result = httplib::stream::Get(cli, "/large-stream");
|
||||
while (result.next()) { /* ... */ }
|
||||
|
||||
// Use for Keep-Alive connections
|
||||
auto res = cli.Get("/api/data"); // Connection can be reused
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Issue #2269](https://github.com/yhirose/cpp-httplib/issues/2269) - Original feature request
|
||||
- [example/ssecli-stream.cc](./example/ssecli-stream.cc) - SSE client with auto-reconnection
|
||||
523
libs/cpp-httplib-0.57.1/README-websocket.md
Normal file
523
libs/cpp-httplib-0.57.1/README-websocket.md
Normal file
@ -0,0 +1,523 @@
|
||||
# WebSocket - RFC 6455 WebSocket Support
|
||||
|
||||
A simple, blocking WebSocket implementation for C++11.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is a blocking I/O WebSocket implementation using a thread-per-connection model (plus one heartbeat thread per connection). It is intended for small- to mid-scale workloads; handling large numbers of simultaneous WebSocket connections is outside the design target of this library. If you need high-concurrency WebSocket support with non-blocking/async I/O (e.g., thousands of simultaneous connections), this is not the one that you want.
|
||||
|
||||
> [!NOTE]
|
||||
> WebSocket extensions (`permessage-deflate` and others defined by RFC 6455) are **not supported**. If a client proposes an extension via `Sec-WebSocket-Extensions`, the server silently declines it — the negotiated connection always runs without extensions.
|
||||
|
||||
## Features
|
||||
|
||||
- **RFC 6455 compliant**: Full WebSocket protocol support (extensions are not implemented)
|
||||
- **Server and Client**: Both sides included
|
||||
- **SSL/TLS support**: `wss://` scheme for secure connections
|
||||
- **Text and Binary**: Both message types supported
|
||||
- **Automatic heartbeat**: Periodic Ping/Pong keeps connections alive
|
||||
- **Unresponsive-peer detection**: Opt-in liveness check via `set_websocket_max_missed_pongs()`
|
||||
- **Subprotocol negotiation**: `Sec-WebSocket-Protocol` support for GraphQL, MQTT, etc.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Server
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("localhost", 8080);
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello");
|
||||
|
||||
std::string msg;
|
||||
if (ws.read(msg)) {
|
||||
std::cout << msg << std::endl; // "echo: hello"
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### ReadResult
|
||||
|
||||
```cpp
|
||||
enum ReadResult : int {
|
||||
Fail = 0, // Connection closed or error
|
||||
Text = 1, // UTF-8 text message
|
||||
Binary = 2, // Binary message
|
||||
Timeout = 3, // Read timeout elapsed; connection still open
|
||||
};
|
||||
```
|
||||
|
||||
Returned by `read()`. Since `Fail` is `0`, the result works naturally in boolean contexts — `while (ws.read(msg))` continues until the connection closes. When you need to distinguish text from binary, check the return value directly.
|
||||
|
||||
`Timeout` is only returned for a read timeout you set yourself with `set_read_timeout()`. It means the timeout elapsed on a message boundary: nothing was consumed and the connection is still open, so you can send on it and read again. The compile-time defaults (`CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND`, 300 seconds on the server; a client waits forever) are a backstop against a peer that has gone quiet, not a request for control: when one of them elapses, `read()` returns `Fail` and closes the connection, so code that never calls `set_read_timeout()` can keep using `while (ws.read(msg))`.
|
||||
|
||||
**`msg` is left untouched on `Timeout`.** Because `Timeout` is non-zero, `while (ws.read(msg))` keeps looping — with the *previous* message still in `msg`. Once you set a read timeout, test the result instead:
|
||||
|
||||
```cpp
|
||||
ws.set_read_timeout(std::chrono::milliseconds(100));
|
||||
std::string msg;
|
||||
while (ws.is_open()) {
|
||||
auto r = ws.read(msg);
|
||||
if (r == httplib::ws::Timeout) { continue; } // nothing yet; send if you like
|
||||
if (r == httplib::ws::Fail) { break; }
|
||||
handle(msg);
|
||||
}
|
||||
```
|
||||
|
||||
### CloseStatus
|
||||
|
||||
```cpp
|
||||
enum class CloseStatus : uint16_t {
|
||||
Normal = 1000,
|
||||
GoingAway = 1001,
|
||||
ProtocolError = 1002,
|
||||
UnsupportedData = 1003,
|
||||
NoStatus = 1005,
|
||||
Abnormal = 1006,
|
||||
InvalidPayload = 1007,
|
||||
PolicyViolation = 1008,
|
||||
MessageTooBig = 1009,
|
||||
MandatoryExtension = 1010,
|
||||
InternalError = 1011,
|
||||
};
|
||||
```
|
||||
|
||||
### Server Registration
|
||||
|
||||
```cpp
|
||||
// Basic handler
|
||||
Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
|
||||
|
||||
// With subprotocol negotiation
|
||||
Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
|
||||
SubProtocolSelector sub_protocol_selector);
|
||||
```
|
||||
|
||||
**Type aliases:**
|
||||
|
||||
```cpp
|
||||
using WebSocketHandler =
|
||||
std::function<void(const Request &, ws::WebSocket &)>;
|
||||
using SubProtocolSelector =
|
||||
std::function<std::string(const std::vector<std::string> &protocols)>;
|
||||
```
|
||||
|
||||
The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols.
|
||||
|
||||
### WebSocket (Server-side)
|
||||
|
||||
Passed to the handler registered with `Server::WebSocket()`. The handler runs in a dedicated thread per connection.
|
||||
|
||||
```cpp
|
||||
// Read next message (blocks until received, returns Fail/Text/Binary)
|
||||
ReadResult read(std::string &msg);
|
||||
|
||||
// Send messages
|
||||
bool send(const std::string &data); // Text
|
||||
bool send(const char *data, size_t len); // Binary
|
||||
|
||||
// Close the connection
|
||||
void close(CloseStatus status = CloseStatus::Normal,
|
||||
const std::string &reason = "");
|
||||
|
||||
// Access the original HTTP upgrade request
|
||||
const Request &request() const;
|
||||
|
||||
// Check if the connection is still open
|
||||
bool is_open() const;
|
||||
```
|
||||
|
||||
### WebSocketClient
|
||||
|
||||
```cpp
|
||||
// Constructor - accepts ws:// or wss:// URL
|
||||
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
||||
const Headers &headers = {});
|
||||
|
||||
// Constructor with a client certificate for mutual TLS (wss:// only,
|
||||
// requires CPPHTTPLIB_OPENSSL_SUPPORT). The certificate is ignored for
|
||||
// ws:// URLs.
|
||||
struct PemMemory {
|
||||
const char *cert_pem;
|
||||
size_t cert_pem_len;
|
||||
const char *key_pem;
|
||||
size_t key_pem_len;
|
||||
const char *private_key_password;
|
||||
};
|
||||
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
||||
const PemMemory &pem, const Headers &headers = {});
|
||||
|
||||
// Check if the URL was parsed successfully
|
||||
bool is_valid() const;
|
||||
|
||||
// Connect (performs HTTP upgrade handshake). The returned Result is truthy
|
||||
// only when the handshake fully succeeded; on failure it describes what went
|
||||
// wrong:
|
||||
// res.error() httplib::Error identifying the failing layer
|
||||
// res.status() HTTP status of the upgrade response (-1 if none)
|
||||
// res.headers() headers of the upgrade response
|
||||
// res.ssl_error() TLS error detail (wss://, SSL builds only)
|
||||
// res.ssl_backend_error() backend-specific TLS error code (SSL builds only)
|
||||
Result connect();
|
||||
|
||||
// Get the subprotocol selected by the server (empty if none)
|
||||
const std::string &subprotocol() const;
|
||||
|
||||
// Read/Send/Close (same as server-side WebSocket)
|
||||
ReadResult read(std::string &msg);
|
||||
bool send(const std::string &data);
|
||||
bool send(const char *data, size_t len);
|
||||
void close(CloseStatus status = CloseStatus::Normal,
|
||||
const std::string &reason = "");
|
||||
bool is_open() const;
|
||||
|
||||
// Timeouts
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
template <class Rep, class Period>
|
||||
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
template <class Rep, class Period>
|
||||
void set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
// SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
void set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||
const std::string &ca_cert_dir_path = std::string());
|
||||
void set_ca_cert_store(tls::ca_store_t store);
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
void enable_server_hostname_verification(bool enabled);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Echo Server with Connection Logging
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::cout << "Connected from " << req.remote_addr << std::endl;
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
|
||||
std::cout << "Disconnected" << std::endl;
|
||||
});
|
||||
|
||||
svr.listen("localhost", 8080);
|
||||
```
|
||||
|
||||
### Client: Continuous Read Loop
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello");
|
||||
ws.send("world");
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) { // blocks until a message arrives
|
||||
std::cout << msg << std::endl; // "echo: hello", "echo: world"
|
||||
}
|
||||
// read() returns false when the server closes the connection
|
||||
}
|
||||
```
|
||||
|
||||
### Inspecting Connection Failures
|
||||
|
||||
`connect()` returns a `Result` that tells you why a connection attempt failed.
|
||||
`error()` distinguishes network problems (`Connection`, `ConnectionTimeout`),
|
||||
TLS problems (`SSLConnection`, `SSLServerVerification`,
|
||||
`SSLServerHostnameVerification`), and upgrade rejections
|
||||
(`WebSocketHandshake`). When the server answered with something other than
|
||||
`101 Switching Protocols`, `status()` and `headers()` carry that response:
|
||||
|
||||
```cpp
|
||||
auto res = ws.connect();
|
||||
if (!res) {
|
||||
std::cerr << "connect failed: " << httplib::to_string(res.error()) << std::endl;
|
||||
if (res.status() != -1) {
|
||||
// The server responded but refused the upgrade (e.g. 401, 404)
|
||||
std::cerr << "HTTP status: " << res.status() << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Text and Binary Messages
|
||||
|
||||
Check the `ReadResult` return value to distinguish between text and binary:
|
||||
|
||||
```cpp
|
||||
// Server
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
httplib::ws::ReadResult ret;
|
||||
while ((ret = ws.read(msg))) {
|
||||
if (ret == httplib::ws::Text) {
|
||||
ws.send("echo: " + msg);
|
||||
} else {
|
||||
ws.send(msg.data(), msg.size()); // Binary echo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Client
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
if (ws.connect()) {
|
||||
// Send binary data
|
||||
const char binary[] = {0x00, 0x01, 0x02, 0x03};
|
||||
ws.send(binary, sizeof(binary));
|
||||
|
||||
// Receive and check the type
|
||||
std::string msg;
|
||||
if (ws.read(msg) == httplib::ws::Binary) {
|
||||
// Process binary data in msg
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Client
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("wss://echo.example.com/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello over TLS");
|
||||
|
||||
std::string msg;
|
||||
if (ws.read(msg)) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Close with Status
|
||||
|
||||
```cpp
|
||||
// Client-side: close with a specific status code and reason
|
||||
ws.close(httplib::ws::CloseStatus::GoingAway, "shutting down");
|
||||
|
||||
// Server-side: close with a policy violation status
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "forbidden");
|
||||
```
|
||||
|
||||
### Accessing the Upgrade Request
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
// Access headers from the original HTTP upgrade request
|
||||
auto auth = req.get_header_value("Authorization");
|
||||
if (auth.empty()) {
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The check above runs after the handshake, so the client sees a successful upgrade followed by a close frame. To refuse the upgrade itself with an HTTP status, use a pre-routing or pre-request handler. Both run before the `101 Switching Protocols` response, and `req.matched_route` is available in the pre-request handler:
|
||||
|
||||
```cpp
|
||||
svr.set_pre_request_handler([](const httplib::Request &req, httplib::Response &res) {
|
||||
if (req.matched_route == "/ws" && req.get_header_value("Authorization").empty()) {
|
||||
res.status = httplib::StatusCode::Unauthorized_401;
|
||||
return httplib::Server::HandlerResponse::Handled; // not upgraded
|
||||
}
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Headers and Timeouts
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
{"Authorization", "Bearer token123"}
|
||||
};
|
||||
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
||||
ws.set_connection_timeout(5, 0); // 5 seconds
|
||||
ws.set_read_timeout(30, 0); // 30 seconds
|
||||
ws.set_write_timeout(10, 0); // 10 seconds
|
||||
|
||||
// std::chrono is also supported
|
||||
ws.set_connection_timeout(std::chrono::seconds(5));
|
||||
ws.set_read_timeout(std::chrono::seconds(30));
|
||||
ws.set_write_timeout(std::chrono::seconds(10));
|
||||
|
||||
if (ws.connect()) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subprotocol Negotiation
|
||||
|
||||
The server can negotiate a subprotocol with the client using `Sec-WebSocket-Protocol`. This is required for protocols like GraphQL over WebSocket (`graphql-ws`) and MQTT.
|
||||
|
||||
```cpp
|
||||
// Server: register a handler with a subprotocol selector
|
||||
svr.WebSocket(
|
||||
"/ws",
|
||||
[](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
},
|
||||
[](const std::vector<std::string> &protocols) -> std::string {
|
||||
// The client proposed a list of subprotocols; pick one
|
||||
for (const auto &p : protocols) {
|
||||
if (p == "graphql-ws" || p == "graphql-transport-ws") {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return ""; // Decline all
|
||||
});
|
||||
|
||||
// Client: propose subprotocols via Sec-WebSocket-Protocol header
|
||||
httplib::Headers headers = {
|
||||
{"Sec-WebSocket-Protocol", "graphql-ws, graphql-transport-ws"}
|
||||
};
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
||||
|
||||
if (ws.connect()) {
|
||||
// Check which subprotocol the server selected
|
||||
std::cout << "Subprotocol: " << ws.subprotocol() << std::endl;
|
||||
// => "graphql-ws"
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Client with Certificate Configuration
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("wss://example.com/ws");
|
||||
ws.set_ca_cert_path("/path/to/ca-bundle.crt");
|
||||
ws.enable_server_certificate_verification(true);
|
||||
ws.enable_server_hostname_verification(true); // default; false skips the identity check
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("secure message");
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Macro | Default | Description |
|
||||
|---------------------------------------------|-------------------|----------------------------------------------------------|
|
||||
| `CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH` | `16777216` (16MB) | Maximum payload size per message |
|
||||
| `CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND` | `0` | Client read timeout (seconds); `0` waits forever |
|
||||
| `CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND` | `300` | Server read timeout (seconds) |
|
||||
| `CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND` | `5` | Timeout for waiting peer's Close response (seconds) |
|
||||
| `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND` | `30` | Automatic Ping interval for heartbeat (seconds) |
|
||||
| `CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS` | `0` (disabled) | Close the connection after N consecutive unacked pings |
|
||||
|
||||
### Runtime Ping Interval
|
||||
|
||||
You can override the ping interval at runtime instead of changing the compile-time macro. Set it to `0` to disable automatic pings entirely.
|
||||
|
||||
```cpp
|
||||
// Server side
|
||||
httplib::Server svr;
|
||||
svr.set_websocket_ping_interval(10); // 10 seconds
|
||||
|
||||
// Or using std::chrono
|
||||
svr.set_websocket_ping_interval(std::chrono::seconds(10));
|
||||
|
||||
// Client side
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
ws.set_websocket_ping_interval(10); // 10 seconds
|
||||
|
||||
// Disable automatic pings
|
||||
ws.set_websocket_ping_interval(0);
|
||||
```
|
||||
|
||||
### Unresponsive-Peer Detection (Pong Timeout)
|
||||
|
||||
By default the heartbeat only sends pings — it does not enforce that pongs come back. To detect a silently dropped connection faster, enable the max-missed-pongs check. Once `max_missed_pongs` consecutive pings go unanswered, the heartbeat thread closes the connection with `CloseStatus::GoingAway` and the reason `"pong timeout"`.
|
||||
|
||||
```cpp
|
||||
ws.set_websocket_max_missed_pongs(2); // close after 2 consecutive unacked pings
|
||||
```
|
||||
|
||||
The server side has the same `set_websocket_max_missed_pongs()`.
|
||||
|
||||
With the default ping interval of 30 seconds, `max_missed_pongs = 2` detects a dead peer within ~60 seconds. The counter is reset every time a Pong frame is received, so the mechanism only works when your code is actively calling `read()` — exactly the pattern a normal WebSocket client already uses.
|
||||
|
||||
**The default is `0`**, which means "never close the connection because of missing pongs." Pings are still sent on the heartbeat interval, but their responses are not checked. On the server side a dead connection still does not linger: while a handler is inside `read()`, `CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND` (default **300 seconds = 5 minutes**) acts as a backstop. A client has no such backstop — it waits forever unless you set a read timeout — so there `max_missed_pongs` is what notices an unresponsive peer at all. On either side it is also the knob for noticing one *faster* than the 5-minute fallback.
|
||||
|
||||
## Threading Model
|
||||
|
||||
WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime.
|
||||
|
||||
The default thread pool uses dynamic scaling: it maintains a base thread count of `CPPHTTPLIB_THREAD_POOL_COUNT` (8 or `std::thread::hardware_concurrency() - 1`, whichever is greater) and can scale up to 4x that count under load (`CPPHTTPLIB_THREAD_POOL_MAX_COUNT`). When all base threads are busy, temporary threads are spawned automatically up to the maximum. These dynamic threads exit after an idle timeout (`CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`, default 3 seconds).
|
||||
|
||||
This dynamic scaling helps accommodate WebSocket connections alongside HTTP requests. However, if you expect many simultaneous WebSocket connections, you should configure the thread pool accordingly:
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/128);
|
||||
};
|
||||
```
|
||||
|
||||
Choose sizes that account for both your expected HTTP load and the maximum number of simultaneous WebSocket connections.
|
||||
|
||||
### Calling from Multiple Threads
|
||||
|
||||
A single `WebSocket` (server-side) or `WebSocketClient` handle is shared by three potential callers: the thread running your handler (or holding the client), the heartbeat thread, and, if your code does its own thing, a separate thread calling `send()`/`close()` while another thread is blocked in `read()`.
|
||||
|
||||
**Supported**: calling `read()` from one thread while calling `send()`/`close()` from another. This is the common pattern for a client that reads incoming messages in a loop on one thread and sends from elsewhere (e.g. a UI thread). A message that is in flight when `close()` is called still arrives intact; `close()` sends the Close frame and returns, leaving the connection's read side to the thread that owns it, so it does not block waiting for the peer's Close reply in that case. The heartbeat thread's automatic pings use the same `send()` path internally, so they are safe to run concurrently with your `read()` loop too — for `wss://` this requires every TLS call on a connection to be serialized internally, which cpp-httplib does for you.
|
||||
|
||||
**Not supported**: calling `read()` from two threads at the same time on the same handle. The calls are serialized rather than left to corrupt each other, but which thread receives which message is unspecified, so there is nothing useful to build on it.
|
||||
|
||||
## Protocol
|
||||
|
||||
The implementation follows [RFC 6455](https://tools.ietf.org/html/rfc6455):
|
||||
|
||||
- Handshake via HTTP Upgrade with `Sec-WebSocket-Key` / `Sec-WebSocket-Accept`
|
||||
- Subprotocol negotiation via `Sec-WebSocket-Protocol`
|
||||
- Frame masking (client-to-server)
|
||||
- Control frames: Close, Ping, Pong
|
||||
- Message fragmentation and reassembly
|
||||
- Close handshake with status codes
|
||||
|
||||
## Browser Test
|
||||
|
||||
Run the echo server example and open `http://localhost:8080` in a browser:
|
||||
|
||||
```bash
|
||||
cd example && make wsecho && ./wsecho
|
||||
```
|
||||
1829
libs/cpp-httplib-0.57.1/README.md
Normal file
1829
libs/cpp-httplib-0.57.1/README.md
Normal file
File diff suppressed because it is too large
Load Diff
51
libs/cpp-httplib-0.57.1/benchmark/Makefile
Normal file
51
libs/cpp-httplib-0.57.1/benchmark/Makefile
Normal file
@ -0,0 +1,51 @@
|
||||
CXXFLAGS = -O2 -I..
|
||||
|
||||
CPPHTTPLIB_CXXFLAGS = -std=c++11
|
||||
CROW_CXXFLAGS = -std=c++17
|
||||
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||
MONITOR = ali http://localhost:8080
|
||||
WAIT = while ! nc -z localhost 8080 >/dev/null 2>&1; do sleep 0.05; done
|
||||
|
||||
# cpp-httplib
|
||||
bench: server
|
||||
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||
@./server & export PID=$$!; $(WAIT); $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor: server
|
||||
@./server & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run : server
|
||||
@./server
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
||||
@./server-crow & export PID=$$!; $(WAIT); $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-crow: server-crow
|
||||
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
|
||||
server-crow : crow/main.cpp crow/crow_all.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-crow
|
||||
|
||||
bench-all: bench-crow bench
|
||||
|
||||
issue:
|
||||
bombardier -c 10 -d 30s localhost:8080
|
||||
|
||||
clean:
|
||||
rm -rf server*
|
||||
286
libs/cpp-httplib-0.57.1/benchmark/ab.sh
Executable file
286
libs/cpp-httplib-0.57.1/benchmark/ab.sh
Executable file
@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# A/B throughput comparison between two git refs.
|
||||
#
|
||||
# Usage: ./ab.sh [--base REF] [--head REF] [--rounds N] [--duration S]
|
||||
# [--connections N] [--threads N] [--path PATH] [--tls]
|
||||
# [--large-mib N] [--timeout S]
|
||||
#
|
||||
# --path selects the workload. The harness serves:
|
||||
# / small body via set_content(); the response line, the
|
||||
# headers and the body already share a single write(), so
|
||||
# this is the least sensitive case
|
||||
# /large large body via set_content()
|
||||
# /static/small.js 1 KiB file from a mount point, where the headers and the
|
||||
# body are two separate writes
|
||||
# /static/large.bin same, with the body large enough to dominate
|
||||
#
|
||||
# --large-mib sizes the two large workloads (default 1).
|
||||
#
|
||||
# --tls runs the same workload over HTTPS, which writes through
|
||||
# SSLSocketStream instead of SocketStream.
|
||||
#
|
||||
# --timeout is bombardier's per-request timeout. Its 2s default aborts large
|
||||
# TLS responses, and the run then fails on the non-2xx check.
|
||||
#
|
||||
# Absolute numbers from a single run are meaningless: on a quiet 8-core laptop
|
||||
# the same binary varies by +/-20% run to run, and shared CI runners are worse.
|
||||
# So both refs are built and then measured alternately in the same session, and
|
||||
# only the ratio of the medians is reported.
|
||||
#
|
||||
# Requires: bombardier, python3, g++ (or $CXX), git.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_REF="master"
|
||||
HEAD_REF="HEAD"
|
||||
ROUNDS=5
|
||||
DURATION="5s"
|
||||
CONNECTIONS=10
|
||||
THREADS=""
|
||||
PORT=8080
|
||||
REQ_PATH="/"
|
||||
TLS=0
|
||||
LARGE_MIB=1
|
||||
TIMEOUT="30s"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--base) BASE_REF="$2"; shift 2 ;;
|
||||
--head) HEAD_REF="$2"; shift 2 ;;
|
||||
--rounds) ROUNDS="$2"; shift 2 ;;
|
||||
--duration) DURATION="$2"; shift 2 ;;
|
||||
--connections) CONNECTIONS="$2"; shift 2 ;;
|
||||
--threads) THREADS="$2"; shift 2 ;;
|
||||
--path) REQ_PATH="$2"; shift 2 ;;
|
||||
--large-mib) LARGE_MIB="$2"; shift 2 ;;
|
||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
--tls) TLS=1; shift ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v bombardier >/dev/null || { echo "Error: bombardier not found" >&2; exit 1; }
|
||||
command -v python3 >/dev/null || { echo "Error: python3 not found" >&2; exit 1; }
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
CXX=${CXX:-g++}
|
||||
|
||||
# Default the thread pool to the core count. The committed benchmark Makefile
|
||||
# hardcodes 16, which heavily oversubscribes a 2-4 vCPU CI runner and inflates
|
||||
# the variance we are trying to see through.
|
||||
if [ -z "$THREADS" ]; then
|
||||
THREADS=$(python3 -c 'import os; print(os.cpu_count() or 4)')
|
||||
fi
|
||||
|
||||
WORKDIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
pkill -f "$WORKDIR/.*/server-ab" 2>/dev/null || true
|
||||
git -C "$REPO_ROOT" worktree remove --force "$WORKDIR/base" 2>/dev/null || true
|
||||
git -C "$REPO_ROOT" worktree remove --force "$WORKDIR/head" 2>/dev/null || true
|
||||
rm -rf "$WORKDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
BASE_SHA=$(git -C "$REPO_ROOT" rev-parse --short "$BASE_REF")
|
||||
HEAD_SHA=$(git -C "$REPO_ROOT" rev-parse --short "$HEAD_REF")
|
||||
|
||||
echo "==> base: $BASE_REF ($BASE_SHA)"
|
||||
echo "==> head: $HEAD_REF ($HEAD_SHA)"
|
||||
echo "==> rounds=$ROUNDS duration=$DURATION connections=$CONNECTIONS threads=$THREADS"
|
||||
echo "==> path=$REQ_PATH tls=$TLS large=${LARGE_MIB}MiB"
|
||||
echo ""
|
||||
|
||||
if [ "$BASE_SHA" = "$HEAD_SHA" ]; then
|
||||
echo "Note: base and head are the same commit; this measures harness noise."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# --- Toolchain bits that depend on --tls ---
|
||||
SCHEME="http"
|
||||
INSECURE=""
|
||||
TLS_CXXFLAGS=""
|
||||
TLS_LDFLAGS=""
|
||||
TLS_ARGS=""
|
||||
if [ "$TLS" = "1" ]; then
|
||||
SCHEME="https"
|
||||
INSECURE="-k"
|
||||
TLS_CXXFLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT"
|
||||
TLS_LDFLAGS="-lssl -lcrypto"
|
||||
if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists openssl; then
|
||||
TLS_CXXFLAGS="$TLS_CXXFLAGS $(pkg-config --cflags openssl)"
|
||||
TLS_LDFLAGS="$(pkg-config --libs openssl)"
|
||||
elif command -v brew >/dev/null 2>&1 && brew --prefix openssl >/dev/null 2>&1; then
|
||||
OPENSSL_PREFIX=$(brew --prefix openssl)
|
||||
TLS_CXXFLAGS="$TLS_CXXFLAGS -I$OPENSSL_PREFIX/include"
|
||||
TLS_LDFLAGS="-L$OPENSSL_PREFIX/lib -lssl -lcrypto"
|
||||
fi
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
TLS_LDFLAGS="$TLS_LDFLAGS -framework CoreFoundation -framework Security"
|
||||
fi
|
||||
TLS_ARGS="--cert $REPO_ROOT/test/cert.pem --key $REPO_ROOT/test/key.pem"
|
||||
for f in "$REPO_ROOT/test/cert.pem" "$REPO_ROOT/test/key.pem"; do
|
||||
[ -f "$f" ] || { echo "Error: $f not found" >&2; exit 1; }
|
||||
done
|
||||
fi
|
||||
|
||||
# --- Build both refs ---
|
||||
# The harness source always comes from the invoking worktree, so both refs run
|
||||
# an identical workload and a ref that predates a harness change stays
|
||||
# measurable. Only httplib.h varies, through -I.
|
||||
HARNESS="$REPO_ROOT/benchmark/cpp-httplib/main.cpp"
|
||||
[ -f "$HARNESS" ] || { echo "Error: $HARNESS not found" >&2; exit 1; }
|
||||
|
||||
build() {
|
||||
local name=$1 ref=$2
|
||||
git -C "$REPO_ROOT" worktree add --detach --quiet "$WORKDIR/$name" "$ref"
|
||||
"$CXX" -o "$WORKDIR/$name/server-ab" -O2 -std=c++11 \
|
||||
-I"$WORKDIR/$name" \
|
||||
-DCPPHTTPLIB_THREAD_POOL_COUNT="$THREADS" \
|
||||
$TLS_CXXFLAGS \
|
||||
"$HARNESS" -lpthread $TLS_LDFLAGS
|
||||
}
|
||||
|
||||
echo "==> Building..."
|
||||
build base "$BASE_REF"
|
||||
build head "$HEAD_REF"
|
||||
|
||||
# --- Measure one ref once, echo rps ---
|
||||
measure() {
|
||||
local name=$1
|
||||
local json rc
|
||||
|
||||
"$WORKDIR/$name/server-ab" --port "$PORT" --dir "$WORKDIR/$name-www" \
|
||||
--large-mib "$LARGE_MIB" $TLS_ARGS >/dev/null 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
# Wait for the listener (no dependency on nc)
|
||||
local i
|
||||
for i in $(seq 1 200); do
|
||||
if (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then exec 3>&- 3<&-; break; fi
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
set +e
|
||||
json=$(bombardier -c "$CONNECTIONS" -d "$DURATION" -t "$TIMEOUT" -o json -p r $INSECURE \
|
||||
"$SCHEME://127.0.0.1:$PORT$REQ_PATH" 2>/dev/null)
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
|
||||
# Wait for the port to be released before the next run
|
||||
for i in $(seq 1 200); do
|
||||
if ! (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then break; fi
|
||||
exec 3>&- 3<&-
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
if [ $rc -ne 0 ] || [ -z "$json" ]; then
|
||||
echo "Error: bombardier failed for $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 -c '
|
||||
import json, sys
|
||||
r = json.load(sys.stdin)["result"]
|
||||
total = sum(r[k] for k in ("req1xx","req2xx","req3xx","req4xx","req5xx","others"))
|
||||
bad = total - r["req2xx"]
|
||||
if bad:
|
||||
sys.stderr.write("Error: %d non-2xx/error responses\n" % bad)
|
||||
sys.exit(1)
|
||||
print("%.1f" % (total / r["timeTakenSeconds"]))
|
||||
' <<<"$json"
|
||||
}
|
||||
|
||||
# --- Alternate, flipping the order each round to cancel ordering bias ---
|
||||
BASE_RESULTS=()
|
||||
HEAD_RESULTS=()
|
||||
|
||||
echo ""
|
||||
echo "==> Measuring..."
|
||||
for ((r = 1; r <= ROUNDS; r++)); do
|
||||
if (( r % 2 == 1 )); then order=("base" "head"); else order=("head" "base"); fi
|
||||
line=" round $r:"
|
||||
for name in "${order[@]}"; do
|
||||
rps=$(measure "$name")
|
||||
if [ "$name" = "base" ]; then BASE_RESULTS+=("$rps"); else HEAD_RESULTS+=("$rps"); fi
|
||||
line="$line $name=$rps"
|
||||
done
|
||||
echo "$line"
|
||||
done
|
||||
|
||||
# --- Report ---
|
||||
SUMMARY=$(python3 -c '
|
||||
import statistics, sys
|
||||
from itertools import combinations
|
||||
|
||||
base = [float(x) for x in sys.argv[1].split()]
|
||||
head = [float(x) for x in sys.argv[2].split()]
|
||||
bm, hm = statistics.median(base), statistics.median(head)
|
||||
|
||||
def spread(v):
|
||||
return (max(v) - min(v)) / statistics.median(v) * 100
|
||||
|
||||
def u_stat(a, b):
|
||||
"""Mann-Whitney U: number of (a, b) pairs where a > b, ties count a half."""
|
||||
return sum((x > y) + 0.5 * (x == y) for x in a for y in b)
|
||||
|
||||
def exact_p(a, b):
|
||||
"""Two-sided permutation p-value. A single slow round cannot swing this
|
||||
the way a min/max spread check can."""
|
||||
n1, n2 = len(a), len(b)
|
||||
pooled = a + b
|
||||
observed = abs(u_stat(a, b) - n1 * n2 / 2)
|
||||
total = extreme = 0
|
||||
for idx in combinations(range(n1 + n2), n1):
|
||||
s = set(idx)
|
||||
ga = [pooled[i] for i in idx]
|
||||
gb = [pooled[i] for i in range(n1 + n2) if i not in s]
|
||||
total += 1
|
||||
if abs(u_stat(ga, gb) - n1 * n2 / 2) >= observed:
|
||||
extreme += 1
|
||||
return extreme / total
|
||||
|
||||
print("| | median req/s | min | max | spread |")
|
||||
print("|---|---|---|---|---|")
|
||||
print("| base | %.0f | %.0f | %.0f | %.1f%% |" % (bm, min(base), max(base), spread(base)))
|
||||
print("| head | %.0f | %.0f | %.0f | %.1f%% |" % (hm, min(head), max(head), spread(head)))
|
||||
print("")
|
||||
print("**ratio: %.3fx** (%+.1f%%)" % (hm / bm, (hm / bm - 1) * 100))
|
||||
print("")
|
||||
|
||||
if len(base) + len(head) > 20:
|
||||
print("> %d rounds: skipping the permutation test (too many combinations)."
|
||||
% len(base))
|
||||
else:
|
||||
p = exact_p(base, head)
|
||||
if p <= 0.05:
|
||||
print("> Separation is consistent across rounds (permutation p = %.3f)." % p)
|
||||
else:
|
||||
print("> Not separated from noise (permutation p = %.3f). Inconclusive;" % p)
|
||||
print("> raise --rounds or --duration, or run on a quieter machine.")
|
||||
min_p = exact_p(list(range(len(base))),
|
||||
list(range(len(base), len(base) + len(head))))
|
||||
if min_p > 0.05:
|
||||
print(">")
|
||||
print("> With %d rounds even perfect separation only reaches p = %.3f,"
|
||||
% (len(base), min_p))
|
||||
print("> so this test can never call a win. Use --rounds 4 or more.")
|
||||
' "${BASE_RESULTS[*]}" "${HEAD_RESULTS[*]}")
|
||||
|
||||
echo ""
|
||||
echo "$SUMMARY"
|
||||
|
||||
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||
{
|
||||
echo "## Benchmark A/B"
|
||||
echo ""
|
||||
echo "- base: \`$BASE_REF\` ($BASE_SHA)"
|
||||
echo "- head: \`$HEAD_REF\` ($HEAD_SHA)"
|
||||
echo "- rounds=$ROUNDS duration=$DURATION connections=$CONNECTIONS threads=$THREADS"
|
||||
echo ""
|
||||
echo "$SUMMARY"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
139
libs/cpp-httplib-0.57.1/benchmark/cpp-httplib/main.cpp
Normal file
139
libs/cpp-httplib-0.57.1/benchmark/cpp-httplib/main.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include "httplib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
namespace {
|
||||
|
||||
// The workloads differ in which part of the write path they exercise:
|
||||
//
|
||||
// / small body set through set_content(); the response line,
|
||||
// the headers and the body already share a single write()
|
||||
// /large large body set through set_content(); the body is copied
|
||||
// into the header buffer before that single write().
|
||||
// --large-mib sets its size (and large.bin's).
|
||||
// /static/small.js small file served from a mount point, where the headers
|
||||
// and the body are two separate writes
|
||||
// /static/large.bin same, with the body large enough to dominate
|
||||
//
|
||||
// Bodies are generated at startup so the repository carries no fixtures.
|
||||
|
||||
const size_t SMALL_SIZE = 1024;
|
||||
const size_t LARGE_SIZE_DEFAULT_MIB = 1;
|
||||
|
||||
std::string filler(size_t n) {
|
||||
std::string s;
|
||||
s.reserve(n);
|
||||
while (s.size() < n) {
|
||||
s += "0123456789abcdef";
|
||||
}
|
||||
s.resize(n);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool write_file(const std::string &path, const std::string &content) {
|
||||
std::ofstream f(path.c_str(), std::ios::binary);
|
||||
f.write(content.data(), static_cast<std::streamsize>(content.size()));
|
||||
return f.good();
|
||||
}
|
||||
|
||||
std::string default_dir() {
|
||||
const char *tmp = std::getenv("TMPDIR");
|
||||
std::string base = tmp && *tmp ? tmp : "/tmp";
|
||||
if (!base.empty() && base[base.size() - 1] == '/') {
|
||||
base.erase(base.size() - 1);
|
||||
}
|
||||
return base + "/cpp-httplib-bench";
|
||||
}
|
||||
|
||||
bool make_dir(const std::string &path) {
|
||||
#ifdef _WIN32
|
||||
return _mkdir(path.c_str()) == 0 || errno == EEXIST;
|
||||
#else
|
||||
return ::mkdir(path.c_str(), 0755) == 0 || errno == EEXIST;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setup(Server &svr, const std::string &large, const std::string &dir) {
|
||||
svr.Get("/", [](const Request &, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/large", [&large](const Request &, Response &res) {
|
||||
res.set_content(large, "application/octet-stream");
|
||||
});
|
||||
|
||||
svr.set_mount_point("/static", dir);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int port = 8080;
|
||||
std::string dir = default_dir();
|
||||
std::string cert;
|
||||
std::string key;
|
||||
size_t large_mib = LARGE_SIZE_DEFAULT_MIB;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
auto last = i + 1 < argc;
|
||||
if (!std::strcmp(argv[i], "--port") && last) {
|
||||
port = std::atoi(argv[++i]);
|
||||
} else if (!std::strcmp(argv[i], "--large-mib") && last) {
|
||||
large_mib = static_cast<size_t>(std::atoi(argv[++i]));
|
||||
} else if (!std::strcmp(argv[i], "--dir") && last) {
|
||||
dir = argv[++i];
|
||||
} else if (!std::strcmp(argv[i], "--cert") && last) {
|
||||
cert = argv[++i];
|
||||
} else if (!std::strcmp(argv[i], "--key") && last) {
|
||||
key = argv[++i];
|
||||
} else {
|
||||
std::fprintf(stderr,
|
||||
"usage: %s [--port N] [--dir PATH] [--large-mib N]"
|
||||
" [--cert PATH --key PATH]\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!make_dir(dir)) {
|
||||
std::fprintf(stderr, "cannot create %s\n", dir.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto large = filler(large_mib * 1024 * 1024);
|
||||
if (!write_file(dir + "/small.js", filler(SMALL_SIZE)) ||
|
||||
!write_file(dir + "/large.bin", large)) {
|
||||
std::fprintf(stderr, "cannot write fixtures under %s\n", dir.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!cert.empty()) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLServer svr(cert.c_str(), key.c_str());
|
||||
if (!svr.is_valid()) {
|
||||
std::fprintf(stderr, "cannot load %s / %s\n", cert.c_str(), key.c_str());
|
||||
return 1;
|
||||
}
|
||||
setup(svr, large, dir);
|
||||
svr.listen("0.0.0.0", port);
|
||||
return 0;
|
||||
#else
|
||||
std::fprintf(stderr, "built without CPPHTTPLIB_OPENSSL_SUPPORT\n");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
Server svr;
|
||||
setup(svr, large, dir);
|
||||
svr.listen("0.0.0.0", port);
|
||||
}
|
||||
13796
libs/cpp-httplib-0.57.1/benchmark/crow/crow_all.h
Normal file
13796
libs/cpp-httplib-0.57.1/benchmark/crow/crow_all.h
Normal file
File diff suppressed because it is too large
Load Diff
17
libs/cpp-httplib-0.57.1/benchmark/crow/main.cpp
Normal file
17
libs/cpp-httplib-0.57.1/benchmark/crow/main.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "crow_all.h"
|
||||
|
||||
class CustomLogger : public crow::ILogHandler {
|
||||
public:
|
||||
void log(const std::string &, crow::LogLevel) {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
CustomLogger logger;
|
||||
crow::logger::setHandler(&logger);
|
||||
|
||||
crow::SimpleApp app;
|
||||
|
||||
CROW_ROUTE(app, "/")([]() { return "Hello world!"; });
|
||||
|
||||
app.port(8080).multithreaded().run();
|
||||
}
|
||||
173
libs/cpp-httplib-0.57.1/cmake/FindBrotli.cmake
Normal file
173
libs/cpp-httplib-0.57.1/cmake/FindBrotli.cmake
Normal file
@ -0,0 +1,173 @@
|
||||
# A simple FindBrotli package for Cmake's find_package function.
|
||||
# Note: This find package doesn't have version support, as the version file doesn't seem to be installed on most systems.
|
||||
#
|
||||
# If you want to find the static packages instead of shared (the default), define BROTLI_USE_STATIC_LIBS as TRUE.
|
||||
# The targets will have the same names, but it will use the static libs.
|
||||
#
|
||||
# Valid find_package COMPONENTS names: "decoder", "encoder", and "common"
|
||||
# Note that if you're requiring "decoder" or "encoder", then "common" will be automatically added as required.
|
||||
#
|
||||
# Defines the libraries (if found): Brotli::decoder, Brotli::encoder, Brotli::common
|
||||
# and the includes path variable: Brotli_INCLUDE_DIR
|
||||
#
|
||||
# If it's failing to find the libraries, try setting BROTLI_ROOT_DIR to the folder containing your library & include dir.
|
||||
|
||||
# If they asked for a specific version, warn/fail since we don't support it.
|
||||
# TODO: if they start distributing the version somewhere, implement finding it.
|
||||
# See https://github.com/google/brotli/issues/773#issuecomment-579133187
|
||||
if(Brotli_FIND_VERSION)
|
||||
set(_brotli_version_error_msg "FindBrotli.cmake doesn't have version support!")
|
||||
# If the package is required, throw a fatal error
|
||||
# Otherwise, if not running quietly, we throw a warning
|
||||
if(Brotli_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "${_brotli_version_error_msg}")
|
||||
elseif(NOT Brotli_FIND_QUIETLY)
|
||||
message(WARNING "${_brotli_version_error_msg}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Since both decoder & encoder require the common lib, force its requirement..
|
||||
# if the user is requiring either of those other libs.
|
||||
if(Brotli_FIND_REQUIRED_decoder OR Brotli_FIND_REQUIRED_encoder)
|
||||
set(Brotli_FIND_REQUIRED_common TRUE)
|
||||
endif()
|
||||
|
||||
# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
|
||||
# Credit to FindOpenSSL.cmake for this
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
set(_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
else()
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Make PkgConfig optional, since some users (mainly Windows) don't have it.
|
||||
# But it's a lot more clean than manually using find_library.
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
# Only used if the PkgConfig libraries aren't used.
|
||||
find_path(Brotli_INCLUDE_DIR
|
||||
NAMES
|
||||
"brotli/decode.h"
|
||||
"brotli/encode.h"
|
||||
HINTS
|
||||
${BROTLI_ROOT_DIR}
|
||||
PATH_SUFFIXES
|
||||
"include"
|
||||
"includes"
|
||||
DOC "The path to Brotli's include directory."
|
||||
)
|
||||
# Hides this var from the GUI
|
||||
mark_as_advanced(Brotli_INCLUDE_DIR)
|
||||
|
||||
# Just used for PkgConfig stuff in the loop below
|
||||
set(_brotli_stat_str "")
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
set(_brotli_stat_str "_STATIC")
|
||||
endif()
|
||||
|
||||
# Each string here is "ComponentName;LiteralName" (the semi-colon is a delimiter)
|
||||
foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
# Split the component name and literal library name from the listvar
|
||||
list(GET _listvar 0 _component_name)
|
||||
list(GET _listvar 1 _libname)
|
||||
|
||||
# NOTE: We can't rely on PkgConf for static libs since the upstream static lib support is broken
|
||||
# See https://github.com/google/brotli/issues/795
|
||||
# TODO: whenever their issue is fixed upstream, remove this "AND NOT BROTLI_USE_STATIC_LIBS" check
|
||||
if(PKG_CONFIG_FOUND AND NOT BROTLI_USE_STATIC_LIBS)
|
||||
# These need to be GLOBAL for MinGW when making ALIAS libraries against them.
|
||||
# Have to postfix _STATIC on the name to tell PkgConfig to find the static libs.
|
||||
pkg_check_modules(Brotli_${_component_name}${_brotli_stat_str} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
|
||||
endif()
|
||||
|
||||
# Check if the target was already found by Pkgconf
|
||||
if(TARGET PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
# ALIAS since we don't want the PkgConfig namespace on the Cmake library (for end-users)
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
endif()
|
||||
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
if(Brotli_FIND_REQUIRED_${_component_name})
|
||||
# If the lib is required, we can add its literal path as a required var for FindPackageHandleStandardArgs
|
||||
# Since it won't accept the PkgConfig targets
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_req_vars "Brotli_${_component_name}_STATIC_LIBRARIES")
|
||||
else()
|
||||
list(APPEND _brotli_req_vars "Brotli_${_component_name}_LINK_LIBRARIES")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Skip searching for the libs with find_library since it was already found by Pkgconf
|
||||
continue()
|
||||
endif()
|
||||
|
||||
if(Brotli_FIND_REQUIRED_${_component_name})
|
||||
# If it's required, we can set the name used in find_library as a required var for FindPackageHandleStandardArgs
|
||||
list(APPEND _brotli_req_vars "Brotli_${_component_name}")
|
||||
endif()
|
||||
|
||||
list(APPEND _brotli_lib_names
|
||||
"brotli${_libname}"
|
||||
"libbrotli${_libname}"
|
||||
)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
# Postfix "-static" to the libnames since we're looking for static libs
|
||||
list(TRANSFORM _brotli_lib_names APPEND "-static")
|
||||
endif()
|
||||
|
||||
find_library(Brotli_${_component_name}
|
||||
NAMES ${_brotli_lib_names}
|
||||
HINTS ${BROTLI_ROOT_DIR}
|
||||
PATH_SUFFIXES
|
||||
"lib"
|
||||
"lib64"
|
||||
"libs"
|
||||
"libs64"
|
||||
"lib/x86_64-linux-gnu"
|
||||
)
|
||||
# Hide the library variable from the Cmake GUI
|
||||
mark_as_advanced(Brotli_${_component_name})
|
||||
|
||||
# Unset since otherwise it'll stick around for the next loop and break things
|
||||
unset(_brotli_lib_names)
|
||||
|
||||
# Check if find_library found the library
|
||||
if(Brotli_${_component_name})
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
|
||||
endif()
|
||||
|
||||
# Attach the literal library and include dir to the IMPORTED target for the end-user
|
||||
set_target_properties("Brotli::${_component_name}" PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}"
|
||||
IMPORTED_LOCATION "${Brotli_${_component_name}}"
|
||||
)
|
||||
else()
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND FALSE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# Sets Brotli_FOUND, and fails the find_package(Brotli) call if it was REQUIRED but missing libs.
|
||||
find_package_handle_standard_args(Brotli
|
||||
FOUND_VAR
|
||||
Brotli_FOUND
|
||||
REQUIRED_VARS
|
||||
Brotli_INCLUDE_DIR
|
||||
${_brotli_req_vars}
|
||||
HANDLE_COMPONENTS
|
||||
)
|
||||
|
||||
# Restore the original find library ordering
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
endif()
|
||||
119
libs/cpp-httplib-0.57.1/cmake/httplibConfig.cmake.in
Normal file
119
libs/cpp-httplib-0.57.1/cmake/httplibConfig.cmake.in
Normal file
@ -0,0 +1,119 @@
|
||||
# Generates a macro to auto-configure everything
|
||||
@PACKAGE_INIT@
|
||||
|
||||
# Setting these here so they're accessible after install.
|
||||
# Might be useful for some users to check which settings were used.
|
||||
set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL @HTTPLIB_IS_USING_WOLFSSL@)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS @HTTPLIB_IS_USING_MBEDTLS@)
|
||||
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
|
||||
set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@)
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO @HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO@)
|
||||
set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
# We add find_dependency calls here to not make the end-user have to call them.
|
||||
find_dependency(Threads)
|
||||
if(@HTTPLIB_IS_USING_OPENSSL@)
|
||||
# OpenSSL COMPONENTS were added in Cmake v3.11
|
||||
if(CMAKE_VERSION VERSION_LESS "3.11")
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@)
|
||||
else()
|
||||
# Once the COMPONENTS were added, they were made optional when not specified.
|
||||
# Since we use both, we need to search for both.
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
|
||||
endif()
|
||||
set(httplib_OpenSSL_FOUND ${OpenSSL_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_ZLIB@)
|
||||
find_dependency(ZLIB)
|
||||
set(httplib_ZLIB_FOUND ${ZLIB_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_WOLFSSL@)
|
||||
find_dependency(wolfssl)
|
||||
set(httplib_wolfssl_FOUND ${wolfssl_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_MBEDTLS@)
|
||||
find_dependency(MbedTLS)
|
||||
set(httplib_MbedTLS_FOUND ${MbedTLS_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
# Needed so we can use our own FindBrotli.cmake in this file.
|
||||
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder)
|
||||
set(httplib_Brotli_FOUND ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_ZSTD@)
|
||||
set(httplib_fd_zstd_quiet_arg)
|
||||
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
|
||||
set(httplib_fd_zstd_quiet_arg QUIET)
|
||||
endif()
|
||||
set(httplib_fd_zstd_required_arg)
|
||||
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
|
||||
set(httplib_fd_zstd_required_arg REQUIRED)
|
||||
endif()
|
||||
find_package(zstd 1.5.6 CONFIG QUIET)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd)
|
||||
|
||||
if(TARGET PkgConfig::zstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
set(httplib_zstd_FOUND ${zstd_FOUND})
|
||||
endif()
|
||||
|
||||
# Mildly useful for end-users
|
||||
# Not really recommended to be used though
|
||||
set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
# Lets the end-user find the header path with the header appended
|
||||
# This is helpful if you're using Cmake's pre-compiled header feature
|
||||
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
|
||||
|
||||
check_required_components(httplib)
|
||||
|
||||
# Brings in the target library, but only if all required components are found
|
||||
if(NOT DEFINED httplib_FOUND OR httplib_FOUND)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
|
||||
endif()
|
||||
|
||||
# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
|
||||
include(FindPackageMessage)
|
||||
if(TARGET httplib::httplib)
|
||||
set(HTTPLIB_FOUND TRUE)
|
||||
|
||||
# Since the compiled version has a lib, show that in the message
|
||||
if(@HTTPLIB_COMPILE@)
|
||||
# The list of configurations is most likely just 1 unless they installed a debug & release
|
||||
get_target_property(_httplib_configs httplib::httplib "IMPORTED_CONFIGURATIONS")
|
||||
# Need to loop since the "IMPORTED_LOCATION" property isn't want we want.
|
||||
# Instead, we need to find the IMPORTED_LOCATION_RELEASE or IMPORTED_LOCATION_DEBUG which has the lib path.
|
||||
foreach(_httplib_conf "${_httplib_configs}")
|
||||
# Grab the path to the lib and sets it to HTTPLIB_LIBRARY
|
||||
get_target_property(HTTPLIB_LIBRARY httplib::httplib "IMPORTED_LOCATION_${_httplib_conf}")
|
||||
# Check if we found it
|
||||
if(HTTPLIB_LIBRARY)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
unset(_httplib_configs)
|
||||
unset(_httplib_conf)
|
||||
|
||||
find_package_message(httplib "Found httplib: ${HTTPLIB_LIBRARY} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_LIBRARY}][${HTTPLIB_HEADER_PATH}]")
|
||||
else()
|
||||
find_package_message(httplib "Found httplib: ${HTTPLIB_HEADER_PATH} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_HEADER_PATH}]")
|
||||
endif()
|
||||
endif()
|
||||
19
libs/cpp-httplib-0.57.1/cmake/modules.cmake
Normal file
19
libs/cpp-httplib-0.57.1/cmake/modules.cmake
Normal file
@ -0,0 +1,19 @@
|
||||
# This file contains C++20 module support requiring CMake 3.28+
|
||||
# Included conditionally to prevent parse errors on older CMake versions
|
||||
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
if(POLICY CMP0155)
|
||||
cmake_policy(SET CMP0155 NEW)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
FILE_SET CXX_MODULES
|
||||
BASE_DIRS
|
||||
"${_httplib_build_includedir}"
|
||||
FILES
|
||||
"${_httplib_build_includedir}/httplib.cppm"
|
||||
)
|
||||
endif()
|
||||
7
libs/cpp-httplib-0.57.1/docker-compose.yml
Normal file
7
libs/cpp-httplib-0.57.1/docker-compose.yml
Normal file
@ -0,0 +1,7 @@
|
||||
services:
|
||||
http:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./docker/html:/html
|
||||
21
libs/cpp-httplib-0.57.1/docker/html/index.html
Normal file
21
libs/cpp-httplib-0.57.1/docker/html/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to cpp-httplib!</title>
|
||||
<style>
|
||||
html { color-scheme: light dark; }
|
||||
body { width: 35em; margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to cpp-httplib!</h1>
|
||||
<p>If you see this page, the cpp-httplib web server is successfully installed and
|
||||
working. Further configuration is required.</p>
|
||||
|
||||
<p>For online documentation and support please refer to
|
||||
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
|
||||
|
||||
<p><em>Thank you for using cpp-httplib.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
303
libs/cpp-httplib-0.57.1/docker/main.cc
Normal file
303
libs/cpp-httplib-0.57.1/docker/main.cc
Normal file
@ -0,0 +1,303 @@
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <format>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const auto SERVER_NAME =
|
||||
std::format("cpp-httplib-server/{}", CPPHTTPLIB_VERSION);
|
||||
|
||||
Server svr;
|
||||
|
||||
void signal_handler(int signal) {
|
||||
if (signal == SIGINT || signal == SIGTERM) {
|
||||
std::cout << "\nReceived signal, shutting down gracefully...\n";
|
||||
svr.stop();
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_time_format() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string get_error_time_format() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// NGINX Combined log format:
|
||||
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
||||
// "$http_referer" "$http_user_agent"
|
||||
void nginx_access_logger(const Request &req, const Response &res) {
|
||||
std::string remote_user =
|
||||
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
||||
auto time_local = get_time_format();
|
||||
auto request = std::format("{} {} {}", req.method, req.path, req.version);
|
||||
auto status = res.status;
|
||||
auto body_bytes_sent = res.body.size();
|
||||
auto http_referer = req.get_header_value("Referer");
|
||||
if (http_referer.empty()) http_referer = "-";
|
||||
auto http_user_agent = req.get_header_value("User-Agent");
|
||||
if (http_user_agent.empty()) http_user_agent = "-";
|
||||
|
||||
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
|
||||
req.remote_addr, remote_user, time_local, request,
|
||||
status, body_bytes_sent, http_referer,
|
||||
http_user_agent)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// NGINX Error log format:
|
||||
// YYYY/MM/DD HH:MM:SS [level] message, client: client_ip, request: "request",
|
||||
// host: "host"
|
||||
void nginx_error_logger(const Error &err, const Request *req) {
|
||||
auto time_local = get_error_time_format();
|
||||
std::string level = "error";
|
||||
|
||||
if (req) {
|
||||
auto request =
|
||||
std::format("{} {} {}", req->method, req->path, req->version);
|
||||
auto host = req->get_header_value("Host");
|
||||
if (host.empty()) host = "-";
|
||||
|
||||
std::cerr << std::format("{} [{}] {}, client: {}, request: "
|
||||
"\"{}\", host: \"{}\"",
|
||||
time_local, level, to_string(err),
|
||||
req->remote_addr, request, host)
|
||||
<< std::endl;
|
||||
} else {
|
||||
// If no request context, just log the error
|
||||
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void print_usage(const char *program_name) {
|
||||
std::cout << "Usage: " << program_name << " [OPTIONS]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << " --host <hostname> Server hostname (default: localhost)"
|
||||
<< std::endl;
|
||||
std::cout << " --port <port> Server port (default: 8080)"
|
||||
<< std::endl;
|
||||
std::cout << " --mount <mount:path> Mount point and document root"
|
||||
<< std::endl;
|
||||
std::cout << " Format: mount_point:document_root"
|
||||
<< std::endl;
|
||||
std::cout << " (default: /:./html)" << std::endl;
|
||||
std::cout << " --trusted-proxy <ip> Add trusted proxy IP address"
|
||||
<< std::endl;
|
||||
std::cout << " (can be specified multiple times)"
|
||||
<< std::endl;
|
||||
std::cout << " --version Show version information"
|
||||
<< std::endl;
|
||||
std::cout << " --help Show this help message" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Examples:" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --trusted-proxy 192.168.1.100 --trusted-proxy 10.0.0.1"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
struct ServerConfig {
|
||||
std::string hostname = "localhost";
|
||||
int port = 8080;
|
||||
std::string mount_point = "/";
|
||||
std::string document_root = "./html";
|
||||
std::vector<std::string> trusted_proxies;
|
||||
};
|
||||
|
||||
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
|
||||
|
||||
ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::HELP_REQUESTED;
|
||||
} else if (strcmp(argv[i], "--host") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --host requires a hostname argument" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.hostname = argv[++i];
|
||||
} else if (strcmp(argv[i], "--port") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --port requires a port number argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.port = std::atoi(argv[++i]);
|
||||
if (config.port <= 0 || config.port > 65535) {
|
||||
std::cerr << "Error: Invalid port number. Must be between 1 and 65535"
|
||||
<< std::endl;
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--mount") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr
|
||||
<< "Error: --mount requires mount_point:document_root argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
std::string mount_arg = argv[++i];
|
||||
auto colon_pos = mount_arg.find(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
std::cerr << "Error: --mount argument must be in format "
|
||||
"mount_point:document_root"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.mount_point = mount_arg.substr(0, colon_pos);
|
||||
config.document_root = mount_arg.substr(colon_pos + 1);
|
||||
|
||||
if (config.mount_point.empty() || config.document_root.empty()) {
|
||||
std::cerr
|
||||
<< "Error: Both mount_point and document_root must be non-empty"
|
||||
<< std::endl;
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--version") == 0) {
|
||||
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
||||
return ParseResult::VERSION_REQUESTED;
|
||||
} else if (strcmp(argv[i], "--trusted-proxy") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --trusted-proxy requires an IP address argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.trusted_proxies.push_back(argv[++i]);
|
||||
} else {
|
||||
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
}
|
||||
return ParseResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool setup_server(Server &svr, const ServerConfig &config) {
|
||||
svr.set_logger(nginx_access_logger);
|
||||
svr.set_error_logger(nginx_error_logger);
|
||||
|
||||
// Set trusted proxies if specified
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
svr.set_trusted_proxies(config.trusted_proxies);
|
||||
}
|
||||
|
||||
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
|
||||
if (!ret) {
|
||||
std::cerr
|
||||
<< std::format(
|
||||
"Error: Cannot mount '{}' to '{}'. Directory may not exist.",
|
||||
config.mount_point, config.document_root)
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
svr.set_file_extension_and_mimetype_mapping("html", "text/html");
|
||||
svr.set_file_extension_and_mimetype_mapping("htm", "text/html");
|
||||
svr.set_file_extension_and_mimetype_mapping("css", "text/css");
|
||||
svr.set_file_extension_and_mimetype_mapping("js", "text/javascript");
|
||||
svr.set_file_extension_and_mimetype_mapping("json", "application/json");
|
||||
svr.set_file_extension_and_mimetype_mapping("xml", "application/xml");
|
||||
svr.set_file_extension_and_mimetype_mapping("png", "image/png");
|
||||
svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg");
|
||||
svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg");
|
||||
svr.set_file_extension_and_mimetype_mapping("gif", "image/gif");
|
||||
svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml");
|
||||
svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon");
|
||||
svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf");
|
||||
svr.set_file_extension_and_mimetype_mapping("zip", "application/zip");
|
||||
svr.set_file_extension_and_mimetype_mapping("txt", "text/plain");
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
if (res.status == 404) {
|
||||
res.set_content(
|
||||
std::format(
|
||||
"<html><head><title>404 Not Found</title></head>"
|
||||
"<body><h1>404 Not Found</h1>"
|
||||
"<p>The requested resource was not found on this server.</p>"
|
||||
"<hr><p>{}</p></body></html>",
|
||||
SERVER_NAME),
|
||||
"text/html");
|
||||
}
|
||||
});
|
||||
|
||||
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Server", SERVER_NAME);
|
||||
return Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
ServerConfig config;
|
||||
|
||||
auto result = parse_command_line(argc, argv, config);
|
||||
switch (result) {
|
||||
case ParseResult::HELP_REQUESTED:
|
||||
case ParseResult::VERSION_REQUESTED: return 0;
|
||||
case ParseResult::ERROR: return 1;
|
||||
case ParseResult::SUCCESS: break;
|
||||
}
|
||||
|
||||
if (!setup_server(svr, config)) { return 1; }
|
||||
|
||||
std::cout << "Serving HTTP on " << config.hostname << ":" << config.port
|
||||
<< std::endl;
|
||||
std::cout << "Mount point: " << config.mount_point << " -> "
|
||||
<< config.document_root << std::endl;
|
||||
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
std::cout << "Trusted proxies: ";
|
||||
for (size_t i = 0; i < config.trusted_proxies.size(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
std::cout << config.trusted_proxies[i];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
|
||||
|
||||
auto ret = svr.listen(config.hostname, config.port);
|
||||
|
||||
std::cout << "Server has been shut down." << std::endl;
|
||||
|
||||
return ret ? 0 : 1;
|
||||
}
|
||||
30
libs/cpp-httplib-0.57.1/docs-src/config.toml
Normal file
30
libs/cpp-httplib-0.57.1/docs-src/config.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[system]
|
||||
theme = "monotone"
|
||||
langs = ["en", "ja"]
|
||||
|
||||
[site]
|
||||
title = "cpp-httplib"
|
||||
version = "0.57.1"
|
||||
hostname = "https://yhirose.github.io"
|
||||
base_path = "/cpp-httplib"
|
||||
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
||||
|
||||
[[nav]]
|
||||
label = "Tour"
|
||||
path = "tour/"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg>'
|
||||
|
||||
[[nav]]
|
||||
label = "Cookbook"
|
||||
path = "cookbook/"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
|
||||
|
||||
[[nav]]
|
||||
label = "LLM App"
|
||||
path = "llm-app/"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
|
||||
|
||||
[[nav]]
|
||||
label = "GitHub"
|
||||
url = "https://github.com/yhirose/cpp-httplib"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>'
|
||||
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "C01. Get the Response Body / Save to a File"
|
||||
order: 1
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
## Get it as a string
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto res = cli.Get("/hello");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
`res->body` is a `std::string`, ready to use as-is. The entire response is loaded into memory.
|
||||
|
||||
> **Warning:** If you fetch a large file with `res->body`, it all goes into memory. For large downloads, use a `ContentReceiver` as shown below.
|
||||
|
||||
## Save to a file
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
std::ofstream ofs("output.bin", std::ios::binary);
|
||||
if (!ofs) {
|
||||
std::cerr << "Failed to open file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto res = cli.Get("/large-file",
|
||||
[&](const char *data, size_t len) {
|
||||
ofs.write(data, len);
|
||||
return static_cast<bool>(ofs);
|
||||
});
|
||||
```
|
||||
|
||||
With a `ContentReceiver`, data arrives in chunks. You can write each chunk straight to disk without buffering the whole body in memory — perfect for large file downloads.
|
||||
|
||||
Return `false` from the callback to abort the download. In the example above, if writing to `ofs` fails, the download stops automatically.
|
||||
|
||||
> **Detail:** Want to check response headers like Content-Length before downloading? Combine a `ResponseHandler` with a `ContentReceiver`.
|
||||
>
|
||||
> ```cpp
|
||||
> auto res = cli.Get("/large-file",
|
||||
> [](const httplib::Response &res) {
|
||||
> auto len = res.get_header_value("Content-Length");
|
||||
> std::cout << "Size: " << len << std::endl;
|
||||
> return true; // return false to skip the download
|
||||
> },
|
||||
> [&](const char *data, size_t len) {
|
||||
> ofs.write(data, len);
|
||||
> return static_cast<bool>(ofs);
|
||||
> });
|
||||
> ```
|
||||
>
|
||||
> The `ResponseHandler` is called after headers arrive but before the body. Return `false` to skip the download entirely.
|
||||
|
||||
> To show download progress, see [C11. Use the progress callback](../c11-progress-callback).
|
||||
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "C02. Send and Receive JSON"
|
||||
order: 2
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib doesn't include a JSON parser. Use a library like [nlohmann/json](https://github.com/nlohmann/json) to build and parse JSON. The examples here use `nlohmann/json`.
|
||||
|
||||
## Send JSON
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
nlohmann::json j = {{"name", "Alice"}, {"age", 30}};
|
||||
auto res = cli.Post("/api/users", j.dump(), "application/json");
|
||||
```
|
||||
|
||||
Pass the JSON string as the second argument to `Post()` and the Content-Type as the third. The same pattern works with `Put()` and `Patch()`.
|
||||
|
||||
> **Warning:** If you omit the Content-Type (the third argument), the server may not recognize the body as JSON. Always specify `"application/json"`.
|
||||
|
||||
## Receive a JSON response
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/api/users/1");
|
||||
if (res && res->status == 200) {
|
||||
auto j = nlohmann::json::parse(res->body);
|
||||
std::cout << j["name"] << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
`res->body` is a `std::string`, so you can pass it straight to your JSON library.
|
||||
|
||||
> **Note:** Servers sometimes return HTML on errors. Check the status code before parsing to be safe. Some APIs also require an `Accept: application/json` header. If you're calling a JSON API repeatedly, [C03. Set default headers](../c03-default-headers) can save you some boilerplate.
|
||||
|
||||
> For how to receive and return JSON on the server side, see [S02. Receive JSON requests and return JSON responses](../s02-json-api).
|
||||
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "C03. Set Default Headers"
|
||||
order: 3
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When you want the same headers on every request, use `set_default_headers()`. Once set, they're attached automatically to every request sent from that client.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
|
||||
cli.set_default_headers({
|
||||
{"Accept", "application/json"},
|
||||
{"User-Agent", "my-app/1.0"},
|
||||
});
|
||||
|
||||
auto res = cli.Get("/users");
|
||||
```
|
||||
|
||||
Register the headers you need on every API call — like `Accept` or `User-Agent` — in one place. No need to repeat them on each request.
|
||||
|
||||
## Send a Bearer token on every request
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
|
||||
cli.set_default_headers({
|
||||
{"Authorization", "Bearer " + token},
|
||||
{"Accept", "application/json"},
|
||||
});
|
||||
|
||||
auto res1 = cli.Get("/me");
|
||||
auto res2 = cli.Get("/projects");
|
||||
```
|
||||
|
||||
Set the auth token once, and every subsequent request carries it. Handy when you're writing an API client that hits multiple endpoints.
|
||||
|
||||
> **Note:** `set_default_headers()` **replaces** the existing default headers. Even if you only want to add one, pass the full set again.
|
||||
|
||||
## Combine with per-request headers
|
||||
|
||||
You can still pass extra headers on individual requests, even with defaults set.
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
{"X-Request-ID", "abc-123"},
|
||||
};
|
||||
auto res = cli.Get("/users", headers);
|
||||
```
|
||||
|
||||
Per-request headers are **added** on top of the defaults. Both are sent to the server.
|
||||
|
||||
> For details on Bearer token auth, see [C06. Call an API with a Bearer token](../c06-bearer-token).
|
||||
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: "C04. Follow Redirects"
|
||||
order: 4
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
By default, cpp-httplib does not follow HTTP redirects (3xx). If the server returns `302 Found`, you'll get it as a response with status code 302 — nothing more.
|
||||
|
||||
To follow redirects automatically, call `set_follow_location(true)`.
|
||||
|
||||
## Follow redirects
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://example.com");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/old-path");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
With `set_follow_location(true)`, the client reads the `Location` header and reissues the request to the new URL automatically. The final response ends up in `res`.
|
||||
|
||||
## Redirects from HTTP to HTTPS
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://example.com");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
Many sites redirect HTTP traffic to HTTPS. With `set_follow_location(true)` on, this case is handled transparently — the client follows redirects even when the scheme or host changes.
|
||||
|
||||
> **Warning:** To follow redirects to HTTPS, you need to build cpp-httplib with OpenSSL (or another TLS backend). Without TLS support, redirects to HTTPS will fail.
|
||||
|
||||
> **Note:** Following redirects adds to the total request time. See [C12. Set timeouts](../c12-timeouts) for timeout configuration.
|
||||
@ -0,0 +1,46 @@
|
||||
---
|
||||
title: "C05. Use Basic Authentication"
|
||||
order: 5
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
For endpoints that require Basic authentication, pass the username and password to `set_basic_auth()`. cpp-httplib builds the `Authorization: Basic ...` header for you.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_basic_auth("alice", "s3cret");
|
||||
|
||||
auto res = cli.Get("/private");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Set it once, and every request from that client carries the credentials. No need to build the header each time.
|
||||
|
||||
## Per-request usage
|
||||
|
||||
If you want credentials on only one specific request, pass headers directly.
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
httplib::make_basic_authentication_header("alice", "s3cret"),
|
||||
};
|
||||
auto res = cli.Get("/private", headers);
|
||||
```
|
||||
|
||||
`make_basic_authentication_header()` builds the Base64-encoded header for you.
|
||||
|
||||
> **Warning:** Basic authentication **encodes** credentials in Base64 — it does not encrypt them. Always use it over HTTPS. Over plain HTTP, your password travels the network in the clear.
|
||||
|
||||
## Digest authentication
|
||||
|
||||
For the more secure Digest authentication scheme, use `set_digest_auth()`. This is only available when cpp-httplib is built with OpenSSL (or another TLS backend).
|
||||
|
||||
```cpp
|
||||
cli.set_digest_auth("alice", "s3cret");
|
||||
```
|
||||
|
||||
> To call an API with a Bearer token, see [C06. Call an API with a Bearer token](../c06-bearer-token).
|
||||
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "C06. Call an API with a Bearer Token"
|
||||
order: 6
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
For Bearer token authentication — common in OAuth 2.0 and modern Web APIs — use `set_bearer_token_auth()`. Pass the token and cpp-httplib builds the `Authorization: Bearer <token>` header for you.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_bearer_token_auth("eyJhbGciOiJIUzI1NiIs...");
|
||||
|
||||
auto res = cli.Get("/me");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Set it once and every subsequent request carries the token. This is the go-to pattern for token-based APIs like GitHub, Slack, or your own OAuth service.
|
||||
|
||||
## Per-request usage
|
||||
|
||||
When you want the token on only one request — or need a different token per request — pass it via headers.
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
httplib::make_bearer_token_authentication_header(token),
|
||||
};
|
||||
auto res = cli.Get("/me", headers);
|
||||
```
|
||||
|
||||
`make_bearer_token_authentication_header()` builds the `Authorization` header for you.
|
||||
|
||||
## Refresh the token
|
||||
|
||||
When a token expires, just call `set_bearer_token_auth()` again with the new one.
|
||||
|
||||
```cpp
|
||||
if (res && res->status == 401) {
|
||||
auto new_token = refresh_token();
|
||||
cli.set_bearer_token_auth(new_token);
|
||||
res = cli.Get("/me");
|
||||
}
|
||||
```
|
||||
|
||||
> **Warning:** A Bearer token is itself a credential. Always send it over HTTPS, and never hard-code it into source or config files.
|
||||
|
||||
> To set multiple headers at once, see [C03. Set default headers](../c03-default-headers).
|
||||
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "C07. Upload a File as Multipart Form Data"
|
||||
order: 7
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When you want to send a file the same way an HTML `<input type="file">` does, use multipart form data (`multipart/form-data`). cpp-httplib offers two APIs — `UploadFormDataItems` and `FormDataProviderItems` — and you pick between them based on **file size**.
|
||||
|
||||
## Send a small file
|
||||
|
||||
Read the file into memory first, then send it. For small files, this is the simplest path.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
std::ifstream ifs("avatar.png", std::ios::binary);
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
httplib::UploadFormDataItems items = {
|
||||
{"name", "Alice", "", ""},
|
||||
{"avatar", content, "avatar.png", "image/png"},
|
||||
};
|
||||
|
||||
auto res = cli.Post("/upload", items);
|
||||
```
|
||||
|
||||
Each `UploadFormData` entry is `{name, content, filename, content_type}`. For plain text fields, leave `filename` and `content_type` empty.
|
||||
|
||||
## Stream a large file
|
||||
|
||||
To avoid loading the whole file into memory, use `make_file_provider()`. It reads the file in chunks as it sends — so even huge files won't blow up your memory footprint.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
httplib::UploadFormDataItems items = {
|
||||
{"name", "Alice", "", ""},
|
||||
};
|
||||
|
||||
httplib::FormDataProviderItems provider_items = {
|
||||
httplib::make_file_provider("video", "large-video.mp4", "", "video/mp4"),
|
||||
};
|
||||
|
||||
auto res = cli.Post("/upload", httplib::Headers{}, items, provider_items);
|
||||
```
|
||||
|
||||
The arguments to `make_file_provider()` are `(form name, file path, file name, content type)`. Leave the file name empty to use the file path as-is.
|
||||
|
||||
> **Note:** You can mix `UploadFormDataItems` and `FormDataProviderItems` in the same request. A clean split is: text fields in `UploadFormDataItems`, files in `FormDataProviderItems`.
|
||||
|
||||
> To show upload progress, see [C11. Use the progress callback](../c11-progress-callback).
|
||||
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "C08. POST a File as Raw Binary"
|
||||
order: 8
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Sometimes you want to send a file's contents as the request body directly — no multipart wrapping. This is common for S3-compatible APIs or endpoints that take raw image data. For this, use `make_file_body()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://storage.example.com");
|
||||
|
||||
auto [size, provider] = httplib::make_file_body("backup.tar.gz");
|
||||
if (size == 0) {
|
||||
std::cerr << "Failed to open file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto res = cli.Put("/bucket/backup.tar.gz", size,
|
||||
provider, "application/gzip");
|
||||
```
|
||||
|
||||
`make_file_body()` returns a pair of file size and a `ContentProvider`. Pass them to `Post()` or `Put()` and the file contents flow straight into the request body.
|
||||
|
||||
The `ContentProvider` reads the file in chunks, so even huge files never sit fully in memory.
|
||||
|
||||
## When the file can't be opened
|
||||
|
||||
If the file can't be opened, `make_file_body()` returns `size` as `0` and `provider` as an empty function object. Sending that would produce garbage — always check `size` first.
|
||||
|
||||
> **Warning:** `make_file_body()` needs to fix the Content-Length up front, so it reads the file size ahead of time. If the file size might change mid-upload, this API isn't the right fit.
|
||||
|
||||
> To send the file as multipart form data instead, see [C07. Upload a file as multipart form data](../c07-multipart-upload).
|
||||
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "C09. Send the Body with Chunked Transfer"
|
||||
order: 9
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When you don't know the body size up front — for data generated on the fly or piped from another stream — use `ContentProviderWithoutLength`. The client sends the body with HTTP chunked transfer encoding.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Post("/stream",
|
||||
[&](size_t offset, httplib::DataSink &sink) {
|
||||
std::string chunk = produce_next_chunk();
|
||||
if (chunk.empty()) {
|
||||
sink.done(); // done sending
|
||||
return true;
|
||||
}
|
||||
return sink.write(chunk.data(), chunk.size());
|
||||
},
|
||||
"application/octet-stream");
|
||||
```
|
||||
|
||||
The lambda's job is just: produce the next chunk and send it with `sink.write()`. When there's no more data, call `sink.done()` and you're finished.
|
||||
|
||||
## When the size is known
|
||||
|
||||
If you **do** know the total size ahead of time, use the `ContentProvider` overload (taking `size_t offset, size_t length, DataSink &sink`) and pass the total size as well.
|
||||
|
||||
```cpp
|
||||
size_t total_size = get_total_size();
|
||||
|
||||
auto res = cli.Post("/upload", total_size,
|
||||
[&](size_t offset, size_t length, httplib::DataSink &sink) {
|
||||
auto data = read_range(offset, length);
|
||||
return sink.write(data.data(), data.size());
|
||||
},
|
||||
"application/octet-stream");
|
||||
```
|
||||
|
||||
With a known size, the request carries a Content-Length header — so the server can show progress. Prefer this form when you can.
|
||||
|
||||
> **Detail:** `sink.write()` returns a `bool` indicating whether the write succeeded. If it returns `false`, the connection is gone — return `false` from the lambda to stop.
|
||||
|
||||
> If you're just sending a file, `make_file_body()` is easier. See [C08. POST a file as raw binary](../c08-post-file-body).
|
||||
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "C10. Receive a Response as a Stream"
|
||||
order: 10
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To receive a response body chunk by chunk, use a `ContentReceiver`. It's the obvious choice for large files, but it's equally handy for NDJSON (newline-delimited JSON) or log streams where you want to start processing data as it arrives.
|
||||
|
||||
## Process each chunk
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Get("/logs/stream",
|
||||
[](const char *data, size_t len) {
|
||||
std::cout.write(data, len);
|
||||
std::cout.flush();
|
||||
return true; // return false to stop receiving
|
||||
});
|
||||
```
|
||||
|
||||
Data arrives in the lambda in the order it's received from the server. Return `false` from the callback to stop the download partway through.
|
||||
|
||||
## Parse NDJSON line by line
|
||||
|
||||
Here's a buffered approach for processing newline-delimited JSON one line at a time.
|
||||
|
||||
```cpp
|
||||
std::string buffer;
|
||||
|
||||
auto res = cli.Get("/events",
|
||||
[&](const char *data, size_t len) {
|
||||
buffer.append(data, len);
|
||||
size_t pos;
|
||||
while ((pos = buffer.find('\n')) != std::string::npos) {
|
||||
auto line = buffer.substr(0, pos);
|
||||
buffer.erase(0, pos + 1);
|
||||
if (!line.empty()) {
|
||||
auto j = nlohmann::json::parse(line);
|
||||
handle_event(j);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
```
|
||||
|
||||
Accumulate into a buffer, then pull out and parse one line each time you see a newline. This is the standard pattern for consuming a streaming API in real time.
|
||||
|
||||
> **Warning:** When you pass a `ContentReceiver`, `res->body` stays **empty**. Store or process the body inside the callback yourself.
|
||||
|
||||
> To track download progress, combine this with [C11. Use the progress callback](../c11-progress-callback).
|
||||
> For Server-Sent Events (SSE), see [E04. Receive SSE on the client](../e04-sse-client).
|
||||
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "C11. Use the Progress Callback"
|
||||
order: 11
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To display download or upload progress, pass a `DownloadProgress` or `UploadProgress` callback. Both take two arguments: `(current, total)`.
|
||||
|
||||
## Download progress
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Get("/large-file",
|
||||
[](size_t current, size_t total) {
|
||||
auto percent = (total > 0) ? (current * 100 / total) : 0;
|
||||
std::cout << "\rDownloading: " << percent << "% ("
|
||||
<< current << "/" << total << ")" << std::flush;
|
||||
return true; // return false to abort
|
||||
});
|
||||
std::cout << std::endl;
|
||||
```
|
||||
|
||||
The callback fires each time data arrives. `total` comes from the Content-Length header — if the server doesn't send one, it may be `0`. In that case, you can't compute a percentage, so just display bytes received.
|
||||
|
||||
## Upload progress
|
||||
|
||||
Uploads work the same way. Pass an `UploadProgress` as the last argument to `Post()` or `Put()`.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
std::string body = load_large_body();
|
||||
|
||||
auto res = cli.Post("/upload", body, "application/octet-stream",
|
||||
[](size_t current, size_t total) {
|
||||
auto percent = current * 100 / total;
|
||||
std::cout << "\rUploading: " << percent << "%" << std::flush;
|
||||
return true;
|
||||
});
|
||||
std::cout << std::endl;
|
||||
```
|
||||
|
||||
## Cancel mid-transfer
|
||||
|
||||
Return `false` from the callback to abort the transfer. This is how you wire up a "Cancel" button in a UI — flip a flag, and the next progress tick stops the transfer.
|
||||
|
||||
```cpp
|
||||
std::atomic<bool> cancelled{false};
|
||||
|
||||
auto res = cli.Get("/large-file",
|
||||
[&](size_t current, size_t total) {
|
||||
return !cancelled.load();
|
||||
});
|
||||
```
|
||||
|
||||
> **Note:** `ContentReceiver` and the progress callback can be used together. When you want to stream to a file and show progress at the same time, pass both.
|
||||
|
||||
> For a concrete example of saving to a file, see [C01. Get the response body / save to a file](../c01-get-response-body).
|
||||
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "C12. Set Timeouts"
|
||||
order: 12
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
The client has three kinds of timeouts, each set independently.
|
||||
|
||||
| Kind | API | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| Connection | `set_connection_timeout` | 300s | Time to wait for the TCP connection to establish |
|
||||
| Read | `set_read_timeout` | 300s | Time to wait for a single `recv` when receiving the response |
|
||||
| Write | `set_write_timeout` | 5s | Time to wait for a single `send` when sending the request |
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
cli.set_connection_timeout(5, 0); // 5 seconds
|
||||
cli.set_read_timeout(10, 0); // 10 seconds
|
||||
cli.set_write_timeout(10, 0); // 10 seconds
|
||||
|
||||
auto res = cli.Get("/api/data");
|
||||
```
|
||||
|
||||
Pass seconds and microseconds as two arguments. If you don't need the sub-second part, you can omit the second argument.
|
||||
|
||||
## Use `std::chrono`
|
||||
|
||||
There's also an overload that takes a `std::chrono` duration directly. It's easier to read — recommended.
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
cli.set_connection_timeout(5s);
|
||||
cli.set_read_timeout(10s);
|
||||
cli.set_write_timeout(500ms);
|
||||
```
|
||||
|
||||
## Watch out for the long 300s default
|
||||
|
||||
Connection and read timeouts default to **300 seconds (5 minutes)**. If the server hangs, you'll be waiting five minutes by default. Shorter values are usually a better idea.
|
||||
|
||||
```cpp
|
||||
cli.set_connection_timeout(3s);
|
||||
cli.set_read_timeout(10s);
|
||||
```
|
||||
|
||||
> **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use [C13. Set an overall timeout](../c13-max-timeout).
|
||||
|
||||
> For WebSocket client timeouts, see [W06. Set Timeouts](../w06-websocket-timeouts).
|
||||
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "C13. Set an Overall Timeout"
|
||||
order: 13
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
The three timeouts from [C12. Set timeouts](../c12-timeouts) all apply to a single `send` or `recv` call. To cap the total time a request can take, use `set_max_timeout()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
cli.set_max_timeout(5000); // 5 seconds (in milliseconds)
|
||||
|
||||
auto res = cli.Get("/slow-endpoint");
|
||||
```
|
||||
|
||||
The value is in milliseconds. Connection, send, and receive together — the whole request is aborted if it exceeds the limit.
|
||||
|
||||
## Use `std::chrono`
|
||||
|
||||
There's also an overload that takes a `std::chrono` duration.
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
cli.set_max_timeout(5s);
|
||||
```
|
||||
|
||||
## When to use which
|
||||
|
||||
`set_read_timeout` fires when no data arrives for a while. If data keeps trickling in bit by bit, it will never fire. An endpoint that sends one byte per second can make `set_read_timeout` useless no matter how short you set it.
|
||||
|
||||
`set_max_timeout` caps elapsed time, so it handles those cases cleanly. It's great for calls to external APIs or anywhere you don't want users waiting forever.
|
||||
|
||||
```cpp
|
||||
cli.set_connection_timeout(3s);
|
||||
cli.set_read_timeout(10s);
|
||||
cli.set_max_timeout(30s); // abort if the whole request takes over 30s
|
||||
```
|
||||
|
||||
> **Note:** `set_max_timeout()` works alongside the regular timeouts. Short stalls get caught by `set_read_timeout`; long-running requests get capped by `set_max_timeout`. Use both for a safety net.
|
||||
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "C14. Understand Connection Reuse and Keep-Alive"
|
||||
order: 14
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When you send multiple requests through the same `httplib::Client` instance, the TCP connection is reused automatically. HTTP/1.1 Keep-Alive does the work for you — you don't pay the TCP and TLS handshake cost on every call.
|
||||
|
||||
## Connections are reused automatically
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
|
||||
auto res1 = cli.Get("/users/1");
|
||||
auto res2 = cli.Get("/users/2"); // reuses the same connection
|
||||
auto res3 = cli.Get("/users/3"); // reuses the same connection
|
||||
```
|
||||
|
||||
No special config required. Just hold on to `cli` — internally, the socket stays open across calls. The effect is especially noticeable over HTTPS, where the TLS handshake is expensive.
|
||||
|
||||
## Disable Keep-Alive explicitly
|
||||
|
||||
To force a fresh connection every time, call `set_keep_alive(false)`. Mostly useful for testing.
|
||||
|
||||
```cpp
|
||||
cli.set_keep_alive(false);
|
||||
```
|
||||
|
||||
For normal use, leave it on (the default).
|
||||
|
||||
## Don't create a `Client` per request
|
||||
|
||||
If you create a `Client` inside a loop and let it fall out of scope each iteration, you lose the reuse benefit. Create the instance outside the loop.
|
||||
|
||||
```cpp
|
||||
// Bad: a new connection every iteration
|
||||
for (auto id : ids) {
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.Get("/users/" + id);
|
||||
}
|
||||
|
||||
// Good: the connection is reused
|
||||
httplib::Client cli("https://api.example.com");
|
||||
for (auto id : ids) {
|
||||
cli.Get("/users/" + id);
|
||||
}
|
||||
```
|
||||
|
||||
## Concurrent requests
|
||||
|
||||
If you want to send requests in parallel from multiple threads, give each thread its own `Client` instance. A single `Client` uses a single TCP connection, so firing concurrent requests at the same instance ends up serializing them anyway.
|
||||
|
||||
> **Note:** If the server closes the connection after its Keep-Alive timeout, cpp-httplib reconnects and retries transparently. You don't need to handle this in application code.
|
||||
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "C15. Enable Compression"
|
||||
order: 15
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib supports compression when sending and decompression when receiving. You just need to build it with zlib or Brotli enabled.
|
||||
|
||||
## Build-time setup
|
||||
|
||||
To use compression, define these macros before including `httplib.h`:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_ZLIB_SUPPORT // gzip / deflate
|
||||
#define CPPHTTPLIB_BROTLI_SUPPORT // brotli
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
You'll also need to link against `zlib` or `brotli`.
|
||||
|
||||
## Compress the request body
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_compress(true);
|
||||
|
||||
std::string big_payload = build_payload();
|
||||
auto res = cli.Post("/api/data", big_payload, "application/json");
|
||||
```
|
||||
|
||||
With `set_compress(true)`, the body of POST or PUT requests gets gzipped before sending. The server needs to handle compressed bodies too.
|
||||
|
||||
## Decompress the response
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_decompress(true); // on by default
|
||||
|
||||
auto res = cli.Get("/api/data");
|
||||
std::cout << res->body << std::endl;
|
||||
```
|
||||
|
||||
With `set_decompress(true)`, the client automatically decompresses responses that arrive with `Content-Encoding: gzip` or similar. `res->body` contains the decompressed data.
|
||||
|
||||
It's on by default, so normally you don't need to do anything. Set it to `false` only if you want the raw compressed bytes.
|
||||
|
||||
> **Warning:** If you build without `CPPHTTPLIB_ZLIB_SUPPORT`, calling `set_compress()` or `set_decompress()` does nothing. If compression isn't working, check the macro definition first.
|
||||
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: "C16. Send Requests Through a Proxy"
|
||||
order: 16
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To route traffic through a corporate network or a specific path, send requests via an HTTP proxy. Just pass the proxy host and port to `set_proxy()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_proxy("proxy.internal", 8080);
|
||||
|
||||
auto res = cli.Get("/users");
|
||||
```
|
||||
|
||||
The request goes through the proxy. For HTTPS, the client uses the CONNECT method to tunnel through — no extra setup required.
|
||||
|
||||
## Proxy authentication
|
||||
|
||||
If the proxy itself requires authentication, use `set_proxy_basic_auth()` or `set_proxy_bearer_token_auth()`.
|
||||
|
||||
```cpp
|
||||
cli.set_proxy("proxy.internal", 8080);
|
||||
cli.set_proxy_basic_auth("user", "password");
|
||||
```
|
||||
|
||||
```cpp
|
||||
cli.set_proxy_bearer_token_auth("token");
|
||||
```
|
||||
|
||||
If cpp-httplib is built with OpenSSL (or another TLS backend), you can also use Digest authentication for the proxy.
|
||||
|
||||
```cpp
|
||||
cli.set_proxy_digest_auth("user", "password");
|
||||
```
|
||||
|
||||
## Combine with end-server authentication
|
||||
|
||||
Proxy authentication is separate from authenticating to the end server ([C05. Use Basic authentication](../c05-basic-auth), [C06. Call an API with a Bearer token](../c06-bearer-token)). When both are needed, set both.
|
||||
|
||||
```cpp
|
||||
cli.set_proxy("proxy.internal", 8080);
|
||||
cli.set_proxy_basic_auth("proxy-user", "proxy-pass");
|
||||
|
||||
cli.set_bearer_token_auth("api-token"); // for the end server
|
||||
```
|
||||
|
||||
`Proxy-Authorization` is sent to the proxy, `Authorization` to the end server.
|
||||
|
||||
## Bypass the proxy for specific hosts
|
||||
|
||||
You often want internal endpoints to skip the proxy. Configure a bypass list with `set_no_proxy()`.
|
||||
|
||||
```cpp
|
||||
cli.set_proxy("proxy.internal", 8080);
|
||||
cli.set_no_proxy({"internal.corp", "10.0.0.0/8", "*.dev.local"});
|
||||
```
|
||||
|
||||
Each entry is one of:
|
||||
|
||||
- `*` — bypass the proxy for all hosts
|
||||
- a hostname suffix (e.g. `example.com`) — matches `example.com` itself and any subdomain (`foo.example.com`). A leading dot is permitted but informational; both forms are equivalent.
|
||||
- a single IP literal (e.g. `192.168.1.1`, `::1`)
|
||||
- a CIDR block (e.g. `10.0.0.0/8`, `fe80::/10`)
|
||||
|
||||
Hostname matching is case-insensitive and uses a dot-boundary rule, so an entry of `example.com` does **not** match `evilexample.com`. IP comparisons are normalized through `inet_pton`, so `127.0.0.1` cannot be bypassed via alternate string forms (e.g. `127.000.000.001`). When an entry matches, the `Proxy-Authorization` header is suppressed as well.
|
||||
|
||||
Malformed entries are silently dropped. Port-specific entries such as `example.com:8080` are not supported (cpp-httplib's other host-keyed APIs are also keyed on hostname only).
|
||||
|
||||
## Read proxy settings from the environment
|
||||
|
||||
cpp-httplib doesn't touch `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` on its own — the config API is always explicit, the same way `set_ca_cert_path()` is. If you'd like that behavior, read the variables in your application and feed them to `set_proxy()` and `set_no_proxy()`.
|
||||
|
||||
```cpp
|
||||
if (const char *v = std::getenv("no_proxy")) {
|
||||
std::vector<std::string> patterns;
|
||||
std::stringstream ss(v);
|
||||
for (std::string item; std::getline(ss, item, ',');) {
|
||||
if (!item.empty()) { patterns.push_back(item); }
|
||||
}
|
||||
cli.set_no_proxy(patterns);
|
||||
}
|
||||
```
|
||||
|
||||
If you also read `HTTP_PROXY` yourself, honor the lowercase `http_proxy` only. The uppercase form is poisoned in CGI/FastCGI environments by the `Proxy:` request header ([CVE-2016-5385 / "httpoxy"](https://httpoxy.org/)). `HTTPS_PROXY` and `NO_PROXY` are safe in either case because their names don't begin with `HTTP_`.
|
||||
@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "C17. Handle Error Codes"
|
||||
order: 17
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
`cli.Get()`, `cli.Post()`, and friends return a `Result`. When the request fails — can't reach the server, times out, etc. — the result is "falsy". To get the specific reason, use `Result::error()`.
|
||||
|
||||
## Basic check
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto res = cli.Get("/api/data");
|
||||
|
||||
if (res) {
|
||||
// the request was sent and a response came back
|
||||
std::cout << "status: " << res->status << std::endl;
|
||||
} else {
|
||||
// the network layer failed
|
||||
std::cerr << "error: " << httplib::to_string(res.error()) << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Use `if (res)` to check success. On failure, `res.error()` returns a `httplib::Error` enum value. Pass it to `to_string()` to get a human-readable description.
|
||||
|
||||
## Common errors
|
||||
|
||||
| Value | Meaning |
|
||||
| --- | --- |
|
||||
| `Error::Connection` | Couldn't connect to the server |
|
||||
| `Error::ConnectionTimeout` | Connection timeout (`set_connection_timeout`) |
|
||||
| `Error::Read` / `Error::Write` | Error during send or receive |
|
||||
| `Error::Timeout` | Overall timeout set via `set_max_timeout` |
|
||||
| `Error::ExceedRedirectCount` | Too many redirects |
|
||||
| `Error::SSLConnection` | TLS handshake failed |
|
||||
| `Error::SSLServerVerification` | Server certificate verification failed |
|
||||
| `Error::Canceled` | A progress callback returned `false` |
|
||||
|
||||
## Network errors vs. HTTP errors
|
||||
|
||||
Even when `res` is truthy, the HTTP status code can still be 4xx or 5xx. These are two different things.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/api/data");
|
||||
if (!res) {
|
||||
// network error (no response received at all)
|
||||
std::cerr << "network error: " << httplib::to_string(res.error()) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (res->status >= 400) {
|
||||
// HTTP error (response received, but the status is bad)
|
||||
std::cerr << "http error: " << res->status << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// success
|
||||
std::cout << res->body << std::endl;
|
||||
```
|
||||
|
||||
Keep them separated in your head: network-layer errors go through `res.error()`, HTTP-level errors through `res->status`.
|
||||
|
||||
> To dig deeper into SSL-related errors, see [C18. Handle SSL errors](../c18-ssl-errors).
|
||||
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "C18. Handle SSL Errors"
|
||||
order: 18
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When an HTTPS request fails, `res.error()` returns values like `Error::SSLConnection` or `Error::SSLServerVerification`. Sometimes that's not enough to pinpoint the cause. That's where `Result::ssl_error()` and `Result::ssl_backend_error()` help.
|
||||
|
||||
## Get the SSL error details
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
auto res = cli.Get("/");
|
||||
|
||||
if (!res) {
|
||||
auto err = res.error();
|
||||
std::cerr << "error: " << httplib::to_string(err) << std::endl;
|
||||
|
||||
if (err == httplib::Error::SSLConnection ||
|
||||
err == httplib::Error::SSLServerVerification) {
|
||||
std::cerr << "ssl_error: " << res.ssl_error() << std::endl;
|
||||
std::cerr << "ssl_backend_error: " << res.ssl_backend_error() << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ssl_error()` returns the error code from the SSL library (e.g., OpenSSL's `SSL_get_error()`). `ssl_backend_error()` gives you the backend's more detailed error value — for OpenSSL, that's `ERR_get_error()`.
|
||||
|
||||
## Format OpenSSL errors as strings
|
||||
|
||||
When you have a value from `ssl_backend_error()`, pass it to OpenSSL's `ERR_error_string()` to get a readable message.
|
||||
|
||||
```cpp
|
||||
#include <openssl/err.h>
|
||||
|
||||
if (res.ssl_backend_error() != 0) {
|
||||
char buf[256];
|
||||
ERR_error_string_n(res.ssl_backend_error(), buf, sizeof(buf));
|
||||
std::cerr << "openssl: " << buf << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
## Common causes
|
||||
|
||||
| Symptom | Usual suspect |
|
||||
| --- | --- |
|
||||
| `SSLServerVerification` | CA certificate path isn't configured, or the cert is self-signed |
|
||||
| `SSLServerHostnameVerification` | The cert's CN/SAN doesn't match the host |
|
||||
| `SSLConnection` | TLS version mismatch, no shared cipher suite |
|
||||
|
||||
> To change certificate verification settings, see [T02. Control SSL certificate verification](../t02-cert-verification).
|
||||
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "C19. Set a Logger on the Client"
|
||||
order: 19
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To log requests sent and responses received by the client, use `set_logger()`. If you only care about errors, there's a separate `set_error_logger()`.
|
||||
|
||||
## Log requests and responses
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
|
||||
cli.set_logger([](const httplib::Request &req, const httplib::Response &res) {
|
||||
std::cout << req.method << " " << req.path
|
||||
<< " -> " << res.status << std::endl;
|
||||
});
|
||||
|
||||
auto res = cli.Get("/users");
|
||||
```
|
||||
|
||||
The callback you pass to `set_logger()` fires once for each completed request. You get both the request and the response as arguments — so you can log the method, path, status, headers, body, or whatever else you need.
|
||||
|
||||
## Catch errors only
|
||||
|
||||
When a network-layer error happens (like `Error::Connection`), `set_logger()` is **not** called — there's no response to log. For those cases, use `set_error_logger()`.
|
||||
|
||||
```cpp
|
||||
cli.set_error_logger([](const httplib::Error &err, const httplib::Request *req) {
|
||||
std::cerr << "error: " << httplib::to_string(err);
|
||||
if (req) {
|
||||
std::cerr << " (" << req->method << " " << req->path << ")";
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
The second argument `req` can be null — it happens when the failure occurred before the request was built. Always null-check before dereferencing.
|
||||
|
||||
## Use both together
|
||||
|
||||
A nice pattern is to log successes through one, failures through the other.
|
||||
|
||||
```cpp
|
||||
cli.set_logger([](const auto &req, const auto &res) {
|
||||
std::cout << "[ok] " << req.method << " " << req.path
|
||||
<< " " << res.status << std::endl;
|
||||
});
|
||||
|
||||
cli.set_error_logger([](const auto &err, const auto *req) {
|
||||
std::cerr << "[ng] " << httplib::to_string(err);
|
||||
if (req) std::cerr << " " << req->method << " " << req->path;
|
||||
std::cerr << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
> **Note:** The log callbacks run synchronously on the same thread as the request. Heavy work inside them slows the request down — push it to a background queue if you need to do anything expensive.
|
||||
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: "E01. Implement an SSE Server"
|
||||
order: 48
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Server-Sent Events (SSE) is a simple protocol for pushing events one-way from server to client. The connection stays open, and the server can send data whenever it wants. It's lighter than WebSocket and fits entirely within HTTP — a nice combination.
|
||||
|
||||
cpp-httplib doesn't have a dedicated SSE server API, but you can implement one with `set_chunked_content_provider()` and `text/event-stream`.
|
||||
|
||||
## Basic SSE server
|
||||
|
||||
```cpp
|
||||
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream",
|
||||
[](size_t offset, httplib::DataSink &sink) {
|
||||
std::string message = "data: hello\n\n";
|
||||
sink.write(message.data(), message.size());
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Three things matter here:
|
||||
|
||||
1. Content-Type is `text/event-stream`
|
||||
2. Messages follow the format `data: <content>\n\n` (the double newline separates events)
|
||||
3. Each `sink.write()` delivers data to the client
|
||||
|
||||
The provider lambda keeps being called as long as the connection is alive.
|
||||
|
||||
## A continuous stream
|
||||
|
||||
Here's a simple example that sends the current time once per second.
|
||||
|
||||
```cpp
|
||||
svr.Get("/time", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream",
|
||||
[&req](size_t offset, httplib::DataSink &sink) {
|
||||
if (req.is_connection_closed()) {
|
||||
sink.done();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto t = std::chrono::system_clock::to_time_t(now);
|
||||
std::string msg = "data: " + std::string(std::ctime(&t)) + "\n";
|
||||
sink.write(msg.data(), msg.size());
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
When the client disconnects, call `sink.done()` to stop. Details in [S16. Detect client disconnection](../s16-disconnect).
|
||||
|
||||
## Heartbeats via comment lines
|
||||
|
||||
Lines starting with `:` are SSE comments — clients ignore them, but they **keep the connection alive**. Handy for preventing proxies and load balancers from closing idle connections.
|
||||
|
||||
```cpp
|
||||
// heartbeat every 30 seconds
|
||||
if (tick_count % 30 == 0) {
|
||||
std::string ping = ": ping\n\n";
|
||||
sink.write(ping.data(), ping.size());
|
||||
}
|
||||
```
|
||||
|
||||
## Relationship with the thread pool
|
||||
|
||||
SSE connections stay open, so each client holds a worker thread. For lots of concurrent connections, enable dynamic scaling on the thread pool.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(8, 128);
|
||||
};
|
||||
```
|
||||
|
||||
See [S21. Configure the thread pool](../s21-thread-pool).
|
||||
|
||||
> **Note:** When `data:` contains newlines, split it into multiple `data:` lines — one per line. This is how the SSE spec requires multiline data to be transmitted.
|
||||
|
||||
> For event names, see [E02. Use named events in SSE](../e02-sse-event-names). For the client side, see [E04. Receive SSE on the client](../e04-sse-client).
|
||||
@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "E02. Use Named Events in SSE"
|
||||
order: 49
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
SSE lets you send multiple kinds of events over the same stream. Give each one a name with the `event:` field, and the client can dispatch to a different handler per type. Great for things like "new message", "user joined", "user left" in a chat app.
|
||||
|
||||
## Send events with names
|
||||
|
||||
```cpp
|
||||
auto send_event = [](httplib::DataSink &sink,
|
||||
const std::string &event,
|
||||
const std::string &data) {
|
||||
std::string msg = "event: " + event + "\n"
|
||||
+ "data: " + data + "\n\n";
|
||||
sink.write(msg.data(), msg.size());
|
||||
};
|
||||
|
||||
svr.Get("/chat/stream", [&](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream",
|
||||
[&, send_event](size_t offset, httplib::DataSink &sink) {
|
||||
send_event(sink, "message", "Hello!");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
send_event(sink, "join", "alice");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
send_event(sink, "leave", "bob");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
A message is `event:` → `data:` → blank line. If you omit `event:`, the client treats it as a default `"message"` event.
|
||||
|
||||
## Attach IDs for reconnect
|
||||
|
||||
When you include an `id:` field, the client automatically sends it back as `Last-Event-ID` on reconnect, telling the server "here's how far I got."
|
||||
|
||||
```cpp
|
||||
auto send_event = [](httplib::DataSink &sink,
|
||||
const std::string &event,
|
||||
const std::string &data,
|
||||
const std::string &id) {
|
||||
std::string msg = "id: " + id + "\n"
|
||||
+ "event: " + event + "\n"
|
||||
+ "data: " + data + "\n\n";
|
||||
sink.write(msg.data(), msg.size());
|
||||
};
|
||||
|
||||
send_event(sink, "message", "Hello!", "42");
|
||||
```
|
||||
|
||||
The ID format is up to you. Monotonic counters or UUIDs both work — just pick something unique and orderable on the server side. See [E03. Handle SSE reconnection](../e03-sse-reconnect) for details.
|
||||
|
||||
## JSON payloads in data
|
||||
|
||||
For structured data, the usual move is to put JSON in `data:`.
|
||||
|
||||
```cpp
|
||||
nlohmann::json payload = {
|
||||
{"user", "alice"},
|
||||
{"text", "Hello!"},
|
||||
};
|
||||
send_event(sink, "message", payload.dump(), "42");
|
||||
```
|
||||
|
||||
On the client, parse the incoming `data` as JSON to get the original object back.
|
||||
|
||||
## Data with newlines
|
||||
|
||||
If the data value contains newlines, split it across multiple `data:` lines.
|
||||
|
||||
```cpp
|
||||
std::string msg = "data: line1\n"
|
||||
"data: line2\n"
|
||||
"data: line3\n\n";
|
||||
sink.write(msg.data(), msg.size());
|
||||
```
|
||||
|
||||
On the client side, these come back as a single `data` string with newlines.
|
||||
|
||||
> **Note:** Using `event:` makes client-side dispatch cleaner, but it also helps in the browser DevTools — events are easier to filter by type. That matters more than you'd expect while debugging.
|
||||
@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "E03. Handle SSE Reconnection"
|
||||
order: 50
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
SSE connections drop for all sorts of network reasons. Clients automatically try to reconnect, so it's a good idea to make your server resume from where it left off.
|
||||
|
||||
## Read `Last-Event-ID`
|
||||
|
||||
When the client reconnects, it sends the ID of the last event it received in the `Last-Event-ID` header. The server reads that and picks up from the next one.
|
||||
|
||||
```cpp
|
||||
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto last_id = req.get_header_value("Last-Event-ID");
|
||||
int start = last_id.empty() ? 0 : std::stoi(last_id) + 1;
|
||||
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream",
|
||||
[start](size_t offset, httplib::DataSink &sink) mutable {
|
||||
static int next_id = 0;
|
||||
if (next_id < start) { next_id = start; }
|
||||
|
||||
std::string msg = "id: " + std::to_string(next_id) + "\n"
|
||||
+ "data: event " + std::to_string(next_id) + "\n\n";
|
||||
sink.write(msg.data(), msg.size());
|
||||
++next_id;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
On the first connect, `Last-Event-ID` is empty, so start from `0`. On reconnect, resume from the next ID. Event history is the server's responsibility — you need to keep recent events around somewhere.
|
||||
|
||||
## Set the reconnect interval
|
||||
|
||||
Sending a `retry:` field tells the client how long to wait before reconnecting, in milliseconds.
|
||||
|
||||
```cpp
|
||||
std::string msg = "retry: 5000\n\n"; // reconnect after 5 seconds
|
||||
sink.write(msg.data(), msg.size());
|
||||
```
|
||||
|
||||
Usually you send this once at the start. During peak load or maintenance windows, a longer retry interval helps reduce reconnect storms.
|
||||
|
||||
## Buffer recent events
|
||||
|
||||
To support reconnection, keep a rolling buffer of recent events on the server.
|
||||
|
||||
```cpp
|
||||
struct EventBuffer {
|
||||
std::mutex mu;
|
||||
std::deque<std::pair<int, std::string>> events; // {id, data}
|
||||
int next_id = 0;
|
||||
|
||||
void push(const std::string &data) {
|
||||
std::lock_guard<std::mutex> lock(mu);
|
||||
events.push_back({next_id++, data});
|
||||
if (events.size() > 1000) { events.pop_front(); }
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, std::string>> since(int id) {
|
||||
std::lock_guard<std::mutex> lock(mu);
|
||||
std::vector<std::pair<int, std::string>> out;
|
||||
for (const auto &e : events) {
|
||||
if (e.first >= id) { out.push_back(e); }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When a client reconnects, call `since(last_id)` to send any events it missed.
|
||||
|
||||
## How much to keep
|
||||
|
||||
The buffer size is a tradeoff between memory and how far back a client can resume. It depends on the use case:
|
||||
|
||||
- Real-time chat: a few minutes to half an hour
|
||||
- Notifications: the last N items
|
||||
- Trading data: persist to a database and pull from there
|
||||
|
||||
> **Warning:** `Last-Event-ID` is a client-provided value — don't trust it blindly. If you read it as a number, validate the range. If it's a string, sanitize it.
|
||||
@ -0,0 +1,99 @@
|
||||
---
|
||||
title: "E04. Receive SSE on the Client"
|
||||
order: 51
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib ships a dedicated `sse::SSEClient` class. It handles auto-reconnect, per-event-name dispatch, and `Last-Event-ID` tracking for you — so receiving SSE is painless.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "data: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start(); // blocking
|
||||
```
|
||||
|
||||
Build an `SSEClient` with a `Client` and a path, register a callback with `on_message()`, and call `start()`. The event loop kicks in and automatically reconnects if the connection drops.
|
||||
|
||||
## Dispatch by event name
|
||||
|
||||
When the server sends events with an `event:` field, register a handler per name via `on_event()`.
|
||||
|
||||
```cpp
|
||||
sse.on_event("message", [](const auto &msg) {
|
||||
std::cout << "chat: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.on_event("join", [](const auto &msg) {
|
||||
std::cout << msg.data << " joined" << std::endl;
|
||||
});
|
||||
|
||||
sse.on_event("leave", [](const auto &msg) {
|
||||
std::cout << msg.data << " left" << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
`on_message()` serves as a generic fallback for unnamed events (the default `message` type).
|
||||
|
||||
## Connection lifecycle and errors
|
||||
|
||||
```cpp
|
||||
sse.on_open([] {
|
||||
std::cout << "connected" << std::endl;
|
||||
});
|
||||
|
||||
sse.on_error([](httplib::Error err) {
|
||||
std::cerr << "error: " << httplib::to_string(err) << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
Hook into connection open and error events. Even when the error handler fires, `SSEClient` keeps trying to reconnect in the background.
|
||||
|
||||
## Run asynchronously
|
||||
|
||||
If you don't want to block the main thread, use `start_async()`.
|
||||
|
||||
```cpp
|
||||
sse.start_async();
|
||||
|
||||
// main thread continues to do other things
|
||||
do_other_work();
|
||||
|
||||
// when you're done, stop it
|
||||
sse.stop();
|
||||
```
|
||||
|
||||
`start_async()` spawns a background thread to run the event loop. Use `stop()` to shut it down cleanly.
|
||||
|
||||
## Configure reconnection
|
||||
|
||||
You can tune the reconnect interval and maximum retries.
|
||||
|
||||
```cpp
|
||||
sse.set_reconnect_interval(5000); // 5 seconds
|
||||
sse.set_max_reconnect_attempts(10); // up to 10 (0 = unlimited)
|
||||
```
|
||||
|
||||
If the server sends a `retry:` field, that takes precedence.
|
||||
|
||||
## Automatic Last-Event-ID
|
||||
|
||||
`SSEClient` tracks the `id` of each received event internally and sends it back as `Last-Event-ID` on reconnect. As long as the server sends events with `id:`, this all works automatically.
|
||||
|
||||
```cpp
|
||||
std::cout << "last id: " << sse.last_event_id() << std::endl;
|
||||
```
|
||||
|
||||
Use `last_event_id()` to read the current value.
|
||||
|
||||
> **Note:** `SSEClient::start()` blocks, which is fine for a one-off command-line tool. For GUI apps or embedded in a server, the `start_async()` + `stop()` pair is the usual pattern.
|
||||
|
||||
> For the server side, see [E01. Implement an SSE server](../e01-sse-server).
|
||||
101
libs/cpp-httplib-0.57.1/docs-src/pages/en/cookbook/index.md
Normal file
101
libs/cpp-httplib-0.57.1/docs-src/pages/en/cookbook/index.md
Normal file
@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "Cookbook"
|
||||
order: 0
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
A collection of recipes that answer "How do I...?" questions. Each recipe is self-contained — read only what you need. For an introduction to the basics, see the [Tour](../tour/).
|
||||
|
||||
## Client
|
||||
|
||||
### Basics
|
||||
- [C01. Get the response body / save to a file](c01-get-response-body)
|
||||
- [C02. Send and receive JSON](c02-json)
|
||||
- [C03. Set default headers](c03-default-headers)
|
||||
- [C04. Follow redirects](c04-follow-location)
|
||||
|
||||
### Authentication
|
||||
- [C05. Use Basic authentication](c05-basic-auth)
|
||||
- [C06. Call an API with a Bearer token](c06-bearer-token)
|
||||
|
||||
### File Upload
|
||||
- [C07. Upload a file as multipart form data](c07-multipart-upload)
|
||||
- [C08. POST a file as raw binary](c08-post-file-body)
|
||||
- [C09. Send the body with chunked transfer](c09-chunked-upload)
|
||||
|
||||
### Streaming & Progress
|
||||
- [C10. Receive a response as a stream](c10-stream-response)
|
||||
- [C11. Use the progress callback](c11-progress-callback)
|
||||
|
||||
### Connection & Performance
|
||||
- [C12. Set timeouts](c12-timeouts)
|
||||
- [C13. Set an overall timeout](c13-max-timeout)
|
||||
- [C14. Understand connection reuse and Keep-Alive behavior](c14-keep-alive)
|
||||
- [C15. Enable compression](c15-compression)
|
||||
- [C16. Send requests through a proxy](c16-proxy)
|
||||
|
||||
### Error Handling & Debugging
|
||||
- [C17. Handle error codes](c17-error-codes)
|
||||
- [C18. Handle SSL errors](c18-ssl-errors)
|
||||
- [C19. Set up client logging](c19-client-logger)
|
||||
|
||||
## Server
|
||||
|
||||
### Basics
|
||||
- [S01. Register GET / POST / PUT / DELETE handlers](s01-handlers)
|
||||
- [S02. Receive JSON requests and return JSON responses](s02-json-api)
|
||||
- [S03. Use path parameters](s03-path-params)
|
||||
- [S04. Set up a static file server](s04-static-files)
|
||||
|
||||
### Streaming & Files
|
||||
- [S05. Stream a large file in the response](s05-stream-response)
|
||||
- [S06. Return a file download response](s06-download-response)
|
||||
- [S07. Receive multipart data as a stream](s07-multipart-reader)
|
||||
- [S08. Return a compressed response](s08-compress-response)
|
||||
|
||||
### Handler Chain
|
||||
- [S09. Add pre-processing to all routes](s09-pre-routing)
|
||||
- [S10. Add response headers with a post-routing handler](s10-post-routing)
|
||||
- [S11. Authenticate per route with a pre-request handler](s11-pre-request)
|
||||
- [S12. Pass data between handlers with `res.user_data`](s12-user-data)
|
||||
|
||||
### Error Handling & Debugging
|
||||
- [S13. Return custom error pages](s13-error-handler)
|
||||
- [S14. Catch exceptions](s14-exception-handler)
|
||||
- [S15. Log requests](s15-server-logger)
|
||||
- [S16. Detect client disconnection](s16-disconnect)
|
||||
|
||||
### Operations & Tuning
|
||||
- [S17. Bind to any available port](s17-bind-any-port)
|
||||
- [S18. Control startup order with `listen_after_bind`](s18-listen-after-bind)
|
||||
- [S19. Shut down gracefully](s19-graceful-shutdown)
|
||||
- [S20. Tune Keep-Alive](s20-keep-alive)
|
||||
- [S21. Configure the thread pool](s21-thread-pool)
|
||||
- [S22. Talk over a Unix domain socket](s22-unix-socket)
|
||||
|
||||
### Protocol Extensions
|
||||
- [S23. Handle custom HTTP methods](s23-custom-methods)
|
||||
|
||||
## TLS / Security
|
||||
|
||||
- [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](t01-tls-backends)
|
||||
- [T02. Control SSL certificate verification](t02-cert-verification)
|
||||
- [T03. Start an SSL/TLS server](t03-ssl-server)
|
||||
- [T04. Configure mTLS](t04-mtls)
|
||||
- [T05. Access the peer certificate on the server](t05-peer-cert)
|
||||
|
||||
## SSE
|
||||
|
||||
- [E01. Implement an SSE server](e01-sse-server)
|
||||
- [E02. Use named events in SSE](e02-sse-event-names)
|
||||
- [E03. Handle SSE reconnection](e03-sse-reconnect)
|
||||
- [E04. Receive SSE on the client](e04-sse-client)
|
||||
|
||||
## WebSocket
|
||||
|
||||
- [W01. Implement a WebSocket echo server and client](w01-websocket-echo)
|
||||
- [W02. Set a WebSocket heartbeat](w02-websocket-ping)
|
||||
- [W03. Handle connection close](w03-websocket-close)
|
||||
- [W04. Send and receive binary frames](w04-websocket-binary)
|
||||
- [W05. Configure TLS for wss:// connections](w05-websocket-tls)
|
||||
- [W06. Set timeouts](w06-websocket-timeouts)
|
||||
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "S01. Register GET / POST / PUT / DELETE Handlers"
|
||||
order: 20
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
With `httplib::Server`, you register a handler per HTTP method. Just pass a pattern and a lambda to `Get()`, `Post()`, `Put()`, or `Delete()`. For methods outside the built-in set, such as WebDAV's `PROPFIND`, use `CustomRoute()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/hello", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content("Hello, World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/api/items", [](const httplib::Request &req, httplib::Response &res) {
|
||||
// req.body holds the request body
|
||||
res.status = 201;
|
||||
res.set_content("Created", "text/plain");
|
||||
});
|
||||
|
||||
svr.Put("/api/items/1", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content("Updated", "text/plain");
|
||||
});
|
||||
|
||||
svr.Delete("/api/items/1", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.status = 204;
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
Handlers take `(const Request&, Response&)`. Use `res.set_content()` to set the body and Content-Type, and `res.status` for the status code. `listen()` starts the server and blocks.
|
||||
|
||||
## Read query parameters
|
||||
|
||||
```cpp
|
||||
svr.Get("/search", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
auto limit = req.get_param_value("limit");
|
||||
res.set_content("q=" + q + ", limit=" + limit, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`req.get_param_value()` pulls a value from the query string. Use `req.has_param("q")` if you want to check existence first.
|
||||
|
||||
## Read request headers
|
||||
|
||||
```cpp
|
||||
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto ua = req.get_header_value("User-Agent");
|
||||
res.set_content("UA: " + ua, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
To add a response header, use `res.set_header("Name", "Value")`.
|
||||
|
||||
> **Note:** `listen()` is a blocking call. To run it on a different thread, wrap it in `std::thread`. If you need non-blocking startup, see [S18. Control startup order with `listen_after_bind`](../s18-listen-after-bind).
|
||||
|
||||
> To use path parameters like `/users/:id`, see [S03. Use path parameters](../s03-path-params).
|
||||
|
||||
> For methods outside the built-in set, such as WebDAV's `PROPFIND`, see [S23. Handle custom HTTP methods](../s23-custom-methods).
|
||||
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: "S02. Receive a JSON Request and Return a JSON Response"
|
||||
order: 21
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib doesn't include a JSON parser. On the server side, combine it with something like [nlohmann/json](https://github.com/nlohmann/json). The examples below use `nlohmann/json`.
|
||||
|
||||
## Receive and return JSON
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Post("/api/users", [](const httplib::Request &req, httplib::Response &res) {
|
||||
try {
|
||||
auto in = nlohmann::json::parse(req.body);
|
||||
|
||||
nlohmann::json out = {
|
||||
{"id", 42},
|
||||
{"name", in["name"]},
|
||||
{"created_at", "2026-04-10T12:00:00Z"},
|
||||
};
|
||||
|
||||
res.status = 201;
|
||||
res.set_content(out.dump(), "application/json");
|
||||
} catch (const std::exception &e) {
|
||||
res.status = 400;
|
||||
res.set_content("{\"error\":\"invalid json\"}", "application/json");
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
`req.body` is a plain `std::string`, so you pass it straight to your JSON library. For the response, `dump()` to a string and set the Content-Type to `application/json`.
|
||||
|
||||
## Check the Content-Type
|
||||
|
||||
```cpp
|
||||
svr.Post("/api/users", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto content_type = req.get_header_value("Content-Type");
|
||||
if (content_type.find("application/json") == std::string::npos) {
|
||||
res.status = 415; // Unsupported Media Type
|
||||
return;
|
||||
}
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
When you strictly want JSON only, verify the Content-Type up front.
|
||||
|
||||
## A helper for JSON responses
|
||||
|
||||
If you're writing the same pattern repeatedly, a small helper saves typing.
|
||||
|
||||
```cpp
|
||||
auto send_json = [](httplib::Response &res, int status, const nlohmann::json &j) {
|
||||
res.status = status;
|
||||
res.set_content(j.dump(), "application/json");
|
||||
};
|
||||
|
||||
svr.Get("/api/health", [&](const auto &req, auto &res) {
|
||||
send_json(res, 200, {{"status", "ok"}});
|
||||
});
|
||||
```
|
||||
|
||||
> **Note:** A large JSON body ends up entirely in `req.body`, which means it all sits in memory. For huge payloads, consider streaming reception — see [S07. Receive multipart data as a stream](../s07-multipart-reader).
|
||||
|
||||
> For the client side, see [C02. Send and receive JSON](../c02-json).
|
||||
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "S03. Use Path Parameters"
|
||||
order: 22
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
For dynamic URLs like `/users/:id` — the staple of REST APIs — just put `:name` in the path pattern. The matched values end up in `req.path_params`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:id", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("user id: " + id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
A request to `/users/42` fills `req.path_params["id"]` with `"42"`. `path_params` is a `std::unordered_map<std::string, std::string>`, so use `at()` to read it.
|
||||
|
||||
## Multiple parameters
|
||||
|
||||
You can have as many as you need.
|
||||
|
||||
```cpp
|
||||
svr.Get("/orgs/:org/repos/:repo", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto org = req.path_params.at("org");
|
||||
auto repo = req.path_params.at("repo");
|
||||
res.set_content(org + "/" + repo, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
This matches paths like `/orgs/anthropic/repos/cpp-httplib`.
|
||||
|
||||
## Regex patterns
|
||||
|
||||
For more flexible matching, use a `std::regex`-based pattern.
|
||||
|
||||
```cpp
|
||||
svr.Get(R"(/users/(\d+))", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto id = req.matches[1];
|
||||
res.set_content("user id: " + std::string(id), "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
Parentheses in the pattern become captures in `req.matches`. `req.matches[0]` is the full match; `req.matches[1]` onward are the captures.
|
||||
|
||||
## Which to use
|
||||
|
||||
- For plain IDs or slugs, `:name` is enough — readable, and the shape is obvious
|
||||
- Use regex when you want to constrain the URL to, say, numbers only or a UUID format
|
||||
- Mixing both can get confusing — stick with one style per project
|
||||
|
||||
> **Note:** Path parameters come in as strings. If you need an integer, convert with `std::stoi()` and don't forget to handle conversion errors.
|
||||
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "S04. Serve Static Files"
|
||||
order: 23
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To serve static files like HTML, CSS, and images, use `set_mount_point()`. Just map a URL path to a local directory, and the whole directory becomes accessible.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
svr.set_mount_point("/", "./public");
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
`./public/index.html` is now reachable at `http://localhost:8080/index.html`, and `./public/css/style.css` at `http://localhost:8080/css/style.css`. The directory layout maps directly to URLs.
|
||||
|
||||
## Multiple mount points
|
||||
|
||||
You can register more than one mount point.
|
||||
|
||||
```cpp
|
||||
svr.set_mount_point("/", "./public");
|
||||
svr.set_mount_point("/assets", "./dist/assets");
|
||||
svr.set_mount_point("/uploads", "./var/uploads");
|
||||
```
|
||||
|
||||
You can even mount multiple directories at the same path — they're searched in registration order, and the first hit wins.
|
||||
|
||||
## Combine with API handlers
|
||||
|
||||
Static files and API handlers coexist happily. Handlers registered with `Get()` and friends take priority; the mount points are searched only when nothing matches.
|
||||
|
||||
```cpp
|
||||
svr.Get("/api/users", [](const auto &req, auto &res) {
|
||||
res.set_content("[]", "application/json");
|
||||
});
|
||||
|
||||
svr.set_mount_point("/", "./public");
|
||||
```
|
||||
|
||||
This gives you an SPA-friendly setup: `/api/*` hits the handlers, everything else is served from `./public/`.
|
||||
|
||||
## Add MIME types
|
||||
|
||||
cpp-httplib ships with a built-in extension-to-Content-Type map, but you can add your own.
|
||||
|
||||
```cpp
|
||||
svr.set_file_extension_and_mimetype_mapping("wasm", "application/wasm");
|
||||
```
|
||||
|
||||
> **Warning:** The static file server methods are **not thread-safe**. Don't call them after `listen()` — configure everything before starting the server.
|
||||
|
||||
> For download-style responses, see [S06. Return a file download response](../s06-download-response).
|
||||
@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "S05. Stream a Large File in the Response"
|
||||
order: 24
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When the response is a huge file or data generated on the fly, loading the whole thing into memory isn't realistic. Use `Response::set_content_provider()` to produce data in chunks as you send it.
|
||||
|
||||
## When the size is known
|
||||
|
||||
```cpp
|
||||
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
|
||||
size_t total_size = get_file_size("large.bin");
|
||||
|
||||
res.set_content_provider(
|
||||
total_size, "application/octet-stream",
|
||||
[](size_t offset, size_t length, httplib::DataSink &sink) {
|
||||
auto data = read_range_from_file("large.bin", offset, length);
|
||||
sink.write(data.data(), data.size());
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The lambda is called repeatedly with `offset` and `length`. Read just that range and write it to `sink`. Only a small chunk sits in memory at any given time.
|
||||
|
||||
## Just send a file
|
||||
|
||||
If you only want to serve a file, `set_file_content()` is far simpler.
|
||||
|
||||
```cpp
|
||||
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_file_content("large.bin", "application/octet-stream");
|
||||
});
|
||||
```
|
||||
|
||||
It streams internally, so even huge files are safe. Omit the Content-Type and it's guessed from the extension.
|
||||
|
||||
## When the size is unknown — chunked transfer
|
||||
|
||||
For data generated on the fly, where you don't know the total size up front, use `set_chunked_content_provider()`. It's sent with HTTP chunked transfer encoding.
|
||||
|
||||
```cpp
|
||||
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, httplib::DataSink &sink) {
|
||||
auto chunk = produce_next_chunk();
|
||||
if (chunk.empty()) {
|
||||
sink.done(); // done sending
|
||||
return true;
|
||||
}
|
||||
sink.write(chunk.data(), chunk.size());
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Call `sink.done()` to signal the end.
|
||||
|
||||
> **Note:** The provider lambda is called multiple times. Watch out for the lifetime of captured variables — wrap them in a `std::shared_ptr` if needed.
|
||||
|
||||
> To serve the file as a download, see [S06. Return a file download response](../s06-download-response).
|
||||
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "S06. Return a File Download Response"
|
||||
order: 25
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To force a browser to show a **download dialog** instead of rendering inline, send a `Content-Disposition` header. There's no special cpp-httplib API for this — it's just a header.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.Get("/download/report", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header("Content-Disposition", "attachment; filename=\"report.pdf\"");
|
||||
res.set_file_content("reports/2026-04.pdf", "application/pdf");
|
||||
});
|
||||
```
|
||||
|
||||
`Content-Disposition: attachment` makes the browser pop up a "Save As" dialog. The `filename=` parameter becomes the default save name.
|
||||
|
||||
## Non-ASCII file names
|
||||
|
||||
For file names with non-ASCII characters or spaces, use the RFC 5987 `filename*` form.
|
||||
|
||||
```cpp
|
||||
svr.Get("/download/report", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"report.pdf\"; "
|
||||
"filename*=UTF-8''%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88.pdf");
|
||||
res.set_file_content("reports/2026-04.pdf", "application/pdf");
|
||||
});
|
||||
```
|
||||
|
||||
The part after `filename*=UTF-8''` is URL-encoded UTF-8. Keep the ASCII `filename=` too, as a fallback for older browsers.
|
||||
|
||||
## Download dynamically generated data
|
||||
|
||||
You don't need a real file — you can serve a generated string as a download directly.
|
||||
|
||||
```cpp
|
||||
svr.Get("/export.csv", [](const httplib::Request &req, httplib::Response &res) {
|
||||
std::string csv = build_csv();
|
||||
res.set_header("Content-Disposition", "attachment; filename=\"export.csv\"");
|
||||
res.set_content(csv, "text/csv");
|
||||
});
|
||||
```
|
||||
|
||||
This is the classic pattern for CSV exports.
|
||||
|
||||
> **Note:** Some browsers will trigger a download based on Content-Type alone, even without `Content-Disposition`. Conversely, setting `inline` tries to render the content in the browser when possible.
|
||||
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "S07. Receive Multipart Data as a Stream"
|
||||
order: 26
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
A naive upload handler puts the whole request into `req.body`, which blows up memory for large files. Use `HandlerWithContentReader` to receive the body chunk by chunk.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload",
|
||||
[](const httplib::Request &req, httplib::Response &res,
|
||||
const httplib::ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
content_reader(
|
||||
// headers of each part
|
||||
[&](const httplib::FormData &file) {
|
||||
std::cout << "name: " << file.name
|
||||
<< ", filename: " << file.filename << std::endl;
|
||||
return true;
|
||||
},
|
||||
// body of each part (called multiple times)
|
||||
[&](const char *data, size_t len) {
|
||||
// write to disk here, for example
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
// plain request body
|
||||
content_reader([&](const char *data, size_t len) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
The `content_reader` has two call shapes. For multipart data, pass two callbacks (one for headers, one for body). For plain bodies, pass just one.
|
||||
|
||||
## Write directly to disk
|
||||
|
||||
Here's how to stream an uploaded file to disk.
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload",
|
||||
[](const httplib::Request &req, httplib::Response &res,
|
||||
const httplib::ContentReader &content_reader) {
|
||||
std::ofstream ofs;
|
||||
|
||||
content_reader(
|
||||
[&](const httplib::FormData &file) {
|
||||
if (!file.filename.empty()) {
|
||||
ofs.open("uploads/" + file.filename, std::ios::binary);
|
||||
}
|
||||
return static_cast<bool>(ofs);
|
||||
},
|
||||
[&](const char *data, size_t len) {
|
||||
ofs.write(data, len);
|
||||
return static_cast<bool>(ofs);
|
||||
});
|
||||
|
||||
res.set_content("uploaded", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
Only a small chunk sits in memory at any moment, so gigabyte-scale files are no problem.
|
||||
|
||||
## Count the parts yourself
|
||||
|
||||
There is a cap on the number of parts, `CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT` (1024 by default), but it only applies to the buffered path, where every part is accumulated into `req.form`. The `ContentReader` keeps nothing on the library side, so the cap does not apply here.
|
||||
|
||||
If you want an upper bound, count the parts yourself and return `false` from the header callback. The parser stops right there.
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload",
|
||||
[](const httplib::Request &req, httplib::Response &res,
|
||||
const httplib::ContentReader &content_reader) {
|
||||
size_t count = 0;
|
||||
|
||||
auto ok = content_reader(
|
||||
[&](const httplib::FormData &file) {
|
||||
if (++count > 100) { return false; } // stop here
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t len) {
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!ok) {
|
||||
res.status = httplib::StatusCode::BadRequest_400;
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
When `content_reader` returns `false`, set the response status yourself. The rest of the body is left unread and the connection is closed, so a client that is still sending sees the connection drop.
|
||||
|
||||
> **Warning:** When you use `HandlerWithContentReader`, `req.body` stays **empty**. Handle the body yourself inside the callbacks.
|
||||
|
||||
> For the client side of multipart uploads, see [C07. Upload a file as multipart form data](../c07-multipart-upload).
|
||||
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "S08. Return a Compressed Response"
|
||||
order: 27
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib automatically compresses response bodies when the client indicates support via `Accept-Encoding`. The handler doesn't need to do anything special. Supported encodings are gzip, Brotli, and Zstd.
|
||||
|
||||
## Build-time setup
|
||||
|
||||
To enable compression, define the relevant macros before including `httplib.h`:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_ZLIB_SUPPORT // gzip
|
||||
#define CPPHTTPLIB_BROTLI_SUPPORT // brotli
|
||||
#define CPPHTTPLIB_ZSTD_SUPPORT // zstd
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
You'll also need to link `zlib`, `brotli`, and `zstd` respectively. Enable only what you need.
|
||||
|
||||
## Usage
|
||||
|
||||
```cpp
|
||||
svr.Get("/api/data", [](const httplib::Request &req, httplib::Response &res) {
|
||||
std::string body = build_large_response();
|
||||
res.set_content(body, "application/json");
|
||||
});
|
||||
```
|
||||
|
||||
That's it. If the client sent `Accept-Encoding: gzip`, cpp-httplib compresses the response with gzip automatically. `Content-Encoding: gzip` and `Vary: Accept-Encoding` are added for you.
|
||||
|
||||
## Encoding priority
|
||||
|
||||
When the client accepts multiple encodings, cpp-httplib picks in this order (among those enabled at build time): Brotli → Zstd → gzip. Your code doesn't need to care — you always get the most efficient option available.
|
||||
|
||||
## Streaming responses are compressed too
|
||||
|
||||
Streaming responses via `set_chunked_content_provider()` get the same automatic compression.
|
||||
|
||||
```cpp
|
||||
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, httplib::DataSink &sink) {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Static files need to be opted in
|
||||
|
||||
Files served as they are, through `set_mount_point()` or `Response::set_file_content()`, are not compressed by default. Turn it on with:
|
||||
|
||||
```cpp
|
||||
svr.set_static_file_compression(true);
|
||||
```
|
||||
|
||||
Only files within a size range are compressed, and both ends of it can be moved:
|
||||
|
||||
```cpp
|
||||
svr.set_static_file_compression_min_length(512);
|
||||
svr.set_static_file_compression_max_length(1024 * 1024);
|
||||
```
|
||||
|
||||
The lower bound defaults to 1400 bytes. A response that already fits in a single 1500-byte MTU is not delivered any faster for being smaller, and a file of a few bytes comes back larger than it went in, because gzip's header and trailer outweigh what deflate saves.
|
||||
|
||||
The upper bound defaults to 4MB and exists for a different reason: the file is compressed on every request, and the compressed bytes stay in memory until the response has been written, so the peak cost scales with the number of requests in flight. It bounds what a single request can cost, and says nothing about how well large files compress, so raising it is reasonable when the files are known and the traffic is not.
|
||||
|
||||
Either bound takes `0` to turn it off, and each has a compile-time default (`CPPHTTPLIB_STATIC_FILE_COMPRESSION_MIN_LENGTH`, `CPPHTTPLIB_STATIC_FILE_COMPRESSION_MAX_LENGTH`).
|
||||
|
||||
A compressed response keeps its `Content-Length`, so `HEAD` reports the same size a `GET` would. Two details to know: Range requests are answered from the uncompressed representation, and the `ETag` carries the coding it belongs to, as in `W/"...-gzip"`.
|
||||
|
||||
Content providers registered with `set_content_provider()` are not covered. Running one through a compressor holds each write back until the internal buffer fills, which stalls providers that build their body a piece at a time. To compress a generated body, use `set_chunked_content_provider()`.
|
||||
|
||||
> **Note:** The size range covers static files only. A body passed to `set_content()` is compressed whenever the client accepts it and the MIME type is compressible, however small it is, so a response of a few bytes ends up larger than it started. Decide in the handler if you want to avoid that.
|
||||
|
||||
> For the client-side counterpart, see [C15. Enable compression](../c15-compression).
|
||||
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "S09. Add Pre-Processing to All Routes"
|
||||
order: 28
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Sometimes you want the same logic to run before every request — auth checks, logging, rate limiting. Register those with `set_pre_routing_handler()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
std::cout << req.method << " " << req.path << std::endl;
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
The pre-routing handler runs **before routing**. It catches every request — including ones that don't match any handler.
|
||||
|
||||
The `HandlerResponse` return value is key:
|
||||
|
||||
- Return `Unhandled` → continue normally (routing and the actual handler run)
|
||||
- Return `Handled` → the response is considered complete, skip the rest
|
||||
|
||||
## Use it for authentication
|
||||
|
||||
Put your shared auth check in one place.
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
if (req.path.rfind("/public", 0) == 0) {
|
||||
return httplib::Server::HandlerResponse::Unhandled; // no auth needed
|
||||
}
|
||||
|
||||
auto auth = req.get_header_value("Authorization");
|
||||
if (auth.empty()) {
|
||||
res.status = 401;
|
||||
res.set_content("unauthorized", "text/plain");
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
}
|
||||
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
If auth fails, return `Handled` to respond with 401 immediately. If it passes, return `Unhandled` and let routing take over.
|
||||
|
||||
## For per-route auth
|
||||
|
||||
If you want different auth rules per route rather than a single global check, `set_pre_request_handler()` is a better fit. See [S11. Authenticate per route with a pre-request handler](../s11-pre-request).
|
||||
|
||||
> **Note:** If all you want is to modify the response, `set_post_routing_handler()` is the right tool. See [S10. Add response headers with a post-routing handler](../s10-post-routing).
|
||||
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "S10. Add Response Headers with a Post-Routing Handler"
|
||||
order: 29
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Sometimes you want to add shared headers to the response after the handler has run — CORS headers, security headers, a request ID, and so on. That's what `set_post_routing_handler()` is for.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.set_post_routing_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header("X-Request-ID", generate_request_id());
|
||||
});
|
||||
```
|
||||
|
||||
The post-routing handler runs **after the route handler, before the response is sent**. From here you can call `res.set_header()` or `res.headers.erase()` to add or remove headers across every response in one place.
|
||||
|
||||
## Add CORS headers
|
||||
|
||||
CORS is a classic use case.
|
||||
|
||||
```cpp
|
||||
svr.set_post_routing_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header("Access-Control-Allow-Origin", "*");
|
||||
res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
});
|
||||
```
|
||||
|
||||
For the preflight `OPTIONS` requests, register a separate handler — or handle them in the pre-routing handler.
|
||||
|
||||
```cpp
|
||||
svr.Options("/.*", [](const auto &req, auto &res) {
|
||||
res.status = 204;
|
||||
});
|
||||
```
|
||||
|
||||
## Bundle your security headers
|
||||
|
||||
Manage browser security headers in one spot.
|
||||
|
||||
```cpp
|
||||
svr.set_post_routing_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header("X-Content-Type-Options", "nosniff");
|
||||
res.set_header("X-Frame-Options", "DENY");
|
||||
res.set_header("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
});
|
||||
```
|
||||
|
||||
No matter which handler produced the response, the same headers get attached.
|
||||
|
||||
> **Note:** The post-routing handler also runs for responses that didn't match any route and for responses from error handlers. That's exactly what you want when you need certain headers on every response, guaranteed.
|
||||
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "S11. Authenticate Per Route with a Pre-Request Handler"
|
||||
order: 30
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
The `set_pre_routing_handler()` from [S09. Add pre-processing to all routes](../s09-pre-routing) runs **before routing**, so it has no idea which route matched. When you want per-route behavior, `set_pre_request_handler()` is what you need.
|
||||
|
||||
## Pre-routing vs. pre-request
|
||||
|
||||
| Hook | When it runs | Route info | Request body |
|
||||
| --- | --- | --- | --- |
|
||||
| `set_pre_routing_handler` | Before routing | Not available | Not read yet |
|
||||
| `set_pre_request_handler` | After routing, right before the route handler | Available via `req.matched_route` | Not read yet |
|
||||
|
||||
In a pre-request handler, `req.matched_route` holds the **pattern string** that matched. You can vary behavior based on the route definition itself.
|
||||
|
||||
Because the body has not been read when the pre-request handler runs, you can reject a request — for example on a failed auth check — without consuming a (potentially large) request body. Note that this also means `req.body` and form fields parsed from the body are not available here; inspect headers, the path, query parameters, or `req.matched_route` instead.
|
||||
|
||||
## Switch auth per route
|
||||
|
||||
```cpp
|
||||
svr.set_pre_request_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
// require auth for routes starting with /admin
|
||||
if (req.matched_route.rfind("/admin", 0) == 0) {
|
||||
auto token = req.get_header_value("Authorization");
|
||||
if (!is_admin_token(token)) {
|
||||
res.status = 403;
|
||||
res.set_content("forbidden", "text/plain");
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
}
|
||||
}
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
`matched_route` is the pattern **before** path parameters are expanded (e.g. `/admin/users/:id`). You compare against the route definition, not the actual request path, so IDs or names don't throw you off.
|
||||
|
||||
The pre-request handler also runs for routes registered with `svr.WebSocket()`. It is called before the `101 Switching Protocols` response, so returning `Handled` sends your HTTP response (such as a 403) and the connection is never upgraded.
|
||||
|
||||
## Return values
|
||||
|
||||
Same as pre-routing — return `HandlerResponse`.
|
||||
|
||||
- `Unhandled`: continue (the route handler runs)
|
||||
- `Handled`: we're done, skip the route handler
|
||||
|
||||
## Passing auth info to the route handler
|
||||
|
||||
To pass decoded user info into the route handler, use `res.user_data`. See [S12. Pass data between handlers with `res.user_data`](../s12-user-data).
|
||||
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "S12. Pass Data Between Handlers with res.user_data"
|
||||
order: 31
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Say your pre-request handler decodes an auth token and you want the route handler to use the result. That "data handoff between handlers" is what `res.user_data` is for — it holds values of arbitrary types.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
struct AuthUser {
|
||||
std::string id;
|
||||
std::string name;
|
||||
bool is_admin;
|
||||
};
|
||||
|
||||
svr.set_pre_request_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
auto token = req.get_header_value("Authorization");
|
||||
auto user = decode_token(token); // decode the auth token
|
||||
res.user_data.set("user", user);
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto *user = res.user_data.get<AuthUser>("user");
|
||||
if (!user) {
|
||||
res.status = 401;
|
||||
return;
|
||||
}
|
||||
res.set_content("Hello, " + user->name, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`user_data.set()` stores a value of any type, and `user_data.get<T>()` retrieves it. If you give the wrong type you get `nullptr` back — so be careful.
|
||||
|
||||
## Typical value types
|
||||
|
||||
Strings, numbers, structs, `std::shared_ptr` — anything copyable or movable works.
|
||||
|
||||
```cpp
|
||||
res.user_data.set("user_id", std::string{"42"});
|
||||
res.user_data.set("is_admin", true);
|
||||
res.user_data.set("started_at", std::chrono::steady_clock::now());
|
||||
```
|
||||
|
||||
## Where to set, where to read
|
||||
|
||||
The usual flow is: set it in `set_pre_routing_handler()` or `set_pre_request_handler()`, read it in the route handler. Pre-request runs after routing, so you can combine it with `req.matched_route` to set values only for specific routes.
|
||||
|
||||
## A gotcha
|
||||
|
||||
`user_data` lives on `Response`, not `Request`. That's because handlers get `Response&` (mutable) but only `const Request&`. It looks odd at first, but it makes sense once you think of it as "the mutable context shared between handlers."
|
||||
|
||||
> **Warning:** `user_data.get<T>()` returns `nullptr` when the type doesn't match. Use the exact same type on set and get. Storing as `AuthUser` and fetching as `const AuthUser` won't work.
|
||||
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "S13. Return a Custom Error Page"
|
||||
order: 32
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To customize the response for 4xx or 5xx errors, use `set_error_handler()`. You can replace the plain default error page with your own HTML or JSON.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
|
||||
auto body = "<h1>Error " + std::to_string(res.status) + "</h1>";
|
||||
res.set_content(body, "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
The error handler runs right before an error response is sent — any time `res.status` is 4xx or 5xx. Replace the body with `res.set_content()` and every error response uses the same template.
|
||||
|
||||
## Branch by status code
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
|
||||
if (res.status == 404) {
|
||||
res.set_content("<h1>Not Found</h1><p>" + req.path + "</p>", "text/html");
|
||||
} else if (res.status >= 500) {
|
||||
res.set_content("<h1>Server Error</h1>", "text/html");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Checking `res.status` lets you show a custom message for 404s and a "contact support" link for 5xx errors.
|
||||
|
||||
## JSON error responses
|
||||
|
||||
For an API server, you probably want errors as JSON.
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
|
||||
nlohmann::json j = {
|
||||
{"error", true},
|
||||
{"status", res.status},
|
||||
{"path", req.path},
|
||||
};
|
||||
res.set_content(j.dump(), "application/json");
|
||||
});
|
||||
```
|
||||
|
||||
Now every error comes back in a consistent JSON shape.
|
||||
|
||||
> **Note:** `set_error_handler()` also fires for 500 responses caused by exceptions thrown from a route handler. To get at the exception itself, combine it with `set_exception_handler()`. See [S14. Catch exceptions](../s14-exception-handler).
|
||||
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "S14. Catch Exceptions"
|
||||
order: 33
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When a route handler throws, cpp-httplib keeps the server running and responds with 500. By default, though, very little of the error information reaches the client. `set_exception_handler()` lets you intercept exceptions and build your own response.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.set_exception_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res,
|
||||
std::exception_ptr ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception &e) {
|
||||
res.status = 500;
|
||||
res.set_content(std::string("error: ") + e.what(), "text/plain");
|
||||
} catch (...) {
|
||||
res.status = 500;
|
||||
res.set_content("unknown error", "text/plain");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The handler receives a `std::exception_ptr`. The idiomatic move is to rethrow it with `std::rethrow_exception()` and catch by type. You can vary status code and message based on the exception type.
|
||||
|
||||
## Branch on custom exception types
|
||||
|
||||
If you throw your own exception types, you can map them to 400 or 404 responses.
|
||||
|
||||
```cpp
|
||||
struct NotFound : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
struct BadRequest : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const auto &req, auto &res, std::exception_ptr ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const NotFound &e) {
|
||||
res.status = 404;
|
||||
res.set_content(e.what(), "text/plain");
|
||||
} catch (const BadRequest &e) {
|
||||
res.status = 400;
|
||||
res.set_content(e.what(), "text/plain");
|
||||
} catch (const std::exception &e) {
|
||||
res.status = 500;
|
||||
res.set_content("internal error", "text/plain");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Now throwing `NotFound("user not found")` inside a handler is enough to return 404. No per-handler try/catch needed.
|
||||
|
||||
## Relationship with set_error_handler
|
||||
|
||||
`set_exception_handler()` runs the moment the exception is thrown. After that, if `res.status` is 4xx or 5xx, `set_error_handler()` also runs. The order is `exception_handler` → `error_handler`. Think of their roles as:
|
||||
|
||||
- **Exception handler**: interpret the exception, set the status and message
|
||||
- **Error handler**: see the status and wrap it in the shared template
|
||||
|
||||
> **Note:** Without an exception handler, cpp-httplib returns a default 500 response and the exception details never make it to logs. Always set one for anything you want to debug.
|
||||
@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "S15. Log Requests on the Server"
|
||||
order: 34
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To log the requests the server receives and the responses it returns, use `Server::set_logger()`. The callback fires once per completed request, making it the foundation for access logs and metrics collection.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const httplib::Request &req, const httplib::Response &res) {
|
||||
std::cout << req.remote_addr << " "
|
||||
<< req.method << " " << req.path
|
||||
<< " -> " << res.status << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
The log callback receives both the `Request` and the `Response`. You can grab the method, path, status, client IP, headers, body — whatever you need.
|
||||
|
||||
## Access-log style format
|
||||
|
||||
Here's an Apache/Nginx-ish access log format.
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto &req, const auto &res) {
|
||||
auto now = std::time(nullptr);
|
||||
char timebuf[32];
|
||||
std::strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
|
||||
std::localtime(&now));
|
||||
|
||||
std::cout << timebuf << " "
|
||||
<< req.remote_addr << " "
|
||||
<< "\"" << req.method << " " << req.path << "\" "
|
||||
<< res.status << " "
|
||||
<< res.body.size() << "B"
|
||||
<< std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
## Measure request time
|
||||
|
||||
To include request duration in the log, stash a start timestamp in `res.user_data` from a pre-routing handler, then subtract in the logger.
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto &req, auto &res) {
|
||||
res.user_data.set("start", std::chrono::steady_clock::now());
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
svr.set_logger([](const auto &req, const auto &res) {
|
||||
auto *start = res.user_data.get<std::chrono::steady_clock::time_point>("start");
|
||||
auto elapsed = start
|
||||
? std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - *start).count()
|
||||
: 0;
|
||||
std::cout << req.method << " " << req.path
|
||||
<< " " << res.status << " " << elapsed << "ms" << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
For more on `user_data`, see [S12. Pass data between handlers with `res.user_data`](../s12-user-data).
|
||||
|
||||
> **Note:** The logger runs synchronously on the same thread as request processing. Heavy work inside it hurts throughput — push it to a queue and process asynchronously if you need anything expensive.
|
||||
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "S16. Detect When the Client Has Disconnected"
|
||||
order: 35
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
During a long-running response, the client might close the connection. There's no point continuing to do work no one's waiting for. In cpp-httplib, check `req.is_connection_closed()`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.Get("/long-task", [](const httplib::Request &req, httplib::Response &res) {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
if (req.is_connection_closed()) {
|
||||
std::cout << "client disconnected" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
do_heavy_work(i);
|
||||
}
|
||||
|
||||
res.set_content("done", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`is_connection_closed` is a `std::function<bool()>`, so call it with `()`. It returns `true` when the client is gone.
|
||||
|
||||
## With a streaming response
|
||||
|
||||
The same check works inside `set_chunked_content_provider()`. Capture the request by reference.
|
||||
|
||||
```cpp
|
||||
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream",
|
||||
[&req](size_t offset, httplib::DataSink &sink) {
|
||||
if (req.is_connection_closed()) {
|
||||
sink.done();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto event = generate_next_event();
|
||||
sink.write(event.data(), event.size());
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
When you detect a disconnect, call `sink.done()` to stop the provider from being called again.
|
||||
|
||||
## How often should you check?
|
||||
|
||||
The call itself is cheap, but calling it in a tight inner loop doesn't add much value. Check at **boundaries where interrupting is safe** — after producing a chunk, after a database query, etc.
|
||||
|
||||
> **Warning:** `is_connection_closed()` is not guaranteed to reflect reality instantly. Because of how TCP works, sometimes you only notice the disconnect when you try to send. Don't expect pixel-perfect real-time detection — think of it as "we'll notice eventually."
|
||||
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "S17. Bind to Any Available Port"
|
||||
order: 36
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Standing up a test server often hits port conflicts. With `bind_to_any_port()`, you let the OS pick a free port and then read back which one it gave you.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/", [](const auto &req, auto &res) {
|
||||
res.set_content("hello", "text/plain");
|
||||
});
|
||||
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
std::cout << "listening on port " << port << std::endl;
|
||||
|
||||
svr.listen_after_bind();
|
||||
```
|
||||
|
||||
`bind_to_any_port()` is equivalent to passing `0` as the port — the OS assigns a free one. The return value is the port actually used.
|
||||
|
||||
After that, call `listen_after_bind()` to start accepting. You can't combine bind and listen into a single call here, so you work in two steps.
|
||||
|
||||
## Useful in tests
|
||||
|
||||
This pattern is great for tests that spin up a server and hit it.
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
svr.Get("/ping", [](const auto &, auto &res) { res.set_content("pong", "text/plain"); });
|
||||
|
||||
int port = svr.bind_to_any_port("127.0.0.1");
|
||||
std::thread t([&] { svr.listen_after_bind(); });
|
||||
|
||||
// run the test while the server is up on another thread
|
||||
httplib::Client cli("127.0.0.1", port);
|
||||
auto res = cli.Get("/ping");
|
||||
assert(res && res->body == "pong");
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
```
|
||||
|
||||
Because the port is assigned at runtime, parallel test runs don't collide.
|
||||
|
||||
> **Note:** `bind_to_any_port()` returns `-1` on failure (permission errors, no available ports, etc.). Always check the return value.
|
||||
|
||||
> To stop the server, see [S19. Shut down gracefully](../s19-graceful-shutdown).
|
||||
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "S18. Control Startup Order with listen_after_bind"
|
||||
order: 37
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Normally `svr.listen("0.0.0.0", 8080)` handles bind and listen in one shot. When you need to do something between the two, split them into two calls.
|
||||
|
||||
## Separate bind and listen
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
|
||||
|
||||
if (!svr.bind_to_port("0.0.0.0", 8080)) {
|
||||
std::cerr << "bind failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// bind is done here. accept hasn't started yet.
|
||||
drop_privileges();
|
||||
signal_ready_to_parent_process();
|
||||
|
||||
svr.listen_after_bind(); // start the accept loop
|
||||
```
|
||||
|
||||
`bind_to_port()` reserves the port; `listen_after_bind()` actually starts accepting. Splitting them gives you a window between the two steps.
|
||||
|
||||
## Common use cases
|
||||
|
||||
**Privilege drop**: Binding to a port under 1024 requires root. Bind as root, drop to a normal user, and all subsequent request handling runs with reduced privileges.
|
||||
|
||||
```cpp
|
||||
svr.bind_to_port("0.0.0.0", 80);
|
||||
drop_privileges();
|
||||
svr.listen_after_bind();
|
||||
```
|
||||
|
||||
**Startup notification**: Tell the parent process or systemd "I'm ready" before starting to accept connections.
|
||||
|
||||
**Test synchronization**: In tests, you can reliably catch "the moment the server is bound" and start the client after that.
|
||||
|
||||
## Check the return values
|
||||
|
||||
`bind_to_port()` returns `false` on failure, for example when you don't have permission to bind to the port. Always check it.
|
||||
|
||||
```cpp
|
||||
if (!svr.bind_to_port("0.0.0.0", 8080)) {
|
||||
std::cerr << "bind failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
`listen_after_bind()` blocks until the server stops and returns `true` on a clean shutdown.
|
||||
|
||||
## Detect a port that's already in use
|
||||
|
||||
With the default settings, you can actually bind to a port another server is already using. That's because cpp-httplib sets `SO_REUSEPORT` (Linux, macOS) or `SO_REUSEADDR` (Windows) on the server socket. A restarted server can bind again right away. The flip side is that a second server on the same port starts without an error, and connections get split between the two.
|
||||
|
||||
To make `bind_to_port()` fail on a port in use, replace the socket options with `set_socket_options()`.
|
||||
|
||||
```cpp
|
||||
svr.set_socket_options([](socket_t sock) {
|
||||
#ifdef _WIN32
|
||||
httplib::set_socket_opt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1);
|
||||
#else
|
||||
httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
|
||||
#endif
|
||||
});
|
||||
|
||||
if (!svr.bind_to_port("0.0.0.0", 8080)) {
|
||||
std::cerr << "port already in use" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
`set_socket_options()` replaces the defaults entirely. Setting `SO_REUSEADDR` on Linux and macOS keeps the "restarted server can bind again right away" behavior.
|
||||
|
||||
> **Note:** `SO_REUSEADDR` alone isn't enough on Windows. Two sockets that both set it can bind to the same port, so use `SO_EXCLUSIVEADDRUSE` instead.
|
||||
|
||||
> **Note:** To auto-pick a free port, see [S17. Bind to any available port](../s17-bind-any-port). Under the hood, that's just `bind_to_any_port()` + `listen_after_bind()`.
|
||||
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "S19. Shut Down Gracefully"
|
||||
order: 38
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To stop the server, call `Server::stop()`. It's safe to call even while requests are in flight, so you can wire it to SIGINT or SIGTERM for a graceful shutdown.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
|
||||
|
||||
std::thread t([&] { svr.listen("0.0.0.0", 8080); });
|
||||
|
||||
// wait for input on the main thread, or whatever
|
||||
std::cin.get();
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
```
|
||||
|
||||
`listen()` blocks, so the typical pattern is: run the server on a background thread and call `stop()` from the main thread. After `stop()`, `listen()` returns and you can `join()`.
|
||||
|
||||
## Shut down on a signal
|
||||
|
||||
Here's how to stop the server on SIGINT (Ctrl+C) or SIGTERM.
|
||||
|
||||
```cpp
|
||||
#include <csignal>
|
||||
|
||||
httplib::Server svr;
|
||||
|
||||
// global so the signal handler can reach it
|
||||
httplib::Server *g_svr = nullptr;
|
||||
|
||||
int main() {
|
||||
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
|
||||
|
||||
g_svr = &svr;
|
||||
std::signal(SIGINT, [](int) { if (g_svr) g_svr->stop(); });
|
||||
std::signal(SIGTERM, [](int) { if (g_svr) g_svr->stop(); });
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
std::cout << "server stopped" << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
`stop()` is thread-safe and signal-safe — you can call it from a signal handler. Even when `listen()` is running on the main thread, the signal pulls it out cleanly.
|
||||
|
||||
## What happens to in-flight requests
|
||||
|
||||
When you call `stop()`, new connections are refused, but requests already being processed are **allowed to finish**. Once all workers drain, `listen()` returns. That's what makes it graceful.
|
||||
|
||||
> **Warning:** There's a wait between calling `stop()` and `listen()` returning — it's the time in-flight requests take to finish. To enforce a timeout, you'll need to add your own shutdown timer in application code.
|
||||
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "S20. Tune Keep-Alive"
|
||||
order: 39
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
`httplib::Server` enables HTTP/1.1 Keep-Alive automatically. From the client's perspective, connections are reused — so they don't pay the TCP handshake cost on every request. When you need to tune the behavior, there are two setters.
|
||||
|
||||
## What you can configure
|
||||
|
||||
| API | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `set_keep_alive_max_count` | 100 | Max requests served over a single connection |
|
||||
| `set_keep_alive_timeout` | 5s | How long an idle connection is kept before closing |
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.set_keep_alive_max_count(20);
|
||||
svr.set_keep_alive_timeout(10); // 10 seconds
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
`set_keep_alive_timeout()` also has a `std::chrono` overload.
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
svr.set_keep_alive_timeout(10s);
|
||||
```
|
||||
|
||||
## Tuning ideas
|
||||
|
||||
**Too many idle connections eating resources**
|
||||
Shorten the timeout so idle connections drop and release their worker threads.
|
||||
|
||||
```cpp
|
||||
svr.set_keep_alive_timeout(2s);
|
||||
```
|
||||
|
||||
**API is hammered and you want max reuse**
|
||||
Raising the per-connection request cap improves benchmark numbers.
|
||||
|
||||
```cpp
|
||||
svr.set_keep_alive_max_count(1000);
|
||||
```
|
||||
|
||||
**Never reuse connections**
|
||||
Set `set_keep_alive_max_count(1)` and every request gets its own connection. Mostly only useful for debugging or compatibility testing.
|
||||
|
||||
## Relationship with the thread pool
|
||||
|
||||
A Keep-Alive connection holds a worker thread for its entire lifetime. If `connections × concurrent requests` exceeds the thread pool size, new requests wait. For thread counts, see [S21. Configure the thread pool](../s21-thread-pool).
|
||||
|
||||
> **Note:** For the client side, see [C14. Understand connection reuse and Keep-Alive behavior](../c14-keep-alive). Even when the server closes the connection on timeout, the client reconnects automatically.
|
||||
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: "S21. Configure the Thread Pool"
|
||||
order: 40
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib serves requests from a thread pool. By default, the base thread count is the greater of `std::thread::hardware_concurrency() - 1` and `8`, and it can scale up dynamically to 4× that. To set thread counts explicitly, provide your own factory via `new_task_queue`.
|
||||
|
||||
## Set thread counts
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/64);
|
||||
};
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
The factory is a lambda returning a `TaskQueue*`. Pass `base_threads` and `max_threads` to `ThreadPool` and the pool scales between them based on load. Idle threads exit after a timeout (3 seconds by default).
|
||||
|
||||
## Also cap the queue
|
||||
|
||||
The pending queue can eat memory if it grows unchecked. You can cap it too.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(
|
||||
/*base_threads=*/12,
|
||||
/*max_threads=*/0, // disable dynamic scaling
|
||||
/*max_queued_requests=*/18);
|
||||
};
|
||||
```
|
||||
|
||||
`max_threads=0` disables dynamic scaling — you get a fixed `base_threads`. Requests that don't fit in `max_queued_requests` are rejected.
|
||||
|
||||
## Use your own thread pool
|
||||
|
||||
You can plug in a fully custom thread pool by subclassing `TaskQueue` and returning it from the factory.
|
||||
|
||||
```cpp
|
||||
class MyTaskQueue : public httplib::TaskQueue {
|
||||
public:
|
||||
MyTaskQueue(size_t n) { pool_.start_with_thread_count(n); }
|
||||
bool enqueue(std::function<void()> fn) override { return pool_.post(std::move(fn)); }
|
||||
void shutdown() override { pool_.shutdown(); }
|
||||
|
||||
private:
|
||||
MyThreadPool pool_;
|
||||
};
|
||||
|
||||
svr.new_task_queue = [] { return new MyTaskQueue(12); };
|
||||
```
|
||||
|
||||
Handy when you already have a thread pool in your project and want to keep thread management unified.
|
||||
|
||||
## Compile-time tuning
|
||||
|
||||
You can set the defaults with macros if you want compile-time configuration.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 16 // base thread count
|
||||
#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT 128 // max thread count
|
||||
#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 5 // seconds before idle threads exit
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
> **Note:** A WebSocket connection holds a worker thread for its entire lifetime. For lots of simultaneous WebSocket connections, enable dynamic scaling (e.g. `ThreadPool(8, 64)`).
|
||||
@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "S22. Talk Over a Unix Domain Socket"
|
||||
order: 41
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
When you want to talk only to other processes on the same host, a Unix domain socket is a nice fit. It avoids TCP overhead and uses filesystem permissions for access control. Local IPC and services sitting behind a reverse proxy are classic use cases.
|
||||
|
||||
## Server side
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
svr.set_address_family(AF_UNIX);
|
||||
|
||||
svr.Get("/", [](const auto &, auto &res) {
|
||||
res.set_content("hello from unix socket", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("/tmp/httplib.sock", 80);
|
||||
```
|
||||
|
||||
Call `set_address_family(AF_UNIX)` first, then pass the socket file path as the first argument to `listen()`. The port number is unused but required by the signature — pass any value.
|
||||
|
||||
## Client side
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("/tmp/httplib.sock");
|
||||
cli.set_address_family(AF_UNIX);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Pass the socket file path to the `Client` constructor and call `set_address_family(AF_UNIX)`. Everything else works like a normal HTTP request.
|
||||
|
||||
## When to use it
|
||||
|
||||
- **Behind a reverse proxy**: An nginx-to-backend setup over a Unix socket is faster than TCP and sidesteps port management
|
||||
- **Local-only APIs**: IPC between tools that shouldn't be reachable from outside
|
||||
- **In-container IPC**: Process-to-process communication within the same pod or container
|
||||
- **Dev environments**: No more worrying about port conflicts
|
||||
|
||||
## Clean up the socket file
|
||||
|
||||
A Unix domain socket creates a real file in the filesystem. It doesn't get removed on shutdown, so delete it before starting if needed.
|
||||
|
||||
```cpp
|
||||
std::remove("/tmp/httplib.sock");
|
||||
svr.listen("/tmp/httplib.sock", 80);
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
You control who can connect via the socket file's permissions.
|
||||
|
||||
```cpp
|
||||
svr.listen("/tmp/httplib.sock", 80);
|
||||
// from another process or thread
|
||||
chmod("/tmp/httplib.sock", 0660); // owner and group only
|
||||
```
|
||||
|
||||
> **Warning:** Some Windows versions support AF_UNIX, but the implementation and behavior differ by platform. Test thoroughly before running cross-platform in production.
|
||||
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "S23. Handle custom HTTP methods"
|
||||
order: 42
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
The server rejects HTTP methods it does not know with `400 Bad Request`. To accept an extension method, such as the WebDAV methods of RFC 4918 (`PROPFIND`, `PROPPATCH`, `MKCOL` and friends) or UPnP's `SUBSCRIBE`, register a handler with `CustomRoute()`. Registering the handler is what makes the server accept the method.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.CustomRoute("PROPFIND", "/dav/:id",
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
// The request body is available as usual
|
||||
auto id = req.path_params.at("id");
|
||||
res.status = httplib::StatusCode::MultiStatus_207;
|
||||
res.set_content(build_multistatus(req.body), "application/xml");
|
||||
});
|
||||
```
|
||||
|
||||
Patterns work the same way as they do for `Get()`. Regular expressions and path parameters are both available.
|
||||
|
||||
## Advertise your methods with OPTIONS
|
||||
|
||||
A WebDAV client asks the server about its capabilities with `OPTIONS` before doing anything else. cpp-httplib generates neither the `DAV:` header nor `Allow`, so return them yourself. Forget this and clients will turn you away even though your `PROPFIND` works.
|
||||
|
||||
```cpp
|
||||
svr.Options("/dav/.*", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_header("DAV", "1");
|
||||
res.set_header("Allow", "OPTIONS, GET, HEAD, PROPFIND, PROPPATCH, MKCOL");
|
||||
});
|
||||
```
|
||||
|
||||
## Read the body as a stream
|
||||
|
||||
There is a content reader overload, just like the one on `Post()`. Use it when you would rather not hold a large XML document in memory all at once.
|
||||
|
||||
```cpp
|
||||
svr.CustomRoute("REPORT", "/dav/.*",
|
||||
[](const httplib::Request &req, httplib::Response &res,
|
||||
const httplib::ContentReader &content_reader) {
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
// Process it a chunk at a time
|
||||
return true;
|
||||
});
|
||||
res.status = httplib::StatusCode::MultiStatus_207;
|
||||
});
|
||||
```
|
||||
|
||||
## Things to keep in mind
|
||||
|
||||
- The method name has to be a valid HTTP method token (RFC 9110), and it must be registered before you call `listen()`
|
||||
- `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`, `PATCH` and `PRI` cannot be registered here. Use the dedicated methods for those
|
||||
- A rejected registration makes `is_valid()` return `false` and `listen()` fail, so the server never starts holding a handler that would never run
|
||||
- Static file serving and WebSocket upgrades stay `GET`/`HEAD` only
|
||||
|
||||
> **Note:** cpp-httplib takes you as far as routing the method. If you want to call it WebDAV, generating the `207 Multi-Status` XML, interpreting the `Depth` header and managing locks are all yours to implement. The protocol itself lives outside the library.
|
||||
|
||||
> For the basics of registering handlers, see [S01. Register GET / POST / PUT / DELETE handlers](../s01-handlers).
|
||||
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: "T01. Choosing Between OpenSSL, mbedTLS, and wolfSSL"
|
||||
order: 43
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
cpp-httplib doesn't ship its own TLS implementation — it uses one of three backends that you pick at build time via a macro.
|
||||
|
||||
| Backend | Macro | Character |
|
||||
| --- | --- | --- |
|
||||
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | Most widely used, richest feature set |
|
||||
| mbedTLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | Lightweight, aimed at embedded |
|
||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | Embedded-friendly, commercial support available |
|
||||
|
||||
## Build-time selection
|
||||
|
||||
Define the macro for your chosen backend before including `httplib.h`:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
You'll also need to link against the backend's libraries (`libssl`, `libcrypto`, `libmbedtls`, `libwolfssl`, etc.).
|
||||
|
||||
## Which to pick
|
||||
|
||||
**When in doubt, OpenSSL**
|
||||
It has the most features and the best documentation. For normal server use or Linux desktop apps, start here — you probably won't need anything else.
|
||||
|
||||
**To shrink binary size or target embedded**
|
||||
mbedTLS or wolfSSL are a better fit. They're far more compact than OpenSSL and run on memory-constrained devices.
|
||||
|
||||
**When you need commercial support**
|
||||
wolfSSL offers commercial licensing and support. If you're shipping in a product, it's worth considering.
|
||||
|
||||
## Supporting multiple backends
|
||||
|
||||
The usual approach is to treat each backend as a build variant and recompile the same source with different macros. cpp-httplib smooths over most of the API differences, but the backends are not 100% identical — always test.
|
||||
|
||||
## APIs that work across all backends
|
||||
|
||||
Certificate verification control, standing up an SSLServer, reading the peer certificate — these all share the same API across backends:
|
||||
|
||||
- [T02. Control SSL certificate verification](../t02-cert-verification)
|
||||
- [T03. Start an SSL/TLS server](../t03-ssl-server)
|
||||
- [T05. Access the peer certificate on the server](../t05-peer-cert)
|
||||
|
||||
> **Note:** On macOS with an OpenSSL-family backend, cpp-httplib automatically loads root certificates from the system keychain (via `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`, on by default). To disable this, define `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`.
|
||||
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "T02. Control SSL Certificate Verification"
|
||||
order: 44
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
By default, an HTTPS client verifies the server certificate — it uses the OS root certificate store to check the chain and the hostname. Here are the APIs for changing that behavior.
|
||||
|
||||
## Specify a custom CA certificate
|
||||
|
||||
When connecting to a server whose certificate is signed by an internal CA, use `set_ca_cert_path()`.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://internal.example.com");
|
||||
cli.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
The first argument is the CA certificate file; the second is an optional CA directory. With the OpenSSL backend, you can also pass an `X509_STORE*` directly via `set_ca_cert_store()`.
|
||||
|
||||
## Disable certificate verification (not recommended)
|
||||
|
||||
For development servers or self-signed certificates, you can skip verification entirely.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://self-signed.example.com");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
That's all it takes to disable chain verification.
|
||||
|
||||
> **Warning:** Disabling certificate verification removes protection against man-in-the-middle attacks. **Never do this in production.** If you find yourself needing it outside of dev/test, pause and make sure you're not doing something wrong.
|
||||
|
||||
## Disable hostname verification only
|
||||
|
||||
There's an in-between option: verify the certificate chain, but skip the hostname check. Useful when you need to reach a server whose cert CN/SAN doesn't match the request's hostname.
|
||||
|
||||
```cpp
|
||||
cli.enable_server_hostname_verification(false);
|
||||
```
|
||||
|
||||
The certificate itself is still validated, so this is safer than fully disabling verification — but still not recommended in production.
|
||||
|
||||
## Use the OS cert store as-is
|
||||
|
||||
On most Linux distributions, root certificates live in a single file like `/etc/ssl/certs/ca-certificates.crt`. cpp-httplib reads the OS default store at startup, so for most servers you don't need to configure anything.
|
||||
|
||||
> The same APIs work on the mbedTLS and wolfSSL backends. For choosing between backends, see [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](../t01-tls-backends).
|
||||
|
||||
> For details on diagnosing failures, see [C18. Handle SSL errors](../c18-ssl-errors).
|
||||
|
||||
> For TLS configuration on a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "T03. Start an SSL/TLS Server"
|
||||
order: 45
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
To stand up an HTTPS server, use `httplib::SSLServer` instead of `httplib::Server`. Pass a certificate and private key to the constructor, and you get back something that works exactly like `Server`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include <httplib.h>
|
||||
|
||||
int main() {
|
||||
httplib::SSLServer svr("cert.pem", "key.pem");
|
||||
|
||||
svr.Get("/", [](const auto &req, auto &res) {
|
||||
res.set_content("hello over TLS", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 443);
|
||||
}
|
||||
```
|
||||
|
||||
Pass the server certificate (PEM format) and private key file paths to the constructor. That's all you need for a TLS-enabled server. Registering handlers and calling `listen()` work the same as with `Server`.
|
||||
|
||||
## Password-protected private keys
|
||||
|
||||
The fifth argument is the private key password.
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer svr("cert.pem", "key.pem",
|
||||
nullptr, nullptr, "password");
|
||||
```
|
||||
|
||||
The third and fourth arguments are for client certificate verification (mTLS, see [T04. Configure mTLS](../t04-mtls)). For now, pass `nullptr`.
|
||||
|
||||
## Load PEM data from memory
|
||||
|
||||
When you want to load certs from memory instead of files, use the `PemMemory` struct.
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer::PemMemory pem{};
|
||||
pem.cert_pem = cert_data.data();
|
||||
pem.cert_pem_len = cert_data.size();
|
||||
pem.key_pem = key_data.data();
|
||||
pem.key_pem_len = key_data.size();
|
||||
|
||||
httplib::SSLServer svr(pem);
|
||||
```
|
||||
|
||||
Handy when you pull certificates from environment variables or a secrets manager.
|
||||
|
||||
## Rotate certificates
|
||||
|
||||
Before a certificate expires, you may want to swap it out without restarting the server. That's what `update_certs_pem()` is for.
|
||||
|
||||
```cpp
|
||||
svr.update_certs_pem(new_cert_pem, new_key_pem);
|
||||
```
|
||||
|
||||
Existing connections keep using the old cert; new connections use the new one.
|
||||
|
||||
## Generating a test certificate
|
||||
|
||||
For a throwaway self-signed cert, use the `openssl` CLI.
|
||||
|
||||
```sh
|
||||
openssl req -x509 -newkey rsa:2048 -days 365 -nodes \
|
||||
-keyout key.pem -out cert.pem -subj "/CN=localhost"
|
||||
```
|
||||
|
||||
In production, use certificates from Let's Encrypt or your internal CA.
|
||||
|
||||
> **Warning:** Binding an HTTPS server to port 443 requires root. For a safe way to do that, see the privilege-drop pattern in [S18. Control startup order with `listen_after_bind`](../s18-listen-after-bind).
|
||||
|
||||
> For mutual TLS (client certificates), see [T04. Configure mTLS](../t04-mtls).
|
||||
@ -0,0 +1,86 @@
|
||||
---
|
||||
title: "T04. Configure mTLS"
|
||||
order: 46
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
Regular TLS verifies the server certificate only. **mTLS** (mutual TLS) adds the other direction: the client presents a certificate too, and the server verifies it. It's common for zero-trust API-to-API traffic and internal system authentication.
|
||||
|
||||
## Server side
|
||||
|
||||
Pass the CA used to verify client certificates as the third (and fourth) argument to `SSLServer`.
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer svr(
|
||||
"server-cert.pem", // server certificate
|
||||
"server-key.pem", // server private key
|
||||
"client-ca.pem", // CA that signs valid client certs
|
||||
nullptr // CA directory (none)
|
||||
);
|
||||
|
||||
svr.Get("/", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content("authenticated", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 443);
|
||||
```
|
||||
|
||||
With this, any connection whose client certificate isn't signed by `client-ca.pem` is rejected at the handshake. By the time a handler runs, the client is already authenticated.
|
||||
|
||||
## Configure with in-memory PEM
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer::PemMemory pem{};
|
||||
pem.cert_pem = server_cert.data();
|
||||
pem.cert_pem_len = server_cert.size();
|
||||
pem.key_pem = server_key.data();
|
||||
pem.key_pem_len = server_key.size();
|
||||
pem.client_ca_pem = client_ca.data();
|
||||
pem.client_ca_pem_len = client_ca.size();
|
||||
|
||||
httplib::SSLServer svr(pem);
|
||||
```
|
||||
|
||||
This is the clean way when you load certificates from environment variables or a secrets manager.
|
||||
|
||||
## Client side
|
||||
|
||||
On the client side, pass the client certificate and key to `SSLClient`.
|
||||
|
||||
```cpp
|
||||
httplib::SSLClient cli("api.example.com", 443,
|
||||
"client-cert.pem",
|
||||
"client-key.pem");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
Note you're using `SSLClient` directly, not `Client`. If the private key has a password, pass it as the fifth argument.
|
||||
|
||||
The client side has the same `PemMemory` struct too, letting you set the client certificate from PEM in memory.
|
||||
|
||||
```cpp
|
||||
httplib::SSLClient::PemMemory pem{};
|
||||
pem.cert_pem = client_cert.data();
|
||||
pem.cert_pem_len = client_cert.size();
|
||||
pem.key_pem = client_key.data();
|
||||
pem.key_pem_len = client_key.size();
|
||||
|
||||
httplib::SSLClient cli("api.example.com", 443, pem);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
> For mTLS with a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||
|
||||
## Read client info from a handler
|
||||
|
||||
To see which client connected from inside a handler, use `req.peer_cert()`. Details in [T05. Access the peer certificate on the server](../t05-peer-cert).
|
||||
|
||||
## Use cases
|
||||
|
||||
- **Microservice-to-microservice calls**: Issue a cert per service, use the cert as identity
|
||||
- **IoT device management**: Burn a cert into each device and use it to gate API access
|
||||
- **An alternative to internal VPN**: Put cert-based auth in front of public endpoints so internal resources can be reached safely
|
||||
|
||||
> **Note:** Issuing and revoking client certificates is more operational work than password-based auth. You'll need either an internal PKI setup or an automated flow using ACME-family tools.
|
||||
@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "T05. Access the Peer Certificate on the Server Side"
|
||||
order: 47
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
In an mTLS setup, you can read the client's certificate from inside a handler. Pull out the CN or SAN to identify the user or log the request.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```cpp
|
||||
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
|
||||
auto cert = req.peer_cert();
|
||||
if (!cert) {
|
||||
res.status = 401;
|
||||
res.set_content("no client certificate", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
auto cn = cert.subject_cn();
|
||||
res.set_content("hello, " + cn, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`req.peer_cert()` returns a `tls::PeerCert`. It's convertible to `bool`, so check whether a cert is present before using it.
|
||||
|
||||
## Available fields
|
||||
|
||||
From a `PeerCert`, you can get:
|
||||
|
||||
```cpp
|
||||
auto cert = req.peer_cert();
|
||||
|
||||
std::string cn = cert.subject_cn(); // CN
|
||||
std::string issuer = cert.issuer_name(); // issuer
|
||||
std::string serial = cert.serial(); // serial number
|
||||
|
||||
time_t not_before, not_after;
|
||||
cert.validity(not_before, not_after); // validity period
|
||||
|
||||
auto sans = cert.sans(); // SANs
|
||||
for (const auto &san : sans) {
|
||||
std::cout << san.value << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
There's also a helper to check if a hostname is covered by the SAN list:
|
||||
|
||||
```cpp
|
||||
if (cert.check_hostname("alice.corp.example.com")) {
|
||||
// matches
|
||||
}
|
||||
```
|
||||
|
||||
## Cert-based authorization
|
||||
|
||||
You can gate routes by CN or SAN.
|
||||
|
||||
```cpp
|
||||
svr.set_pre_request_handler(
|
||||
[](const httplib::Request &req, httplib::Response &res) {
|
||||
auto cert = req.peer_cert();
|
||||
if (!cert) {
|
||||
res.status = 401;
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
}
|
||||
|
||||
if (req.matched_route.rfind("/admin", 0) == 0) {
|
||||
auto cn = cert.subject_cn();
|
||||
if (!is_admin_cn(cn)) {
|
||||
res.status = 403;
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
}
|
||||
}
|
||||
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
Combined with a pre-request handler, you can keep all authorization logic in one place. See [S11. Authenticate per route with a pre-request handler](../s11-pre-request).
|
||||
|
||||
## SNI (Server Name Indication)
|
||||
|
||||
cpp-httplib handles SNI automatically. If one server hosts multiple domains, SNI is used under the hood — but normally handlers don't need to care.
|
||||
|
||||
> **Warning:** `req.peer_cert()` only returns a meaningful value when mTLS is enabled and the client actually presented a certificate. For plain TLS, you get an empty `PeerCert`. Always do the `bool` check before using it.
|
||||
|
||||
> To set up mTLS, see [T04. Configure mTLS](../t04-mtls).
|
||||
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: "W01. Implement a WebSocket Echo Server and Client"
|
||||
order: 52
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
WebSocket is a protocol for **two-way** messaging between client and server. cpp-httplib provides APIs for both sides. Let's start with the simplest example: an echo server.
|
||||
|
||||
## Server: echo server
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.is_open()) {
|
||||
auto result = ws.read(msg);
|
||||
if (result == httplib::ws::ReadResult::Fail) {
|
||||
break;
|
||||
}
|
||||
ws.send(msg); // echo back what we received
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
Register a WebSocket handler with `svr.WebSocket()`. By the time the handler runs, the WebSocket handshake is already complete. Inside the loop, just `ws.read()` and `ws.send()` to get a working echo.
|
||||
|
||||
The `read()` return value is a `ReadResult` enum:
|
||||
|
||||
- `ReadResult::Text`: received a text message
|
||||
- `ReadResult::Binary`: received a binary message
|
||||
- `ReadResult::Fail`: error, or connection closed
|
||||
- `ReadResult::Timeout`: a read timeout you set with `set_read_timeout()` elapsed with nothing received; the connection is still open. The compile-time default timeout closes the connection and is reported as `Fail` instead — see [W06. Set Timeouts](../w06-websocket-timeouts)
|
||||
|
||||
## Client: talk to the echo server
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
|
||||
int main() {
|
||||
httplib::ws::WebSocketClient cli("ws://localhost:8080/echo");
|
||||
if (!cli.connect()) {
|
||||
std::cerr << "failed to connect" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
cli.send("Hello, WebSocket!");
|
||||
|
||||
std::string msg;
|
||||
if (cli.read(msg) != httplib::ws::ReadResult::Fail) {
|
||||
std::cout << "received: " << msg << std::endl;
|
||||
}
|
||||
|
||||
cli.close();
|
||||
}
|
||||
```
|
||||
|
||||
Use a `ws://` (plain) or `wss://` (TLS) URL. Call `connect()` to do the handshake, then `send()` and `read()` work the same as on the server side.
|
||||
|
||||
## Text vs. binary
|
||||
|
||||
`send()` has two overloads that let you choose the frame type.
|
||||
|
||||
```cpp
|
||||
ws.send("Hello"); // text frame
|
||||
ws.send(binary_data, binary_data_size); // binary frame
|
||||
```
|
||||
|
||||
The `std::string` overload sends as **text**; the `const char*` + size overload sends as **binary**. A bit subtle, but once you know it, it's intuitive. See [W04. Send and receive binary frames](../w04-websocket-binary) for details.
|
||||
|
||||
## Thread pool implications
|
||||
|
||||
A WebSocket handler holds its worker thread for the entire life of the connection — one connection per thread. For many concurrent clients, configure a dynamic thread pool.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(8, 128);
|
||||
};
|
||||
```
|
||||
|
||||
See [S21. Configure the thread pool](../s21-thread-pool).
|
||||
|
||||
> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL. For CA and client certificate configuration, see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: "W02. Set a WebSocket Heartbeat"
|
||||
order: 53
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
WebSocket connections stay open for a long time, and proxies or load balancers will sometimes drop them for being "idle." To prevent that, you periodically send Ping frames to keep the connection alive. cpp-httplib can do this for you automatically.
|
||||
|
||||
## Server side
|
||||
|
||||
```cpp
|
||||
svr.set_websocket_ping_interval(30); // ping every 30 seconds
|
||||
|
||||
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Just pass the interval in seconds. Every WebSocket connection this server accepts will be pinged on that interval.
|
||||
|
||||
There's a `std::chrono` overload too.
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
svr.set_websocket_ping_interval(30s);
|
||||
```
|
||||
|
||||
## Client side
|
||||
|
||||
The client has the same API.
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient cli("ws://localhost:8080/chat");
|
||||
cli.set_websocket_ping_interval(30);
|
||||
cli.connect();
|
||||
```
|
||||
|
||||
Call it before `connect()`.
|
||||
|
||||
## The default
|
||||
|
||||
The default interval is set by the build-time macro `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND`. Usually you won't need to change it, but adjust downward if you're dealing with an aggressive proxy.
|
||||
|
||||
## What about Pong?
|
||||
|
||||
The WebSocket protocol requires that Ping frames are answered with Pong frames. cpp-httplib responds to Pings automatically — you don't need to think about it in application code.
|
||||
|
||||
## Picking an interval
|
||||
|
||||
| Environment | Suggested |
|
||||
| --- | --- |
|
||||
| Normal internet | 30–60s |
|
||||
| Strict proxies (e.g. AWS ALB) | 15–30s |
|
||||
| Mobile networks | 60s+ (too short drains battery) |
|
||||
|
||||
Too short wastes bandwidth; too long and connections get dropped. As a rule of thumb, target about **half the idle timeout** of whatever's between you and the client.
|
||||
|
||||
> **Warning:** A very short ping interval spawns background work per connection and increases CPU usage. For servers with many connections, keep the interval modest.
|
||||
|
||||
## Detecting an unresponsive peer
|
||||
|
||||
Sending pings alone doesn't tell you anything if the peer just silently dies — the TCP socket might still look open while the process on the other end is long gone. To catch that, enable the max-missed-pongs check: if N consecutive pings go unanswered, the connection is closed.
|
||||
|
||||
```cpp
|
||||
cli.set_websocket_max_missed_pongs(2); // close after 2 consecutive unacked pings
|
||||
```
|
||||
|
||||
The server side has the same `set_websocket_max_missed_pongs()`.
|
||||
|
||||
With a 30-second ping interval and `max_missed_pongs = 2`, a dead peer is detected within roughly 60 seconds and the connection is closed with `CloseStatus::GoingAway` and the reason `"pong timeout"`.
|
||||
|
||||
The counter is reset whenever `read()` consumes an incoming Pong frame, so this only works if your code is actively calling `read()` in a loop — which is what a normal WebSocket client does anyway.
|
||||
|
||||
### Why the default is 0
|
||||
|
||||
`max_missed_pongs` defaults to `0`, which means "never close the connection because of missing pongs." Pings are still sent on the heartbeat interval, but their responses aren't checked. If you want unresponsive-peer detection, set it explicitly to `1` or higher.
|
||||
|
||||
On the server side, even with `0`, a dead connection won't linger forever: while a handler is inside `read()`, `CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND` (default **300 seconds = 5 minutes**) acts as a backstop. A client has no backstop of its own — it waits forever unless you set a read timeout — so there `max_missed_pongs` is what notices an unresponsive peer at all. On either side, it is also how you notice one **faster** than that 5-minute fallback.
|
||||
|
||||
> For handling a closed connection, see [W03. Handle connection close](../w03-websocket-close).
|
||||
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "W03. Handle Connection Close"
|
||||
order: 54
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
A WebSocket ends when either side closes it explicitly, or when the network drops. Handle close cleanly, and your cleanup and reconnect logic stays tidy.
|
||||
|
||||
## Detect a closed connection
|
||||
|
||||
When `ws.read()` returns `ReadResult::Fail`, the connection is gone — either cleanly or with an error. Break out of the loop and the handler will finish.
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/chat", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.is_open()) {
|
||||
auto result = ws.read(msg);
|
||||
if (result == httplib::ws::ReadResult::Fail) {
|
||||
std::cout << "disconnected" << std::endl;
|
||||
break;
|
||||
}
|
||||
handle_message(ws, msg);
|
||||
}
|
||||
|
||||
// cleanup runs once we're out of the loop
|
||||
cleanup_user_session(req);
|
||||
});
|
||||
```
|
||||
|
||||
You can also check `ws.is_open()` — it's the same signal from a different angle.
|
||||
|
||||
## Close from the server side
|
||||
|
||||
To close explicitly, call `close()`.
|
||||
|
||||
```cpp
|
||||
ws.close(httplib::ws::CloseStatus::Normal, "bye");
|
||||
```
|
||||
|
||||
The first argument is the close status; the second is an optional reason. Common `CloseStatus` values:
|
||||
|
||||
| Value | Meaning |
|
||||
| --- | --- |
|
||||
| `Normal` (1000) | Normal closure |
|
||||
| `GoingAway` (1001) | Server is shutting down |
|
||||
| `ProtocolError` (1002) | Protocol violation detected |
|
||||
| `UnsupportedData` (1003) | Received data that can't be handled |
|
||||
| `PolicyViolation` (1008) | Violated a policy |
|
||||
| `MessageTooBig` (1009) | Message too large |
|
||||
| `InternalError` (1011) | Server-side error |
|
||||
|
||||
## Close from the client side
|
||||
|
||||
The client API is identical.
|
||||
|
||||
```cpp
|
||||
cli.close(httplib::ws::CloseStatus::Normal);
|
||||
```
|
||||
|
||||
Destroying the client also closes the connection, but calling `close()` explicitly makes the intent clearer.
|
||||
|
||||
## Graceful shutdown
|
||||
|
||||
To notify in-flight clients that the server is going down, use `GoingAway`.
|
||||
|
||||
```cpp
|
||||
ws.close(httplib::ws::CloseStatus::GoingAway, "server restarting");
|
||||
```
|
||||
|
||||
The client can inspect that status and decide whether to reconnect.
|
||||
|
||||
## Example: a tiny chat with quit
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
|
||||
std::string msg;
|
||||
while (ws.is_open()) {
|
||||
if (ws.read(msg) == httplib::ws::ReadResult::Fail) break;
|
||||
|
||||
if (msg == "/quit") {
|
||||
ws.send("goodbye");
|
||||
ws.close(httplib::ws::CloseStatus::Normal, "user quit");
|
||||
break;
|
||||
}
|
||||
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> **Note:** On a sudden network drop, `read()` returns `Fail` with no chance to call `close()`. Put your cleanup at the end of the handler, and both paths — clean close and abrupt disconnect — end up in the same place.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user