├── .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 |
2 |
3 | [](https://circleci.com/gh/spotify/NFHTTP/tree/master)
4 | [](LICENSE)
5 | [](https://slackin.spotify.com)
6 | [](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