├── .circleci └── config.yml ├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── NFHTTP.png ├── README.md ├── ci ├── android.py ├── androidlinux.py ├── build_options.py ├── ci.yaml ├── ios.py ├── linux.py ├── linux.sh ├── llvm-run.sh ├── nfbuild.py ├── nfbuildlinux.py ├── nfbuildosx.py ├── nfbuildwindows.py ├── osx.py ├── osx.sh ├── requirements.txt ├── windows.ps1 └── windows.py ├── include └── NFHTTP │ ├── Client.h │ ├── NFHTTP.h │ ├── Request.h │ ├── RequestToken.h │ ├── Response.h │ └── ResponseImplementation.h ├── libraries ├── CMakeLists.txt ├── config_openssl.sh ├── curl.patch └── sqlite │ ├── shell.c │ ├── sqlite3.c │ ├── sqlite3.h │ └── sqlite3ext.h ├── resources ├── hello-requests.json ├── localhost │ └── world └── world-responses.json ├── source ├── CMakeLists.txt ├── CacheLocationApple.mm ├── CacheLocationLinux.cpp ├── CacheLocationWindows.cpp ├── CachingClient.cpp ├── CachingClient.h ├── CachingDatabase.cpp ├── CachingDatabase.h ├── CachingDatabaseDelegate.h ├── CachingSQLiteDatabase.cpp ├── CachingSQLiteDatabase.h ├── Client.cpp ├── ClientCpprestsdk.cpp ├── ClientCpprestsdk.h ├── ClientCurl.cpp ├── ClientCurl.h ├── ClientModifierImplementation.cpp ├── ClientModifierImplementation.h ├── ClientMultiRequestImplementation.cpp ├── ClientMultiRequestImplementation.h ├── ClientNSURLSession.h ├── ClientNSURLSession.mm ├── NFHTTP.cpp ├── NFHTTPCLI.cpp ├── Request.cpp ├── RequestImplementation.cpp ├── RequestImplementation.h ├── RequestTokenDelegate.h ├── RequestTokenImplementation.cpp ├── RequestTokenImplementation.h ├── ResponseImplementation.cpp ├── sha256.cpp └── sha256.h └── tools └── generate-version.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | buildlinuxclang: 4 | docker: 5 | - image: ubuntu:bionic 6 | steps: 7 | - checkout 8 | - run: 9 | name: Build Linux with clang 10 | command: USE_CURL=1 sh ci/linux.sh clang_build 11 | - store_artifacts: 12 | path: build/output/libNFHTTP.zip 13 | destination: libNFHTTP.zip 14 | buildlinuxgcc: 15 | docker: 16 | - image: ubuntu:bionic 17 | steps: 18 | - checkout 19 | - run: 20 | name: Build Linux with gcc 21 | command: USE_CURL=1 sh ci/linux.sh gcc_build 22 | - store_artifacts: 23 | path: build/output/libNFHTTP.zip 24 | destination: libNFHTTP.zip 25 | buildlinuxandroid: 26 | docker: 27 | - image: ubuntu:bionic 28 | steps: 29 | - checkout 30 | - run: 31 | name: Build Android 32 | command: BUILD_ANDROID=1 USE_CURL=1 sh ci/linux.sh build 33 | - store_artifacts: 34 | path: libNFHTTP-androidx86.zip 35 | destination: libNFHTTP-androidx86.zip 36 | - store_artifacts: 37 | path: libNFHTTP-androidArm64.zip 38 | destination: libNFHTTP-androidArm64.zip 39 | buildmac: 40 | macos: 41 | xcode: "11.5.0" 42 | environment: 43 | HOMEBREW_NO_AUTO_UPDATE: 1 44 | steps: 45 | - checkout 46 | - run: brew update 47 | - run: git submodule sync 48 | - run: git submodule update --init --recursive 49 | - run: 50 | name: Build OSX 51 | command: USE_CURL=1 sh ci/osx.sh build 52 | - store_artifacts: 53 | path: build/output/libNFHTTP.zip 54 | destination: libNFHTTP.zip 55 | buildmacios: 56 | macos: 57 | xcode: "11.5.0" 58 | environment: 59 | HOMEBREW_NO_AUTO_UPDATE: 1 60 | steps: 61 | - checkout 62 | - run: brew update 63 | - run: git submodule sync 64 | - run: git submodule update --init --recursive 65 | - run: 66 | name: Build iOS 67 | command: BUILD_IOS=1 USE_CURL=1 sh ci/osx.sh build 68 | - store_artifacts: 69 | path: build/output/libNFHTTP.zip 70 | destination: libNFHTTP.zip 71 | buildmacandroid: 72 | macos: 73 | xcode: "11.5.0" 74 | environment: 75 | HOMEBREW_NO_AUTO_UPDATE: 1 76 | steps: 77 | - checkout 78 | - run: brew update 79 | - run: git submodule sync 80 | - run: git submodule update --init --recursive 81 | # Android NDK does not pass. 82 | - run: sudo spctl --master-disable 83 | - run: 84 | name: Build Android 85 | command: BUILD_ANDROID=1 USE_CURL=1 sh ci/osx.sh build 86 | - store_artifacts: 87 | path: libNFHTTP-androidx86.zip 88 | destination: libNFHTTP-androidx86.zip 89 | - store_artifacts: 90 | path: libNFHTTP-androidArm64.zip 91 | destination: libNFHTTP-androidArm64.zip 92 | workflows: 93 | version: 2 94 | build: 95 | jobs: 96 | - buildlinuxclang 97 | - buildlinuxgcc 98 | - buildlinuxandroid 99 | - buildmac 100 | - buildmacios 101 | - buildmacandroid 102 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # This file was generated with clang-format 4.0.0 2 | # $ clang-format -style Google -dump-config > .clang-format 3 | # 4 | # Read all about the available options here: 5 | # http://releases.llvm.org/4.0.0/tools/clang/docs/ClangFormatStyleOptions.html 6 | # 7 | # Spotify specific tweaks: 8 | # 9 | # --- 10 | # Language: Cpp 11 | # 12 | # - Standard Auto -> Cpp11 13 | # A> instead of A > 14 | # 15 | # - ColumnLimit 80 -> 100 16 | # We allow up to 100 characters per line 17 | # 18 | # - PointerAlignment Left -> Right 19 | # Always put '*' and '&' close to variable/function 20 | # Guidelines allows both alignments, but we want right (for legacy reasons) 21 | # 22 | # - DerivePointerAlignment: true -> false 23 | # Always put '*' and '&' close to variable/function 24 | # Guidelines allows both alignments, but we want right (for legacy reasons) 25 | # 26 | # - AllowShortFunctionsOnASingleLine: All -> Inline 27 | # We don't want to put out of class function definitions on a single line. 28 | # Standard allows it, but we prefer to keep the oneliners for methods inside classes. 29 | # 30 | # - BinPackArguments: true -> false 31 | # A function declaration’s or function definition’s parameters will either all be on the same 32 | # line or will have one line each. 33 | # Guidelines allows both true and false, but we like false better so we prefer that. 34 | # 35 | # - BinPackParameters: true -> false 36 | # A function call’s arguments will either be all on the same line or will have one line each. 37 | # Guidelines allows both true and false, but we like false better so we prefer that. 38 | # 39 | # - ForEachMacros: Remove all listed macros 40 | # We don't use foreach macros so clang-format shouldn't special treat any keywords. 41 | # 42 | # - IncludeCategories: 43 | # Tweaked priorities to match our preferred order. 44 | # 45 | # - ObjCSpaceAfterProperty: false -> true 46 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 47 | # 48 | # - ObjCSpaceBeforeProtocolList: false -> true 49 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 50 | # 51 | # --- 52 | # Language: ObjC 53 | # 54 | # Hand tweaked config that aims to match Spotifys Objective-C coding style, 55 | # see https://github.com/spotify/ios-style. 56 | # 57 | --- 58 | Language: Cpp 59 | # BasedOnStyle: Google 60 | AccessModifierOffset: -1 61 | AlignAfterOpenBracket: Align 62 | AlignConsecutiveAssignments: false 63 | AlignConsecutiveDeclarations: false 64 | AlignEscapedNewlinesLeft: true 65 | AlignOperands: true 66 | AlignTrailingComments: true 67 | AllowAllParametersOfDeclarationOnNextLine: true 68 | AllowShortBlocksOnASingleLine: false 69 | AllowShortCaseLabelsOnASingleLine: false 70 | AllowShortFunctionsOnASingleLine: Inline 71 | AllowShortIfStatementsOnASingleLine: true 72 | AllowShortLoopsOnASingleLine: true 73 | AlwaysBreakAfterDefinitionReturnType: None 74 | AlwaysBreakAfterReturnType: None 75 | AlwaysBreakBeforeMultilineStrings: true 76 | AlwaysBreakTemplateDeclarations: true 77 | BinPackArguments: false 78 | BinPackParameters: false 79 | # Note: BraceWrapping is ignored since BreakBeforeBraces isn't set to Custom 80 | BraceWrapping: 81 | AfterClass: false 82 | AfterControlStatement: false 83 | AfterEnum: false 84 | AfterFunction: false 85 | AfterNamespace: false 86 | AfterObjCDeclaration: false 87 | AfterStruct: false 88 | AfterUnion: false 89 | BeforeCatch: false 90 | BeforeElse: false 91 | IndentBraces: false 92 | BreakBeforeBinaryOperators: None 93 | BreakBeforeBraces: Attach 94 | BreakBeforeTernaryOperators: true 95 | BreakConstructorInitializersBeforeComma: false 96 | BreakAfterJavaFieldAnnotations: false 97 | BreakStringLiterals: true 98 | ColumnLimit: 100 99 | CommentPragmas: '^ IWYU pragma:' 100 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 101 | ConstructorInitializerIndentWidth: 4 102 | ContinuationIndentWidth: 4 103 | Cpp11BracedListStyle: true 104 | DerivePointerAlignment: false 105 | DisableFormat: false 106 | ExperimentalAutoDetectBinPacking: false 107 | ForEachMacros: [] 108 | IncludeCategories: 109 | - Regex: '^' 110 | Priority: 3 111 | - Regex: '^' 112 | Priority: 4 113 | - Regex: '^["<]base/.*.h[">]' 114 | Priority: 5 115 | - Regex: '^".*"' 116 | Priority: 6 117 | - Regex: '^<[a-z_]*>' 118 | Priority: 1 119 | - Regex: '^<.*>' 120 | Priority: 2 121 | IncludeIsMainRegex: '([-_](test|unittest))?$' 122 | IndentCaseLabels: true 123 | IndentWidth: 2 124 | IndentWrappedFunctionNames: false 125 | JavaScriptQuotes: Leave 126 | JavaScriptWrapImports: true 127 | KeepEmptyLinesAtTheStartOfBlocks: false 128 | MacroBlockBegin: '' 129 | MacroBlockEnd: '' 130 | MaxEmptyLinesToKeep: 1 131 | NamespaceIndentation: None 132 | ObjCBlockIndentWidth: 2 133 | ObjCSpaceAfterProperty: true 134 | ObjCSpaceBeforeProtocolList: true 135 | PenaltyBreakBeforeFirstCallParameter: 1 136 | PenaltyBreakComment: 300 137 | PenaltyBreakFirstLessLess: 120 138 | PenaltyBreakString: 1000 139 | PenaltyExcessCharacter: 1000000 140 | PenaltyReturnTypeOnItsOwnLine: 200 141 | PointerAlignment: Right 142 | ReflowComments: true 143 | SortIncludes: true 144 | SpaceAfterCStyleCast: false 145 | SpaceAfterTemplateKeyword: true 146 | SpaceBeforeAssignmentOperators: true 147 | SpaceBeforeParens: ControlStatements 148 | SpaceInEmptyParentheses: false 149 | SpacesBeforeTrailingComments: 2 150 | SpacesInAngles: false 151 | SpacesInCStyleCastParentheses: false 152 | SpacesInContainerLiterals: true 153 | SpacesInParentheses: false 154 | SpacesInSquareBrackets: false 155 | Standard: Cpp11 156 | TabWidth: 8 157 | UseTab: Never 158 | 159 | # 160 | # Objective-C 161 | # 162 | --- 163 | Language: ObjC 164 | # BasedOnStyle: Google 165 | AccessModifierOffset: -1 166 | AlignAfterOpenBracket: Align 167 | AlignConsecutiveAssignments: false 168 | AlignConsecutiveDeclarations: false 169 | AlignEscapedNewlinesLeft: true 170 | AlignOperands: true 171 | AlignTrailingComments: true 172 | AllowAllParametersOfDeclarationOnNextLine: true 173 | AllowShortBlocksOnASingleLine: false 174 | AllowShortCaseLabelsOnASingleLine: false 175 | AllowShortFunctionsOnASingleLine: false 176 | AllowShortIfStatementsOnASingleLine: false 177 | AllowShortLoopsOnASingleLine: false 178 | AlwaysBreakAfterDefinitionReturnType: None 179 | AlwaysBreakAfterReturnType: None 180 | AlwaysBreakBeforeMultilineStrings: true 181 | AlwaysBreakTemplateDeclarations: true 182 | BinPackArguments: false 183 | BinPackParameters: false 184 | BraceWrapping: 185 | AfterClass: false 186 | AfterControlStatement: false 187 | AfterEnum: false 188 | AfterFunction: true 189 | AfterNamespace: false 190 | AfterObjCDeclaration: false 191 | AfterStruct: false 192 | AfterUnion: false 193 | BeforeCatch: false 194 | BeforeElse: false 195 | IndentBraces: false 196 | BreakBeforeBinaryOperators: None 197 | BreakBeforeBraces: Custom 198 | BreakBeforeTernaryOperators: true 199 | BreakConstructorInitializersBeforeComma: false 200 | BreakAfterJavaFieldAnnotations: false 201 | BreakStringLiterals: true 202 | ColumnLimit: 120 203 | CommentPragmas: '^ IWYU pragma:' 204 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 205 | ConstructorInitializerIndentWidth: 4 206 | ContinuationIndentWidth: 4 207 | Cpp11BracedListStyle: true 208 | DerivePointerAlignment: false 209 | DisableFormat: false 210 | ExperimentalAutoDetectBinPacking: false 211 | ForEachMacros: [] 212 | IncludeCategories: 213 | - Regex: '^' 214 | Priority: 3 215 | - Regex: '^' 216 | Priority: 4 217 | - Regex: '^["<]base/.*.h[">]' 218 | Priority: 5 219 | - Regex: '^".*"' 220 | Priority: 6 221 | - Regex: '^<[a-z_]*>' 222 | Priority: 1 223 | - Regex: '^<.*>' 224 | Priority: 2 225 | IncludeIsMainRegex: '([-_](test|unittest))?$' 226 | IndentCaseLabels: true 227 | IndentWidth: 4 228 | IndentWrappedFunctionNames: false 229 | JavaScriptQuotes: Leave 230 | JavaScriptWrapImports: true 231 | KeepEmptyLinesAtTheStartOfBlocks: false 232 | MacroBlockBegin: '' 233 | MacroBlockEnd: '' 234 | MaxEmptyLinesToKeep: 1 235 | NamespaceIndentation: None 236 | ObjCBlockIndentWidth: 4 237 | ObjCSpaceAfterProperty: true 238 | ObjCSpaceBeforeProtocolList: true 239 | PenaltyBreakBeforeFirstCallParameter: 1 240 | PenaltyBreakComment: 300 241 | PenaltyBreakFirstLessLess: 120 242 | PenaltyBreakString: 1000 243 | PenaltyExcessCharacter: 1000000 244 | PenaltyReturnTypeOnItsOwnLine: 200 245 | PointerAlignment: Right 246 | ReflowComments: true 247 | SortIncludes: true 248 | SpaceAfterCStyleCast: false 249 | SpaceAfterTemplateKeyword: true 250 | SpaceBeforeAssignmentOperators: true 251 | SpaceBeforeParens: ControlStatements 252 | SpaceInEmptyParentheses: false 253 | SpacesBeforeTrailingComments: 2 254 | SpacesInAngles: false 255 | SpacesInCStyleCastParentheses: false 256 | SpacesInContainerLiterals: true 257 | SpacesInParentheses: false 258 | SpacesInSquareBrackets: false 259 | Standard: Cpp11 260 | TabWidth: 4 261 | UseTab: Never -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Environment normalization: 2 | /.bundle 3 | /vendor/bundle 4 | 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | responses 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | project.xcworkspace 25 | 26 | ## Other 27 | *.xccheckout 28 | *.moved-aside 29 | *.xcuserstate 30 | *.xcscmblueprint 31 | .DS_Store 32 | 33 | ## Obj-C/Swift specific 34 | *.hmap 35 | *.ipa 36 | 37 | # Carthage 38 | Carthage/Build 39 | 40 | # Ignore changes to our project.xcconfig that gets overridden by the build system 41 | project.xcconfig 42 | 43 | # Python 44 | *.egg* 45 | *.pyc 46 | nfhttp_env 47 | 48 | # Boost download 49 | libraries/boost_* 50 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libraries/json"] 2 | path = libraries/json 3 | url = https://github.com/nlohmann/json.git 4 | [submodule "libraries/curl"] 5 | path = libraries/curl 6 | url = https://github.com/curl/curl.git 7 | [submodule "libraries/openssl"] 8 | path = libraries/openssl 9 | url = https://github.com/openssl/openssl.git 10 | [submodule "libraries/cpprestsdk"] 11 | path = libraries/cpprestsdk 12 | url = https://github.com/Microsoft/cpprestsdk.git 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Spotify AB. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | cmake_minimum_required(VERSION 3.6) 20 | 21 | project(NFHTTP) 22 | 23 | set(CMAKE_CXX_STANDARD 11) 24 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 25 | 26 | if(NOT WIN32) 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") 28 | endif() 29 | 30 | if(LLVM_STDLIB) 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ \ 32 | -Wno-tautological-undefined-compare") 33 | endif() 34 | 35 | set(NFHTTP_INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include") 36 | set(NFHTTP_LIBRARIES_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libraries") 37 | 38 | if(USE_ADDRESS_SANITIZER) 39 | message("Using Address & Leak Sanitizer") 40 | set( 41 | CMAKE_CXX_FLAGS 42 | "${CMAKE_CXX_FLAGS} -fsanitize=address -g -fno-omit-frame-pointer") 43 | set( 44 | CMAKE_EXE_LINKER_FLAGS 45 | "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -g -fno-omit-frame-pointer") 46 | endif() 47 | 48 | if(CODE_COVERAGE) 49 | message("Using Code Coverage") 50 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 51 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 52 | endif() 53 | 54 | if(NOT DEFINED USE_CURL) 55 | if(CMAKE_SYSTEM_NAME MATCHES "Linux" OR ANDROID) 56 | set(USE_CURL TRUE CACHE BOOL "Build with curl") 57 | else() 58 | set(USE_CURL FALSE CACHE BOOL "Build with curl") 59 | endif() 60 | endif() 61 | 62 | if(USE_CURL) 63 | add_definitions(-DUSE_CURL=1) 64 | endif() 65 | 66 | if(NOT DEFINED USE_CPPRESTSDK) 67 | if(WIN32) 68 | set(USE_CPPRESTSDK TRUE CACHE BOOL "Build with Microsoft's C++ Rest SDK") 69 | else() 70 | set(USE_CPPRESTSDK FALSE CACHE BOOL "Build with Microsoft's C++ Rest SDK") 71 | endif() 72 | endif() 73 | 74 | if(USE_CPPRESTSDK) 75 | add_definitions(-DUSE_CPPRESTSDK=1) 76 | endif() 77 | 78 | if(WIN32) 79 | set(WINDOWS_FLAGS "/W3") 80 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /WX") 81 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /bigobj") 82 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_CRT_SECURE_NO_WARNINGS=1") 83 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4003") 84 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4018") 85 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4091") 86 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4200") 87 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4250") 88 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4275") 89 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4355") 90 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4520") 91 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4530") 92 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4146") 93 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4053") 94 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4063") 95 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4064") 96 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4503") 97 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DWIN32") 98 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DUNICODE") 99 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_UNICODE") 100 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WINDOWS") 101 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DNOMINMAX") 102 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WIN32_IE=0x0700") 103 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WIN32_WINNT=0x0601") 104 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4447") 105 | set(WINDOWS_LINKER_FLAGS "/DYNAMICBASE:NO") 106 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /INCREMENTAL:NO") 107 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /OPT:ICF") 108 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /OPT:REF") 109 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /NXCOMPAT:NO") 110 | 111 | # TODO fix and use windows flags 112 | set(CMAKE_EXE_LINKER_FLAGS 113 | "${CMAKE_EXE_LINKER_FLAGS} ${WINDOWS_LINKER_FLAGS}") 114 | endif() 115 | 116 | set(OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/output") 117 | message("OUTPUT_DIRECTORY: ${OUTPUT_DIRECTORY}") 118 | execute_process(COMMAND python 119 | "${CMAKE_CURRENT_SOURCE_DIR}/tools/generate-version.py" 120 | "${OUTPUT_DIRECTORY}" 121 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 122 | 123 | add_subdirectory(libraries) 124 | add_subdirectory(source) 125 | 126 | message(STATUS "********** Build configuration ************") 127 | message(STATUS "Compiling for ${CMAKE_SYSTEM_NAME}") 128 | message(STATUS "Configuration: ${CMAKE_BUILD_TYPE}") 129 | message(STATUS "C Compiler: ${CMAKE_C_COMPILER}") 130 | message(STATUS "C++ Compiler: ${CMAKE_CXX_COMPILER}") 131 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are welcomed. Open a pull-request or an issue. 3 | 4 | ## Code of conduct 5 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 6 | 7 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 8 | -------------------------------------------------------------------------------- /NFHTTP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFHTTP/86c1e3683626fc52175fdc1d972cfeda1fd8b64a/NFHTTP.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NFHTTP 2 | 3 | [![CircleCI](https://circleci.com/gh/spotify/NFHTTP/tree/master.svg?style=svg)](https://circleci.com/gh/spotify/NFHTTP/tree/master) 4 | [![License](https://img.shields.io/github/license/spotify/NFHTTP.svg)](LICENSE) 5 | [![Spotify FOSS Slack](https://slackin.spotify.com/badge.svg)](https://slackin.spotify.com) 6 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/spotify/nfhttp)](http://clayallsopp.github.io/readme-score?url=https://github.com/spotify/nfhttp) 7 | 8 | A cross platform C++ HTTP framework. 9 | 10 | - [x] 📱 [iOS](https://www.apple.com/ios/) 9.0+ 11 | - [x] 💻 [OS X](https://www.apple.com/macos/) 10.11+ 12 | - [x] 🐧 [Ubuntu](https://www.ubuntu.com/) Trusty 14.04+ 13 | - [x] 🤖 [Android](https://developer.android.com/studio/) SDK r24+ 14 | - [x] 🖥️ [Microsoft UWP](https://developer.microsoft.com/en-us/windows/apps) 15 | 16 | Developed at Spotify 2019-2022, Discontinued and handed over to new maintainers January 2023 17 | 18 | ## Raison D'être :thought_balloon: 19 | At Spotify we have performed studies that show the efficacy of using native backed solutions for interfacing to backends, especially when it came to the battery life of certain devices. In order to carry this forward in the cross-platform C++ world, we created this library that provides a common interface to many of the system level HTTP interfaces, and predictable caching and request hooking. We found that many of the current solutions that claimed to do this lacked key supports for many kinds of platforms, and ended up being libraries that heavily favoured 1 platform and gave the other platforms a generic implementation. We also wanted to provide a caching layer that was consistent across all platforms in our layered architecture. 20 | 21 | ## Architecture :triangular_ruler: 22 | `NFHTTP` is designed as a common C++ interface to communicate with different systems over HTTP! The API allows you to create objects to make `Requests` and read `Responses`. To initiate, send and receive messages you create and use a `Client` object. This is a layered architecture where requests and responses can pass through multiple places in the stack and get decorated or have actions taken upon them. 23 | 24 | The layer design is as follows: 25 | - **The Modification layer**, which takes requests and responses, performs any modifications on them that might be required by the functions provided to the factory, and forwards them on. 26 | - **The Multi-Request Layer**, which takes a request, determines if the same request is currently being executed, then ties the response to that request with the response currently coming in from the previously sent request. 27 | - **The Caching Layer**, which takes a request, determines whether it is cached and if so sends a response immediately, if not forwards the request, and when it receives the response stores the response in its cache. 28 | - **The Native Layer**, which takes a request and converts it to a system level call depending on the system the user is using, then converts the response back to an NFHTTP response and sends the response back up the chain. 29 | 30 | Our support table looks like so: 31 | 32 | | OS | Underlying Framework | Status | 33 | | ------------- |:------------------------------------------------------------------------------------------------------------:| -------:| 34 | | iOS | [NSURLSession](https://developer.apple.com/documentation/foundation/nsurlsession) | Stable | 35 | | OSX | [NSURLSession](https://developer.apple.com/documentation/foundation/nsurlsession) | Stable | 36 | | Linux | [curl](https://curl.haxx.se/) | Stable | 37 | | Android | [curl](https://curl.haxx.se/) | Beta | 38 | | Windows | [WinHTTP](https://docs.microsoft.com/en-us/windows/desktop/winhttp/about-winhttp) | Alpha | 39 | 40 | In addition to this, it is also possible to use curl on any of the above platforms or boost ASIO (provided by CPP REST SDK). 41 | 42 | ## Dependencies :globe_with_meridians: 43 | * [C++ REST SDK](https://github.com/Microsoft/cpprestsdk) 44 | * [curl](https://curl.haxx.se/) 45 | * [JSON for Modern C++](https://github.com/nlohmann/json) 46 | * [OpenSSL](https://www.openssl.org/) 47 | * [SQLite](https://www.sqlite.org/index.html) 48 | * [boost](https://www.boost.org/) 49 | 50 | ## Installation :inbox_tray: 51 | `NFHTTP` is a [Cmake](https://cmake.org/) project, while you are free to download the prebuilt static libraries it is recommended to use Cmake to install this project into your wider project. In order to add this into a wider Cmake project (who needs monorepos anyway?), simply add the following lines to your `CMakeLists.txt` file: 52 | ``` 53 | add_subdirectory(NFHTTP) 54 | 55 | # Link NFHTTP to your executables or target libs 56 | target_link_libraries(your_target_lib_or_executable NFHTTP) 57 | ``` 58 | 59 | ### For iOS/OSX 60 | Generate an [Xcode](https://developer.apple.com/xcode/) project from the Cmake project like so: 61 | ```shell 62 | $ git submodule update --init --recursive 63 | $ mkdir build 64 | $ cd build 65 | $ cmake .. -GXcode 66 | ``` 67 | 68 | ### For linux 69 | Generate a [Ninja](https://ninja-build.org/) project from the Cmake project like so: 70 | ```shell 71 | $ git submodule update --init --recursive 72 | $ mkdir build 73 | $ cd build 74 | $ cmake .. -GNinja 75 | ``` 76 | 77 | ### For Android 78 | Use [gradle](https://gradle.org/) 79 | ``` 80 | android { 81 | compileSdkVersion 26 82 | defaultConfig { 83 | applicationId "com.spotify.nfhttptest_android" 84 | minSdkVersion 19 85 | targetSdkVersion 26 86 | versionCode 1 87 | versionName "1.0" 88 | externalNativeBuild { 89 | cmake { 90 | cppFlags "" 91 | arguments "-DANDROID_APP=1 -DANDROID=1" 92 | } 93 | } 94 | } 95 | 96 | sourceSets { 97 | main { 98 | jniLibs.srcDirs = ['src/main/cpp'] 99 | } 100 | } 101 | 102 | externalNativeBuild { 103 | cmake { 104 | path "../CMakeLists.txt" 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | ### For Windows 111 | Generate a [Visual Studio](https://visualstudio.microsoft.com/) project from the Cmake project like so: 112 | 113 | ```shell 114 | $ mkdir build 115 | $ cd build 116 | $ cmake .. -G "Visual Studio 12 2013 Win64" 117 | ``` 118 | 119 | ## Usage example :eyes: 120 | In order to execute HTTP requests, you must first create a client like so: 121 | ```C++ 122 | auto client = nativeformat::http::createClient(nativeformat::http::standardCacheLocation(), 123 | "NFHTTP-" + nativeformat::http::version()); 124 | ``` 125 | 126 | It is wise to only create one client per application instance, in reality you will only need one (unless you need to separate the caching mechanism for your own reasons). After you have done this you can proceed to creating request objects like so: 127 | ```C++ 128 | const std::string url = "http://localhost:6582/world"; 129 | auto request = nativeformat::http::createRequest(url, std::unordered_map()); 130 | ``` 131 | 132 | This will create a GET request with no added headers to send to the localhost:682/world location. This does not mean other headers will not be added, we have multiple layers that will add caching requirement headers, language headers, content size headers and the native layer can also add headers as it sees fit. After we have created our request we can then execute it: 133 | ```C++ 134 | auto token = client->performRequest(request, [](const std::shared_ptr &response) { 135 | printf("Received Response: %s\n", response->data()); 136 | }); 137 | ``` 138 | 139 | The callback will be called asynchronously in whatever thread the native libraries post the response on, so watch out for thread safety within this callback. In order to execute requests synchronously on whatever thread you happen to be on, you can perform the follow actions: 140 | ```C++ 141 | auto response = client->performSynchronousRequest(request); 142 | printf("Received Response: %s\n", response->data()); 143 | ``` 144 | 145 | You might wonder how you can hook requests and responses, this can be done when creating the client, for example: 146 | ```C++ 147 | auto client = nativeformat::http::createClient(nativeformat::http::standardCacheLocation(), 148 | "NFHTTP-" + nativeformat::http::version(), 149 | [](std::function &request)> callback, 150 | const std::shared_ptr &request) { 151 | printf("Request URL: %s\n", request->url().c_str()); 152 | callback(request); 153 | }, 154 | [](std::function &response, bool retry)> callback, 155 | const std::shared_ptr &response) { 156 | printf("Response URL: %s\n", response->request()->url().c_str()); 157 | callback(response, false); 158 | }); 159 | ``` 160 | 161 | Here we have hooked the client up to receive requests and responses via the hook functions. Because we are now part of the layered architecture, we can perform any changes we want on the requests or responses, such as decorating with OAuth tokens, redirecting to other URLs, retrying responses or even cancelling responses altogether. If you are interested in the concept of cache pinning, it can be done like so: 162 | ```C++ 163 | client->pinResponse(response, "my-offlined-entity-token"); 164 | ``` 165 | 166 | This will then ensure that the response is in the cache until it is explicitly removed, and ignore all backend caching directives. 167 | 168 | ## Contributing :mailbox_with_mail: 169 | Contributions are welcomed, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. 170 | 171 | ## License :memo: 172 | The project is available under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) license. 173 | 174 | ### Acknowledgements 175 | - Icon in readme banner is “[Download](https://thenounproject.com/search/?q=http&i=174663)” by romzicon from the Noun Project. 176 | 177 | #### Contributors 178 | * [Will Sackfield](https://github.com/8W9aG) 179 | * [Julia Cox](https://github.com/astrocox) 180 | * [David Rubinstein](https://github.com/drubinstein) 181 | * [Justin Sarma](https://github.com/jsarma) 182 | 183 | -------------------------------------------------------------------------------- /ci/android.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("makeBuildDirectoryX86", 32 | "Wipe existing build directory for X86 build.") 33 | buildOptions.addOption("generateProjectX86", "Regenerate project for X86 build") 34 | 35 | buildOptions.addOption("buildTargetLibraryX86", "Build Target: Library (X86)") 36 | 37 | buildOptions.addOption("makeBuildDirectoryArm64", 38 | "Wipe existing build directory for ARM64 build.") 39 | buildOptions.addOption("generateProjectArm64", "Regenerate project for ARM64 build") 40 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 41 | buildOptions.addOption("buildTargetLibraryArm64", "Build Target: Library (ARM64)") 42 | 43 | buildOptions.setDefaultWorkflow("Empty workflow", []) 44 | 45 | buildOptions.addWorkflow("build", "Production Build (Android OSX)", [ 46 | 'makeBuildDirectoryX86', 47 | 'generateProjectX86', 48 | 'buildTargetLibraryX86', 49 | 'packageArtifacts', 50 | 'makeBuildDirectoryArm64', 51 | 'generateProjectArm64', 52 | 'buildTargetLibraryArm64', 53 | 'packageArtifacts', 54 | ]) 55 | 56 | buildOptions.addWorkflow("buildX86", "Production Build (X86)", [ 57 | 'makeBuildDirectoryX86', 58 | 'generateProjectX86', 59 | 'buildTargetLibraryX86', 60 | 'packageArtifacts' 61 | ]) 62 | 63 | buildOptions.addWorkflow("buildArm64", "Production Build (ARM64)", [ 64 | 'makeBuildDirectoryArm64', 65 | 'generateProjectArm64', 66 | 'buildTargetLibraryArm64', 67 | 'packageArtifacts' 68 | ]) 69 | 70 | options = buildOptions.parseArgs() 71 | buildOptions.verbosePrintBuildOptions(options) 72 | 73 | library_target = 'NFHTTP' 74 | nfbuild = NFBuildOSX() 75 | 76 | if buildOptions.checkOption(options, 'makeBuildDirectoryX86'): 77 | nfbuild.makeBuildDirectory() 78 | 79 | if buildOptions.checkOption(options, 'generateProjectX86'): 80 | nfbuild.generateProject(android=True, android_arm=False) 81 | 82 | if buildOptions.checkOption(options, 'buildTargetLibraryX86'): 83 | nfbuild.buildTarget(library_target) 84 | 85 | if buildOptions.checkOption(options, 'packageArtifacts'): 86 | nfbuild.packageArtifacts() 87 | 88 | if buildOptions.checkOption(options, 'makeBuildDirectoryArm64'): 89 | nfbuild.makeBuildDirectory() 90 | 91 | if buildOptions.checkOption(options, 'generateProjectArm64'): 92 | nfbuild.generateProject(android=True, android_arm=True) 93 | 94 | if buildOptions.checkOption(options, 'buildTargetLibraryArm64'): 95 | nfbuild.buildTarget(library_target) 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /ci/androidlinux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildlinux import NFBuildLinux 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | 32 | buildOptions.addOption("makeBuildDirectoryX86", 33 | "Wipe existing build directory for X86 build.") 34 | buildOptions.addOption("generateProjectX86", "Regenerate project for X86 build") 35 | 36 | buildOptions.addOption("buildTargetLibraryX86", "Build Target: Library (X86)") 37 | 38 | buildOptions.addOption("makeBuildDirectoryArm64", 39 | "Wipe existing build directory for ARM64 build.") 40 | buildOptions.addOption("generateProjectArm64", "Regenerate project for ARM64 build") 41 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 42 | buildOptions.addOption("buildTargetLibraryArm64", "Build Target: Library (ARM64)") 43 | 44 | buildOptions.setDefaultWorkflow("Empty workflow", []) 45 | 46 | buildOptions.addWorkflow("build", "Production Build (Android Linux)", [ 47 | 'makeBuildDirectoryX86', 48 | 'generateProjectX86', 49 | 'buildTargetLibraryX86', 50 | 'packageArtifacts', 51 | 'makeBuildDirectoryArm64', 52 | 'generateProjectArm64', 53 | 'buildTargetLibraryArm64', 54 | 'packageArtifacts' 55 | ]) 56 | 57 | buildOptions.addWorkflow("buildX86", "Production Build (X86)", [ 58 | 'makeBuildDirectoryX86', 59 | 'generateProjectX86', 60 | 'buildTargetLibraryX86', 61 | 'packageArtifacts' 62 | ]) 63 | 64 | buildOptions.addWorkflow("buildArm64", "Production Build (ARM64)", [ 65 | 'makeBuildDirectoryArm64', 66 | 'generateProjectArm64', 67 | 'buildTargetLibraryArm64', 68 | 'packageArtifacts' 69 | ]) 70 | 71 | options = buildOptions.parseArgs() 72 | buildOptions.verbosePrintBuildOptions(options) 73 | 74 | library_target = 'NFHTTP' 75 | nfbuild = NFBuildLinux() 76 | 77 | if buildOptions.checkOption(options, 'makeBuildDirectoryX86'): 78 | nfbuild.makeBuildDirectory() 79 | 80 | if buildOptions.checkOption(options, 'generateProjectX86'): 81 | nfbuild.generateProject(android=True, android_arm=False) 82 | 83 | if buildOptions.checkOption(options, 'buildTargetLibraryX86'): 84 | nfbuild.buildTarget(library_target) 85 | 86 | if buildOptions.checkOption(options, 'packageArtifacts'): 87 | nfbuild.packageArtifacts() 88 | 89 | if buildOptions.checkOption(options, 'makeBuildDirectoryArm64'): 90 | nfbuild.makeBuildDirectory() 91 | 92 | if buildOptions.checkOption(options, 'generateProjectArm64'): 93 | nfbuild.generateProject(android=True, android_arm=True) 94 | 95 | if buildOptions.checkOption(options, 'buildTargetLibraryArm64'): 96 | nfbuild.buildTarget(library_target) 97 | 98 | if buildOptions.checkOption(options, 'packageArtifacts'): 99 | nfbuild.packageArtifacts() 100 | 101 | 102 | if __name__ == "__main__": 103 | main() 104 | -------------------------------------------------------------------------------- /ci/build_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import argparse 5 | 6 | # Argument parsing abstraction designed for the build context. 7 | # Generates an array of buildOptions from an input arg list. 8 | # Allows options to be grouped into workflows, and workflows 9 | # to be overridden with minor changes. 10 | 11 | # Example commands: 12 | 13 | # run the lint workflow with an override to disable dependencies install: 14 | # python build_options.py lint -installDependencies=0 -v 15 | 16 | # run the default workflow with an override to make it fly. 17 | # python build_options.py -doFlyAway=1 -v 18 | 19 | 20 | class BuildOptions: 21 | 22 | def __init__(self): 23 | self.options = {} 24 | self.workflows = {} 25 | self.verbose = True 26 | 27 | # Define a build option, with documentation. 28 | def addOption(self, option, doc): 29 | self.options[option] = doc 30 | 31 | # Define a workflow, which consists of a group of build options. 32 | # This will be an optional single positional argument. 33 | def addWorkflow(self, workflow, doc, options): 34 | for option in options: 35 | if option not in self.options: 36 | self.flushed_print( 37 | "Error: Workflow %s contains invalid option %s" 38 | % (workflow, option)) 39 | exit(1) 40 | self.workflows[workflow] = { 41 | 'doc': doc, 42 | 'options': {x: '1' for x in options} 43 | } 44 | 45 | # Define the default workflow, if the cmdline input doesn't include 46 | # a position workflow arg. 47 | def setDefaultWorkflow(self, doc, options): 48 | self.addWorkflow("default", doc, options) 49 | 50 | def getOptionDoc(self, option): 51 | return self.options[option] 52 | 53 | def getWorkflowHelp(self): 54 | str = "" 55 | for workflow, data in self.workflows.items(): 56 | str += "%s:\n\t%s\n" % (workflow, data['doc']) 57 | return str 58 | 59 | # Parse input arguments 60 | def parseArgs(self): 61 | parser = argparse.ArgumentParser( 62 | formatter_class=argparse.RawTextHelpFormatter) 63 | 64 | # Verbose is automatically defined 65 | parser.add_argument("-quiet", "-q", 66 | help="Mute build steps", 67 | action='store_true') 68 | 69 | # Can have any number of workflows. If 0, it runs the default workflow. 70 | # If more than 1, the built options get intersected 71 | parser.add_argument("workflows", nargs='*', default=['default'], 72 | help=self.getWorkflowHelp()) 73 | 74 | # Define build options with leading - 75 | for k, v in self.options.items(): 76 | parser.add_argument("-" + k, help=v) 77 | args = parser.parse_args() 78 | argHash = vars(args) 79 | result = {} 80 | 81 | if 'quiet' in argHash and argHash['quiet']: 82 | self.verbose = False 83 | 84 | for workflow in argHash['workflows']: 85 | if workflow not in self.workflows: 86 | self.flushed_print("Error: Specified invalid workflow %s" % 87 | workflow) 88 | exit(1) 89 | # Load options from selected workflows 90 | result.update(self.workflows[workflow]['options']) 91 | 92 | # Apply option overrides to workflow 93 | for option, doc in self.options.items(): 94 | if option in argHash and argHash[option]: 95 | result[option] = argHash[option] 96 | 97 | return [k for k, v in result.items() if v == '1'] 98 | 99 | # Print which build options are enabled. 100 | def verbosePrintBuildOptions(self, args): 101 | if self.verbose: 102 | self.flushed_print("Build Options: " + str(args)) 103 | 104 | # Caller should call this when a build option is being executed 105 | # for verbose build logging. 106 | def verbosePrint(self, option): 107 | if self.verbose: 108 | self.flushed_print("===== %s =====" % self.getOptionDoc(option)) 109 | 110 | # Check if this given build option is defined. If so, print the step has 111 | # begun and return true so caller can run the step. 112 | # Sanity check the input option to catch typos. 113 | def checkOption(self, args, arg, quiet=False): 114 | if arg not in self.options: 115 | self.flushed_print("Error: Checked undefined option %s" % arg) 116 | exit(1) 117 | if arg in args: 118 | if not quiet: 119 | self.verbosePrint(arg) 120 | return True 121 | return False 122 | 123 | def flushed_print(self, str): 124 | print(str) 125 | sys.stdout.flush() 126 | 127 | 128 | # Create a toy version for testing if user executes this file directly 129 | def test_version(): 130 | 131 | buildOptions = BuildOptions() 132 | buildOptions.addOption("option1", "option 1 description") 133 | buildOptions.addOption("option2", "option 2 description") 134 | buildOptions.addOption("option3", "option 3 description") 135 | buildOptions.setDefaultWorkflow("Default workflow description", [ 136 | 'option1' 137 | ]) 138 | buildOptions.addWorkflow("workflow1", "workflow1 description", [ 139 | 'option1', 140 | 'option2' 141 | ]) 142 | buildOptions.addWorkflow("workflow2", "workflow2 description", [ 143 | 'option2', 144 | 'option3' 145 | ]) 146 | 147 | options = buildOptions.parseArgs() 148 | buildOptions.verbosePrintBuildOptions(options) 149 | 150 | if buildOptions.checkOption(options, "option1"): 151 | buildOptions.flushed_print("running option1") 152 | 153 | if buildOptions.checkOption(options, "option2"): 154 | buildOptions.flushed_print("running option2") 155 | 156 | if buildOptions.checkOption(options, "option3"): 157 | buildOptions.flushed_print("running option3") 158 | 159 | 160 | if __name__ == "__main__": 161 | test_version() 162 | -------------------------------------------------------------------------------- /ci/ci.yaml: -------------------------------------------------------------------------------- 1 | # NFHTTP CI config 2 | 'static_analyzer_exceptions': 3 | 'integration_tests': 4 | - requests: resources/hello-requests.json 5 | responses: resources/world-responses.json 6 | -------------------------------------------------------------------------------- /ci/ios.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | from nfbuildosx import NFBuildOSX 6 | from build_options import BuildOptions 7 | 8 | 9 | def main(): 10 | buildOptions = BuildOptions() 11 | buildOptions.addOption("lintCpp", "Lint CPP Files") 12 | buildOptions.addOption("lintCppWithInlineChange", 13 | "Lint CPP Files and fix them") 14 | buildOptions.addOption("makeBuildDirectory", 15 | "Wipe existing build directory") 16 | buildOptions.addOption("generateProject", "Regenerate xcode project") 17 | buildOptions.addOption("buildTargetIphoneSimulator", 18 | "Build Target: iPhone Simulator") 19 | buildOptions.addOption("buildTargetIphoneOS", "Build Target: iPhone OS") 20 | 21 | buildOptions.setDefaultWorkflow("Empty workflow", []) 22 | 23 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 24 | 'lintCppWithInlineChange' 25 | ]) 26 | 27 | buildOptions.addWorkflow("build", "Production Build", [ 28 | 'lintCpp', 29 | 'makeBuildDirectory', 30 | 'generateProject', 31 | 'buildTargetIphoneSimulator', 32 | 'buildTargetIphoneOS', 33 | ]) 34 | 35 | options = buildOptions.parseArgs() 36 | buildOptions.verbosePrintBuildOptions(options) 37 | 38 | library_target = 'NFHTTP' 39 | nfbuild = NFBuildOSX() 40 | 41 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 42 | nfbuild.lintCPP(make_inline_changes=True) 43 | elif buildOptions.checkOption(options, 'lintCpp'): 44 | nfbuild.lintCPP(make_inline_changes=False) 45 | 46 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 47 | nfbuild.makeBuildDirectory() 48 | 49 | if buildOptions.checkOption(options, 'generateProject'): 50 | nfbuild.generateProject(ios=True) 51 | 52 | if buildOptions.checkOption(options, 'buildTargetIphoneSimulator'): 53 | nfbuild.buildTarget(library_target, 54 | sdk='iphonesimulator', 55 | arch='x86_64') 56 | 57 | if buildOptions.checkOption(options, 'buildTargetIphoneOS'): 58 | nfbuild.buildTarget(library_target, sdk='iphoneos', arch='arm64') 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | -------------------------------------------------------------------------------- /ci/linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import os 24 | import sys 25 | 26 | from nfbuildlinux import NFBuildLinux 27 | from build_options import BuildOptions 28 | 29 | 30 | def main(): 31 | buildOptions = BuildOptions() 32 | buildOptions.addOption("debug", "Enable Debug Mode") 33 | buildOptions.addOption("installDependencies", "Install dependencies") 34 | buildOptions.addOption("lintCppWithInlineChange", 35 | "Lint CPP Files and fix them") 36 | buildOptions.addOption("makeBuildDirectory", 37 | "Wipe existing build directory") 38 | buildOptions.addOption("generateProject", "Regenerate project") 39 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 40 | buildOptions.addOption("gnuToolchain", "Build with gcc and libstdc++") 41 | buildOptions.addOption("llvmToolchain", "Build with clang and libc++") 42 | buildOptions.addOption("runIntegrationTests", "Run the integration tests") 43 | buildOptions.addOption("packageArtifacts", "Package the Artifacts") 44 | 45 | buildOptions.setDefaultWorkflow("Empty workflow", []) 46 | 47 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 48 | 'lintCppWithInlineChange' 49 | ]) 50 | 51 | buildOptions.addWorkflow("clang_build", "Production Clang Build", [ 52 | 'llvmToolchain', 53 | 'makeBuildDirectory', 54 | 'generateProject', 55 | 'buildTargetLibrary', 56 | 'runIntegrationTests', 57 | 'packageArtifacts' 58 | ]) 59 | 60 | buildOptions.addWorkflow("gcc_build", "Production build with gcc", [ 61 | 'gnuToolchain', 62 | 'makeBuildDirectory', 63 | 'generateProject', 64 | 'buildTargetLibrary', 65 | 'runIntegrationTests', 66 | 'packageArtifacts' 67 | ]) 68 | 69 | options = buildOptions.parseArgs() 70 | buildOptions.verbosePrintBuildOptions(options) 71 | 72 | library_target = 'NFHTTP' 73 | nfbuild = NFBuildLinux() 74 | 75 | if buildOptions.checkOption(options, 'debug'): 76 | nfbuild.build_type = 'Debug' 77 | 78 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 79 | nfbuild.lintCPP(make_inline_changes=True) 80 | 81 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 82 | nfbuild.makeBuildDirectory() 83 | 84 | if buildOptions.checkOption(options, 'generateProject'): 85 | if buildOptions.checkOption(options, 'gnuToolchain'): 86 | os.environ['CC'] = 'gcc' 87 | os.environ['CXX'] = 'g++' 88 | nfbuild.generateProject(gcc=True) 89 | elif buildOptions.checkOption(options, 'llvmToolchain'): 90 | os.environ['CC'] = 'clang' 91 | os.environ['CXX'] = 'clang++' 92 | nfbuild.generateProject(gcc=False) 93 | else: 94 | nfbuild.generateProject() 95 | 96 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 97 | nfbuild.buildTarget(library_target) 98 | if buildOptions.checkOption(options, "runIntegrationTests"): 99 | nfbuild.runIntegrationTests() 100 | if buildOptions.checkOption(options, 'packageArtifacts'): 101 | nfbuild.packageArtifacts() 102 | 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /ci/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2018 Spotify AB. 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | 21 | # Exit on any non-zero status 22 | set -e 23 | 24 | # Install system dependencies 25 | apt-get update 26 | apt-get install sudo 27 | sudo apt-get -q update 28 | sudo apt-get install -y -q --no-install-recommends apt-utils \ 29 | clang \ 30 | clang-format \ 31 | libcurl4-openssl-dev \ 32 | libc++-dev \ 33 | libc++abi-dev \ 34 | ninja-build \ 35 | python-virtualenv \ 36 | virtualenv \ 37 | wget \ 38 | libyaml-dev \ 39 | libssl-dev \ 40 | gobjc++ \ 41 | python-dev \ 42 | python3-dev \ 43 | python2.7 \ 44 | git \ 45 | unzip \ 46 | software-properties-common \ 47 | make \ 48 | build-essential \ 49 | cmake 50 | 51 | # Update submodules 52 | git submodule sync 53 | git submodule update --init --recursive 54 | 55 | # Install virtualenv 56 | virtualenv --python=$(which python3) nfhttp_env 57 | . nfhttp_env/bin/activate 58 | 59 | # Install Python Packages 60 | pip install -r ${PWD}/ci/requirements.txt 61 | 62 | # Execute our python build tools 63 | if [ -n "$BUILD_ANDROID" ]; then 64 | # Install Android NDK 65 | NDK='android-ndk-r17b-linux-x86_64' 66 | ZIP='zip' 67 | wget https://dl.google.com/android/repository/${NDK}.${ZIP} -O ${PWD}/${NDK}.${ZIP} 68 | unzip -o -q ${NDK}.${ZIP} 69 | rm -rf ~/ndk 70 | mv android-ndk-r17b ~/ndk 71 | 72 | chmod +x -R ~/ndk 73 | 74 | python ci/androidlinux.py "$@" 75 | else 76 | python ci/linux.py "$@" 77 | fi 78 | -------------------------------------------------------------------------------- /ci/llvm-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec xcrun llvm-cov gcov "$@" -------------------------------------------------------------------------------- /ci/nfbuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import filecmp 24 | import fnmatch 25 | import json 26 | import os 27 | import pprint 28 | import signal 29 | import shutil 30 | import subprocess 31 | import sys 32 | import time 33 | import yaml 34 | 35 | 36 | class NFBuild(object): 37 | def __init__(self): 38 | ci_yaml_file = os.path.join('ci', 'ci.yaml') 39 | self.build_configuration = yaml.load(open(ci_yaml_file, 'r')) 40 | self.pretty_printer = pprint.PrettyPrinter(indent=4) 41 | self.current_working_directory = os.getcwd() 42 | self.build_directory = 'build' 43 | self.build_type = 'Release' 44 | self.output_directory = os.path.join(self.build_directory, 'output') 45 | self.statically_analyzed_files = [] 46 | 47 | def build_print(self, print_string): 48 | print(print_string) 49 | sys.stdout.flush() 50 | 51 | def makeBuildDirectory(self): 52 | if os.path.exists(self.build_directory): 53 | shutil.rmtree(self.build_directory) 54 | os.makedirs(self.build_directory) 55 | os.makedirs(self.output_directory) 56 | 57 | def generateProject(self, 58 | code_coverage=False, 59 | address_sanitizer=False, 60 | thread_sanitizer=False, 61 | undefined_behaviour_sanitizer=False, 62 | ios=False, 63 | use_curl=False, 64 | android=False, 65 | android_arm=False): 66 | assert True, "generateProject should be overridden by subclass" 67 | 68 | def buildTarget(self, target, sdk='macosx'): 69 | assert True, "buildTarget should be overridden by subclass" 70 | 71 | def packageArtifacts(self): 72 | assert True, "packageArtifacts should be overridden by subclass" 73 | 74 | def lintCPPFile(self, filepath, make_inline_changes=False): 75 | current_source = open(filepath, 'r').read() 76 | clang_format_call = [self.clang_format_binary] 77 | if make_inline_changes: 78 | clang_format_call.append('-i') 79 | clang_format_call.append(filepath) 80 | new_source = subprocess.check_output(clang_format_call).decode() 81 | if current_source != new_source and not make_inline_changes: 82 | self.build_print( 83 | filepath + " failed C++ lint, file should look like:") 84 | self.build_print(new_source) 85 | return False 86 | return True 87 | 88 | def lintCPPDirectory(self, directory, make_inline_changes=False): 89 | passed = True 90 | for root, dirnames, filenames in os.walk(directory): 91 | for filename in filenames: 92 | if not filename.endswith(('.cpp', '.h', '.m', '.mm')): 93 | continue 94 | full_filepath = os.path.join(root, filename) 95 | if not self.lintCPPFile(full_filepath, make_inline_changes): 96 | passed = False 97 | return passed 98 | 99 | def lintCPP(self, make_inline_changes=False): 100 | lint_result = self.lintCPPDirectory('source', make_inline_changes) 101 | lint_result &= self.lintCPPDirectory('include', make_inline_changes) 102 | if not lint_result: 103 | sys.exit(1) 104 | 105 | def runIntegrationTests(self): 106 | # Build the CLI target 107 | cli_target_name = 'NFHTTPCLI' 108 | cli_binary = self.targetBinary(cli_target_name) 109 | self.buildTarget(cli_target_name) 110 | # Launch the dummy server 111 | root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') 112 | cwd = os.path.join(os.path.join(root_path, 'resources'), 'localhost') 113 | cmd = 'python -m http.server 6582' 114 | pro = subprocess.Popen(cmd, stdout=subprocess.PIPE, preexec_fn=os.setsid, cwd=cwd, shell=True) 115 | time.sleep(3) 116 | cli_result = self.runIntegrationTestsUnderDummyServer(cli_binary, root_path) 117 | os.killpg(os.getpgid(pro.pid), signal.SIGTERM) 118 | if cli_result: 119 | sys.exit(cli_result) 120 | 121 | def runIntegrationTestsUnderDummyServer(self, cli_binary, root_path): 122 | output_responses = os.path.join(root_path, 'responses') 123 | resources_path = os.path.join(root_path, 'resources') 124 | for integration_test in self.build_configuration['integration_tests']: 125 | if os.path.exists(output_responses): 126 | shutil.rmtree(output_responses) 127 | os.makedirs(output_responses) 128 | requests = integration_test['requests'] 129 | self.build_print("Running Integration Test: " + requests) 130 | cli_result = subprocess.call([cli_binary, '-i', os.path.join(root_path, requests), '-o', output_responses]) 131 | if cli_result: 132 | return cli_result 133 | expected_responses = integration_test['responses'] 134 | expected_responses_json = json.load(open(expected_responses)) 135 | actual_responses = os.path.join(output_responses, 'responses.json') 136 | actual_responses_json = json.load(open(actual_responses)) 137 | requests_json = json.load(open(requests)) 138 | for request in requests_json['requests']: 139 | request_id = request['id'] 140 | expected_response = expected_responses_json['responses'][request_id] 141 | actual_response = actual_responses_json['responses'][request_id] 142 | if not self.checkResponses(expected_response, actual_response, resources_path, integration_test): 143 | return 1 144 | self.build_print("Integration Test Passed") 145 | return 0 146 | 147 | def checkResponses(self, expected_response, actual_response, resources_path, integration_test): 148 | if not filecmp.cmp(os.path.join(resources_path, expected_response['payload']), actual_response['payload']): 149 | self.build_print("ERROR: Integration Test " + integration_test['requests'] + " failed") 150 | self.build_print("Payloads do not match") 151 | return False 152 | return True 153 | 154 | def targetBinary(self, target): 155 | for root, dirnames, filenames in os.walk(self.build_directory): 156 | for filename in fnmatch.filter(filenames, target): 157 | full_target_file = os.path.join(root, filename) 158 | return full_target_file 159 | return '' 160 | 161 | def collectCodeCoverage(self): 162 | for root, dirnames, filenames in os.walk('build'): 163 | for filename in fnmatch.filter(filenames, '*.gcda'): 164 | full_filepath = os.path.join(root, filename) 165 | if full_filepath.startswith('build/source/') and \ 166 | '/tests/' not in full_filepath: 167 | continue 168 | os.remove(full_filepath) 169 | llvm_run_script = os.path.join( 170 | os.path.join( 171 | self.current_working_directory, 172 | 'ci'), 173 | 'llvm-run.sh') 174 | cov_info = os.path.join('build', 'cov.info') 175 | lcov_result = subprocess.call([ 176 | self.lcov_binary, 177 | '--directory', 178 | '.', 179 | '--base-directory', 180 | '.', 181 | '--gcov-tool', 182 | llvm_run_script, 183 | '--capture', 184 | '-o', 185 | cov_info]) 186 | if lcov_result: 187 | sys.exit(lcov_result) 188 | coverage_output = os.path.join(self.output_directory, 'code_coverage') 189 | genhtml_result = subprocess.call([ 190 | self.genhtml_binary, 191 | cov_info, 192 | '-o', 193 | coverage_output]) 194 | if genhtml_result: 195 | sys.exit(genhtml_result) 196 | 197 | def find_file(self, directory, file_name, multiple_files=False): 198 | matches = [] 199 | for root, dirnames, filenames in os.walk(directory): 200 | for filename in fnmatch.filter(filenames, file_name): 201 | matches.append(os.path.join(root, filename)) 202 | if not multiple_files: 203 | break 204 | if not multiple_files and len(matches) > 0: 205 | break 206 | return matches 207 | 208 | def make_archive(self, source, destination): 209 | base = os.path.basename(destination) 210 | name = base.split('.')[0] 211 | format = base.split('.')[1] 212 | archive_from = os.path.dirname(source) 213 | archive_to = os.path.basename(source.strip(os.sep)) 214 | print(source, destination, archive_from, archive_to) 215 | shutil.make_archive(name, format, archive_from, archive_to) 216 | shutil.move('%s.%s'%(name,format), destination) 217 | -------------------------------------------------------------------------------- /ci/nfbuildlinux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildLinux(NFBuild): 36 | clang_format_binary = 'clang-format-3.9' 37 | 38 | def __init__(self): 39 | super(self.__class__, self).__init__() 40 | self.cmake_binary = 'cmake' 41 | self.curl_directory = self.current_working_directory + '/libraries/curl' 42 | self.android_ndk_folder = '~/ndk' 43 | 44 | def generateProject(self, 45 | code_coverage=False, 46 | address_sanitizer=False, 47 | thread_sanitizer=False, 48 | undefined_behaviour_sanitizer=False, 49 | ios=False, 50 | android=False, 51 | android_arm=False, 52 | gcc=False): 53 | cmake_call = [self.cmake_binary, '..', '-GNinja'] 54 | if self.build_type == 'Release': 55 | cmake_call.append('-DCREATE_RELEASE_BUILD=1') 56 | else: 57 | cmake_call.append('-DCREATE_RELEASE_BUILD=0') 58 | if android or android_arm: 59 | android_abi = 'x86_64' 60 | android_toolchain_name = 'x86_64-llvm' 61 | if android_arm: 62 | android_abi = 'arm64-v8a' 63 | android_toolchain_name = 'arm64-llvm' 64 | cmake_call.extend([ 65 | '-DANDROID=1', 66 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 67 | '-DANDROID_NDK=' + self.android_ndk_folder, 68 | '-DANDROID_ABI=' + android_abi, 69 | '-DANDROID_NATIVE_API_LEVEL=21', 70 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name, 71 | '-DANDROID_STL=c++_shared']) 72 | if gcc: 73 | cmake_call.extend(['-DLLVM_STDLIB=0']) 74 | else: 75 | cmake_call.extend(['-DLLVM_STDLIB=1']) 76 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 77 | if cmake_result != 0: 78 | sys.exit(cmake_result) 79 | 80 | def buildTarget(self, target, sdk='linux', arch='x86_64'): 81 | ninja_call = ['ninja', '-C', self.build_directory] 82 | result = subprocess.call(ninja_call, cwd=self.current_working_directory) 83 | if result != 0: 84 | sys.exit(result) 85 | 86 | def packageArtifacts(self): 87 | lib_name = 'libNFHTTP.a' 88 | cli_name = 'NFHTTPCLI' 89 | output_folder = os.path.join(self.build_directory, 'output') 90 | artifacts_folder = os.path.join(output_folder, 'NFHTTP') 91 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 92 | source_folder = os.path.join(self.build_directory, 'source') 93 | lib_matches = self.find_file(source_folder, lib_name) 94 | cli_matches = self.find_file(source_folder, cli_name) 95 | shutil.copyfile(lib_matches[0], os.path.join(artifacts_folder, lib_name)) 96 | shutil.copyfile(cli_matches[0], os.path.join(artifacts_folder, cli_name)) 97 | output_zip = os.path.join(output_folder, 'libNFHTTP.zip') 98 | self.make_archive(artifacts_folder, output_zip) 99 | -------------------------------------------------------------------------------- /ci/nfbuildosx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildOSX(NFBuild): 36 | def __init__(self): 37 | super(self.__class__, self).__init__() 38 | self.project_file = os.path.join( 39 | self.build_directory, 40 | 'NFHTTP.xcodeproj') 41 | self.cmake_binary = 'cmake' 42 | self.curl_directory = self.current_working_directory + '/libraries/curl' 43 | self.clang_format_binary = 'clang-format' 44 | self.android_ndk_folder = '/usr/local/share/android-ndk' 45 | self.ninja_binary = 'ninja' 46 | self.ios = False 47 | self.android = False 48 | self.android_arm = False 49 | 50 | def generateProject(self, 51 | code_coverage=False, 52 | address_sanitizer=False, 53 | thread_sanitizer=False, 54 | undefined_behaviour_sanitizer=False, 55 | ios=False, 56 | use_curl=False, 57 | use_cpprest=False, 58 | android=False, 59 | android_arm=False, 60 | gcc=False): 61 | self.use_ninja = android or android_arm 62 | self.android = android 63 | self.android_arm = android_arm 64 | self.ios = ios 65 | cmake_call = [ 66 | self.cmake_binary, 67 | '..'] 68 | if self.use_ninja: 69 | cmake_call.append('-GNinja') 70 | else: 71 | cmake_call.append('-GXcode') 72 | if self.build_type == 'Release': 73 | cmake_call.append('-DCREATE_RELEASE_BUILD=1') 74 | else: 75 | cmake_call.append('-DCREATE_RELEASE_BUILD=0') 76 | if address_sanitizer: 77 | cmake_call.append('-DUSE_ADDRESS_SANITIZER=1') 78 | cmake_call.append('-DUSE_CURL={}'.format(1 if use_curl else 0)) 79 | if use_cpprest: 80 | cmake_call.append('-DUSE_CPPRESTSDK=1') 81 | if code_coverage: 82 | cmake_call.append('-DCODE_COVERAGE=1') 83 | else: 84 | cmake_call.append('-DCODE_COVERAGE=0') 85 | if android or android_arm: 86 | android_abi = 'x86_64' 87 | android_toolchain_name = 'x86_64-llvm' 88 | if android_arm: 89 | android_abi = 'arm64-v8a' 90 | android_toolchain_name = 'arm64-llvm' 91 | cmake_call.extend([ 92 | '-DANDROID=1', 93 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 94 | '-DANDROID_NDK=' + self.android_ndk_folder, 95 | '-DANDROID_ABI=' + android_abi, 96 | '-DANDROID_NATIVE_API_LEVEL=21', 97 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name, 98 | '-DANDROID_STL=c++_shared']) 99 | self.project_file = 'build.ninja' 100 | if ios: 101 | cmake_call.extend(['-DIOS=1']) 102 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 103 | if cmake_result != 0: 104 | sys.exit(cmake_result) 105 | 106 | def buildTarget(self, target, sdk='macosx', arch='x86_64'): 107 | result = 0 108 | if self.use_ninja: 109 | result = subprocess.call([ 110 | self.ninja_binary, 111 | '-C', 112 | self.build_directory, 113 | '-f', 114 | self.project_file, 115 | target]) 116 | else: 117 | result = subprocess.call([ 118 | 'xcodebuild', 119 | '-project', 120 | self.project_file, 121 | '-target', 122 | target, 123 | '-sdk', 124 | sdk, 125 | '-arch', 126 | arch, 127 | '-configuration', 128 | self.build_type, 129 | 'build']) 130 | if result != 0: 131 | sys.exit(result) 132 | 133 | def staticallyAnalyse(self, target, include_regex=None): 134 | diagnostics_key = 'diagnostics' 135 | files_key = 'files' 136 | exceptions_key = 'static_analyzer_exceptions' 137 | static_file_exceptions = [] 138 | static_analyzer_result = subprocess.check_output([ 139 | 'xcodebuild', 140 | '-project', 141 | self.project_file, 142 | '-target', 143 | target, 144 | '-sdk', 145 | 'macosx', 146 | '-configuration', 147 | self.build_type, 148 | '-dry-run', 149 | 'analyze']).decode() 150 | analyze_command = '--analyze' 151 | for line in static_analyzer_result.splitlines(): 152 | if analyze_command not in line: 153 | continue 154 | static_analyzer_line_words = line.split() 155 | analyze_command_index = static_analyzer_line_words.index( 156 | analyze_command) 157 | source_file = static_analyzer_line_words[analyze_command_index + 1] 158 | if source_file.startswith(self.current_working_directory): 159 | source_file = source_file[ 160 | len(self.current_working_directory)+1:] 161 | if include_regex is not None: 162 | if not re.match(include_regex, source_file): 163 | continue 164 | if source_file in self.statically_analyzed_files: 165 | continue 166 | self.build_print('Analysing ' + source_file) 167 | stripped_command = line.strip() 168 | clang_result = subprocess.call(stripped_command, shell=True) 169 | if clang_result: 170 | sys.exit(clang_result) 171 | self.statically_analyzed_files.append(source_file) 172 | static_analyzer_check_passed = True 173 | for root, dirnames, filenames in os.walk(self.build_directory): 174 | for filename in fnmatch.filter(filenames, '*.plist'): 175 | full_filepath = os.path.join(root, filename) 176 | static_analyzer_result = {} 177 | with open(full_filepath, "rb") as plist_file_handle: 178 | static_analyzer_result = plistlib.load(plist_file_handle) 179 | if 'clang_version' not in static_analyzer_result \ 180 | or files_key not in static_analyzer_result \ 181 | or diagnostics_key not in static_analyzer_result: 182 | continue 183 | if len(static_analyzer_result[files_key]) == 0: 184 | continue 185 | for static_analyzer_file in static_analyzer_result[files_key]: 186 | if static_analyzer_file in static_file_exceptions: 187 | continue 188 | if self.current_working_directory not in static_analyzer_file: 189 | continue 190 | normalised_file = static_analyzer_file[ 191 | len(self.current_working_directory)+1:] 192 | if normalised_file in \ 193 | self.build_configuration[exceptions_key]: 194 | continue 195 | self.build_print('Issues found in: ' + normalised_file) 196 | for static_analyzer_issue in \ 197 | static_analyzer_result[diagnostics_key]: 198 | self.pretty_printer.pprint(static_analyzer_issue) 199 | sys.stdout.flush() 200 | static_analyzer_check_passed = False 201 | if not static_analyzer_check_passed: 202 | sys.exit(1) 203 | -------------------------------------------------------------------------------- /ci/nfbuildwindows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import fnmatch 4 | import os 5 | import plistlib 6 | import re 7 | import shutil 8 | import subprocess 9 | import sys 10 | import time 11 | 12 | from distutils import dir_util 13 | from nfbuild import NFBuild 14 | 15 | 16 | class NFBuildWindows(NFBuild): 17 | def __init__(self): 18 | super(self.__class__, self).__init__() 19 | self.project_file = 'build.ninja' 20 | 21 | def installClangFormat(self): 22 | clang_format_vulcan_file = os.path.join('tools', 'clang-format.vulcan') 23 | clang_format_extraction_folder = self.vulcanDownload( 24 | clang_format_vulcan_file, 25 | 'clang-format-5.0.0') 26 | self.clang_format_binary = os.path.join( 27 | os.path.join( 28 | os.path.join( 29 | clang_format_extraction_folder, 30 | 'clang-format'), 31 | 'bin'), 32 | 'clang-format') 33 | 34 | def installNinja(self): 35 | ninja_vulcan_file = os.path.join( 36 | os.path.join( 37 | os.path.join( 38 | os.path.join('tools', 'buildtools'), 39 | 'spotify_buildtools'), 40 | 'software'), 41 | 'ninja.vulcan') 42 | ninja_extraction_folder = self.vulcanDownload( 43 | ninja_vulcan_file, 44 | 'ninja-1.6.0') 45 | self.ninja_binary = os.path.join( 46 | ninja_extraction_folder, 47 | 'ninja') 48 | if 'PATH' not in os.environ: 49 | os.environ['PATH'] = '' 50 | if len(os.environ['PATH']) > 0: 51 | os.environ['PATH'] += os.pathsep 52 | os.environ['PATH'] += ninja_extraction_folder 53 | 54 | def installMake(self): 55 | make_vulcan_file = os.path.join('tools', 'make.vulcan') 56 | make_extraction_folder = self.vulcanDownload( 57 | make_vulcan_file, 58 | 'make-4.2.1') 59 | make_bin_folder = os.path.join( 60 | make_extraction_folder, 61 | 'bin') 62 | os.environ['PATH'] += os.pathsep + make_bin_folder 63 | 64 | def installVisualStudio(self): 65 | vs_vulcan_file = os.path.join( 66 | os.path.join( 67 | os.path.join( 68 | os.path.join('tools', 'buildtools'), 69 | 'spotify_buildtools'), 70 | 'software'), 71 | 'visualstudio.vulcan') 72 | self.vs_extraction_folder = self.vulcanDownload( 73 | vs_vulcan_file, 74 | 'visualstudio-2017') 75 | sdk_version = '10.0.15063.0' 76 | vc_tools_version = '14.10.25017' 77 | vc_redist_version = '14.10.25008' 78 | vc_redist_crt = 'Microsoft.VC150.CRT' 79 | vs_root = self.vs_extraction_folder 80 | sdk_root = os.path.join(vs_root, 'win10sdk') 81 | vc_root = os.path.join(vs_root, 'VC') 82 | vc_tools_root = os.path.join(vc_root, 'Tools', 'MSVC') 83 | vc_redist_root = os.path.join(vc_root, 'Redist', 'MSVC') 84 | os.environ['VS_ROOT'] = vs_root 85 | os.environ['SDK_ROOT'] = sdk_root 86 | os.environ['INCLUDE'] = os.pathsep.join([ 87 | os.path.join(sdk_root, 'Include', sdk_version, 'um'), 88 | os.path.join(sdk_root, 'Include', sdk_version, 'ucrt'), 89 | os.path.join(sdk_root, 'Include', sdk_version, 'shared'), 90 | os.path.join(sdk_root, 'Include', sdk_version, 'winrt'), 91 | os.path.join(vc_tools_root, vc_tools_version, 'include'), 92 | os.path.join(vc_tools_root, vc_tools_version, 'atlmfc', 'include'), 93 | os.environ.get('INCLUDE', '')]) 94 | os.environ['PATH'] = os.pathsep.join([ 95 | os.path.join(sdk_root, 'bin', sdk_version, 'x86'), 96 | os.path.join(vc_tools_root, vc_tools_version, 'bin', 'HostX64', 'x86'), 97 | os.path.join(vc_tools_root, vc_tools_version, 'bin', 'HostX64', 'x64'), 98 | os.path.join(vc_redist_root, vc_redist_version, 'x64', vc_redist_crt), 99 | os.path.join(vs_root, 'SystemCRT'), 100 | os.environ.get('PATH', '')]) 101 | os.environ['LIB'] = os.pathsep.join([ 102 | os.path.join(sdk_root, 'Lib', sdk_version, 'um', 'x86'), 103 | os.path.join(sdk_root, 'Lib', sdk_version, 'ucrt', 'x86'), 104 | os.path.join(vc_tools_root, vc_tools_version, 'lib', 'x86'), 105 | os.path.join(vc_tools_root, vc_tools_version, 'atlmfc', 'lib', 'x86'), 106 | os.environ.get('LIB', '')]) 107 | os.environ['LIBPATH'] = os.pathsep.join([ 108 | os.path.join(vc_tools_root, vc_tools_version, 'lib', 'x86', 'store', 'references'), 109 | os.path.join(sdk_root, 'UnionMetadata', sdk_version), 110 | os.environ.get('LIBPATH', '')]) 111 | 112 | def installVulcanDependencies(self, android=False): 113 | super(self.__class__, self).installVulcanDependencies(android) 114 | self.installClangFormat() 115 | self.installMake() 116 | self.installVisualStudio() 117 | self.installNinja() 118 | 119 | def generateProject(self, 120 | ios=False, 121 | android=False, 122 | android_arm=False): 123 | self.use_ninja = android or android_arm 124 | cmake_call = [ 125 | self.cmake_binary, 126 | '..', 127 | '-GNinja'] 128 | if android or android_arm: 129 | android_abi = 'x86_64' 130 | android_toolchain_name = 'x86_64-llvm' 131 | if android_arm: 132 | android_abi = 'arm64-v8a' 133 | android_toolchain_name = 'arm64-llvm' 134 | cmake_call.extend([ 135 | '-DANDROID=1', 136 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 137 | '-DANDROID_NDK=' + self.android_ndk_folder, 138 | '-DANDROID_ABI=' + android_abi, 139 | '-DANDROID_NATIVE_API_LEVEL=21', 140 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name, 141 | '-DANDROID_WINDOWS=1', 142 | '-DANDROID_STL=c++_shared']) 143 | self.project_file = 'build.ninja' 144 | else: 145 | cl_exe = os.path.join(self.vs_extraction_folder, 'VC', 'Tools', 'MSVC', '14.10.25017', 'bin', 'HostX64', 'x86', 'cl.exe').replace('\\', '/') 146 | rc_exe = os.path.join(self.vs_extraction_folder, 'win10sdk', 'bin', '10.0.15063.0', 'x64', 'rc.exe').replace('\\', '/') 147 | link_exe = os.path.join(self.vs_extraction_folder, 'VC', 'Tools', 'MSVC', '14.10.25017', 'bin', 'HostX64', 'x86', 'link.exe').replace('\\', '/') 148 | cmake_call.extend([ 149 | '-DCMAKE_C_COMPILER=' + cl_exe, 150 | '-DCMAKE_CXX_COMPILER=' + cl_exe, 151 | '-DCMAKE_RC_COMPILER=' + rc_exe, 152 | '-DCMAKE_LINKER=' + link_exe, 153 | '-DWINDOWS=1']) 154 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 155 | if cmake_result != 0: 156 | sys.exit(cmake_result) 157 | 158 | def buildTarget(self, target, sdk='macosx', arch='x86_64'): 159 | result = subprocess.call([ 160 | self.ninja_binary, 161 | '-C', 162 | self.build_directory, 163 | '-f', 164 | self.project_file, 165 | target]) 166 | if result != 0: 167 | sys.exit(result) 168 | 169 | def targetBinary(self, target): 170 | bin_name = target + '.exe' 171 | for root, dirnames, filenames in os.walk(self.build_directory): 172 | for filename in fnmatch.filter(filenames, bin_name): 173 | full_target_file = os.path.join(root, filename) 174 | return full_target_file 175 | return '' 176 | 177 | def runIntegrationTests(self): 178 | # Build the CLI target 179 | cli_target_name = 'NFHTTPCLI' 180 | cli_binary = self.targetBinary(cli_target_name) 181 | self.buildTarget(cli_target_name) 182 | # Launch the dummy server 183 | root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') 184 | cwd = os.path.join(os.path.join(root_path, 'resources'), 'localhost') 185 | cmd = 'python -m SimpleHTTPServer 6582' 186 | pro = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=cwd) 187 | print 'CLI binary: ' + str(cli_binary) 188 | print 'CWD: ' + str(cwd) 189 | time.sleep(3) 190 | cli_result = self.runIntegrationTestsUnderDummyServer(cli_binary, root_path) 191 | subprocess.call(['taskkill', '/F', '/T', '/PID', str(pro.pid)]) 192 | if cli_result: 193 | sys.exit(cli_result) 194 | 195 | -------------------------------------------------------------------------------- /ci/osx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | import os 25 | 26 | from nfbuildosx import NFBuildOSX 27 | from build_options import BuildOptions 28 | 29 | 30 | def main(): 31 | buildOptions = BuildOptions() 32 | buildOptions.addOption("debug", "Enable Debug Mode") 33 | buildOptions.addOption("installDependencies", "Install dependencies") 34 | buildOptions.addOption("lintCpp", "Lint CPP Files") 35 | buildOptions.addOption("lintCppWithInlineChange", 36 | "Lint CPP Files and fix them") 37 | 38 | buildOptions.addOption("integrationTests", "Run Integration Tests") 39 | 40 | buildOptions.addOption("makeBuildDirectory", 41 | "Wipe existing build directory") 42 | buildOptions.addOption("generateProject", "Regenerate xcode project") 43 | 44 | buildOptions.addOption("addressSanitizer", 45 | "Enable Address Sanitizer in generate project") 46 | buildOptions.addOption("codeCoverage", 47 | "Enable code coverage in generate project") 48 | buildOptions.addOption("curl", "Use curl in generate project") 49 | buildOptions.addOption("cpprest", "Use cpprest in generate project") 50 | 51 | buildOptions.addOption("buildTargetCLI", "Build Target: CLI") 52 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 53 | buildOptions.addOption("gnuToolchain", "Build with gcc and libstdc++") 54 | buildOptions.addOption("llvmToolchain", "Build with clang and libc++") 55 | 56 | buildOptions.addOption("staticAnalysis", "Run Static Analysis") 57 | 58 | buildOptions.setDefaultWorkflow("Empty workflow", []) 59 | 60 | buildOptions.addWorkflow("local_it", "Run local integration tests", [ 61 | 'debug', 62 | 'integrationTests' 63 | ]) 64 | 65 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 66 | 'lintCppWithInlineChange' 67 | ]) 68 | 69 | buildOptions.addWorkflow("address_sanitizer", "Run address sanitizer", [ 70 | 'debug', 71 | 'lintCpp', 72 | 'makeBuildDirectory', 73 | 'generateProject', 74 | 'addressSanitizer', 75 | 'buildTargetCLI', 76 | 'integrationTests' 77 | ]) 78 | 79 | buildOptions.addWorkflow("code_coverage", "Collect code coverage", [ 80 | 'debug', 81 | 'lintCpp', 82 | 'makeBuildDirectory', 83 | 'generateProject', 84 | 'codeCoverage', 85 | 'buildTargetCLI', 86 | 'integrationTests' 87 | ]) 88 | 89 | buildOptions.addWorkflow("build", "Production Build", [ 90 | 'lintCpp', 91 | 'makeBuildDirectory', 92 | 'generateProject', 93 | 'buildTargetCLI', 94 | 'buildTargetLibrary', 95 | 'staticAnalysis', 96 | 'integrationTests' 97 | ]) 98 | 99 | options = buildOptions.parseArgs() 100 | buildOptions.verbosePrintBuildOptions(options) 101 | 102 | library_target = 'NFHTTP' 103 | cli_target = 'NFHTTPCLI' 104 | nfbuild = NFBuildOSX() 105 | 106 | if buildOptions.checkOption(options, 'debug'): 107 | nfbuild.build_type = 'Debug' 108 | 109 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 110 | nfbuild.lintCPP(make_inline_changes=True) 111 | elif buildOptions.checkOption(options, 'lintCpp'): 112 | nfbuild.lintCPP(make_inline_changes=False) 113 | 114 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 115 | nfbuild.makeBuildDirectory() 116 | 117 | if buildOptions.checkOption(options, 'generateProject'): 118 | nfbuild.generateProject( 119 | code_coverage='codeCoverage' in options, 120 | address_sanitizer='addressSanitizer' in options, 121 | use_curl='curl' in options, 122 | use_cpprest='cpprest' in options 123 | ) 124 | 125 | if buildOptions.checkOption(options, 'generateProject'): 126 | if buildOptions.checkOption(options, 'gnuToolchain'): 127 | os.environ['CC'] = 'gcc-4.9' 128 | os.environ['CXX'] = 'g++-4.9' 129 | nfbuild.generateProject(gcc=True) 130 | elif buildOptions.checkOption(options, 'llvmToolchain'): 131 | os.environ['CC'] = 'clang-3.9' 132 | os.environ['CXX'] = 'clang++-3.9' 133 | nfbuild.generateProject(gcc=False) 134 | else: 135 | nfbuild.generateProject() 136 | 137 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 138 | nfbuild.buildTarget(library_target) 139 | if buildOptions.checkOption(options, 'staticAnalysis'): 140 | nfbuild.staticallyAnalyse(library_target, 141 | include_regex='source/.*') 142 | 143 | if buildOptions.checkOption(options, 'buildTargetCLI'): 144 | nfbuild.buildTarget(cli_target) 145 | if buildOptions.checkOption(options, 'staticAnalysis'): 146 | nfbuild.staticallyAnalyse(cli_target, 147 | include_regex='source/.*') 148 | 149 | if buildOptions.checkOption(options, 'integrationTests'): 150 | nfbuild.runIntegrationTests() 151 | 152 | if buildOptions.checkOption(options, 'codeCoverage'): 153 | nfbuild.collectCodeCoverage() 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /ci/osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2018 Spotify AB. 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | 21 | # Exit on any non-zero status 22 | set -e 23 | 24 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" 25 | 26 | # Homebrew on circleci consistently fails with an error like: 27 | # 28 | # ==> Checking for dependents of upgraded formulae... Error: No such file or 29 | # directory - /usr/local/Cellar/git/2.26.2_1 30 | # 31 | # Completely unpredictable, because it's just homebrew cleaning itself up and 32 | # has nothing to do with the install itself! Just continue and hope the build 33 | # fails if one of these tools is not installed. 34 | if ! HOMEBREW_NO_AUTO_UPDATE=1 brew install \ 35 | clang-format \ 36 | cmake \ 37 | ninja \ 38 | wget ; then 39 | echo "Homebrew install had an error, review output and try manually." 40 | fi 41 | 42 | # Undo homebrew's potential meddling: https://github.com/pypa/pip/issues/5048 43 | # Homebrew will upgrade python to 3.8, but virtualenv hard codes the path to 3.7 44 | # in its shebang. 45 | pip3 uninstall --yes virtualenv && pip3 install virtualenv 46 | 47 | # Install virtualenv 48 | virtualenv --python=$(which python3) nfhttp_env 49 | source nfhttp_env/bin/activate 50 | 51 | # Install Python Packages 52 | pip install -r ${DIR}/requirements.txt 53 | 54 | # Execute our python build tools 55 | if [ -n "$BUILD_IOS" ]; then 56 | python ci/ios.py "$@" 57 | else 58 | if [ -n "$BUILD_ANDROID" ]; then 59 | brew install android-ndk 60 | 61 | python ci/android.py "$@" 62 | else 63 | python ci/osx.py "$@" 64 | fi 65 | fi 66 | -------------------------------------------------------------------------------- /ci/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml==5.4.1 2 | flake8==3.8.4 3 | requests==2.25.1 4 | -------------------------------------------------------------------------------- /ci/windows.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$build = "windows" 3 | ) 4 | 5 | Write-Host "NFHTTP build process starting..." 6 | Write-Host $build 7 | 8 | $ErrorActionPreference = "Stop" 9 | 10 | try 11 | { 12 | # Get python version 13 | $python_version = python --version 14 | Write-Host $python_version 15 | 16 | # Start virtualenv 17 | $virtualenv_vulcan_output = python tools/vulcan/bin/vulcan.py -v -f tools/virtualenv.vulcan -p virtualenv-15.1.0 18 | $virtualenv_bin = Join-Path $virtualenv_vulcan_output /virtualenv-15.1.0/virtualenv.py 19 | python $virtualenv_bin nfdriver_env 20 | 21 | & ./nfdriver_env/Scripts/activate.bat 22 | 23 | # Install Python Packages 24 | & nfdriver_env/Scripts/pip.exe install urllib3 25 | & nfdriver_env/Scripts/pip.exe install pyyaml 26 | & nfdriver_env/Scripts/pip.exe install flake8 27 | 28 | if($build -eq "android"){ 29 | & nfdriver_env/Scripts/python.exe ci/androidwindows.py 30 | } else { 31 | & nfdriver_env/Scripts/python.exe ci/windows.py 32 | } 33 | 34 | if($LASTEXITCODE -ne 0){ 35 | exit $LASTEXITCODE 36 | } 37 | 38 | & ./nfdriver_env/Scripts/deactivate.bat 39 | } 40 | catch 41 | { 42 | echo $_.Exception|format-list -force 43 | exit 1 44 | } 45 | -------------------------------------------------------------------------------- /ci/windows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | from nfbuildwindows import NFBuildWindows 6 | 7 | 8 | def main(): 9 | library_target = 'NFHTTP' 10 | cli_target = 'NFHTTPCLI' 11 | nfbuild = NFBuildWindows() 12 | nfbuild.build_print("Installing Dependencies") 13 | nfbuild.installDependencies() 14 | # Make our main build artifacts 15 | nfbuild.build_print("C++ Build Start (x86)") 16 | nfbuild.makeBuildDirectory() 17 | nfbuild.generateProject() 18 | targets = [library_target, cli_target] 19 | for target in targets: 20 | nfbuild.buildTarget(target) 21 | # nfbuild.runIntegrationTests() 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /include/NFHTTP/Client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace nativeformat { 34 | namespace http { 35 | 36 | typedef std::function &request)> callback, 37 | const std::shared_ptr &request)> 38 | REQUEST_MODIFIER_FUNCTION; 39 | typedef std::function &response, bool retry)> callback, 41 | const std::shared_ptr &response)> 42 | RESPONSE_MODIFIER_FUNCTION; 43 | 44 | extern const REQUEST_MODIFIER_FUNCTION DO_NOT_MODIFY_REQUESTS_FUNCTION; 45 | extern const RESPONSE_MODIFIER_FUNCTION DO_NOT_MODIFY_RESPONSES_FUNCTION; 46 | 47 | class Client { 48 | public: 49 | virtual ~Client(); 50 | 51 | virtual std::shared_ptr performRequest( 52 | const std::shared_ptr &request, 53 | std::function &)> callback) = 0; 54 | virtual const std::shared_ptr performRequestSynchronously( 55 | const std::shared_ptr &request); 56 | virtual void pinResponse(const std::shared_ptr &response, 57 | const std::string &pin_identifier); 58 | virtual void unpinResponse(const std::shared_ptr &response, 59 | const std::string &pin_identifier); 60 | virtual void removePinnedResponseForIdentifier(const std::string &pin_identifier); 61 | virtual void pinnedResponsesForIdentifier( 62 | const std::string &pin_identifier, 63 | std::function> &)> callback); 64 | virtual void pinningIdentifiers( 65 | std::function &identifiers)> callback); 66 | }; 67 | 68 | extern std::shared_ptr createClient( 69 | const std::string &cache_location, 70 | const std::string &user_agent, 71 | REQUEST_MODIFIER_FUNCTION request_modifier_function = DO_NOT_MODIFY_REQUESTS_FUNCTION, 72 | RESPONSE_MODIFIER_FUNCTION response_modifier_function = DO_NOT_MODIFY_RESPONSES_FUNCTION); 73 | extern std::string standardCacheLocation(); 74 | 75 | } // namespace http 76 | } // namespace nativeformat 77 | -------------------------------------------------------------------------------- /include/NFHTTP/NFHTTP.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | namespace nativeformat { 31 | namespace http { 32 | 33 | extern std::string version(); 34 | 35 | } // namespace http 36 | } // namespace nativeformat 37 | -------------------------------------------------------------------------------- /include/NFHTTP/Request.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | extern const std::string GetMethod; 31 | extern const std::string PostMethod; 32 | extern const std::string PutMethod; 33 | extern const std::string HeadMethod; 34 | extern const std::string DeleteMethod; 35 | extern const std::string OptionsMethod; 36 | extern const std::string ConnectMethod; 37 | 38 | class Request { 39 | public: 40 | typedef struct CacheControl { 41 | const int max_age; 42 | const int max_stale; 43 | const int min_fresh; 44 | const bool no_cache; 45 | const bool no_store; 46 | const bool no_transform; 47 | const bool only_if_cached; 48 | } CacheControl; 49 | 50 | virtual std::string url() const = 0; 51 | virtual void setUrl(const std::string &url) = 0; 52 | virtual std::string operator[](const std::string &header_name) const = 0; 53 | virtual std::string &operator[](const std::string &header_name) = 0; 54 | virtual std::unordered_map &headerMap() = 0; 55 | virtual std::unordered_map headerMap() const = 0; 56 | virtual std::string hash() const = 0; 57 | virtual std::string serialise() const = 0; 58 | virtual std::string method() const = 0; 59 | virtual void setMethod(const std::string &method) = 0; 60 | virtual const unsigned char *data(size_t &data_length) const = 0; 61 | virtual void setData(const unsigned char *data, size_t data_length) = 0; 62 | virtual CacheControl cacheControl() const = 0; 63 | }; 64 | 65 | extern std::shared_ptr createRequest( 66 | const std::string &url, const std::unordered_map &header_map); 67 | extern std::shared_ptr createRequest(const std::shared_ptr &request); 68 | 69 | } // namespace http 70 | } // namespace nativeformat 71 | -------------------------------------------------------------------------------- /include/NFHTTP/RequestToken.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | namespace nativeformat { 27 | namespace http { 28 | 29 | class RequestToken { 30 | public: 31 | virtual void cancel() = 0; 32 | virtual std::string identifier() const = 0; 33 | virtual bool cancelled() = 0; 34 | virtual std::shared_ptr createDependentToken() = 0; 35 | virtual int dependents() = 0; 36 | }; 37 | 38 | } // namespace http 39 | } // namespace nativeformat 40 | -------------------------------------------------------------------------------- /include/NFHTTP/Response.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | typedef enum : int { 31 | StatusCodeInvalid = 0, 32 | // Informational 33 | StatusCodeContinue = 100, 34 | StatusCodeSwitchProtocols = 101, 35 | // Successful 36 | StatusCodeOK = 200, 37 | StatusCodeCreated = 201, 38 | StatusCodeAccepted = 202, 39 | StatusCodeNonAuthoritiveInformation = 203, 40 | StatusCodeNoContent = 204, 41 | StatusCodeResetContent = 205, 42 | StatusCodePartialContent = 206, 43 | // Redirection 44 | StatusCodeMovedMultipleChoices = 300, 45 | StatusCodeMovedPermanently = 301, 46 | StatusCodeFound = 302, 47 | StatusCodeSeeOther = 303, 48 | StatusCodeNotModified = 304, 49 | StatusCodeUseProxy = 305, 50 | StatusCodeUnused = 306, 51 | StatusCodeTemporaryRedirect = 307, 52 | // Client Error 53 | StatusCodeBadRequest = 400, 54 | StatusCodeUnauthorised = 401, 55 | StatusCodePaymentRequired = 402, 56 | StatusCodeForbidden = 403, 57 | StatusCodeNotFound = 404, 58 | StatusCodeMethodNotAllowed = 405, 59 | StatusCodeNotAcceptable = 406, 60 | StatusCodeProxyAuthenticationRequired = 407, 61 | StatusCodeRequestTimeout = 408, 62 | StatusCodeConflict = 409, 63 | StatusCodeGone = 410, 64 | StatusCodeLengthRequired = 411, 65 | StatusCodePreconditionFailed = 412, 66 | StatusCodeRequestEntityTooLarge = 413, 67 | StatusCodeRequestURITooLong = 414, 68 | StatusCodeUnsupportedMediaTypes = 415, 69 | StatusCodeRequestRangeUnsatisfied = 416, 70 | StatusCodeExpectationFail = 417, 71 | // Server Error 72 | StatusCodeInternalServerError = 500, 73 | StatusCodeNotImplemented = 501, 74 | StatusCodeBadGateway = 502, 75 | StatusCodeServiceUnavailable = 503, 76 | StatusCodeGatewayTimeout = 504, 77 | StatusCodeHTTPVersionNotSupported = 505 78 | } StatusCode; 79 | 80 | class Response { 81 | public: 82 | typedef struct CacheControl { 83 | const bool must_revalidate; 84 | const bool no_cache; 85 | const bool no_store; 86 | const bool no_transform; 87 | const bool access_control_public; 88 | const bool access_control_private; 89 | const bool proxy_revalidate; 90 | const int max_age; 91 | const int shared_max_age; 92 | } CacheControl; 93 | 94 | virtual const std::shared_ptr request() const = 0; 95 | virtual const unsigned char *data(size_t &data_length) const = 0; 96 | virtual StatusCode statusCode() const = 0; 97 | virtual bool cancelled() const = 0; 98 | virtual std::string serialise() const = 0; 99 | virtual std::string operator[](const std::string &header_name) const = 0; 100 | virtual std::string &operator[](const std::string &header_name) = 0; 101 | virtual std::unordered_map &headerMap() = 0; 102 | virtual std::unordered_map headerMap() const = 0; 103 | virtual CacheControl cacheControl() const = 0; 104 | virtual std::unordered_map metadata() const = 0; 105 | virtual void setMetadata(const std::string &key, const std::string &value) = 0; 106 | }; 107 | 108 | } // namespace http 109 | } // namespace nativeformat 110 | -------------------------------------------------------------------------------- /include/NFHTTP/ResponseImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace http { 31 | 32 | class ResponseImplementation : public Response { 33 | public: 34 | ResponseImplementation(const std::shared_ptr &request, 35 | const unsigned char *data, 36 | size_t data_length, 37 | StatusCode status_code, 38 | bool cancelled); 39 | ResponseImplementation(const std::string &serialised, 40 | const unsigned char *data, 41 | size_t data_length, 42 | const std::shared_ptr &response = nullptr); 43 | virtual ~ResponseImplementation(); 44 | 45 | // Response 46 | const std::shared_ptr request() const override; 47 | const unsigned char *data(size_t &data_length) const override; 48 | StatusCode statusCode() const override; 49 | bool cancelled() const override; 50 | std::string serialise() const override; 51 | std::string operator[](const std::string &header_name) const override; 52 | std::string &operator[](const std::string &header_name) override; 53 | std::unordered_map &headerMap() override; 54 | std::unordered_map headerMap() const override; 55 | CacheControl cacheControl() const override; 56 | std::unordered_map metadata() const override; 57 | void setMetadata(const std::string &key, const std::string &value) override; 58 | 59 | private: 60 | std::shared_ptr _request; 61 | unsigned char *_data; 62 | const size_t _data_length; 63 | StatusCode _status_code; 64 | const bool _cancelled; 65 | std::unordered_map _headers; 66 | std::unordered_map _metadata; 67 | }; 68 | 69 | } // namespace http 70 | } // namespace nativeformat 71 | -------------------------------------------------------------------------------- /libraries/config_openssl.sh: -------------------------------------------------------------------------------- 1 | if [ ".$PERL" = . ] ; then 2 | for i in . `echo $PATH | sed 's/:/ /g'`; do 3 | if [ -f "$i/perl5$EXE" ] ; then 4 | PERL="$i/perl5$EXE" 5 | break; 6 | fi; 7 | done 8 | fi 9 | 10 | if [ ".$PERL" = . ] ; then 11 | for i in . `echo $PATH | sed 's/:/ /g'`; do 12 | if [ -f "$i/perl$EXE" ] ; then 13 | if "$i/perl$EXE" -e 'exit($]<5.0)'; then 14 | PERL="$i/perl$EXE" 15 | break; 16 | fi; 17 | fi; 18 | done 19 | fi 20 | 21 | if [ ".$PERL" = . ] ; then 22 | echo "You need Perl 5." 23 | exit 1 24 | fi 25 | 26 | ${PERL} ./Configure no-asm VC-WIN32 27 | -------------------------------------------------------------------------------- /libraries/curl.patch: -------------------------------------------------------------------------------- 1 | commit f6aec94cb12382ad28e7ec18e775f9ed4748cc95 2 | Author: sackfield 3 | Date: Thu Mar 1 12:51:17 2018 -0500 4 | 5 | Add Cmake option to turn off installs 6 | 7 | * Installs are not desirable on systems that do 8 | not use shared libraries (e.g. android or iOS) 9 | * Attempting to build OpenSSL in Cmake and then 10 | linking it with cURL static libraries fails on 11 | the install step due to these libraries being 12 | static and not exported properly 13 | 14 | diff --git a/CMakeLists.txt b/CMakeLists.txt 15 | index 490cc19ef..eabbbfb25 100644 16 | --- a/CMakeLists.txt 17 | +++ b/CMakeLists.txt 18 | @@ -78,6 +78,7 @@ option(PICKY_COMPILER "Enable picky compiler options" ON) 19 | option(BUILD_CURL_EXE "Set to ON to build curl executable." ON) 20 | option(CURL_STATICLIB "Set to ON to build libcurl with static linking." OFF) 21 | option(ENABLE_ARES "Set to ON to enable c-ares support" OFF) 22 | +option(CURL_INSTALL "Set to ON to enable installation support" ON) 23 | if(WIN32) 24 | option(CURL_STATIC_CRT "Set to ON to build libcurl with static CRT on Windows (/MT)." OFF) 25 | option(ENABLE_INET_PTON "Set to OFF to prevent usage of inet_pton when building against modern SDKs while still requiring compatibility with older Windows versions, such as Windows XP, Windows Server 2003 etc." ON) 26 | diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt 27 | index 1fabdba90..0c3c67db3 100644 28 | --- a/lib/CMakeLists.txt 29 | +++ b/lib/CMakeLists.txt 30 | @@ -111,21 +111,23 @@ endif() 31 | target_include_directories(${LIB_NAME} INTERFACE 32 | $) 33 | 34 | -install(TARGETS ${LIB_NAME} 35 | - EXPORT libcurl-target 36 | - ARCHIVE DESTINATION lib 37 | - LIBRARY DESTINATION lib 38 | - RUNTIME DESTINATION bin 39 | -) 40 | - 41 | -export(TARGETS ${LIB_NAME} 42 | - APPEND FILE ${PROJECT_BINARY_DIR}/libcurl-target.cmake 43 | - NAMESPACE CURL:: 44 | -) 45 | - 46 | -install(EXPORT libcurl-target 47 | - FILE libcurl-target.cmake 48 | - NAMESPACE CURL:: 49 | - DESTINATION ${CURL_INSTALL_CMAKE_DIR} 50 | -) 51 | +if(CURL_INSTALL) 52 | + install(TARGETS ${LIB_NAME} 53 | + EXPORT libcurl-target 54 | + ARCHIVE DESTINATION lib 55 | + LIBRARY DESTINATION lib 56 | + RUNTIME DESTINATION bin 57 | + ) 58 | + 59 | + export(TARGETS ${LIB_NAME} 60 | + APPEND FILE ${PROJECT_BINARY_DIR}/libcurl-target.cmake 61 | + NAMESPACE CURL:: 62 | + ) 63 | + 64 | + install(EXPORT libcurl-target 65 | + FILE libcurl-target.cmake 66 | + NAMESPACE CURL:: 67 | + DESTINATION ${CURL_INSTALL_CMAKE_DIR} 68 | + ) 69 | +endif() 70 | 71 | -------------------------------------------------------------------------------- /resources/hello-requests.json: -------------------------------------------------------------------------------- 1 | { 2 | "requests": [ 3 | { 4 | "url": "http://localhost:6582/world", 5 | "id": "hello" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /resources/localhost/world: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /resources/world-responses.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": { 3 | "hello" : { 4 | "payload": "localhost/world" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Spotify AB. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | set(SOURCE_FILES 20 | Client.cpp 21 | CachingClient.h 22 | CachingClient.cpp 23 | Request.cpp 24 | RequestImplementation.cpp 25 | ClientNSURLSession.h 26 | ClientNSURLSession.mm 27 | RequestTokenImplementation.h 28 | RequestTokenImplementation.cpp 29 | RequestTokenDelegate.h 30 | ResponseImplementation.cpp 31 | sha256.h 32 | sha256.cpp 33 | CachingDatabase.h 34 | CachingDatabase.cpp 35 | CachingSQLiteDatabase.h 36 | CachingSQLiteDatabase.cpp 37 | CachingDatabaseDelegate.h 38 | CacheLocationLinux.cpp 39 | CacheLocationApple.mm 40 | CacheLocationWindows.cpp 41 | ClientModifierImplementation.h 42 | ClientModifierImplementation.cpp 43 | RequestImplementation.h 44 | ClientMultiRequestImplementation.h 45 | ClientMultiRequestImplementation.cpp 46 | NFHTTP.cpp) 47 | 48 | if(USE_CURL) 49 | list(APPEND 50 | SOURCE_FILES 51 | ClientCurl.h 52 | ClientCurl.cpp) 53 | 54 | find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) 55 | mark_as_advanced(CURL_INCLUDE_DIR) 56 | endif() 57 | 58 | if(USE_CPPRESTSDK) 59 | list(APPEND 60 | SOURCE_FILES 61 | ClientCpprestsdk.h 62 | ClientCpprestsdk.cpp) 63 | endif() 64 | 65 | add_library(NFHTTP "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/Client.h" 66 | "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/Request.h" 67 | "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/RequestToken.h" 68 | "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/Response.h" 69 | "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/ResponseImplementation.h" 70 | "${NFHTTP_INCLUDE_DIRECTORY}/NFHTTP/NFHTTP.h" 71 | ${SOURCE_FILES}) 72 | 73 | 74 | target_include_directories(NFHTTP PUBLIC "${NFHTTP_INCLUDE_DIRECTORY}" 75 | "${NFHTTP_LIBRARIES_DIRECTORY}/sqlite" 76 | "${NFHTTP_LIBRARIES_DIRECTORY}/curl/include" 77 | ${CPPREST_INCLUDE_DIR} 78 | ${OUTPUT_DIRECTORY}) 79 | 80 | set(LINK_LIBRARIES sqlite) 81 | 82 | if(NOT WIN32 AND NOT ANDROID) 83 | set(LINK_LIBRARIES sqlite pthread) 84 | endif() 85 | 86 | add_executable(NFHTTPCLI NFHTTPCLI.cpp) 87 | target_include_directories(NFHTTPCLI PUBLIC "${NFHTTP_INCLUDE_DIRECTORY}" 88 | "${NFHTTP_LIBRARIES_DIRECTORY}/sqlite" 89 | "${NFHTTP_LIBRARIES_DIRECTORY}/curl/include" 90 | ${CPPREST_INCLUDE_DIR} 91 | ${OUTPUT_DIRECTORY}) 92 | 93 | if(USE_CURL) 94 | list(APPEND LINK_LIBRARIES libcurl) 95 | if(NOT ANDROID) 96 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lcurl") 97 | endif() 98 | endif() 99 | 100 | if(USE_CPPRESTSDK) 101 | list(APPEND LINK_LIBRARIES cpprest) 102 | endif() 103 | 104 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 105 | find_library(FOUNDATION Foundation) 106 | list(APPEND LINK_LIBRARIES ${FOUNDATION}) 107 | set_source_files_properties(ClientNSURLSession.mm 108 | PROPERTIES COMPILE_FLAGS "-fobjc-arc") 109 | set_source_files_properties(CacheLocationApple.mm 110 | PROPERTIES COMPILE_FLAGS "-fobjc-arc") 111 | endif() 112 | 113 | target_link_libraries(NFHTTP PUBLIC ${LINK_LIBRARIES} nlohmann_json) 114 | target_link_libraries(NFHTTPCLI NFHTTP nlohmann_json) 115 | 116 | if(USE_CURL) 117 | target_compile_definitions(NFHTTP PRIVATE USE_CURL=1) 118 | endif() 119 | -------------------------------------------------------------------------------- /source/CacheLocationApple.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #if __APPLE__ 22 | 23 | #include 24 | 25 | #import 26 | 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace http { 31 | 32 | std::string standardCacheLocation() 33 | { 34 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); 35 | NSString *applicationSupportDirectory = [paths firstObject]; 36 | NSString *cacheLocation = [applicationSupportDirectory stringByAppendingPathComponent:@"nfsmartplayer"]; 37 | 38 | BOOL isDir = NO; 39 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 40 | if (![fileManager fileExistsAtPath:cacheLocation isDirectory:&isDir] && !isDir) { 41 | if (![fileManager createDirectoryAtPath:cacheLocation 42 | withIntermediateDirectories:YES 43 | attributes:nil 44 | error:NULL]) { 45 | printf("Failed to create cache directory: %s\n", cacheLocation.UTF8String); 46 | } 47 | } 48 | return cacheLocation.UTF8String; 49 | } 50 | 51 | } // namespace http 52 | } // namespace nativeformat 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /source/CacheLocationLinux.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifdef __linux__ 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace nativeformat { 31 | namespace http { 32 | 33 | std::string standardCacheLocation() { 34 | struct passwd *pw = getpwuid(getuid()); 35 | const char *homedir = pw->pw_dir; 36 | std::stringstream ss; 37 | ss << homedir << "/.cache"; 38 | std::string cachedir = ss.str(); 39 | struct stat st = {0}; 40 | if (stat(cachedir.c_str(), &st) == -1) { 41 | mkdir(cachedir.c_str(), 0700); 42 | } 43 | return cachedir; 44 | } 45 | 46 | } // namespace http 47 | } // namespace nativeformat 48 | #endif 49 | -------------------------------------------------------------------------------- /source/CacheLocationWindows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifdef _WIN32 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace http { 31 | 32 | std::string standardCacheLocation() { 33 | wchar_t *localAppData = NULL; 34 | SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); 35 | std::stringstream ss; 36 | ss << localAppData << "/NativeFormat/"; 37 | CreateDirectory(ss.str().c_str(), NULL); 38 | CoTaskMemFree(static_cast(localAppData)); 39 | return ss.str(); 40 | } 41 | 42 | } // namespace http 43 | } // namespace nativeformat 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /source/CachingClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "CachingDatabase.h" 31 | #include "RequestTokenDelegate.h" 32 | 33 | namespace nativeformat { 34 | namespace http { 35 | 36 | class CachingClient : public Client, 37 | public RequestTokenDelegate, 38 | public std::enable_shared_from_this, 39 | public CachingDatabaseDelegate { 40 | public: 41 | CachingClient(const std::shared_ptr &client, const std::string &cache_location); 42 | virtual ~CachingClient(); 43 | 44 | // RequestTokenDelegate 45 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 46 | 47 | // CachingDatabaseDelegate 48 | void deleteDatabaseFile(const std::string &header_hash) override; 49 | 50 | // Client 51 | std::shared_ptr performRequest( 52 | const std::shared_ptr &request, 53 | std::function &)> callback) override; 54 | void pinResponse(const std::shared_ptr &response, 55 | const std::string &pin_identifier) override; 56 | void unpinResponse(const std::shared_ptr &response, 57 | const std::string &pin_identifier) override; 58 | void removePinnedResponseForIdentifier(const std::string &pin_identifier) override; 59 | void pinnedResponsesForIdentifier( 60 | const std::string &pin_identifier, 61 | std::function> &)> callback) override; 62 | void pinningIdentifiers( 63 | std::function &identifiers)> callback) override; 64 | 65 | void initialise(); 66 | 67 | private: 68 | static void pruneThread(CachingClient *client); 69 | 70 | const std::shared_ptr responseFromCacheItem( 71 | const CacheItem &item, const std::shared_ptr &response = nullptr) const; 72 | bool shouldCacheRequest(const std::shared_ptr &request); 73 | 74 | const std::shared_ptr _client; 75 | const std::string _cache_location; 76 | std::thread _prune_thread; 77 | std::shared_ptr _database; 78 | 79 | std::unordered_map, std::weak_ptr> _tokens; 80 | std::atomic _shutdown_prune_thread; 81 | }; 82 | 83 | } // namespace http 84 | } // namespace nativeformat 85 | -------------------------------------------------------------------------------- /source/CachingDatabase.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "CachingDatabase.h" 22 | 23 | #include "CachingSQLiteDatabase.h" 24 | 25 | namespace nativeformat { 26 | namespace http { 27 | 28 | std::shared_ptr createCachingDatabase( 29 | const std::string &cache_location, 30 | const std::string &cache_type_hint, 31 | const std::weak_ptr &delegate) { 32 | return std::make_shared(cache_location, delegate); 33 | } 34 | 35 | } // namespace http 36 | } // namespace nativeformat 37 | -------------------------------------------------------------------------------- /source/CachingDatabase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "CachingDatabaseDelegate.h" 32 | 33 | namespace nativeformat { 34 | namespace http { 35 | 36 | typedef struct CacheItem { 37 | const std::time_t expiry_time; 38 | const std::time_t last_accessed_time; 39 | const std::string etag; 40 | const std::time_t last_modified; 41 | const std::string response; 42 | const std::string payload_filename; 43 | const bool valid; 44 | } CacheItem; 45 | 46 | class CachingDatabase { 47 | public: 48 | typedef enum : int { ErrorCodeNone } ErrorCode; 49 | 50 | virtual std::string cachingType() const = 0; 51 | virtual void fetchItemForRequest(const std::string &request_identifier, 52 | std::function callback) = 0; 53 | virtual void storeResponse( 54 | const std::shared_ptr &response, 55 | std::function &response)> callback) = 0; 56 | virtual void prune() = 0; 57 | virtual void pinItem(const CacheItem &item, const std::string &pin_identifier) = 0; 58 | virtual void unpinItem(const CacheItem &item, const std::string &pin_identifier) = 0; 59 | virtual void removePinnedItemsForIdentifier(const std::string &pin_identifier) = 0; 60 | virtual void pinnedItemsForIdentifier( 61 | const std::string &pin_identifier, 62 | std::function &)> callback) = 0; 63 | virtual void pinningIdentifiers( 64 | std::function &)> callback) = 0; 65 | }; 66 | 67 | extern std::shared_ptr createCachingDatabase( 68 | const std::string &cache_location, 69 | const std::string &cache_type_hint, 70 | const std::weak_ptr &delegate); 71 | 72 | } // namespace http 73 | } // namespace nativeformat 74 | -------------------------------------------------------------------------------- /source/CachingDatabaseDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | namespace nativeformat { 24 | namespace http { 25 | 26 | class CachingDatabase; 27 | 28 | class CachingDatabaseDelegate { 29 | public: 30 | virtual void deleteDatabaseFile(const std::string &header_hash) = 0; 31 | }; 32 | 33 | } // namespace http 34 | } // namespace nativeformat 35 | -------------------------------------------------------------------------------- /source/CachingSQLiteDatabase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include "CachingDatabase.h" 24 | 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | class CachingSQLiteDatabase : public CachingDatabase { 31 | public: 32 | CachingSQLiteDatabase(const std::string &cache_location, 33 | const std::weak_ptr &delegate); 34 | virtual ~CachingSQLiteDatabase(); 35 | 36 | // CachingDatabase 37 | std::string cachingType() const override; 38 | void fetchItemForRequest(const std::string &request_identifier, 39 | std::function callback) override; 40 | void storeResponse( 41 | const std::shared_ptr &response, 42 | std::function &response)> callback) override; 43 | void prune() override; 44 | void pinItem(const CacheItem &item, const std::string &pin_identifier) override; 45 | void unpinItem(const CacheItem &item, const std::string &pin_identifier) override; 46 | void removePinnedItemsForIdentifier(const std::string &pin_identifier) override; 47 | void pinnedItemsForIdentifier( 48 | const std::string &pin_identifier, 49 | std::function &)> callback) override; 50 | void pinningIdentifiers(std::function &)> callback) override; 51 | 52 | private: 53 | static int sqliteSelectHTTPCallback(void *context, int argc, char **argv, char **column_names); 54 | static int sqliteReplaceHTTPCallback(void *context, int argc, char **argv, char **column_names); 55 | static int sqliteSelectVectorHTTPCallback(void *context, 56 | int argc, 57 | char **argv, 58 | char **column_names); 59 | static std::time_t timeFromSQLDateTimeString(const std::string &date_time_string); 60 | 61 | sqlite3 *_sqlite_handle; 62 | const std::weak_ptr _delegate; 63 | }; 64 | 65 | } // namespace http 66 | } // namespace nativeformat 67 | -------------------------------------------------------------------------------- /source/Client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #include 24 | 25 | #include "CachingClient.h" 26 | #include "ClientCpprestsdk.h" 27 | #include "ClientCurl.h" 28 | #include "ClientModifierImplementation.h" 29 | #include "ClientMultiRequestImplementation.h" 30 | #include "ClientNSURLSession.h" 31 | 32 | namespace nativeformat { 33 | namespace http { 34 | 35 | static void doNotModifyRequestsFunction( 36 | std::function &request)> callback, 37 | const std::shared_ptr &request) { 38 | callback(request); 39 | } 40 | 41 | static void doNotModifyResponsesFunction( 42 | std::function &response, bool retry)> callback, 43 | const std::shared_ptr &response) { 44 | callback(response, false); 45 | } 46 | 47 | const REQUEST_MODIFIER_FUNCTION DO_NOT_MODIFY_REQUESTS_FUNCTION = &doNotModifyRequestsFunction; 48 | const RESPONSE_MODIFIER_FUNCTION DO_NOT_MODIFY_RESPONSES_FUNCTION = &doNotModifyResponsesFunction; 49 | 50 | Client::~Client() {} 51 | 52 | const std::shared_ptr Client::performRequestSynchronously( 53 | const std::shared_ptr &request) { 54 | std::mutex mutex; 55 | std::condition_variable cv; 56 | std::atomic response_ready(false); 57 | std::shared_ptr output_response = nullptr; 58 | performRequest(request, [&](const std::shared_ptr &response) { 59 | { 60 | std::lock_guard lock(mutex); 61 | output_response = response; 62 | response_ready = true; 63 | } 64 | cv.notify_one(); 65 | }); 66 | std::unique_lock lock(mutex); 67 | while (!response_ready) { 68 | cv.wait(lock); 69 | } 70 | return output_response; 71 | } 72 | 73 | void Client::pinResponse(const std::shared_ptr &response, 74 | const std::string &pin_identifier) {} 75 | 76 | void Client::unpinResponse(const std::shared_ptr &response, 77 | const std::string &pin_identifier) {} 78 | 79 | void Client::removePinnedResponseForIdentifier(const std::string &pin_identifier) {} 80 | 81 | void Client::pinnedResponsesForIdentifier( 82 | const std::string &pin_identifier, 83 | std::function> &)> callback) {} 84 | 85 | void Client::pinningIdentifiers( 86 | std::function &identifiers)> callback) {} 87 | 88 | std::shared_ptr createNativeClient(const std::string &cache_location, 89 | const std::string &user_agent, 90 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 91 | RESPONSE_MODIFIER_FUNCTION response_modifier_function) { 92 | #if USE_CURL 93 | return createCurlClient(); 94 | #elif USE_CPPRESTSDK 95 | return createCpprestsdkClient(); 96 | #elif __APPLE__ 97 | return createNSURLSessionClient(); 98 | #else 99 | return createCurlClient(); 100 | #endif 101 | } 102 | 103 | std::shared_ptr createCachingClient(const std::string &cache_location, 104 | const std::string &user_agent, 105 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 106 | RESPONSE_MODIFIER_FUNCTION response_modifier_function) { 107 | auto native_client = createNativeClient( 108 | cache_location, user_agent, request_modifier_function, response_modifier_function); 109 | // TODO: Make caching client work 110 | // auto caching_client = std::make_shared(native_client, 111 | // cache_location, user_agent); caching_client->initialise(); return 112 | // caching_client; 113 | return native_client; 114 | } 115 | 116 | std::shared_ptr createMultiRequestClient( 117 | const std::string &cache_location, 118 | const std::string &user_agent, 119 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 120 | RESPONSE_MODIFIER_FUNCTION response_modifier_function) { 121 | auto caching_client = createCachingClient( 122 | cache_location, user_agent, request_modifier_function, response_modifier_function); 123 | return std::make_shared(caching_client); 124 | } 125 | 126 | std::shared_ptr createModifierClient( 127 | const std::string &cache_location, 128 | const std::string &user_agent, 129 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 130 | RESPONSE_MODIFIER_FUNCTION response_modifier_function) { 131 | auto multi_request_client = createMultiRequestClient( 132 | cache_location, user_agent, request_modifier_function, response_modifier_function); 133 | return std::make_shared( 134 | request_modifier_function, response_modifier_function, multi_request_client); 135 | } 136 | 137 | std::shared_ptr createClient(const std::string &cache_location, 138 | const std::string &user_agent, 139 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 140 | RESPONSE_MODIFIER_FUNCTION response_modifier_function) { 141 | return createModifierClient( 142 | cache_location, user_agent, request_modifier_function, response_modifier_function); 143 | } 144 | 145 | } // namespace http 146 | } // namespace nativeformat 147 | -------------------------------------------------------------------------------- /source/ClientCpprestsdk.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ClientCpprestsdk.h" 23 | 24 | #include 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | using namespace utility; // Common utilities like string conversions 31 | using namespace web; // Common features like URIs. 32 | using namespace web::http; // Common HTTP functionality 33 | using namespace web::http::client; // HTTP client features 34 | using namespace concurrency::streams; // Asynchronous streams 35 | 36 | // File scope helper functions 37 | static const std::map &methodMap(); 38 | static const web::http::client::http_client_config &clientConfigForProxy(); 39 | static const bool isRedirect(StatusCode code); 40 | static const std::string getRedirectUrl(const http_response &response, 41 | const std::string &request_url); 42 | 43 | // ClientCpprestsdk members 44 | ClientCpprestsdk::ClientCpprestsdk() {} 45 | ClientCpprestsdk::~ClientCpprestsdk() {} 46 | 47 | std::shared_ptr ClientCpprestsdk::performRequest( 48 | const std::shared_ptr &request, 49 | std::function &)> callback) { 50 | container_buffer buf; 51 | http_headers headers; 52 | http_request req; 53 | const std::string base_url = request->url(); 54 | http_client client(conversions::utf8_to_utf16(base_url), clientConfigForProxy()); 55 | std::shared_ptr r = nullptr; 56 | 57 | for (const auto h : request->headerMap()) { 58 | headers.add(conversions::utf8_to_utf16(h.first), conversions::utf8_to_utf16(h.second)); 59 | } 60 | req.headers() = headers; 61 | req.set_method(methodMap().at(request->method())); 62 | 63 | // printf("Starting request to %s...\n", request->url().c_str()); 64 | auto resp = client.request(req).then([=](http_response response) -> void { 65 | // On request completion, create response and call callback 66 | StatusCode status = StatusCode(response.status_code()); 67 | 68 | // Perform redirect if needed 69 | if (isRedirect(status)) { 70 | const std::string new_url = getRedirectUrl(response, request->url()); 71 | std::shared_ptr new_request = createRequest(new_url, request->headerMap()); 72 | this->performRequest(new_request, callback); 73 | return; 74 | } 75 | response.body().read_to_end(buf).wait(); 76 | // printf("Response body:\n%s\n", buf.collection().c_str()); 77 | // printf("Returning response (status = %d)\n", response.status_code()); 78 | const std::string &data = buf.collection(); 79 | std::shared_ptr r = std::make_shared( 80 | request, (const unsigned char *)data.c_str(), data.size(), status, false); 81 | callback(r); 82 | }); 83 | 84 | /* 85 | try { 86 | resp.wait(); 87 | } catch (web::http::http_exception& e) { 88 | std::cout << "HTTP EXCEPTION: " << e.what() << " CODE: " << 89 | e.error_code() << std::endl; raise(SIGTRAP); } catch (std::exception& e) { 90 | std::cout << "EXCEPTION: " << e.what() << std::endl; 91 | raise(SIGTRAP); 92 | } 93 | */ 94 | 95 | std::string request_hash = request->hash(); 96 | std::shared_ptr request_token = 97 | std::make_shared(shared_from_this(), request_hash); 98 | return request_token; 99 | } 100 | 101 | void ClientCpprestsdk::requestTokenDidCancel(const std::shared_ptr &request_token) {} 102 | 103 | const std::map &methodMap() { 104 | static const std::map method_map = { 105 | {GetMethod, web::http::methods::GET}, 106 | {PostMethod, web::http::methods::POST}, 107 | {PutMethod, web::http::methods::PUT}, 108 | {HeadMethod, web::http::methods::HEAD}, 109 | {DeleteMethod, web::http::methods::DEL}, 110 | {OptionsMethod, web::http::methods::OPTIONS}, 111 | {ConnectMethod, web::http::methods::CONNECT}}; 112 | return method_map; 113 | } 114 | 115 | const web::http::client::http_client_config &clientConfigForProxy() { 116 | static web::http::client::http_client_config client_config; 117 | #ifdef _WIN32 118 | wchar_t *pValue = nullptr; 119 | std::unique_ptr holder(nullptr, [](wchar_t *p) { free(p); }); 120 | size_t len = 0; 121 | auto err = _wdupenv_s(&pValue, &len, L"http_proxy"); 122 | if (pValue) holder.reset(pValue); 123 | if (!err && pValue && len) { 124 | std::wstring env_http_proxy_string(pValue, len - 1); 125 | #else 126 | if (const char *env_http_proxy = std::getenv("http_proxy")) { 127 | std::string env_http_proxy_string(env_http_proxy); 128 | #endif 129 | if (env_http_proxy_string == conversions::utf8_to_utf16("auto")) 130 | client_config.set_proxy(web::web_proxy::use_auto_discovery); 131 | else 132 | client_config.set_proxy(web::web_proxy(env_http_proxy_string)); 133 | } 134 | 135 | return client_config; 136 | } 137 | 138 | static const bool isRedirect(StatusCode code) { 139 | if (code >= StatusCodeMovedMultipleChoices && code <= StatusCodeTemporaryRedirect) { 140 | return true; 141 | } 142 | return false; 143 | } 144 | 145 | static const std::string getRedirectUrl(const http_response &response, 146 | const std::string &request_url) { 147 | // Assume if we are here this is definitely a 3xx response code 148 | // Also assume we're just going to grab the Location header for all 3xx 149 | static const auto LOCATION_HEADER = U("Location"); 150 | std::string location = 151 | response.headers().has(LOCATION_HEADER) 152 | ? conversions::utf16_to_utf8(response.headers().find(LOCATION_HEADER)->second) 153 | : ""; 154 | return !std::strncmp("http", location.c_str(), 4) ? location : request_url + location; 155 | } 156 | 157 | std::shared_ptr createCpprestsdkClient() { 158 | return std::make_shared(); 159 | } 160 | 161 | } // namespace http 162 | } // namespace nativeformat 163 | -------------------------------------------------------------------------------- /source/ClientCpprestsdk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #ifdef USE_CPPRESTSDK 24 | 25 | #include 26 | #include 27 | 28 | #include "RequestTokenDelegate.h" 29 | #include "RequestTokenImplementation.h" 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | namespace nativeformat { 37 | namespace http { 38 | 39 | class ClientCpprestsdk : public Client, 40 | public RequestTokenDelegate, 41 | public std::enable_shared_from_this { 42 | public: 43 | ClientCpprestsdk(); 44 | virtual ~ClientCpprestsdk(); 45 | 46 | // Client 47 | std::shared_ptr performRequest( 48 | const std::shared_ptr &request, 49 | std::function &)> callback) override; 50 | 51 | // RequestTokenDelegate 52 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 53 | }; 54 | 55 | extern std::shared_ptr createCpprestsdkClient(); 56 | 57 | } // namespace http 58 | } // namespace nativeformat 59 | 60 | #endif // USE_CPPRESTSDK 61 | -------------------------------------------------------------------------------- /source/ClientCurl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | #include "curl/curl.h" 27 | 28 | #include 29 | #include 30 | 31 | #include "RequestTokenDelegate.h" 32 | #include "RequestTokenImplementation.h" 33 | 34 | namespace nativeformat { 35 | namespace http { 36 | 37 | class ClientCurl : public Client, 38 | public RequestTokenDelegate, 39 | public std::enable_shared_from_this { 40 | struct HandleInfo { 41 | CURL *handle; 42 | const std::shared_ptr request; 43 | std::string request_hash; 44 | std::string response; 45 | curl_slist *request_headers; 46 | std::unordered_map response_headers; 47 | std::function &)> callback; 48 | HandleInfo(std::shared_ptr req, 49 | std::function &)> cbk); 50 | HandleInfo(); 51 | ~HandleInfo(); 52 | 53 | void configureHeaders(); 54 | void configureCurlHandle(); 55 | }; 56 | 57 | public: 58 | ClientCurl(); 59 | virtual ~ClientCurl(); 60 | 61 | static const long MAX_CONNECTIONS = 10; 62 | 63 | // Client 64 | std::shared_ptr performRequest( 65 | const std::shared_ptr &request, 66 | std::function &)> callback) override; 67 | 68 | // RequestTokenDelegate 69 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 70 | 71 | // Private members 72 | private: 73 | // Obtain this lock before modifying any members 74 | std::mutex _client_mutex; 75 | 76 | CURLM *_curl; 77 | std::condition_variable _new_info_condition; 78 | bool _new_request; 79 | std::atomic _is_terminated; 80 | std::thread _request_thread; 81 | 82 | std::unordered_map> _handles; 83 | std::atomic _request_count; 84 | 85 | void mainClientLoop(); 86 | void requestCleanup(std::string hash); 87 | 88 | // Curl callbacks 89 | public: 90 | static size_t write_callback(char *data, size_t size, size_t nitems, void *str); 91 | static size_t header_callback(char *data, size_t size, size_t nitems, void *str); 92 | }; 93 | 94 | extern std::shared_ptr createCurlClient(); 95 | 96 | } // namespace http 97 | } // namespace nativeformat 98 | -------------------------------------------------------------------------------- /source/ClientModifierImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "ClientModifierImplementation.h" 22 | 23 | #include "RequestTokenImplementation.h" 24 | 25 | namespace nativeformat { 26 | namespace http { 27 | 28 | ClientModifierImplementation::ClientModifierImplementation( 29 | REQUEST_MODIFIER_FUNCTION request_modifier_function, 30 | RESPONSE_MODIFIER_FUNCTION response_modifier_function, 31 | std::shared_ptr &wrapped_client) 32 | : _request_modifier_function(request_modifier_function), 33 | _response_modifier_function(response_modifier_function), 34 | _wrapped_client(wrapped_client) {} 35 | 36 | ClientModifierImplementation::~ClientModifierImplementation() {} 37 | 38 | std::shared_ptr ClientModifierImplementation::performRequest( 39 | const std::shared_ptr &request, 40 | std::function &)> callback) { 41 | auto weak_this = std::weak_ptr(shared_from_this()); 42 | auto request_identifier = request->hash(); 43 | auto request_token = std::make_shared(weak_this, request_identifier); 44 | _request_modifier_function( 45 | [weak_this, callback, request_token, request_identifier]( 46 | const std::shared_ptr &request) { 47 | if (request_token->cancelled()) { 48 | return; 49 | } 50 | if (auto strong_this = weak_this.lock()) { 51 | auto new_request_token = strong_this->_wrapped_client->performRequest( 52 | request, 53 | [callback, weak_this, request, request_identifier]( 54 | const std::shared_ptr &response) { 55 | if (auto strong_this = weak_this.lock()) { 56 | strong_this->_response_modifier_function( 57 | [callback, weak_this, request_identifier]( 58 | const std::shared_ptr &response, bool retry) { 59 | if (retry) { 60 | if (auto strong_this = weak_this.lock()) { 61 | auto request_token = 62 | strong_this->performRequest(response->request(), callback); 63 | auto new_request_identifier = request_token->identifier(); 64 | { 65 | std::lock_guard request_map_lock( 66 | strong_this->_request_map_mutex); 67 | strong_this->_request_identifier_map[request_identifier] = 68 | new_request_identifier; 69 | strong_this->_request_token_map[new_request_identifier] = 70 | request_token; 71 | } 72 | return; 73 | } 74 | } 75 | callback(response); 76 | if (auto strong_this = weak_this.lock()) { 77 | std::lock_guard request_map_lock( 78 | strong_this->_request_map_mutex); 79 | strong_this->_request_token_map.erase( 80 | strong_this->_request_identifier_map[request_identifier]); 81 | strong_this->_request_identifier_map.erase(request_identifier); 82 | } 83 | }, 84 | response); 85 | } 86 | }); 87 | auto new_request_identifier = new_request_token->identifier(); 88 | { 89 | std::lock_guard request_map_lock(strong_this->_request_map_mutex); 90 | strong_this->_request_identifier_map[request_identifier] = new_request_identifier; 91 | strong_this->_request_token_map[new_request_identifier] = new_request_token; 92 | } 93 | } 94 | }, 95 | request); 96 | return request_token; 97 | } 98 | 99 | void ClientModifierImplementation::pinResponse(const std::shared_ptr &response, 100 | const std::string &pin_identifier) { 101 | return _wrapped_client->pinResponse(response, pin_identifier); 102 | } 103 | 104 | void ClientModifierImplementation::unpinResponse(const std::shared_ptr &response, 105 | const std::string &pin_identifier) { 106 | return _wrapped_client->unpinResponse(response, pin_identifier); 107 | } 108 | 109 | void ClientModifierImplementation::removePinnedResponseForIdentifier( 110 | const std::string &pin_identifier) { 111 | _wrapped_client->removePinnedResponseForIdentifier(pin_identifier); 112 | } 113 | 114 | void ClientModifierImplementation::pinnedResponsesForIdentifier( 115 | const std::string &pin_identifier, 116 | std::function> &)> callback) { 117 | _wrapped_client->pinnedResponsesForIdentifier(pin_identifier, callback); 118 | } 119 | 120 | void ClientModifierImplementation::pinningIdentifiers( 121 | std::function &identifiers)> callback) { 122 | _wrapped_client->pinningIdentifiers(callback); 123 | } 124 | 125 | void ClientModifierImplementation::requestTokenDidCancel( 126 | const std::shared_ptr &request_token) { 127 | std::lock_guard request_map_lock(_request_map_mutex); 128 | auto identifier = request_token->identifier(); 129 | auto new_identifier = _request_identifier_map[identifier]; 130 | auto new_request_token = _request_token_map[new_identifier]; 131 | if (auto new_request_token_strong = new_request_token.lock()) { 132 | new_request_token_strong->cancel(); 133 | } 134 | _request_identifier_map.erase(identifier); 135 | _request_token_map.erase(new_identifier); 136 | } 137 | 138 | } // namespace http 139 | } // namespace nativeformat 140 | -------------------------------------------------------------------------------- /source/ClientModifierImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include "RequestTokenDelegate.h" 29 | 30 | namespace nativeformat { 31 | namespace http { 32 | 33 | class ClientModifierImplementation 34 | : public Client, 35 | public std::enable_shared_from_this, 36 | public RequestTokenDelegate { 37 | public: 38 | ClientModifierImplementation(REQUEST_MODIFIER_FUNCTION request_modifier_function, 39 | RESPONSE_MODIFIER_FUNCTION response_modifier_function, 40 | std::shared_ptr &wrapped_client); 41 | virtual ~ClientModifierImplementation(); 42 | 43 | // Client 44 | virtual std::shared_ptr performRequest( 45 | const std::shared_ptr &request, 46 | std::function &)> callback); 47 | virtual void pinResponse(const std::shared_ptr &response, 48 | const std::string &pin_identifier); 49 | virtual void unpinResponse(const std::shared_ptr &response, 50 | const std::string &pin_identifier); 51 | virtual void removePinnedResponseForIdentifier(const std::string &pin_identifier); 52 | virtual void pinnedResponsesForIdentifier( 53 | const std::string &pin_identifier, 54 | std::function> &)> callback); 55 | virtual void pinningIdentifiers( 56 | std::function &identifiers)> callback); 57 | 58 | // RequestTokenDelegate 59 | virtual void requestTokenDidCancel(const std::shared_ptr &request_token); 60 | 61 | private: 62 | const REQUEST_MODIFIER_FUNCTION _request_modifier_function; 63 | const RESPONSE_MODIFIER_FUNCTION _response_modifier_function; 64 | const std::shared_ptr _wrapped_client; 65 | 66 | std::unordered_map _request_identifier_map; 67 | std::unordered_map> _request_token_map; 68 | std::mutex _request_map_mutex; 69 | }; 70 | 71 | } // namespace http 72 | } // namespace nativeformat 73 | -------------------------------------------------------------------------------- /source/ClientMultiRequestImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "ClientMultiRequestImplementation.h" 22 | 23 | #include "RequestTokenImplementation.h" 24 | 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | namespace { 31 | static const std::string MULTICAST_KEY("multicasted"); 32 | } // namespace 33 | 34 | ClientMultiRequestImplementation::ClientMultiRequestImplementation( 35 | std::shared_ptr &wrapped_client) 36 | : _wrapped_client(wrapped_client) {} 37 | 38 | ClientMultiRequestImplementation::~ClientMultiRequestImplementation() {} 39 | 40 | std::shared_ptr ClientMultiRequestImplementation::performRequest( 41 | const std::shared_ptr &request, 42 | std::function &)> callback) { 43 | std::lock_guard lock(_requests_in_flight_mutex); 44 | auto hash = request->hash(); 45 | auto request_it = _requests_in_flight.find(hash); 46 | if (request_it == _requests_in_flight.end()) { 47 | MultiRequests multi_requests; 48 | std::weak_ptr weak_this = shared_from_this(); 49 | multi_requests.request_token = _wrapped_client->performRequest( 50 | request, [weak_this](const std::shared_ptr &response) { 51 | if (auto strong_this = weak_this.lock()) { 52 | std::vector &)>> callbacks; 53 | { 54 | std::lock_guard lock(strong_this->_requests_in_flight_mutex); 55 | auto hash = response->request()->hash(); 56 | auto &multi_requests = strong_this->_requests_in_flight[hash]; 57 | response->setMetadata(MULTICAST_KEY, 58 | std::to_string(multi_requests.multi_requests.size() > 1)); 59 | for (const auto &multi_request : multi_requests.multi_requests) { 60 | callbacks.push_back(multi_request.callback); 61 | } 62 | strong_this->_requests_in_flight.erase(hash); 63 | } 64 | for (const auto &callback : callbacks) { 65 | callback(response); 66 | } 67 | } 68 | }); 69 | _requests_in_flight[hash] = multi_requests; 70 | } 71 | auto token = std::make_shared(shared_from_this(), hash); 72 | MultiRequest multi_request = {callback, token}; 73 | _requests_in_flight[hash].multi_requests.push_back(multi_request); 74 | return token; 75 | } 76 | 77 | void ClientMultiRequestImplementation::pinResponse(const std::shared_ptr &response, 78 | const std::string &pin_identifier) { 79 | _wrapped_client->pinResponse(response, pin_identifier); 80 | } 81 | 82 | void ClientMultiRequestImplementation::unpinResponse(const std::shared_ptr &response, 83 | const std::string &pin_identifier) { 84 | _wrapped_client->unpinResponse(response, pin_identifier); 85 | } 86 | 87 | void ClientMultiRequestImplementation::removePinnedResponseForIdentifier( 88 | const std::string &pin_identifier) { 89 | _wrapped_client->removePinnedResponseForIdentifier(pin_identifier); 90 | } 91 | 92 | void ClientMultiRequestImplementation::pinnedResponsesForIdentifier( 93 | const std::string &pin_identifier, 94 | std::function> &)> callback) { 95 | _wrapped_client->pinnedResponsesForIdentifier(pin_identifier, callback); 96 | } 97 | 98 | void ClientMultiRequestImplementation::pinningIdentifiers( 99 | std::function &identifiers)> callback) { 100 | _wrapped_client->pinningIdentifiers(callback); 101 | } 102 | 103 | void ClientMultiRequestImplementation::requestTokenDidCancel( 104 | const std::shared_ptr &request_token) { 105 | std::lock_guard lock(_requests_in_flight_mutex); 106 | auto identifier = request_token->identifier(); 107 | auto &multi_requests = _requests_in_flight[identifier]; 108 | auto &multi_requests_vector = multi_requests.multi_requests; 109 | multi_requests_vector.erase(std::remove_if(multi_requests_vector.begin(), 110 | multi_requests_vector.end(), 111 | [&](MultiRequest &multi_request) { 112 | return multi_request.request_token.lock().get() == 113 | request_token.get(); 114 | }), 115 | multi_requests_vector.end()); 116 | if (multi_requests_vector.empty()) { 117 | multi_requests.request_token->cancel(); 118 | _requests_in_flight.erase(identifier); 119 | } 120 | } 121 | 122 | } // namespace http 123 | } // namespace nativeformat 124 | -------------------------------------------------------------------------------- /source/ClientMultiRequestImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include "RequestTokenDelegate.h" 29 | 30 | namespace nativeformat { 31 | namespace http { 32 | 33 | class ClientMultiRequestImplementation 34 | : public Client, 35 | public std::enable_shared_from_this, 36 | public RequestTokenDelegate { 37 | public: 38 | ClientMultiRequestImplementation(std::shared_ptr &wrapped_client); 39 | virtual ~ClientMultiRequestImplementation(); 40 | 41 | // Client 42 | std::shared_ptr performRequest( 43 | const std::shared_ptr &request, 44 | std::function &)> callback) override; 45 | void pinResponse(const std::shared_ptr &response, 46 | const std::string &pin_identifier) override; 47 | void unpinResponse(const std::shared_ptr &response, 48 | const std::string &pin_identifier) override; 49 | void removePinnedResponseForIdentifier(const std::string &pin_identifier) override; 50 | void pinnedResponsesForIdentifier( 51 | const std::string &pin_identifier, 52 | std::function> &)> callback) override; 53 | void pinningIdentifiers( 54 | std::function &identifiers)> callback) override; 55 | 56 | // RequestTokenDelegate 57 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 58 | 59 | private: 60 | struct MultiRequest { 61 | std::function &)> callback; 62 | std::weak_ptr request_token; 63 | }; 64 | struct MultiRequests { 65 | std::vector multi_requests; 66 | std::shared_ptr request_token; 67 | }; 68 | 69 | const std::shared_ptr _wrapped_client; 70 | 71 | std::unordered_map _requests_in_flight; 72 | std::mutex _requests_in_flight_mutex; 73 | }; 74 | 75 | } // namespace http 76 | } // namespace nativeformat 77 | -------------------------------------------------------------------------------- /source/ClientNSURLSession.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #if __APPLE__ 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | extern std::shared_ptr createNSURLSessionClient(); 31 | 32 | } // namespace http 33 | } // namespace nativeformat 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /source/ClientNSURLSession.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #if __APPLE__ 22 | 23 | #include "ClientNSURLSession.h" 24 | 25 | #include 26 | 27 | #include 28 | 29 | #include "RequestTokenDelegate.h" 30 | #include "RequestTokenImplementation.h" 31 | 32 | #import 33 | 34 | namespace nativeformat { 35 | namespace http { 36 | 37 | static NSString *languageHeaderValue(); 38 | static NSString *generateLanguageHeaderValue(); 39 | 40 | class ClientNSURLSession : public Client, 41 | public RequestTokenDelegate, 42 | public std::enable_shared_from_this { 43 | public: 44 | ClientNSURLSession(); 45 | virtual ~ClientNSURLSession(); 46 | 47 | // Client 48 | std::shared_ptr performRequest( 49 | const std::shared_ptr &request, 50 | std::function &)> callback) override; 51 | 52 | // RequestTokenDelegate 53 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 54 | 55 | private: 56 | static NSURLRequest *requestFromRequest(const std::shared_ptr &request); 57 | static const std::shared_ptr responseFromResponse(NSHTTPURLResponse *response, 58 | const std::shared_ptr request, 59 | NSData *data); 60 | 61 | NSURLSession *_session; 62 | std::unordered_map, NSURLSessionTask *> _tokens; 63 | std::mutex _tokens_mutex; 64 | }; 65 | 66 | ClientNSURLSession::ClientNSURLSession() 67 | : _session([NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]) 68 | { 69 | } 70 | 71 | ClientNSURLSession::~ClientNSURLSession() 72 | { 73 | std::lock_guard lock(_tokens_mutex); 74 | for (auto &token_task : _tokens) { 75 | [token_task.second cancel]; 76 | } 77 | [_session invalidateAndCancel]; 78 | } 79 | 80 | std::shared_ptr ClientNSURLSession::performRequest( 81 | const std::shared_ptr &request, std::function &)> callback) 82 | { 83 | @autoreleasepool { 84 | NSURLRequest *urlRequest = requestFromRequest(request); 85 | const std::shared_ptr copied_request = request; 86 | std::shared_ptr request_token = 87 | std::make_shared(shared_from_this(), request->hash()); 88 | NSURLSessionTask *task = 89 | [_session dataTaskWithRequest:urlRequest 90 | completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 91 | { 92 | std::lock_guard lock(_tokens_mutex); 93 | _tokens.erase(request_token); 94 | } 95 | if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 96 | callback(responseFromResponse((NSHTTPURLResponse *)response, copied_request, data)); 97 | } else { 98 | callback(responseFromResponse(nil, copied_request, data)); 99 | } 100 | }]; 101 | { 102 | std::lock_guard lock(_tokens_mutex); 103 | _tokens[request_token] = task; 104 | } 105 | [task resume]; 106 | return request_token; 107 | } 108 | } 109 | 110 | void ClientNSURLSession::requestTokenDidCancel(const std::shared_ptr &request_token) 111 | { 112 | std::lock_guard lock(_tokens_mutex); 113 | [_tokens[request_token] cancel]; 114 | } 115 | 116 | NSURLRequest *ClientNSURLSession::requestFromRequest(const std::shared_ptr &request) 117 | { 118 | static NSString *const AcceptLanguageHeader = @"Accept-Language"; 119 | 120 | NSString *urlString = [NSString stringWithUTF8String:request->url().c_str()]; 121 | NSURL *url = [NSURL URLWithString:urlString]; 122 | NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url 123 | cachePolicy:NSURLRequestReloadIgnoringCacheData 124 | timeoutInterval:30.0]; 125 | mutableRequest.HTTPMethod = [NSString stringWithUTF8String:request->method().c_str()]; 126 | for (const auto &header : request->headerMap()) { 127 | [mutableRequest addValue:[NSString stringWithUTF8String:header.second.c_str()] 128 | forHTTPHeaderField:[NSString stringWithUTF8String:header.first.c_str()]]; 129 | } 130 | size_t data_length = 0; 131 | const unsigned char *data = request->data(data_length); 132 | if (data_length > 0) { 133 | mutableRequest.HTTPBody = [NSData dataWithBytes:data length:data_length]; 134 | } 135 | if (!mutableRequest.allHTTPHeaderFields[AcceptLanguageHeader]) { 136 | [mutableRequest setValue:languageHeaderValue() forHTTPHeaderField:AcceptLanguageHeader]; 137 | } 138 | return mutableRequest.copy; 139 | } 140 | 141 | const std::shared_ptr ClientNSURLSession::responseFromResponse(NSHTTPURLResponse *response, 142 | const std::shared_ptr request, 143 | NSData *data) 144 | { 145 | std::shared_ptr new_response = std::make_shared( 146 | request, (const unsigned char *)data.bytes, data.length, (StatusCode)response.statusCode, false); 147 | for (NSString *header in response.allHeaderFields.allKeys) { 148 | (*new_response)[header.UTF8String] = [response.allHeaderFields[header] UTF8String]; 149 | } 150 | return new_response; 151 | } 152 | 153 | // Lifted from 154 | // https://raw.githubusercontent.com/spotify/SPTDataLoader/master/SPTDataLoader/SPTDataLoaderRequest.m 155 | 156 | static NSString *generateLanguageHeaderValue() 157 | { 158 | const NSInteger SPTDataLoaderRequestMaximumLanguages = 2; 159 | NSString *const SPTDataLoaderRequestEnglishLanguageValue = @"en"; 160 | NSString *const SPTDataLoaderRequestLanguageHeaderValuesJoiner = @", "; 161 | 162 | NSString * (^constructLanguageHeaderValue)(NSString *, double) = 163 | ^NSString *(NSString *language, double languageImportance) 164 | { 165 | NSString *const SPTDataLoaderRequestLanguageFormatString = @"%@;q=%.2f"; 166 | return [NSString stringWithFormat:SPTDataLoaderRequestLanguageFormatString, language, languageImportance]; 167 | }; 168 | 169 | NSArray *languages = [NSBundle mainBundle].preferredLocalizations; 170 | if (languages.count > SPTDataLoaderRequestMaximumLanguages) { 171 | languages = [languages subarrayWithRange:NSMakeRange(0, SPTDataLoaderRequestMaximumLanguages)]; 172 | } 173 | double languageImportanceCounter = 1.0; 174 | NSMutableArray *languageHeaderValues = [NSMutableArray arrayWithCapacity:languages.count]; 175 | BOOL containsEnglish = NO; 176 | for (NSString *language in languages) { 177 | if (!containsEnglish) { 178 | NSString *const SPTDataLoaderRequestLanguageLocaleSeparator = @"-"; 179 | NSString *languageValue = 180 | [language componentsSeparatedByString:SPTDataLoaderRequestLanguageLocaleSeparator].firstObject; 181 | if ([languageValue isEqualToString:SPTDataLoaderRequestEnglishLanguageValue]) { 182 | containsEnglish = YES; 183 | } 184 | } 185 | 186 | if (languageImportanceCounter == 1.0) { 187 | [languageHeaderValues addObject:language]; 188 | } else { 189 | [languageHeaderValues addObject:constructLanguageHeaderValue(language, languageImportanceCounter)]; 190 | } 191 | languageImportanceCounter -= (1.0 / languages.count); 192 | } 193 | if (!containsEnglish) { 194 | [languageHeaderValues addObject:constructLanguageHeaderValue(SPTDataLoaderRequestEnglishLanguageValue, 0.01)]; 195 | } 196 | return [languageHeaderValues componentsJoinedByString:SPTDataLoaderRequestLanguageHeaderValuesJoiner]; 197 | } 198 | 199 | static NSString *languageHeaderValue() 200 | { 201 | static NSString *languageHeaderValue = nil; 202 | static dispatch_once_t onceToken; 203 | dispatch_once(&onceToken, ^{ 204 | languageHeaderValue = generateLanguageHeaderValue(); 205 | }); 206 | return languageHeaderValue; 207 | } 208 | 209 | std::shared_ptr createNSURLSessionClient() 210 | { 211 | return std::make_shared(); 212 | } 213 | 214 | } // namespace http 215 | } // namespace nativeformat 216 | 217 | #endif 218 | -------------------------------------------------------------------------------- /source/NFHTTP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #include 24 | 25 | namespace nativeformat { 26 | namespace http { 27 | 28 | std::string version() { 29 | return NFHTTP_VERSION; 30 | } 31 | 32 | } // namespace http 33 | } // namespace nativeformat 34 | -------------------------------------------------------------------------------- /source/NFHTTPCLI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | int main(int argc, char *argv[]) { 30 | // Parse our arguments 31 | std::string input_json_file = ""; 32 | std::string output_directory = ""; 33 | for (int i = 1; i < argc; ++i) { 34 | std::string arg_string = argv[i]; 35 | if (arg_string == "-i") { 36 | input_json_file = argv[++i]; 37 | } else if (arg_string == "-o") { 38 | output_directory = argv[++i]; 39 | } 40 | } 41 | 42 | // Create our client 43 | auto client = nativeformat::http::createClient(nativeformat::http::standardCacheLocation(), 44 | "NFHTTP-" + nativeformat::http::version()); 45 | 46 | // Setup our responses array 47 | nlohmann::json output_json; 48 | nlohmann::json responses_json; 49 | std::ofstream output_file(output_directory + "/responses.json"); 50 | 51 | // Parse the requests json 52 | std::ifstream input_json_stream(input_json_file); 53 | nlohmann::json input_json = nlohmann::json::parse(input_json_stream); 54 | auto requests = input_json["requests"]; 55 | for (auto it = requests.begin(); it != requests.end(); ++it) { 56 | const std::string url = (*it)["url"]; 57 | const std::string id = (*it)["id"]; 58 | 59 | // Send our request 60 | auto request = 61 | nativeformat::http::createRequest(url, std::unordered_map()); 62 | auto response = client->performRequestSynchronously(request); 63 | size_t data_length = 0; 64 | const unsigned char *data = response->data(data_length); 65 | 66 | // Generate a random file to dump the payload to 67 | const size_t random_file_length = 20; 68 | auto randchar = []() { 69 | const char charset[] = 70 | "0123456789" 71 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 72 | "abcdefghijklmnopqrstuvwxyz"; 73 | const size_t max_index = (sizeof(charset) - 1); 74 | return charset[rand() % max_index]; 75 | }; 76 | std::string random_file_name(random_file_length, 0); 77 | std::generate_n(random_file_name.begin(), random_file_length, randchar); 78 | random_file_name.insert(0, "/"); 79 | random_file_name.insert(0, output_directory); 80 | std::ofstream random_file; 81 | random_file.open(random_file_name); 82 | random_file.write((const char *)data, data_length); 83 | random_file.close(); 84 | 85 | // Create our response JSON 86 | nlohmann::json response_json = {{"payload", random_file_name}}; 87 | responses_json[id] = response_json; 88 | } 89 | 90 | // Write out responses JSON to disk 91 | output_json["responses"] = responses_json; 92 | output_file << std::setw(4) << output_json << std::endl; 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /source/Request.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #include "RequestImplementation.h" 24 | 25 | namespace nativeformat { 26 | namespace http { 27 | 28 | const std::string GetMethod("GET"); 29 | const std::string PostMethod("POST"); 30 | const std::string PutMethod("PUT"); 31 | const std::string HeadMethod("HEAD"); 32 | const std::string DeleteMethod("DELETE"); 33 | const std::string OptionsMethod("OPTIONS"); 34 | const std::string ConnectMethod("CONNECT"); 35 | 36 | std::shared_ptr createRequest( 37 | const std::string &url, const std::unordered_map &header_map) { 38 | return std::make_shared(url, header_map); 39 | } 40 | 41 | std::shared_ptr createRequest(const std::shared_ptr &request) { 42 | return std::make_shared(*request.get()); 43 | } 44 | 45 | } // namespace http 46 | } // namespace nativeformat 47 | -------------------------------------------------------------------------------- /source/RequestImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "RequestImplementation.h" 22 | 23 | #include 24 | 25 | #include "sha256.h" 26 | 27 | namespace nativeformat { 28 | namespace http { 29 | 30 | static const std::string url_key("url"); 31 | static const std::string headers_key("headers"); 32 | static const std::string method_key("method"); 33 | static const std::string content_length_key("Content-Length"); 34 | 35 | RequestImplementation::RequestImplementation( 36 | const std::string &url, const std::unordered_map &header_map) 37 | : _url(url), _headers(header_map), _method(GetMethod), _data(nullptr), _data_length(0) { 38 | _headers[content_length_key] = "0"; 39 | } 40 | 41 | RequestImplementation::RequestImplementation(const Request &request) 42 | : _url(request.url()), 43 | _headers(request.headerMap()), 44 | _method(request.method()), 45 | _data(nullptr), 46 | _data_length(0) { 47 | size_t data_length = 0; 48 | const unsigned char *data = request.data(data_length); 49 | if (data_length > 0) { 50 | _data = (unsigned char *)malloc(data_length); 51 | memcpy(_data, data, data_length); 52 | _data_length = data_length; 53 | } 54 | } 55 | 56 | RequestImplementation::RequestImplementation(const std::string &serialised) 57 | : _data(nullptr), _data_length(0) { 58 | nlohmann::json j = nlohmann::json::parse(serialised); 59 | _url = j[url_key].get(); 60 | auto o = j[headers_key]; 61 | for (nlohmann::json::iterator it = o.begin(); it != o.end(); ++it) { 62 | _headers[it.key()] = it.value().get(); 63 | } 64 | _method = j[method_key].get(); 65 | } 66 | 67 | RequestImplementation::~RequestImplementation() { 68 | if (_data) { 69 | free(_data); 70 | } 71 | } 72 | 73 | std::string RequestImplementation::url() const { 74 | return _url; 75 | } 76 | 77 | void RequestImplementation::setUrl(const std::string &url) { 78 | _url = url; 79 | } 80 | 81 | std::string RequestImplementation::operator[](const std::string &header_name) const { 82 | return _headers.at(header_name); 83 | } 84 | 85 | std::string &RequestImplementation::operator[](const std::string &header_name) { 86 | return _headers[header_name]; 87 | } 88 | 89 | std::unordered_map &RequestImplementation::headerMap() { 90 | return _headers; 91 | } 92 | 93 | std::unordered_map RequestImplementation::headerMap() const { 94 | return _headers; 95 | } 96 | 97 | std::string RequestImplementation::hash() const { 98 | // Support "Vary" headers 99 | std::vector excluded_headers; 100 | const auto &vary_iterator = _headers.find("Vary"); 101 | if (vary_iterator != _headers.end()) { 102 | std::istringstream ss((*vary_iterator).second); 103 | std::string token; 104 | while (std::getline(ss, token, ',')) { 105 | token.erase(remove_if(token.begin(), token.end(), isspace), token.end()); 106 | excluded_headers.push_back(token); 107 | } 108 | } 109 | 110 | std::string amalgamation = url(); 111 | for (const auto &header_pair : _headers) { 112 | if (std::find(excluded_headers.begin(), excluded_headers.end(), header_pair.first) != 113 | excluded_headers.end()) { 114 | continue; 115 | } 116 | amalgamation += header_pair.first + header_pair.second; 117 | } 118 | if (_data != nullptr) { 119 | amalgamation.append((const char *)_data, _data_length); 120 | } 121 | return sha256(amalgamation); 122 | } 123 | 124 | std::string RequestImplementation::serialise() const { 125 | nlohmann::json j = {{url_key, _url}, {headers_key, _headers}, {method_key, _method}}; 126 | return j.dump(); 127 | } 128 | 129 | std::string RequestImplementation::method() const { 130 | return _method; 131 | } 132 | 133 | void RequestImplementation::setMethod(const std::string &method) { 134 | _method = method; 135 | } 136 | 137 | const unsigned char *RequestImplementation::data(size_t &data_length) const { 138 | data_length = _data_length; 139 | return _data; 140 | } 141 | 142 | void RequestImplementation::setData(const unsigned char *data, size_t data_length) { 143 | if (_data) { 144 | free(_data); 145 | _data = nullptr; 146 | _data_length = data_length; 147 | } 148 | if (data_length > 0) { 149 | _data = (unsigned char *)malloc(data_length + 1); 150 | memcpy(_data, data, data_length); 151 | _data[data_length] = 0; 152 | _data_length = data_length; 153 | } 154 | _headers[content_length_key] = std::to_string(data_length); 155 | } 156 | 157 | Request::CacheControl RequestImplementation::cacheControl() const { 158 | const auto &cache_control_iterator = _headers.find("Cache-Control"); 159 | if (cache_control_iterator == _headers.end()) { 160 | return {0, 0, 0, false, false, false, false}; 161 | } 162 | 163 | std::unordered_map control_directives; 164 | std::istringstream ss((*cache_control_iterator).second); 165 | std::string token; 166 | while (std::getline(ss, token, ',')) { 167 | token.erase(remove_if(token.begin(), token.end(), isspace), token.end()); 168 | const auto equal_index = token.find("="); 169 | if (equal_index == std::string::npos || equal_index == token.length() - 1) { 170 | control_directives[token] = ""; 171 | } else { 172 | control_directives[token.substr(0, equal_index)] = token.substr(equal_index + 1); 173 | } 174 | } 175 | 176 | return {std::stoi(control_directives["max-age"]), 177 | std::stoi(control_directives["max-stale"]), 178 | std::stoi(control_directives["min-fresh"]), 179 | control_directives.find("no-cache") != control_directives.end(), 180 | control_directives.find("no-store") != control_directives.end(), 181 | control_directives.find("no-transform") != control_directives.end(), 182 | control_directives.find("only-if-cached") != control_directives.end()}; 183 | } 184 | 185 | } // namespace http 186 | } // namespace nativeformat 187 | -------------------------------------------------------------------------------- /source/RequestImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | namespace nativeformat { 26 | namespace http { 27 | 28 | class RequestImplementation : public Request { 29 | public: 30 | RequestImplementation(const std::string &url, 31 | const std::unordered_map &header_map); 32 | RequestImplementation(const Request &request); 33 | RequestImplementation(const std::string &serialised); 34 | virtual ~RequestImplementation(); 35 | 36 | // Request 37 | std::string url() const override; 38 | void setUrl(const std::string &url) override; 39 | std::string operator[](const std::string &header_name) const override; 40 | std::string &operator[](const std::string &header_name) override; 41 | std::unordered_map &headerMap() override; 42 | std::unordered_map headerMap() const override; 43 | std::string hash() const override; 44 | std::string serialise() const override; 45 | std::string method() const override; 46 | void setMethod(const std::string &method) override; 47 | const unsigned char *data(size_t &data_length) const override; 48 | void setData(const unsigned char *data, size_t data_length) override; 49 | CacheControl cacheControl() const override; 50 | 51 | private: 52 | std::string _url; 53 | std::unordered_map _headers; 54 | std::string _method; 55 | unsigned char *_data; 56 | size_t _data_length; 57 | }; 58 | 59 | } // namespace http 60 | } // namespace nativeformat 61 | -------------------------------------------------------------------------------- /source/RequestTokenDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | namespace nativeformat { 24 | namespace http { 25 | 26 | class RequestToken; 27 | 28 | class RequestTokenDelegate { 29 | public: 30 | virtual void requestTokenDidCancel(const std::shared_ptr &request_token) = 0; 31 | }; 32 | 33 | } // namespace http 34 | } // namespace nativeformat 35 | -------------------------------------------------------------------------------- /source/RequestTokenImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "RequestTokenImplementation.h" 22 | 23 | namespace nativeformat { 24 | namespace http { 25 | 26 | RequestTokenImplementation::RequestTokenImplementation( 27 | const std::weak_ptr &delegate, const std::string &identifier) 28 | : _delegate(delegate), _identifier(identifier), _cancelled(false), _dependents(0) {} 29 | 30 | RequestTokenImplementation::~RequestTokenImplementation() {} 31 | 32 | void RequestTokenImplementation::cancel() { 33 | _cancelled = true; 34 | if (auto delegate = _delegate.lock()) { 35 | delegate->requestTokenDidCancel(shared_from_this()); 36 | } 37 | } 38 | 39 | std::string RequestTokenImplementation::identifier() const { 40 | return _identifier; 41 | } 42 | 43 | bool RequestTokenImplementation::cancelled() { 44 | return _cancelled && dependents() == 0; 45 | } 46 | 47 | std::shared_ptr RequestTokenImplementation::createDependentToken() { 48 | _dependents++; 49 | return std::make_shared(shared_from_this(), _identifier); 50 | } 51 | 52 | int RequestTokenImplementation::dependents() { 53 | return _dependents; 54 | } 55 | 56 | void RequestTokenImplementation::requestTokenDidCancel( 57 | const std::shared_ptr &request_token) { 58 | _dependents--; 59 | } 60 | 61 | } // namespace http 62 | } // namespace nativeformat 63 | -------------------------------------------------------------------------------- /source/RequestTokenImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include "RequestTokenDelegate.h" 28 | 29 | namespace nativeformat { 30 | namespace http { 31 | 32 | class RequestTokenImplementation : public RequestToken, 33 | public std::enable_shared_from_this, 34 | public RequestTokenDelegate { 35 | public: 36 | RequestTokenImplementation(const std::weak_ptr &delegate, 37 | const std::string &identifier); 38 | virtual ~RequestTokenImplementation(); 39 | 40 | // RequestToken 41 | void cancel() override; 42 | std::string identifier() const override; 43 | bool cancelled() override; 44 | std::shared_ptr createDependentToken() override; 45 | int dependents() override; 46 | 47 | // RequestTokenDelegate 48 | void requestTokenDidCancel(const std::shared_ptr &request_token) override; 49 | 50 | private: 51 | const std::weak_ptr _delegate; 52 | const std::string _identifier; 53 | 54 | bool _cancelled; 55 | std::atomic _dependents; 56 | }; 57 | 58 | } // namespace http 59 | } // namespace nativeformat 60 | -------------------------------------------------------------------------------- /source/ResponseImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #include "RequestImplementation.h" 24 | 25 | #include 26 | 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace http { 31 | 32 | static const std::string status_code_key("status_code"); 33 | static const std::string request_key("request"); 34 | static const std::string headers_key("headers"); 35 | static const std::string maxage_key("max-age"); 36 | static const std::string s_maxage_key("s-maxage"); 37 | 38 | ResponseImplementation::ResponseImplementation(const std::shared_ptr &request, 39 | const unsigned char *data, 40 | size_t data_length, 41 | StatusCode status_code, 42 | bool cancelled) 43 | : _request(request), 44 | _data(data_length == 0 ? nullptr : (unsigned char *)malloc(data_length)), 45 | _data_length(data_length), 46 | _status_code(status_code), 47 | _cancelled(cancelled) { 48 | if (data_length > 0) { 49 | memcpy(_data, data, data_length); 50 | } 51 | } 52 | 53 | ResponseImplementation::ResponseImplementation(const std::string &serialised, 54 | const unsigned char *data, 55 | size_t data_length, 56 | const std::shared_ptr &response) 57 | : _data(data_length == 0 ? nullptr : (unsigned char *)malloc(data_length)), 58 | _data_length(data_length), 59 | _status_code(StatusCodeInvalid), 60 | _cancelled(false) { 61 | nlohmann::json j = nlohmann::json::parse(serialised); 62 | _request = std::make_shared(j[request_key].get()); 63 | _status_code = j[status_code_key]; 64 | auto o = j[headers_key]; 65 | for (nlohmann::json::iterator it = o.begin(); it != o.end(); ++it) { 66 | _headers[it.key()] = it.value().get(); 67 | } 68 | if (response) { 69 | for (const auto &header_pair : response->headerMap()) { 70 | _headers[header_pair.first] = header_pair.second; 71 | } 72 | } 73 | } 74 | 75 | ResponseImplementation::~ResponseImplementation() { 76 | if (_data != nullptr) { 77 | free(_data); 78 | } 79 | } 80 | 81 | const std::shared_ptr ResponseImplementation::request() const { 82 | return _request; 83 | } 84 | 85 | const unsigned char *ResponseImplementation::data(size_t &data_length) const { 86 | data_length = _data_length; 87 | return _data; 88 | } 89 | 90 | StatusCode ResponseImplementation::statusCode() const { 91 | return _status_code; 92 | } 93 | 94 | bool ResponseImplementation::cancelled() const { 95 | return _cancelled; 96 | } 97 | 98 | std::string ResponseImplementation::serialise() const { 99 | nlohmann::json j = {{status_code_key, statusCode()}, {request_key, _request->serialise()}}; 100 | return j.dump(); 101 | } 102 | 103 | std::string ResponseImplementation::operator[](const std::string &header_name) const { 104 | return _headers.at(header_name); 105 | } 106 | 107 | std::string &ResponseImplementation::operator[](const std::string &header_name) { 108 | return _headers[header_name]; 109 | } 110 | 111 | std::unordered_map &ResponseImplementation::headerMap() { 112 | return _headers; 113 | } 114 | 115 | std::unordered_map ResponseImplementation::headerMap() const { 116 | return _headers; 117 | } 118 | 119 | Response::CacheControl ResponseImplementation::cacheControl() const { 120 | const auto &cache_control_iterator = _headers.find("Cache-Control"); 121 | if (cache_control_iterator == _headers.end()) { 122 | return {false, false, false, false, false, false, false, 0, 0}; 123 | } 124 | 125 | std::unordered_map control_directives; 126 | std::istringstream ss((*cache_control_iterator).second); 127 | std::string token; 128 | while (std::getline(ss, token, ',')) { 129 | token.erase(remove_if(token.begin(), token.end(), isspace), token.end()); 130 | const auto equal_index = token.find("="); 131 | if (equal_index == std::string::npos || equal_index == token.length() - 1) { 132 | control_directives[token] = ""; 133 | } else { 134 | control_directives[token.substr(0, equal_index)] = token.substr(equal_index + 1); 135 | } 136 | } 137 | 138 | int max_age = 0; 139 | if (control_directives.find(maxage_key) != control_directives.end()) { 140 | max_age = std::stoi(control_directives[maxage_key]); 141 | } 142 | int s_maxage = 0; 143 | if (control_directives.find(s_maxage_key) != control_directives.end()) { 144 | s_maxage = std::stoi(control_directives[s_maxage_key]); 145 | } 146 | return {control_directives.find("must-revalidate") != control_directives.end(), 147 | control_directives.find("no-cache") != control_directives.end(), 148 | control_directives.find("no-store") != control_directives.end(), 149 | control_directives.find("no-transform") != control_directives.end(), 150 | control_directives.find("public") != control_directives.end(), 151 | control_directives.find("private") != control_directives.end(), 152 | control_directives.find("proxy-revalidate") != control_directives.end(), 153 | max_age, 154 | s_maxage}; 155 | } 156 | 157 | std::unordered_map ResponseImplementation::metadata() const { 158 | return _metadata; 159 | } 160 | 161 | void ResponseImplementation::setMetadata(const std::string &key, const std::string &value) { 162 | _metadata[key] = value; 163 | } 164 | 165 | } // namespace http 166 | } // namespace nativeformat 167 | -------------------------------------------------------------------------------- /source/sha256.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Updated to C++, zedwood.com 2012 3 | * Based on Olivier Gay's version 4 | * See Modified BSD License below: 5 | * 6 | * FIPS 180-2 SHA-224/256/384/512 implementation 7 | * Issue date: 04/30/2005 8 | * http://www.ouah.org/ogay/sha2/ 9 | * 10 | * Copyright (C) 2005, 2007 Olivier Gay 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions 15 | * are met: 16 | * 1. Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * 2. Redistributions in binary form must reproduce the above copyright 19 | * notice, this list of conditions and the following disclaimer in the 20 | * documentation and/or other materials provided with the distribution. 21 | * 3. Neither the name of the project nor the names of its contributors 22 | * may be used to endorse or promote products derived from this software 23 | * without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 26 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 29 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | * SUCH DAMAGE. 36 | */ 37 | #include "sha256.h" 38 | #include 39 | #include 40 | 41 | const unsigned int SHA256::sha256_k[64] = // UL = uint32 42 | {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 43 | 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 44 | 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 45 | 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 46 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 47 | 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 48 | 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 49 | 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 50 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 51 | 0xc67178f2}; 52 | 53 | void SHA256::transform(const unsigned char *message, unsigned int block_nb) { 54 | uint32 w[64]; 55 | uint32 wv[8]; 56 | uint32 t1, t2; 57 | const unsigned char *sub_block; 58 | int i; 59 | int j; 60 | for (i = 0; i < (int)block_nb; i++) { 61 | sub_block = message + (i << 6); 62 | for (j = 0; j < 16; j++) { 63 | SHA2_PACK32(&sub_block[j << 2], &w[j]); 64 | } 65 | for (j = 16; j < 64; j++) { 66 | w[j] = SHA256_F4(w[j - 2]) + w[j - 7] + SHA256_F3(w[j - 15]) + w[j - 16]; 67 | } 68 | for (j = 0; j < 8; j++) { 69 | wv[j] = m_h[j]; 70 | } 71 | for (j = 0; j < 64; j++) { 72 | t1 = wv[7] + SHA256_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; 73 | t2 = SHA256_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]); 74 | wv[7] = wv[6]; 75 | wv[6] = wv[5]; 76 | wv[5] = wv[4]; 77 | wv[4] = wv[3] + t1; 78 | wv[3] = wv[2]; 79 | wv[2] = wv[1]; 80 | wv[1] = wv[0]; 81 | wv[0] = t1 + t2; 82 | } 83 | for (j = 0; j < 8; j++) { 84 | m_h[j] += wv[j]; 85 | } 86 | } 87 | } 88 | 89 | void SHA256::init() { 90 | m_h[0] = 0x6a09e667; 91 | m_h[1] = 0xbb67ae85; 92 | m_h[2] = 0x3c6ef372; 93 | m_h[3] = 0xa54ff53a; 94 | m_h[4] = 0x510e527f; 95 | m_h[5] = 0x9b05688c; 96 | m_h[6] = 0x1f83d9ab; 97 | m_h[7] = 0x5be0cd19; 98 | m_len = 0; 99 | m_tot_len = 0; 100 | } 101 | 102 | void SHA256::update(const unsigned char *message, unsigned int len) { 103 | unsigned int block_nb; 104 | unsigned int new_len, rem_len, tmp_len; 105 | const unsigned char *shifted_message; 106 | tmp_len = SHA224_256_BLOCK_SIZE - m_len; 107 | rem_len = len < tmp_len ? len : tmp_len; 108 | memcpy(&m_block[m_len], message, rem_len); 109 | if (m_len + len < SHA224_256_BLOCK_SIZE) { 110 | m_len += len; 111 | return; 112 | } 113 | new_len = len - rem_len; 114 | block_nb = new_len / SHA224_256_BLOCK_SIZE; 115 | shifted_message = message + rem_len; 116 | transform(m_block, 1); 117 | transform(shifted_message, block_nb); 118 | rem_len = new_len % SHA224_256_BLOCK_SIZE; 119 | memcpy(m_block, &shifted_message[block_nb << 6], rem_len); 120 | m_len = rem_len; 121 | m_tot_len += (block_nb + 1) << 6; 122 | } 123 | 124 | void SHA256::final(unsigned char *digest) { 125 | unsigned int block_nb; 126 | unsigned int pm_len; 127 | unsigned int len_b; 128 | int i; 129 | block_nb = (1 + ((SHA224_256_BLOCK_SIZE - 9) < (m_len % SHA224_256_BLOCK_SIZE))); 130 | len_b = (m_tot_len + m_len) << 3; 131 | pm_len = block_nb << 6; 132 | memset(m_block + m_len, 0, pm_len - m_len); 133 | m_block[m_len] = 0x80; 134 | SHA2_UNPACK32(len_b, m_block + pm_len - 4); 135 | transform(m_block, block_nb); 136 | for (i = 0; i < 8; i++) { 137 | SHA2_UNPACK32(m_h[i], &digest[i << 2]); 138 | } 139 | } 140 | 141 | std::string sha256(std::string input) { 142 | unsigned char digest[SHA256::DIGEST_SIZE]; 143 | memset(digest, 0, SHA256::DIGEST_SIZE); 144 | 145 | SHA256 ctx = SHA256(); 146 | ctx.init(); 147 | ctx.update((unsigned char *)input.c_str(), input.length()); 148 | ctx.final(digest); 149 | 150 | char buf[2 * SHA256::DIGEST_SIZE + 1]; 151 | buf[2 * SHA256::DIGEST_SIZE] = 0; 152 | for (int i = 0; i < SHA256::DIGEST_SIZE; i++) sprintf(buf + i * 2, "%02x", digest[i]); 153 | return std::string(buf); 154 | } -------------------------------------------------------------------------------- /source/sha256.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Updated to C++, zedwood.com 2012 3 | * Based on Olivier Gay's version 4 | * See Modified BSD License below: 5 | * 6 | * FIPS 180-2 SHA-224/256/384/512 implementation 7 | * Issue date: 04/30/2005 8 | * http://www.ouah.org/ogay/sha2/ 9 | * 10 | * Copyright (C) 2005, 2007 Olivier Gay 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions 15 | * are met: 16 | * 1. Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * 2. Redistributions in binary form must reproduce the above copyright 19 | * notice, this list of conditions and the following disclaimer in the 20 | * documentation and/or other materials provided with the distribution. 21 | * 3. Neither the name of the project nor the names of its contributors 22 | * may be used to endorse or promote products derived from this software 23 | * without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 26 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 29 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | * SUCH DAMAGE. 36 | */ 37 | #ifndef SHA256_H 38 | #define SHA256_H 39 | #include 40 | 41 | class SHA256 { 42 | protected: 43 | typedef unsigned char uint8; 44 | typedef unsigned int uint32; 45 | typedef unsigned long long uint64; 46 | 47 | const static uint32 sha256_k[]; 48 | static const unsigned int SHA224_256_BLOCK_SIZE = (512 / 8); 49 | 50 | public: 51 | void init(); 52 | void update(const unsigned char *message, unsigned int len); 53 | void final(unsigned char *digest); 54 | static const unsigned int DIGEST_SIZE = (256 / 8); 55 | 56 | protected: 57 | void transform(const unsigned char *message, unsigned int block_nb); 58 | unsigned int m_tot_len; 59 | unsigned int m_len; 60 | unsigned char m_block[2 * SHA224_256_BLOCK_SIZE]; 61 | uint32 m_h[8]; 62 | }; 63 | 64 | std::string sha256(std::string input); 65 | 66 | #define SHA2_SHFR(x, n) (x >> n) 67 | #define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) 68 | #define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) 69 | #define SHA2_CH(x, y, z) ((x & y) ^ (~x & z)) 70 | #define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) 71 | #define SHA256_F1(x) (SHA2_ROTR(x, 2) ^ SHA2_ROTR(x, 13) ^ SHA2_ROTR(x, 22)) 72 | #define SHA256_F2(x) (SHA2_ROTR(x, 6) ^ SHA2_ROTR(x, 11) ^ SHA2_ROTR(x, 25)) 73 | #define SHA256_F3(x) (SHA2_ROTR(x, 7) ^ SHA2_ROTR(x, 18) ^ SHA2_SHFR(x, 3)) 74 | #define SHA256_F4(x) (SHA2_ROTR(x, 17) ^ SHA2_ROTR(x, 19) ^ SHA2_SHFR(x, 10)) 75 | #define SHA2_UNPACK32(x, str) \ 76 | { \ 77 | *((str) + 3) = (uint8)((x)); \ 78 | *((str) + 2) = (uint8)((x) >> 8); \ 79 | *((str) + 1) = (uint8)((x) >> 16); \ 80 | *((str) + 0) = (uint8)((x) >> 24); \ 81 | } 82 | #define SHA2_PACK32(str, x) \ 83 | { \ 84 | *(x) = ((uint32) * ((str) + 3)) | ((uint32) * ((str) + 2) << 8) | \ 85 | ((uint32) * ((str) + 1) << 16) | ((uint32) * ((str) + 0) << 24); \ 86 | } 87 | #endif -------------------------------------------------------------------------------- /tools/generate-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import os 24 | import subprocess 25 | import sys 26 | 27 | 28 | def main(): 29 | output_dir = os.path.join(os.path.join('build', 'output')) 30 | if len(sys.argv) > 1: 31 | output_dir = sys.argv[1] 32 | if not os.path.exists(output_dir): 33 | os.makedirs(output_dir) 34 | print ('Generating nfhttp_generated_header.h in ' + output_dir) 35 | generated_header_filename = os.path.join(output_dir, 'nfhttp_generated_header.h') 36 | generated_header = open(generated_header_filename, 'w') 37 | generated_header.write('// This is a generated header from generate-version.py\n') 38 | cwd = os.getcwd() 39 | print ('PYTHON CWD: ' + cwd) 40 | git_count = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], cwd = cwd, universal_newlines = True) 41 | git_describe = subprocess.check_output(['git', 'describe', '--always'], cwd = cwd, universal_newlines = True) 42 | generated_header.write('#define NFHTTP_VERSION "' + git_count.strip() + '-' + git_describe.strip() + '"\n') 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | --------------------------------------------------------------------------------