├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── THIRD_PARTY_LICENSES.txt ├── examples ├── hello │ ├── Dockerfile │ ├── README.md │ ├── func.go │ ├── func.yaml │ └── go.mod └── tracing │ ├── func.go │ ├── func.yaml │ └── go.mod ├── fdk.go ├── fdk_example_test.go ├── fdk_test.go ├── go.mod ├── handler.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | .DS_Store 3 | fdk-go.iml 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Please refer to main contribution doc: 2 | 3 | https://github.com/fnproject/fn/blob/master/CONTRIBUTING.md 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Fn Project 2 | ============ 3 | 4 | Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | ========================================================================== 19 | Third Party Dependencies 20 | ========================================================================== 21 | 22 | This project includes or depends on code from third party projects. 23 | Attributions are contained in THIRD_PARTY_LICENSES.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/fnproject/fdk-go?status.svg)](https://godoc.org/github.com/fnproject/fdk-go) 2 | 3 | # Go FDK Documentation 4 | This is documentation for the Go function development kit (FDK) which provides convenience functions for writing Go Fn code. 5 | 6 | For getting started using the Go FDK , see the tutorial [here.](https://github.com/fnproject/tutorials/tree/master/Introduction) 7 | 8 | To get the Go FDK simply import it in your program `github.com/fnproject/fdk-go`, and use `go get` to get it. 9 | 10 | ## User Information 11 | * See the Fn [Quickstart](https://github.com/fnproject/fn/blob/master/README.md) for sample commands. 12 | * [Detailed installation instructions](http://fnproject.io/tutorials/install/). 13 | * [Configure your CLI Context](http://fnproject.io/tutorials/install/#ConfigureyourContext). 14 | * For a list of commands see [Fn CLI Command Guide and Reference](https://github.com/fnproject/docs/blob/master/cli/README.md). 15 | * For general information see Fn [docs](https://github.com/fnproject/docs) and [tutorials](https://fnproject.io/tutorials/). 16 | 17 | ## Go FDK Development 18 | See [CONTRIBUTING](https://github.com/fnproject/fn/blob/master/CONTRIBUTING.md) for information on contributing to the project. 19 | 20 | 21 | -------------------------------------------------------------------------------- /THIRD_PARTY_LICENSES.txt: -------------------------------------------------------------------------------- 1 | Third Party Attributions 2 | 3 | The following software (or subsets of the software) are dependencies 4 | of this product. They are identified by the Fn Project Go FDK module(s) that use 5 | them. 6 | 7 | The first section ("Third Party Runtime Dependencies") contains dependencies 8 | that might be used at runtime by an Fn Project Go FDK application. 9 | 10 | The second section ("Third Party Attributions for Examples, Tests, Builds, etc") 11 | contains dependencies that are used in examples and to test and build Fn Project Go FDK. 12 | They are likely not needed at runtime by an Fn Project Go FDK application. 13 | 14 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 15 | Third Party Runtime Dependencies 16 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 17 | 18 | fdk-go is licensed under the Apache License, Version 2.0. 19 | License: A copy of the Apache License V2 is included at the end of this file. 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | Packages imported from the Go Standard Library: 24 | 25 | bytes 26 | context 27 | fmt 28 | io 29 | io/ioutil 30 | log 31 | net 32 | net/http 33 | net/url 34 | os 35 | path/filepath 36 | runtime 37 | strconv 38 | strings 39 | sync 40 | time 41 | 42 | Copyright (c) 2009 The Go Authors. All rights reserved. 43 | 44 | Redistribution and use in source and binary forms, with or without 45 | modification, are permitted provided that the following conditions are 46 | met: 47 | 48 | * Redistributions of source code must retain the above copyright 49 | notice, this list of conditions and the following disclaimer. 50 | * Redistributions in binary form must reproduce the above 51 | copyright notice, this list of conditions and the following disclaimer 52 | in the documentation and/or other materials provided with the 53 | distribution. 54 | * Neither the name of Google Inc. nor the names of its 55 | contributors may be used to endorse or promote products derived from 56 | this software without specific prior written permission. 57 | 58 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 59 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 60 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 61 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 62 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 63 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 64 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 65 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 66 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 67 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 68 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 69 | 70 | License: BSD 3-Clause License 71 | 72 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 73 | Third Party Attributions for Examples, Tests, Builds, etc 74 | 75 | The following software (or subsets of the software) is used when building the 76 | Fn Project Go FDK, or in the examples and tests. They are generally not required by 77 | users of the Fn Project Go FDK and not required during runtime. 78 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 79 | 80 | Packages imported from the Go Standard Library: 81 | 82 | context 83 | encoding/json 84 | io 85 | io/ioutil 86 | net 87 | net/http 88 | net/http/httptest 89 | os 90 | path/filepath 91 | strings 92 | testing 93 | time 94 | 95 | Copyright (c) 2009 The Go Authors. All rights reserved. 96 | 97 | Redistribution and use in source and binary forms, with or without 98 | modification, are permitted provided that the following conditions are 99 | met: 100 | 101 | * Redistributions of source code must retain the above copyright 102 | notice, this list of conditions and the following disclaimer. 103 | * Redistributions in binary form must reproduce the above 104 | copyright notice, this list of conditions and the following disclaimer 105 | in the documentation and/or other materials provided with the 106 | distribution. 107 | * Neither the name of Google Inc. nor the names of its 108 | contributors may be used to endorse or promote products derived from 109 | this software without specific prior written permission. 110 | 111 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 112 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 113 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 114 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 115 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 116 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 117 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 118 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 119 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 120 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 121 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 122 | 123 | License: BSD 3-Clause License 124 | ================================================================================ 125 | Apache License 126 | Version 2.0, January 2004 127 | http://www.apache.org/licenses/ 128 | 129 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 130 | 131 | 1. Definitions. 132 | 133 | "License" shall mean the terms and conditions for use, reproduction, 134 | and distribution as defined by Sections 1 through 9 of this document. 135 | 136 | "Licensor" shall mean the copyright owner or entity authorized by 137 | the copyright owner that is granting the License. 138 | 139 | "Legal Entity" shall mean the union of the acting entity and all 140 | other entities that control, are controlled by, or are under common 141 | control with that entity. For the purposes of this definition, 142 | "control" means (i) the power, direct or indirect, to cause the 143 | direction or management of such entity, whether by contract or 144 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 145 | outstanding shares, or (iii) beneficial ownership of such entity. 146 | 147 | "You" (or "Your") shall mean an individual or Legal Entity 148 | exercising permissions granted by this License. 149 | 150 | "Source" form shall mean the preferred form for making modifications, 151 | including but not limited to software source code, documentation 152 | source, and configuration files. 153 | 154 | "Object" form shall mean any form resulting from mechanical 155 | transformation or translation of a Source form, including but 156 | not limited to compiled object code, generated documentation, 157 | and conversions to other media types. 158 | 159 | "Work" shall mean the work of authorship, whether in Source or 160 | Object form, made available under the License, as indicated by a 161 | copyright notice that is included in or attached to the work 162 | (an example is provided in the Appendix below). 163 | 164 | "Derivative Works" shall mean any work, whether in Source or Object 165 | form, that is based on (or derived from) the Work and for which the 166 | editorial revisions, annotations, elaborations, or other modifications 167 | represent, as a whole, an original work of authorship. For the purposes 168 | of this License, Derivative Works shall not include works that remain 169 | separable from, or merely link (or bind by name) to the interfaces of, 170 | the Work and Derivative Works thereof. 171 | 172 | "Contribution" shall mean any work of authorship, including 173 | the original version of the Work and any modifications or additions 174 | to that Work or Derivative Works thereof, that is intentionally 175 | submitted to Licensor for inclusion in the Work by the copyright owner 176 | or by an individual or Legal Entity authorized to submit on behalf of 177 | the copyright owner. For the purposes of this definition, "submitted" 178 | means any form of electronic, verbal, or written communication sent 179 | to the Licensor or its representatives, including but not limited to 180 | communication on electronic mailing lists, source code control systems, 181 | and issue tracking systems that are managed by, or on behalf of, the 182 | Licensor for the purpose of discussing and improving the Work, but 183 | excluding communication that is conspicuously marked or otherwise 184 | designated in writing by the copyright owner as "Not a Contribution." 185 | 186 | "Contributor" shall mean Licensor and any individual or Legal Entity 187 | on behalf of whom a Contribution has been received by Licensor and 188 | subsequently incorporated within the Work. 189 | 190 | 2. Grant of Copyright License. Subject to the terms and conditions of 191 | this License, each Contributor hereby grants to You a perpetual, 192 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 193 | copyright license to reproduce, prepare Derivative Works of, 194 | publicly display, publicly perform, sublicense, and distribute the 195 | Work and such Derivative Works in Source or Object form. 196 | 197 | 3. Grant of Patent License. Subject to the terms and conditions of 198 | this License, each Contributor hereby grants to You a perpetual, 199 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 200 | (except as stated in this section) patent license to make, have made, 201 | use, offer to sell, sell, import, and otherwise transfer the Work, 202 | where such license applies only to those patent claims licensable 203 | by such Contributor that are necessarily infringed by their 204 | Contribution(s) alone or by combination of their Contribution(s) 205 | with the Work to which such Contribution(s) was submitted. If You 206 | institute patent litigation against any entity (including a 207 | cross-claim or counterclaim in a lawsuit) alleging that the Work 208 | or a Contribution incorporated within the Work constitutes direct 209 | or contributory patent infringement, then any patent licenses 210 | granted to You under this License for that Work shall terminate 211 | as of the date such litigation is filed. 212 | 213 | 4. Redistribution. You may reproduce and distribute copies of the 214 | Work or Derivative Works thereof in any medium, with or without 215 | modifications, and in Source or Object form, provided that You 216 | meet the following conditions: 217 | 218 | (a) You must give any other recipients of the Work or 219 | Derivative Works a copy of this License; and 220 | 221 | (b) You must cause any modified files to carry prominent notices 222 | stating that You changed the files; and 223 | 224 | (c) You must retain, in the Source form of any Derivative Works 225 | that You distribute, all copyright, patent, trademark, and 226 | attribution notices from the Source form of the Work, 227 | excluding those notices that do not pertain to any part of 228 | the Derivative Works; and 229 | 230 | (d) If the Work includes a "NOTICE" text file as part of its 231 | distribution, then any Derivative Works that You distribute must 232 | include a readable copy of the attribution notices contained 233 | within such NOTICE file, excluding those notices that do not 234 | pertain to any part of the Derivative Works, in at least one 235 | of the following places: within a NOTICE text file distributed 236 | as part of the Derivative Works; within the Source form or 237 | documentation, if provided along with the Derivative Works; or, 238 | within a display generated by the Derivative Works, if and 239 | wherever such third-party notices normally appear. The contents 240 | of the NOTICE file are for informational purposes only and 241 | do not modify the License. You may add Your own attribution 242 | notices within Derivative Works that You distribute, alongside 243 | or as an addendum to the NOTICE text from the Work, provided 244 | that such additional attribution notices cannot be construed 245 | as modifying the License. 246 | 247 | You may add Your own copyright statement to Your modifications and 248 | may provide additional or different license terms and conditions 249 | for use, reproduction, or distribution of Your modifications, or 250 | for any such Derivative Works as a whole, provided Your use, 251 | reproduction, and distribution of the Work otherwise complies with 252 | the conditions stated in this License. 253 | 254 | 5. Submission of Contributions. Unless You explicitly state otherwise, 255 | any Contribution intentionally submitted for inclusion in the Work 256 | by You to the Licensor shall be under the terms and conditions of 257 | this License, without any additional terms or conditions. 258 | Notwithstanding the above, nothing herein shall supersede or modify 259 | the terms of any separate license agreement you may have executed 260 | with Licensor regarding such Contributions. 261 | 262 | 6. Trademarks. This License does not grant permission to use the trade 263 | names, trademarks, service marks, or product names of the Licensor, 264 | except as required for reasonable and customary use in describing the 265 | origin of the Work and reproducing the content of the NOTICE file. 266 | 267 | 7. Disclaimer of Warranty. Unless required by applicable law or 268 | agreed to in writing, Licensor provides the Work (and each 269 | Contributor provides its Contributions) on an "AS IS" BASIS, 270 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 271 | implied, including, without limitation, any warranties or conditions 272 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 273 | PARTICULAR PURPOSE. You are solely responsible for determining the 274 | appropriateness of using or redistributing the Work and assume any 275 | risks associated with Your exercise of permissions under this License. 276 | 277 | 8. Limitation of Liability. In no event and under no legal theory, 278 | whether in tort (including negligence), contract, or otherwise, 279 | unless required by applicable law (such as deliberate and grossly 280 | negligent acts) or agreed to in writing, shall any Contributor be 281 | liable to You for damages, including any direct, indirect, special, 282 | incidental, or consequential damages of any character arising as a 283 | result of this License or out of the use or inability to use the 284 | Work (including but not limited to damages for loss of goodwill, 285 | work stoppage, computer failure or malfunction, or any and all 286 | other commercial damages or losses), even if such Contributor 287 | has been advised of the possibility of such damages. 288 | 289 | 9. Accepting Warranty or Additional Liability. While redistributing 290 | the Work or Derivative Works thereof, You may choose to offer, 291 | and charge a fee for, acceptance of support, warranty, indemnity, 292 | or other liability obligations and/or rights consistent with this 293 | License. However, in accepting such obligations, You may act only 294 | on Your own behalf and on Your sole responsibility, not on behalf 295 | of any other Contributor, and only if You agree to indemnify, 296 | defend, and hold each Contributor harmless for any liability 297 | incurred by, or claims asserted against, such Contributor by reason 298 | of your accepting any such warranty or additional liability. 299 | 300 | END OF TERMS AND CONDITIONS 301 | 302 | APPENDIX: How to apply the Apache License to your work. 303 | 304 | To apply the Apache License to your work, attach the following 305 | boilerplate notice, with the fields enclosed by brackets "[]" 306 | replaced with your own identifying information. (Don't include 307 | the brackets!) The text should be enclosed in the appropriate 308 | comment syntax for the file format. We also recommend that a 309 | file or class name and description of purpose be included on the 310 | same "printed page" as the copyright notice for easier 311 | identification within third-party archives. 312 | 313 | Copyright {yyyy} {name of copyright owner} 314 | 315 | Licensed under the Apache License, Version 2.0 (the "License"); 316 | you may not use this file except in compliance with the License. 317 | You may obtain a copy of the License at 318 | 319 | http://www.apache.org/licenses/LICENSE-2.0 320 | 321 | Unless required by applicable law or agreed to in writing, software 322 | distributed under the License is distributed on an "AS IS" BASIS, 323 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 324 | See the License for the specific language governing permissions and 325 | limitations under the License. 326 | 327 | 328 | -------------------------------------------------------------------------------- /examples/hello/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | FROM fnproject/go:dev as build-stage 18 | ADD . /go/src/func/ 19 | ENV GO111MODULE=on 20 | RUN cd /go/src/func/ && go mod vendor && go build -o func 21 | FROM fnproject/go 22 | WORKDIR /function 23 | COPY --from=build-stage /go/src/func/func /function/ 24 | ENTRYPOINT ["./func"] 25 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | # Function Examples 2 | 3 | The goal of the `fdk`'s are to make functions easy to write. 4 | 5 | This is an example of a function using the fdk-go bindings. The [function 6 | documentation](https://github.com/fnproject/fn/blob/master/docs/developers/fn-format.md) 7 | contains details of how this example works under the hood. With any of the 8 | examples provided here, you may use any format to configure your functions in 9 | `fn` itself. 10 | 11 | ### How to run the example 12 | 13 | Install the CLI tool, start a Fn server and run `docker login` to login to 14 | DockerHub. See the [front page](https://github.com/fnproject/fn) for 15 | instructions. 16 | 17 | Initialize the example with an image name you can access: 18 | 19 | ```sh 20 | fn init --runtime docker --name hello 21 | ``` 22 | 23 | Build and deploy the function to the Fn server (default localhost:8080) 24 | 25 | ```sh 26 | fn deploy --app myapp 27 | ``` 28 | 29 | Now call your function (may take a sec to pull image): 30 | 31 | ```sh 32 | echo '{"name":"Clarice"}' | fn invoke myapp hello 33 | ``` 34 | 35 | **Note** that this expects you were in a directory named 'hello' (where this 36 | example lives), if this doesn't work, replace 'hello' with your `$PWD` from 37 | the `deploy` command. 38 | 39 | ### Details 40 | 41 | If you poke around in the Dockerfile you'll see that we're simply adding the 42 | file found in this directory, getting the `fdk-go` package with `go mod` 43 | and then building a binary and building an image with that binary. That then 44 | gets deployed to dockerhub and fn. 45 | 46 | Scoping out `func.go` you can see that the handler code only deals with input 47 | and output, and doesn't have to deal with decoding the formatting from 48 | functions (i.e. i/o is presented through `io.Writer` and `io.Reader`). This 49 | makes it much easier to write functions. 50 | -------------------------------------------------------------------------------- /examples/hello/func.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | 25 | fdk "github.com/fnproject/fdk-go" 26 | ) 27 | 28 | func main() { 29 | fdk.Handle(fdk.HandlerFunc(myHandler)) 30 | } 31 | 32 | type Person struct { 33 | Name string `json:"name"` 34 | } 35 | 36 | func myHandler(ctx context.Context, in io.Reader, out io.Writer) { 37 | p := &Person{Name: "World"} 38 | json.NewDecoder(in).Decode(p) 39 | msg := struct { 40 | Msg string `json:"message"` 41 | }{ 42 | Msg: fmt.Sprintf("Hello %s", p.Name), 43 | } 44 | json.NewEncoder(out).Encode(&msg) 45 | } 46 | -------------------------------------------------------------------------------- /examples/hello/func.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | schema_version: 20180708 18 | name: hello 19 | version: 0.0.1 20 | runtime: docker 21 | entrypoint: ./func 22 | -------------------------------------------------------------------------------- /examples/hello/go.mod: -------------------------------------------------------------------------------- 1 | module hello 2 | 3 | require github.com/fnproject/fdk-go master 4 | -------------------------------------------------------------------------------- /examples/tracing/func.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log" 9 | "strconv" 10 | "time" 11 | 12 | fdk "github.com/fnproject/fdk-go" 13 | zipkin "github.com/openzipkin/zipkin-go" 14 | "github.com/openzipkin/zipkin-go/model" 15 | zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http" 16 | ) 17 | 18 | func main() { 19 | fdk.Handle(fdk.HandlerFunc(myHandler)) 20 | } 21 | 22 | type Person struct { 23 | Name string `json:"name"` 24 | } 25 | 26 | func myHandler(ctx context.Context, in io.Reader, out io.Writer) { 27 | p := &Person{Name: "World"} 28 | json.NewDecoder(in).Decode(p) 29 | newCtx := fdk.GetContext(ctx) 30 | 31 | // Span main method 32 | // set up a span reporter 33 | reporter := zipkinHttpReporter.NewReporter(newCtx.TracingContextData().TraceCollectorURL()) 34 | defer reporter.Close() 35 | 36 | // create our local service endpoint 37 | endpoint, err := zipkin.NewEndpoint(newCtx.ServiceName(), "") 38 | if err != nil { 39 | log.Fatalf("unable to create local endpoint: %+v\n", err) 40 | } 41 | 42 | // initialize our tracer 43 | tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint)) 44 | 45 | // Set Context 46 | sctx := setContext(newCtx) 47 | sopt := zipkin.Parent(sctx) 48 | 49 | span, ctx := tracer.StartSpanFromContext(context.Background(), "Main Method", sopt) 50 | 51 | time.Sleep(15 * time.Millisecond) 52 | if err != nil { 53 | log.Fatalf("unable to create tracer: %+v\n", err) 54 | } 55 | 56 | oneMethod(ctx, tracer) 57 | 58 | msg := struct { 59 | Msg string `json:"message"` 60 | }{ 61 | Msg: fmt.Sprintf("Hello %s :: and Service :: %s", newCtx.AppID(), newCtx.AppName()), 62 | } 63 | log.Print("Inside Go Hello World function") 64 | json.NewEncoder(out).Encode(&msg) 65 | span.Finish() 66 | } 67 | 68 | func setContext(ctx fdk.Context) model.SpanContext { 69 | traceId, err := model.TraceIDFromHex(ctx.TracingContextData().TraceId()) 70 | 71 | if err != nil { 72 | log.Println("TRACE ID NOT DEFINED.....") 73 | return model.SpanContext{} 74 | } 75 | 76 | id, err := strconv.ParseUint(ctx.TracingContextData().SpanId(), 16, 64) 77 | if err != nil { 78 | log.Println("SPAN ID NOT DEFINED.....") 79 | return model.SpanContext{} 80 | } 81 | i := model.ID(id) 82 | 83 | sctx := model.SpanContext{ 84 | TraceID: traceId, 85 | ID: i, 86 | ParentID: nil, 87 | Sampled: BoolAddr(ctx.TracingContextData().IsSampled()), 88 | } 89 | 90 | return sctx 91 | } 92 | 93 | func BoolAddr(b bool) *bool { 94 | boolVar := b 95 | return &boolVar 96 | } 97 | 98 | func oneMethod(ctx context.Context, tracer *zipkin.Tracer) { 99 | span, newCtx := tracer.StartSpanFromContext(ctx, "OneChildSpan") 100 | time.Sleep(80 * time.Millisecond) 101 | ctx = newCtx 102 | secMethod(ctx, tracer) 103 | span.Finish() 104 | } 105 | 106 | func secMethod(ctx context.Context, tracer *zipkin.Tracer) { 107 | span, newCtx := tracer.StartSpanFromContext(ctx, "SecChildSpan") 108 | time.Sleep(100 * time.Millisecond) 109 | ctx = newCtx 110 | span.Finish() 111 | } 112 | -------------------------------------------------------------------------------- /examples/tracing/func.yaml: -------------------------------------------------------------------------------- 1 | schema_version: 20180708 2 | name: gofn 3 | version: 0.0.23 4 | runtime: go 5 | entrypoint: ./func 6 | -------------------------------------------------------------------------------- /examples/tracing/go.mod: -------------------------------------------------------------------------------- 1 | module func 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/fnproject/fdk-go master 7 | github.com/openzipkin/zipkin-go v0.2.5 8 | ) 9 | -------------------------------------------------------------------------------- /fdk.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fdk 18 | 19 | import ( 20 | "context" 21 | "io" 22 | "log" 23 | "net/http" 24 | "os" 25 | "time" 26 | ) 27 | 28 | // Handler is a function handler, representing 1 invocation of a function 29 | type Handler interface { 30 | // Serve contains a context with request configuration, the body of the 31 | // request as a stream of bytes, and a writer to output to; user's may set 32 | // headers via the resp writer using the fdk's SetHeader/AddHeader methods - 33 | // if you've a better idea, pipe up. 34 | Serve(ctx context.Context, body io.Reader, resp io.Writer) 35 | } 36 | 37 | // HandlerFunc makes a Handler so that you don't have to! 38 | type HandlerFunc func(ctx context.Context, in io.Reader, out io.Writer) 39 | 40 | // Serve implements Handler 41 | func (f HandlerFunc) Serve(ctx context.Context, in io.Reader, out io.Writer) { 42 | f(ctx, in, out) 43 | } 44 | 45 | // HTTPHandler makes a Handler from an http.Handler, if the function invocation 46 | // is from an http trigger the request is identical to the client request to the 47 | // http gateway (sans some hop headers). 48 | func HTTPHandler(h http.Handler) Handler { 49 | return &httpHandlerFunc{h} 50 | } 51 | 52 | type httpHandlerFunc struct { 53 | http.Handler 54 | } 55 | 56 | // Serve implements Handler 57 | func (f *httpHandlerFunc) Serve(ctx context.Context, in io.Reader, out io.Writer) { 58 | reqURL := "http://localhost/invoke" 59 | reqMethod := "POST" 60 | if ctx, ok := GetContext(ctx).(HTTPContext); ok { 61 | reqURL = ctx.RequestURL() 62 | reqMethod = ctx.RequestMethod() 63 | } 64 | 65 | req, err := http.NewRequest(reqMethod, reqURL, in) 66 | if err != nil { 67 | panic("cannot re-create request from context") 68 | } 69 | 70 | req.Header = GetContext(ctx).Header() 71 | req = req.WithContext(ctx) 72 | 73 | rw, ok := out.(http.ResponseWriter) 74 | if !ok { 75 | panic("output is not a response writer, this was poorly planned please yell at me") 76 | } 77 | 78 | f.ServeHTTP(rw, req) 79 | } 80 | 81 | // GetContext will return an fdk Context that can be used to read configuration and 82 | // request information from an incoming request. 83 | func GetContext(ctx context.Context) Context { 84 | return ctx.Value(ctxKey).(Context) 85 | } 86 | 87 | // WithContext adds an fn context to a context context. It is unclear why this is 88 | // an exported method but hey here ya go don't hurt yourself. 89 | func WithContext(ctx context.Context, fnctx Context) context.Context { 90 | return context.WithValue(ctx, ctxKey, fnctx) 91 | } 92 | 93 | type key struct{} 94 | 95 | var ctxKey = new(key) 96 | 97 | // Context contains all configuration for a function invocation 98 | type Context interface { 99 | // Config is a map of all env vars set on a function, the base set of fn 100 | // headers in addition to any app and function configuration 101 | Config() map[string]string 102 | 103 | // Header are the headers sent to this function invocation 104 | Header() http.Header 105 | 106 | // ContentType is Header().Get("Content-Type") but with 15 less chars, you are welcome 107 | ContentType() string 108 | 109 | // CallID is the call id for this function invocation 110 | CallID() string 111 | 112 | // AppName is Config()["FN_APP_ID"] 113 | AppID() string 114 | 115 | // FnID is Config()["FN_FN_ID"] 116 | FnID() string 117 | 118 | // AppName is Config()["FN_APP_ID"] 119 | AppName() string 120 | 121 | // FnName is Config()["FN_FN_Name"] 122 | FnName() string 123 | 124 | // Tracing Context Data if available 125 | TracingContextData() TracingContext 126 | } 127 | 128 | // HTTPContext contains all configuration for a function invocation sourced 129 | // from an http gateway trigger, which will make the function appear to receive 130 | // from the client request they were sourced from, with no additional headers. 131 | type HTTPContext interface { 132 | Context 133 | 134 | // RequestURL is the request url from the gateway client http request 135 | RequestURL() string 136 | 137 | // RequestMethod is the request method from the gateway client http request 138 | RequestMethod() string 139 | } 140 | 141 | // TracingContext contains all configuration for a function invocated to 142 | // get the tracing context data. 143 | type TracingContext interface { 144 | /** 145 | * Returns true if tracing is enabled for this function invocation 146 | * @return whether tracing is enabled 147 | */ 148 | IsTracingEnabled() bool 149 | 150 | /** 151 | * Returns the URL to be used in tracing libraries as the destination for 152 | * the tracing data 153 | * @return a string containing the trace collector URL 154 | */ 155 | TraceCollectorURL() string 156 | 157 | /** 158 | * Returns the current trace ID as extracted from Zipkin B3 headers if they 159 | * are present on the request 160 | * @return the trace ID as a string 161 | */ 162 | TraceId() string 163 | 164 | /** 165 | * Returns the current span ID as extracted from Zipkin B3 headers if they 166 | * are present on the request 167 | * @return the span ID as a string 168 | */ 169 | SpanId() string 170 | 171 | /** 172 | * Returns the parent span ID as extracted from Zipkin B3 headers if they 173 | * are present on the request 174 | * @return the parent span ID as a string 175 | */ 176 | ParentSpanId() string 177 | 178 | /** 179 | * Returns the value of the Sampled header of the Zipkin B3 headers if they 180 | * are present on the request 181 | * @return true if sampling is enabled for the request 182 | */ 183 | IsSampled() bool 184 | 185 | /** 186 | * Returns the value of the Flags header of the Zipkin B3 headers if they 187 | * are present on the request 188 | * @return the verbatim value of the X-B3-Flags header 189 | */ 190 | Flags() string 191 | 192 | // ServiceName is Config()["FN_APP_ID"] + Config()["FN_FN_Name"] 193 | ServiceName() string 194 | } 195 | 196 | type baseCtx struct { 197 | header http.Header 198 | config map[string]string 199 | callID string 200 | tracingContext tracingCtx 201 | } 202 | 203 | type httpCtx struct { 204 | // XXX(reed): if we embed we won't preserve the original headers. since we have an 205 | // interface handy now we could change this under the covers when/if we want... idk 206 | baseCtx 207 | requestURL string 208 | requestMethod string 209 | } 210 | 211 | type tracingCtx struct { 212 | traceCollectorURL string 213 | traceId string 214 | spanId string 215 | parentSpanId string 216 | sampled bool 217 | flags string 218 | tracingEnabled bool 219 | serviceName string 220 | } 221 | 222 | func (c baseCtx) Config() map[string]string { return c.config } 223 | func (c baseCtx) Header() http.Header { return c.header } 224 | func (c baseCtx) ContentType() string { return c.header.Get("Content-Type") } 225 | func (c baseCtx) CallID() string { return c.callID } 226 | func (c baseCtx) AppID() string { return c.config["FN_APP_ID"] } 227 | func (c baseCtx) FnID() string { return c.config["FN_FN_ID"] } 228 | func (c baseCtx) TracingContextData() TracingContext { 229 | return c.tracingContext 230 | } 231 | 232 | func (c httpCtx) RequestURL() string { return c.requestURL } 233 | func (c httpCtx) RequestMethod() string { return c.requestMethod } 234 | 235 | func (c baseCtx) AppName() string { return c.config["FN_APP_NAME"] } 236 | func (c baseCtx) FnName() string { return c.config["FN_FN_NAME"] } 237 | func (c tracingCtx) ServiceName() string { 238 | return c.serviceName 239 | } 240 | func (c tracingCtx) IsTracingEnabled() bool { return c.tracingEnabled } 241 | func (c tracingCtx) TraceCollectorURL() string { return c.traceCollectorURL } 242 | func (c tracingCtx) TraceId() string { return c.traceId } 243 | func (c tracingCtx) SpanId() string { return c.spanId } 244 | func (c tracingCtx) ParentSpanId() string { return c.parentSpanId } 245 | func (c tracingCtx) IsSampled() bool { return c.sampled } 246 | func (c tracingCtx) Flags() string { return c.flags } 247 | 248 | func ctxWithDeadline(ctx context.Context, fnDeadline string) (context.Context, context.CancelFunc) { 249 | t, err := time.Parse(time.RFC3339, fnDeadline) 250 | if err == nil { 251 | return context.WithDeadline(ctx, t) 252 | } 253 | return context.WithCancel(ctx) 254 | } 255 | 256 | // AddHeader will add a header onto the function response 257 | func AddHeader(out io.Writer, key, value string) { 258 | if resp, ok := out.(http.ResponseWriter); ok { 259 | resp.Header().Add(key, value) 260 | } 261 | } 262 | 263 | // SetHeader will set a header on the function response 264 | func SetHeader(out io.Writer, key, value string) { 265 | if resp, ok := out.(http.ResponseWriter); ok { 266 | resp.Header().Set(key, value) 267 | } 268 | } 269 | 270 | // WriteStatus will set the status code to return in the function response 271 | func WriteStatus(out io.Writer, status int) { 272 | if resp, ok := out.(http.ResponseWriter); ok { 273 | resp.WriteHeader(status) 274 | } 275 | } 276 | 277 | // Handle will run the event loop for a function. Handle should be invoked 278 | // through main() in a user's function and can handle communication between the 279 | // function and fn server via any of the supported formats. 280 | func Handle(handler Handler) { 281 | HandleContext(context.Background(), handler) 282 | } 283 | 284 | // HandleContext works the same as Handle, but takes a context that will 285 | // exit the handler loop when canceled/timed out. 286 | func HandleContext(ctx context.Context, handler Handler) { 287 | format, _ := os.LookupEnv("FN_FORMAT") 288 | if format != "" && format != "http-stream" { 289 | log.Fatal("only http-stream format is supported, please set function.format=http-stream against your fn service") 290 | } 291 | path := os.Getenv("FN_LISTENER") 292 | startHTTPServer(ctx, handler, path) 293 | } 294 | -------------------------------------------------------------------------------- /fdk_example_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fdk_test 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "io" 23 | "net/http" 24 | 25 | fdk "github.com/fnproject/fdk-go" 26 | ) 27 | 28 | func main() { 29 | fdk.Handle(fdk.HandlerFunc(myHandler)) 30 | } 31 | 32 | func Example() { println("use main()") } 33 | 34 | // TODO make http.Handler example 35 | 36 | func myHandler(ctx context.Context, in io.Reader, out io.Writer) { 37 | fnctx, ok := fdk.GetContext(ctx).(fdk.HTTPContext) 38 | if !ok { 39 | // optionally, this may be a good idea 40 | fdk.WriteStatus(out, 400) 41 | fdk.SetHeader(out, "Content-Type", "application/json") 42 | io.WriteString(out, `{"error":"function not invoked via http trigger"}`) 43 | return 44 | } 45 | 46 | contentType := fnctx.Header().Get("Content-Type") 47 | if contentType != "application/json" { 48 | // can assert content type for your api this way 49 | fdk.WriteStatus(out, 400) 50 | fdk.SetHeader(out, "Content-Type", "application/json") 51 | io.WriteString(out, `{"error":"invalid content type"}`) 52 | return 53 | } 54 | 55 | if fnctx.RequestMethod() != "PUT" { 56 | // can assert certain request methods for certain endpoints 57 | fdk.WriteStatus(out, 404) 58 | fdk.SetHeader(out, "Content-Type", "application/json") 59 | io.WriteString(out, `{"error":"route not found, method not supported"}`) 60 | return 61 | } 62 | 63 | var person struct { 64 | Name string `json:"name"` 65 | } 66 | json.NewDecoder(in).Decode(&person) 67 | 68 | // this is where you might insert person into a database or do something else 69 | 70 | all := struct { 71 | Name string `json:"name"` 72 | URL string `json:"url"` 73 | Header http.Header `json:"header"` 74 | Config map[string]string `json:"config"` 75 | }{ 76 | Name: person.Name, 77 | URL: fnctx.RequestURL(), 78 | Header: fnctx.Header(), 79 | Config: fnctx.Config(), 80 | } 81 | 82 | // you can write your own headers & status, if you'd like to 83 | fdk.SetHeader(out, "Content-Type", "application/json") 84 | fdk.WriteStatus(out, 201) 85 | json.NewEncoder(out).Encode(&all) 86 | } 87 | -------------------------------------------------------------------------------- /fdk_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fdk 18 | 19 | import ( 20 | "context" 21 | "io" 22 | "io/ioutil" 23 | "net" 24 | "net/http" 25 | "net/http/httptest" 26 | "os" 27 | "path/filepath" 28 | "strconv" 29 | "strings" 30 | "testing" 31 | "time" 32 | ) 33 | 34 | // XXX(reed): test cloudevents in http-stream land 35 | 36 | // echoHandler echos the body and all headers back 37 | func echoHandler(ctx context.Context, in io.Reader, out io.Writer) { 38 | for k, vs := range GetContext(ctx).Header() { 39 | for _, v := range vs { 40 | AddHeader(out, k, v) 41 | } 42 | } 43 | 44 | // XXX(reed): could configure this to test too 45 | WriteStatus(out, http.StatusTeapot+2) 46 | io.Copy(out, in) 47 | } 48 | 49 | // echoContextHandler echos the tracing context back 50 | func echoContextHandler(ctx context.Context, in io.Reader, out io.Writer) { 51 | nctx := GetContext(ctx) 52 | 53 | if resp, ok := out.(http.ResponseWriter); ok { 54 | resp.Header().Add("traceId", nctx.TracingContextData().TraceId()) 55 | resp.Header().Add("spanId", nctx.TracingContextData().SpanId()) 56 | resp.Header().Add("parentSpanId", nctx.TracingContextData().ParentSpanId()) 57 | resp.Header().Add("sampled", strconv.FormatBool(nctx.TracingContextData().IsSampled())) 58 | resp.Header().Add("ociURL", nctx.TracingContextData().TraceCollectorURL()) 59 | resp.Header().Add("flag", nctx.TracingContextData().Flags()) 60 | resp.Header().Add("ServiceName", nctx.TracingContextData().ServiceName()) 61 | } 62 | // XXX(reed): could configure this to test too 63 | 64 | WriteStatus(out, http.StatusTeapot+2) 65 | io.Copy(out, in) 66 | } 67 | 68 | // echoContextHandler echos the context app data back 69 | func echoContextHandlerAppData(ctx context.Context, in io.Reader, out io.Writer) { 70 | nctx := GetContext(ctx) 71 | 72 | if resp, ok := out.(http.ResponseWriter); ok { 73 | resp.Header().Add("AppId", nctx.AppID()) 74 | resp.Header().Add("AppName", nctx.AppName()) 75 | resp.Header().Add("FnId", nctx.FnID()) 76 | resp.Header().Add("FnName", nctx.FnName()) 77 | resp.Header().Add("CallId", nctx.CallID()) 78 | } 79 | // XXX(reed): could configure this to test too 80 | 81 | WriteStatus(out, http.StatusTeapot+2) 82 | io.Copy(out, in) 83 | } 84 | 85 | func TestHTTPStreamSock(t *testing.T) { 86 | // XXX(reed): move to fdk_linux_test.go with build tag 87 | // XXX(reed): extract the underlying server handler / write tests against it instead of starting uds for other tests 88 | 89 | ctx, cancel := context.WithCancel(context.Background()) 90 | defer cancel() 91 | 92 | tmpSock, err := ioutil.TempDir("/tmp", "fdk-go-test") 93 | if err != nil { 94 | t.Fatal("couldn't make tmpdir for testing") 95 | } 96 | defer os.RemoveAll(tmpSock) 97 | 98 | tmpSock = filepath.Join(tmpSock, "fn.sock") 99 | 100 | go startHTTPServer(ctx, HandlerFunc(echoHandler), "unix:"+tmpSock) 101 | 102 | // let the uds server start... could inotify but don't want the dependency for tests... 103 | time.Sleep(1 * time.Second) 104 | 105 | client := http.Client{ 106 | Transport: &http.Transport{ 107 | MaxIdleConns: 1, 108 | MaxIdleConnsPerHost: 1, 109 | IdleConnTimeout: 1 * time.Second, 110 | DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { 111 | var d net.Dialer 112 | return d.DialContext(ctx, "unix", tmpSock) 113 | }, 114 | }, 115 | } 116 | 117 | // TODO headers? 118 | bodyString := "yodawg" 119 | req, err := http.NewRequest("POST", "http://localhost/call", strings.NewReader(bodyString)) 120 | if err != nil { 121 | t.Fatal("error making req", err) 122 | } 123 | req.Header.Set("yo", "dawg") 124 | 125 | res, err := client.Do(req) 126 | if err != nil { 127 | t.Fatal("error doing uds request", err) 128 | } 129 | defer res.Body.Close() 130 | 131 | if res.StatusCode != http.StatusOK { 132 | t.Fatal("got wrong status code:", res.StatusCode) 133 | } 134 | 135 | if res.Header.Get("Fn-Fdk-Version") != versionHeader { 136 | t.Errorf("Expected \"%s\" but got \"%s\"", versionHeader, res.Header.Get("Fn-Fdk-Version")) 137 | } 138 | 139 | if res.Header.Get("Fn-Fdk-Runtime") != runtimeHeader { 140 | t.Errorf("Expected \"%s\" but got \"%s\"", runtimeHeader, res.Header.Get("Fn-Fdk-Runtime")) 141 | } 142 | 143 | outBody, err := ioutil.ReadAll(res.Body) 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | 148 | if res.Header.Get("yo") != "dawg" { 149 | t.Fatal("expected yo dawg header, didn't get it", res.Header) 150 | } 151 | 152 | if string(outBody) != bodyString { 153 | t.Fatal("body mismatch:", string(outBody), bodyString) 154 | } 155 | } 156 | 157 | func TestHandler(t *testing.T) { 158 | tests := []struct { 159 | name string 160 | inBody string 161 | inHeader http.Header 162 | outBody string 163 | outHeader http.Header 164 | }{ 165 | {"invoke", "yodawg", http.Header{"Yo": {"dawg"}}, "yodawg", http.Header{"Yo": {"dawg"}}}, 166 | {"httpgw", "yodawg", http.Header{"Fn-Intent": {"httprequest"}, "Fn-Http-H-Yo": {"dawg"}}, "yodawg", http.Header{"Fn-Http-H-Yo": {"dawg"}, "Fn-Http-Status": {"420"}}}, 167 | {"httpgw-rm-nongw", "yodawg", http.Header{"Fn-Intent": {"httprequest"}, "Yo": {"dawg"}}, "yodawg", http.Header{"Fn-Http-Status": {"420"}}}, 168 | // TODO(reed): test Fn-Http-Request-Url, Fn-Http-Method, Fn-Call-Id, Fn-Deadline... 169 | } 170 | 171 | // TODO make it so echoHandler takes expected headers to test 172 | handler := &httpHandler{HandlerFunc(echoHandler)} 173 | 174 | for _, test := range tests { 175 | req, err := http.NewRequest("POST", "http://localhost/call", strings.NewReader(test.inBody)) 176 | if err != nil { 177 | t.Fatal("error making req", err) 178 | } 179 | req.Header = test.inHeader 180 | 181 | w := httptest.NewRecorder() 182 | handler.ServeHTTP(w, req) 183 | resp := w.Result() 184 | 185 | if w.Body.String() != test.outBody { 186 | t.Error("body mismatch", test.name, w.Body.String(), test.outBody) 187 | } 188 | 189 | for k := range test.outHeader { 190 | if resp.Header.Get(k) != test.outHeader.Get(k) { 191 | t.Error("header mismatch", test.name, k, resp.Header.Get(k), test.outHeader.Get(k)) 192 | } 193 | } 194 | } 195 | } 196 | 197 | func TestTracingContextInHandlerWithOCITracingDisabled(t *testing.T) { 198 | tests := []struct { 199 | name string 200 | inBody string 201 | inHeader http.Header 202 | outBody string 203 | outHeader http.Header 204 | }{ 205 | {"testinvoke", "yodawg", http.Header{"Fn-Intent": []string{"httprequest"}, "Fn-Call-Id": []string{"dawg"}}, "yodawg", 206 | http.Header{"Fn-Http-Status": {"420"}, "Fn-Http-H-Ociurl": {""}, "Fn-Http-H-Parentspanid": {""}, 207 | "Fn-Http-H-Sampled": {"false"}, "Fn-Http-H-Spanid": {""}, "Fn-Http-H-Traceid": {""}, 208 | "Fn-Http-H-Flag": {""}}}, 209 | } 210 | 211 | os.Setenv("FN_APP_ID", "GOLANGAPP1234323dsw") 212 | os.Setenv("FN_FN_ID", "12342r321422b41") 213 | os.Setenv("FN_APP_NAME", "GOLANGAPP") 214 | os.Setenv("FN_FN_NAME", "FNTESTAPP") 215 | os.Setenv("OCI_TRACING_ENABLED", "0") 216 | 217 | handler := &httpHandler{HandlerFunc(echoContextHandler)} 218 | 219 | for _, test := range tests { 220 | req, err := http.NewRequest("POST", "http://localhost/invoke", strings.NewReader(test.inBody)) 221 | if err != nil { 222 | t.Fatal("error making req", err) 223 | } 224 | req.Header = test.inHeader 225 | req.Header.Set("Fn-Call-Id", "fncall") 226 | 227 | w := httptest.NewRecorder() 228 | handler.ServeHTTP(w, req) 229 | resp := w.Result() 230 | 231 | if w.Body.String() != test.outBody { 232 | t.Error("body mismatch", test.name, w.Body.String(), test.outBody) 233 | } 234 | 235 | for k := range test.outHeader { 236 | if resp.Header.Get(k) != test.outHeader.Get(k) { 237 | t.Error("header mismatch", test.name, k, resp.Header.Get(k), test.outHeader.Get(k)) 238 | } 239 | } 240 | } 241 | } 242 | 243 | func TestTracingContextInHandler(t *testing.T) { 244 | tests := []struct { 245 | name string 246 | inBody string 247 | inHeader http.Header 248 | outBody string 249 | outHeader http.Header 250 | }{ 251 | {"testinvoke", "yodawg", http.Header{"Fn-Intent": []string{"httprequest"}, "Fn-Call-Id": []string{"dawg"}}, "yodawg", 252 | http.Header{"Fn-Http-Status": {"420"}, "Fn-Http-H-Ociurl": {"localhost"}, "Fn-Http-H-Parentspanid": {""}, 253 | "Fn-Http-H-Sampled": {"true"}, "Fn-Http-H-Spanid": {"12345675685432"}, "Fn-Http-H-Traceid": {"12345675685432"}, 254 | "Fn-Http-H-Flag": {"1"}, "Fn-Http-H-Servicename": {"golangapp::fntestapp"}}}, 255 | } 256 | 257 | os.Setenv("FN_APP_ID", "GOLANGAPP1234323dsw") 258 | os.Setenv("FN_FN_ID", "12342r321422b41") 259 | os.Setenv("FN_APP_NAME", "GOLANGAPP") 260 | os.Setenv("FN_FN_NAME", "FNTESTAPP") 261 | os.Setenv("OCI_TRACING_ENABLED", "1") 262 | os.Setenv("OCI_TRACE_COLLECTOR_URL", "localhost") 263 | 264 | handler := &httpHandler{HandlerFunc(echoContextHandler)} 265 | 266 | for _, test := range tests { 267 | req, err := http.NewRequest("POST", "http://localhost/invoke", strings.NewReader(test.inBody)) 268 | if err != nil { 269 | t.Fatal("error making req", err) 270 | } 271 | req.Header = test.inHeader 272 | req.Header.Set("x-b3-traceid", "12345675685432") 273 | req.Header.Set("x-b3-spanid", "12345675685432") 274 | req.Header.Set("x-b3-parentspanid", "") 275 | req.Header.Set("x-b3-flags", "1") 276 | req.Header.Set("x-b3-sampled", "1") 277 | req.Header.Set("Fn-Call-Id", "fncall") 278 | 279 | w := httptest.NewRecorder() 280 | handler.ServeHTTP(w, req) 281 | resp := w.Result() 282 | 283 | if w.Body.String() != test.outBody { 284 | t.Error("body mismatch", test.name, w.Body.String(), test.outBody) 285 | } 286 | 287 | for k := range test.outHeader { 288 | if resp.Header.Get(k) != test.outHeader.Get(k) { 289 | t.Error("header mismatch", test.name, k, resp.Header.Get(k), test.outHeader.Get(k)) 290 | } 291 | } 292 | } 293 | } 294 | 295 | func TestContextInHandlerWithAppFunctionData(t *testing.T) { 296 | tests := []struct { 297 | name string 298 | inBody string 299 | inHeader http.Header 300 | outBody string 301 | outHeader http.Header 302 | }{ 303 | {"testinvoke", "yodawg", http.Header{"Fn-Intent": []string{"httprequest"}, "Fn-Call-Id": []string{"dawg"}}, "yodawg", 304 | http.Header{"Fn-Http-Status": {"420"}, "Fn-Http-H-Appid": {"GOLANGAPP1234323dsw"}, "Fn-Http-H-Appname": {"GOLANGAPP"}, 305 | "Fn-Http-H-Callid": {"fncall"}, "Fn-Http-H-Fnid": {"12342r321422b41"}, "Fn-Http-H-Fnname": {"FNTESTAPP"}}}, 306 | } 307 | 308 | os.Setenv("FN_APP_ID", "GOLANGAPP1234323dsw") 309 | os.Setenv("FN_FN_ID", "12342r321422b41") 310 | os.Setenv("FN_APP_NAME", "GOLANGAPP") 311 | os.Setenv("FN_FN_NAME", "FNTESTAPP") 312 | os.Setenv("OCI_TRACING_ENABLED", "0") 313 | 314 | handler := &httpHandler{HandlerFunc(echoContextHandlerAppData)} 315 | 316 | for _, test := range tests { 317 | req, err := http.NewRequest("POST", "http://localhost/invoke", strings.NewReader(test.inBody)) 318 | if err != nil { 319 | t.Fatal("error making req", err) 320 | } 321 | req.Header = test.inHeader 322 | req.Header.Set("Fn-Call-Id", "fncall") 323 | 324 | w := httptest.NewRecorder() 325 | handler.ServeHTTP(w, req) 326 | resp := w.Result() 327 | 328 | if w.Body.String() != test.outBody { 329 | t.Error("body mismatch", test.name, w.Body.String(), test.outBody) 330 | } 331 | 332 | for k := range test.outHeader { 333 | if resp.Header.Get(k) != test.outHeader.Get(k) { 334 | t.Error("header mismatch", test.name, k, resp.Header.Get(k), test.outHeader.Get(k)) 335 | } 336 | } 337 | } 338 | } 339 | 340 | // NOTE: the below may serve as a reminder that memory allocs suck and you can do better 341 | 342 | const mappers = 10 343 | 344 | func memory1nMap(m map[int]int) map[int]int { 345 | rm := make(map[int]int, len(m)) 346 | for _, i := range m { 347 | rm[mappers-i] = i 348 | } 349 | return rm 350 | } 351 | 352 | func memory2nMap(m map[int]int) map[int]int { 353 | for _, i := range m { 354 | _ = i 355 | continue 356 | } 357 | 358 | for _, i := range m { 359 | m[mappers-i] = i 360 | } 361 | return m 362 | } 363 | 364 | func BenchmarkMapCrap1(b *testing.B) { 365 | m := make(map[int]int) 366 | for i := 0; i < mappers; i++ { 367 | m[i] = i 368 | } 369 | 370 | for i := 0; i < b.N; i++ { 371 | memory1nMap(m) 372 | } 373 | } 374 | 375 | func BenchmarkMapCrap2(b *testing.B) { 376 | m := make(map[int]int) 377 | for i := 0; i < mappers; i++ { 378 | m[i] = i 379 | } 380 | 381 | for i := 0; i < b.N; i++ { 382 | memory2nMap(m) 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fnproject/fdk-go 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fdk 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "fmt" 23 | "io" 24 | "io/ioutil" 25 | "log" 26 | "net" 27 | "net/http" 28 | "net/url" 29 | "os" 30 | "path/filepath" 31 | "runtime" 32 | "strconv" 33 | "strings" 34 | "sync" 35 | ) 36 | 37 | // in case we go over the timeout, need to use a pool since prev buffer may not be freed 38 | var bufPool = &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }} 39 | 40 | type httpHandler struct { 41 | handler Handler 42 | } 43 | 44 | func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 45 | buf := bufPool.Get().(*bytes.Buffer) 46 | defer bufPool.Put(buf) 47 | 48 | resp := response{ 49 | Buffer: buf, 50 | status: 200, 51 | header: w.Header(), 52 | } 53 | 54 | ctx, cancel := buildCtx(r.Context(), r) 55 | defer cancel() 56 | 57 | logFrameHeader(r) 58 | 59 | h.handler.Serve(ctx, r.Body, &resp) 60 | 61 | io.Copy(ioutil.Discard, r.Body) // Ignoring error since r.Body may already be closed 62 | r.Body.Close() 63 | 64 | if _, ok := GetContext(ctx).(HTTPContext); ok { 65 | // XXX(reed): could put this in a response writer to clean up? not as easy as it looks (ordering wrt WriteHeader()) 66 | encapHeaders(w.Header()) 67 | // here we set the code in headers, but don't write it to the client writer 68 | w.Header().Set("Fn-Http-Status", strconv.Itoa(resp.status)) 69 | } 70 | // NOTE: FDKs don't set call status directly on the response at the moment... 71 | 72 | // send back our version 73 | w.Header().Set("Fn-Fdk-Version", versionHeader) 74 | w.Header().Set("Fn-Fdk-Runtime", runtimeHeader) 75 | 76 | // XXX(reed): 504 if ctx is past due / handle errors with 5xx? just 200 for now 77 | // copy response from user back up now with headers in place... 78 | io.Copy(w, buf) 79 | 80 | // XXX(reed): handle streaming, we have to intercept headers but not necessarily body (ie no buffer) 81 | } 82 | 83 | // XXX(reed): we can't use this if we want streaming. just. let. go. 84 | type response struct { 85 | status int 86 | header http.Header 87 | 88 | // use bytes.Buffer for io.ReaderFrom / io.WriterTo / et al optimization helper methods 89 | *bytes.Buffer 90 | } 91 | 92 | var _ http.ResponseWriter = new(response) 93 | 94 | func (r *response) WriteHeader(code int) { r.status = code } 95 | func (r *response) Header() http.Header { return r.header } 96 | 97 | func buildConfig() map[string]string { 98 | cfg := make(map[string]string, 16) 99 | 100 | for _, e := range os.Environ() { 101 | vs := strings.SplitN(e, "=", 2) 102 | if len(vs) < 2 { 103 | vs = append(vs, "") 104 | } 105 | cfg[vs[0]] = vs[1] 106 | } 107 | return cfg 108 | } 109 | 110 | // encapHeaders modifies headers in place per http gateway protocol 111 | func encapHeaders(hdr http.Header) { 112 | for k, vs := range hdr { 113 | if k == "Content-Type" || strings.HasPrefix(k, "Fn-Http-H-") { 114 | continue // we've passed this one 115 | } 116 | 117 | // remove them all to add them all back 118 | hdr.Del(k) 119 | 120 | // prepend this guy, add it back 121 | k = "Fn-Http-H-" + k 122 | hdr[k] = vs 123 | } 124 | } 125 | 126 | func withHTTPContext(ctx context.Context) context.Context { 127 | rctx, ok := GetContext(ctx).(baseCtx) 128 | if !ok { 129 | panic("danger will robinson: only call this method with a base context") 130 | } 131 | 132 | hdr := rctx.Header() 133 | hctx := httpCtx{baseCtx: rctx} 134 | 135 | // remove garbage (non-'Fn-Http-H-') headers and fixed http headers on first 136 | // pass, on 2nd pass we can replace all Fn-Http-H with stripped version and 137 | // skip all we've done. this costs 2n time (2 iterations) to keep memory 138 | // usage flat (in place), we can't in place replace in linear time since go 139 | // map iteration is not 'stable' and we may hit a key twice in 1 iteration 140 | // and don't know if it's garbage or not. benchmarks prove it's worth it for all n. 141 | for k, vs := range hdr { 142 | switch { 143 | case k == "Content-Type" || strings.HasPrefix(k, "Fn-Http-H-"): // don't delete 144 | case k == "Fn-Http-Request-Url": 145 | hctx.requestURL = vs[0] 146 | delete(hdr, k) 147 | case k == "Fn-Http-Method": 148 | hctx.requestMethod = vs[0] 149 | delete(hdr, k) 150 | default: 151 | delete(hdr, k) 152 | } 153 | } 154 | 155 | for k, vs := range hdr { 156 | switch { 157 | case strings.HasPrefix(k, "Fn-Http-H-"): 158 | hdr[strings.TrimPrefix(k, "Fn-Http-H-")] = vs 159 | default: // we've already stripped / Content-Type 160 | } 161 | } 162 | 163 | return WithContext(ctx, hctx) 164 | } 165 | 166 | func setTracingContext(config map[string]string, header http.Header) tracingCtx { 167 | if config["OCI_TRACING_ENABLED"] == "0" { 168 | // When tracing is not enabled then we 169 | // assign empty tracing context to 170 | // the context 171 | return tracingCtx{} 172 | } 173 | tctx := tracingCtx{ 174 | traceCollectorURL: config["OCI_TRACE_COLLECTOR_URL"], 175 | traceId: header.Get("x-b3-traceid"), 176 | spanId: header.Get("x-b3-spanid"), 177 | parentSpanId: header.Get("x-b3-parentspanid"), 178 | flags: header.Get("x-b3-flags"), 179 | sampled: true, 180 | serviceName: strings.ToLower(config["FN_APP_NAME"] + "::" + config["FN_FN_NAME"]), 181 | } 182 | 183 | if header.Get("x-b3-sampled") != "" { 184 | isSampled, err := strconv.ParseBool(header.Get("x-b3-sampled")) 185 | if err == nil { 186 | tctx.sampled = isSampled 187 | } 188 | } 189 | 190 | isEnabled, err := strconv.ParseBool(config["OCI_TRACING_ENABLED"]) 191 | tctx.tracingEnabled = false 192 | if err == nil { 193 | tctx.tracingEnabled = isEnabled 194 | } 195 | 196 | return tctx 197 | } 198 | 199 | func withBaseContext(ctx context.Context, r *http.Request) (_ context.Context, cancel func()) { 200 | configData := buildConfig() // from env vars (stinky, but effective...) 201 | rctx := baseCtx{ 202 | config: configData, 203 | callID: r.Header.Get("Fn-Call-Id"), 204 | header: r.Header, 205 | tracingContext: setTracingContext(configData, r.Header), 206 | } 207 | 208 | ctx = WithContext(ctx, rctx) 209 | deadline := r.Header.Get("Fn-Deadline") 210 | return ctxWithDeadline(ctx, deadline) 211 | } 212 | 213 | func buildCtx(ctx context.Context, r *http.Request) (_ context.Context, cancel func()) { 214 | ctx, cancel = withBaseContext(ctx, r) 215 | 216 | if GetContext(ctx).Header().Get("Fn-Intent") == "httprequest" { 217 | ctx = withHTTPContext(ctx) 218 | } 219 | 220 | return ctx, cancel 221 | } 222 | 223 | func startHTTPServer(ctx context.Context, handler Handler, path string) { 224 | uri, err := url.Parse(path) 225 | if err != nil { 226 | log.Fatalln("url parse error: ", path, err) 227 | } 228 | 229 | if uri.Scheme != "unix" || uri.Path == "" { 230 | log.Fatalln("url scheme must be unix with a valid path, got: ", uri.String()) 231 | } 232 | 233 | server := http.Server{ 234 | Handler: &httpHandler{ 235 | handler: handler, 236 | }, 237 | } 238 | 239 | // try to remove pre-existing UDS: ignore errors here 240 | phonySock := filepath.Join(filepath.Dir(uri.Path), "phony"+filepath.Base(uri.Path)) 241 | if uri.Scheme == "unix" { 242 | os.Remove(phonySock) 243 | os.Remove(uri.Path) 244 | } 245 | 246 | listener, err := net.Listen(uri.Scheme, phonySock) 247 | if err != nil { 248 | log.Fatalln("net.Listen error: ", err) 249 | } 250 | 251 | if uri.Scheme == "unix" { 252 | sockPerm(phonySock, uri.Path) 253 | } 254 | 255 | go func() { 256 | <-ctx.Done() 257 | server.Shutdown(ctx) // this ctx won't wait for listeners, but alas... 258 | // XXX(reed): we're supposed to wait before returning from startHTTPServer... lazy for now 259 | }() 260 | 261 | err = server.Serve(listener) 262 | if err != nil && err != http.ErrServerClosed { 263 | log.Fatalln("serve error: ", err) 264 | } 265 | } 266 | 267 | func sockPerm(phonySock, realSock string) { 268 | runtime.LockOSThread() 269 | defer runtime.UnlockOSThread() 270 | 271 | // somehow this is the best way to get a permissioned sock file, don't ask questions, life is sad and meaningless 272 | err := os.Chmod(phonySock, 0666) 273 | if err != nil { 274 | log.Fatalln("error giving sock file a perm", err) 275 | } 276 | 277 | err = os.Symlink(filepath.Base(phonySock), realSock) 278 | if err != nil { 279 | log.Fatalln("error linking fake sock to real sock", err) 280 | } 281 | } 282 | 283 | // If enabled, print the log framing content. 284 | func logFrameHeader(r *http.Request) { 285 | framer := os.Getenv("FN_LOGFRAME_NAME") 286 | if framer == "" { 287 | return 288 | } 289 | valueSrc := os.Getenv("FN_LOGFRAME_HDR") 290 | if valueSrc == "" { 291 | return 292 | } 293 | id := r.Header.Get(valueSrc) 294 | if id != "" { 295 | fmt.Fprintf(os.Stderr, "\n%s=%s\n", framer, id) 296 | fmt.Fprintf(os.Stdout, "\n%s=%s\n", framer, id) 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fdk 18 | 19 | import ( 20 | "fmt" 21 | "runtime" 22 | "strings" 23 | ) 24 | 25 | // Version is the FDK version 26 | const Version = "0.0.58" 27 | 28 | var versionHeader = fmt.Sprintf("fdk-go/%s", Version) 29 | var runtimeHeader = fmt.Sprintf("go/%s", strings.TrimLeft(runtime.Version(), "go")) 30 | --------------------------------------------------------------------------------