├── .codespellrc ├── .github ├── dependabot.yml └── workflows │ ├── check-arduino.yml │ ├── compile-examples.yml │ ├── report-size-deltas.yml │ ├── spell-check.yml │ └── sync-labels.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── examples ├── BasicAuthGet │ ├── BasicAuthGet.ino │ └── arduino_secrets.h ├── CustomHeader │ ├── CustomHeader.ino │ └── arduino_secrets.h ├── DweetGet │ ├── DweetGet.ino │ └── arduino_secrets.h ├── DweetPost │ ├── DweetPost.ino │ └── arduino_secrets.h ├── HueBlink │ ├── HueBlink.ino │ └── arduino_secrets.h ├── ParseURL │ └── ParseURL.ino ├── PostWithHeaders │ ├── PostWithHeaders.ino │ └── arduino_secrets.h ├── SimpleDelete │ ├── SimpleDelete.ino │ └── arduino_secrets.h ├── SimpleGet │ ├── SimpleGet.ino │ └── arduino_secrets.h ├── SimpleHttpExample │ ├── SimpleHttpExample.ino │ └── arduino_secrets.h ├── SimplePost │ ├── SimplePost.ino │ └── arduino_secrets.h ├── SimplePut │ ├── SimplePut.ino │ └── arduino_secrets.h ├── SimpleWebSocket │ ├── SimpleWebSocket.ino │ └── arduino_secrets.h └── node_test_server │ ├── getPostPutDelete.js │ └── package.json ├── keywords.txt ├── library.properties └── src ├── ArduinoHttpClient.h ├── HttpClient.cpp ├── HttpClient.h ├── URLEncoder.cpp ├── URLEncoder.h ├── URLParser.h ├── WebSocketClient.cpp ├── WebSocketClient.h ├── b64.cpp ├── b64.h └── utility └── URLParser ├── LICENSE ├── README.md ├── http_parser.c └── http_parser.h /.codespellrc: -------------------------------------------------------------------------------- 1 | # See: https://github.com/codespell-project/codespell#using-a-config-file 2 | [codespell] 3 | # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: 4 | ignore-words-list = , 5 | check-filenames = 6 | check-hidden = 7 | skip = ./.git,./src/utility/URLParser 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file 2 | version: 2 3 | 4 | updates: 5 | # Configure check for outdated GitHub Actions actions in workflows. 6 | # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot 7 | - package-ecosystem: github-actions 8 | directory: / # Check the repository's workflows under /.github/workflows/ 9 | schedule: 10 | interval: daily 11 | labels: 12 | - "topic: infrastructure" 13 | -------------------------------------------------------------------------------- /.github/workflows/check-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Check Arduino 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Arduino Lint 22 | uses: arduino/arduino-lint-action@v2 23 | with: 24 | compliance: specification 25 | library-manager: update 26 | # Always use this setting for official repositories. Remove for 3rd party projects. 27 | official: true 28 | project-type: library 29 | -------------------------------------------------------------------------------- /.github/workflows/compile-examples.yml: -------------------------------------------------------------------------------- 1 | name: Compile Examples 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | paths: 7 | - ".github/workflows/compile-examples.yml" 8 | - "examples/**" 9 | - "src/**" 10 | pull_request: 11 | paths: 12 | - ".github/workflows/compile-examples.yml" 13 | - "examples/**" 14 | - "src/**" 15 | schedule: 16 | # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). 17 | - cron: "0 8 * * TUE" 18 | workflow_dispatch: 19 | repository_dispatch: 20 | 21 | jobs: 22 | build: 23 | name: ${{ matrix.board.fqbn }} 24 | runs-on: ubuntu-latest 25 | 26 | env: 27 | SKETCHES_REPORTS_PATH: sketches-reports 28 | 29 | strategy: 30 | fail-fast: false 31 | 32 | matrix: 33 | board: 34 | - fqbn: arduino:samd:mkr1000 35 | platforms: | 36 | - name: arduino:samd 37 | artifact-name-suffix: arduino-samd-mkr1000 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | - name: Compile examples 44 | uses: arduino/compile-sketches@v1 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | fqbn: ${{ matrix.board.fqbn }} 48 | platforms: ${{ matrix.board.platforms }} 49 | libraries: | 50 | # Install the library from the local path. 51 | - source-path: ./ 52 | - name: WiFi101 53 | sketch-paths: | 54 | - examples 55 | enable-deltas-report: true 56 | sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 57 | 58 | - name: Save sketches report as workflow artifact 59 | uses: actions/upload-artifact@v4 60 | with: 61 | if-no-files-found: error 62 | path: ${{ env.SKETCHES_REPORTS_PATH }} 63 | name: sketches-report-${{ matrix.board.artifact-name-suffix }} 64 | -------------------------------------------------------------------------------- /.github/workflows/report-size-deltas.yml: -------------------------------------------------------------------------------- 1 | name: Report Size Deltas 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | paths: 7 | - ".github/workflows/report-size-deltas.yml" 8 | schedule: 9 | # Run at the minimum interval allowed by GitHub Actions. 10 | # Note: GitHub Actions periodically has outages which result in workflow failures. 11 | # In this event, the workflows will start passing again once the service recovers. 12 | - cron: "*/5 * * * *" 13 | workflow_dispatch: 14 | repository_dispatch: 15 | 16 | jobs: 17 | report: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Comment size deltas reports to PRs 21 | uses: arduino/report-size-deltas@v1 22 | with: 23 | # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow 24 | sketches-reports-source: ^sketches-report-.+ 25 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | spellcheck: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Spell check 22 | uses: codespell-project/actions-codespell@master 23 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md 2 | name: Sync Labels 3 | 4 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 5 | on: 6 | push: 7 | paths: 8 | - ".github/workflows/sync-labels.ya?ml" 9 | - ".github/label-configuration-files/*.ya?ml" 10 | pull_request: 11 | paths: 12 | - ".github/workflows/sync-labels.ya?ml" 13 | - ".github/label-configuration-files/*.ya?ml" 14 | schedule: 15 | # Run daily at 8 AM UTC to sync with changes to shared label configurations. 16 | - cron: "0 8 * * *" 17 | workflow_dispatch: 18 | repository_dispatch: 19 | 20 | env: 21 | CONFIGURATIONS_FOLDER: .github/label-configuration-files 22 | CONFIGURATIONS_ARTIFACT: label-configuration-files 23 | 24 | jobs: 25 | check: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Download JSON schema for labels configuration file 33 | id: download-schema 34 | uses: carlosperate/download-file-action@v2 35 | with: 36 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json 37 | location: ${{ runner.temp }}/label-configuration-schema 38 | 39 | - name: Install JSON schema validator 40 | run: | 41 | sudo npm install \ 42 | --global \ 43 | ajv-cli \ 44 | ajv-formats 45 | 46 | - name: Validate local labels configuration 47 | run: | 48 | # See: https://github.com/ajv-validator/ajv-cli#readme 49 | ajv validate \ 50 | --all-errors \ 51 | -c ajv-formats \ 52 | -s "${{ steps.download-schema.outputs.file-path }}" \ 53 | -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" 54 | 55 | download: 56 | needs: check 57 | runs-on: ubuntu-latest 58 | 59 | strategy: 60 | matrix: 61 | filename: 62 | # Filenames of the shared configurations to apply to the repository in addition to the local configuration. 63 | # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels 64 | - universal.yml 65 | 66 | steps: 67 | - name: Download 68 | uses: carlosperate/download-file-action@v2 69 | with: 70 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} 71 | 72 | - name: Pass configuration files to next job via workflow artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | path: | 76 | *.yaml 77 | *.yml 78 | if-no-files-found: error 79 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 80 | 81 | sync: 82 | needs: download 83 | runs-on: ubuntu-latest 84 | 85 | steps: 86 | - name: Set environment variables 87 | run: | 88 | # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 89 | echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" 90 | 91 | - name: Determine whether to dry run 92 | id: dry-run 93 | if: > 94 | github.event_name == 'pull_request' || 95 | ( 96 | ( 97 | github.event_name == 'push' || 98 | github.event_name == 'workflow_dispatch' 99 | ) && 100 | github.ref != format('refs/heads/{0}', github.event.repository.default_branch) 101 | ) 102 | run: | 103 | # Use of this flag in the github-label-sync command will cause it to only check the validity of the 104 | # configuration. 105 | echo "::set-output name=flag::--dry-run" 106 | 107 | - name: Checkout repository 108 | uses: actions/checkout@v4 109 | 110 | - name: Download configuration files artifact 111 | uses: actions/download-artifact@v4 112 | with: 113 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 114 | path: ${{ env.CONFIGURATIONS_FOLDER }} 115 | 116 | - name: Remove unneeded artifact 117 | uses: geekyeggo/delete-artifact@v5 118 | with: 119 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 120 | 121 | - name: Merge label configuration files 122 | run: | 123 | # Merge all configuration files 124 | shopt -s extglob 125 | cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" 126 | 127 | - name: Install github-label-sync 128 | run: sudo npm install --global github-label-sync 129 | 130 | - name: Sync labels 131 | env: 132 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 133 | run: | 134 | # See: https://github.com/Financial-Times/github-label-sync 135 | github-label-sync \ 136 | --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ 137 | ${{ steps.dry-run.outputs.flag }} \ 138 | ${{ github.repository }} 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .development 2 | examples/node_test_server/node_modules/ 3 | *.DS_Store 4 | */.DS_Store 5 | examples/.DS_Store 6 | .idea/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## ArduinoHttpClient 0.4.0 - 2019.04.09 2 | 3 | * Added URLEncoder helper 4 | 5 | ## ArduinoHttpClient 0.3.2 - 2019.02.04 6 | 7 | * Changed Flush return value resulting in compilation error. Thanks @forGGe 8 | 9 | ## ArduinoHttpClient 0.3.1 - 2017.09.25 10 | 11 | * Changed examples to support Arduino Create secret tabs 12 | * Increase WebSocket secret-key length to 24 characters 13 | 14 | ## ArduinoHttpClient 0.3.0 - 2017.04.20 15 | 16 | * Added support for PATCH operations 17 | * Added support for chunked response bodies 18 | * Added new beginBody API 19 | 20 | ## ArduinoHttpClient 0.2.0 - 2017.01.12 21 | 22 | * Added PATCH method 23 | * Added basic auth example 24 | * Added custom header example 25 | 26 | ## ArduinoHttpClient 0.1.1 - 2016.12.16 27 | 28 | * More robust response parser 29 | 30 | ## ArduinoHttpClient 0.1.0 - 2016.07.05 31 | 32 | * Initial release 33 | 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoHttpClient 2 | 3 | [![Check Arduino status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml) 4 | [![Compile Examples status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml) 5 | [![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) 6 | 7 | ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. 8 | 9 | Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) 10 | 11 | ## Dependencies 12 | 13 | - Requires a networking hardware and a library that provides transport specific `Client` instance, such as: 14 | - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) 15 | - [WiFi101](https://github.com/arduino-libraries/WiFi101) 16 | - [Ethernet](https://github.com/arduino-libraries/Ethernet) 17 | - [MKRGSM](https://github.com/arduino-libraries/MKRGSM) 18 | - [MKRNB](https://github.com/arduino-libraries/MKRNB) 19 | - [WiFi](https://github.com/arduino-libraries/WiFi) 20 | - [GSM](https://github.com/arduino-libraries/GSM) 21 | 22 | ## Usage 23 | 24 | In normal usage, handles the outgoing request and Host header. The returned status code is parsed for you, as is the Content-Length header (if present). 25 | 26 | Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with WiFiClient, EthernetClient and GSMClient. 27 | 28 | See the examples for more detail on how the library is used. 29 | 30 | -------------------------------------------------------------------------------- /examples/BasicAuthGet/BasicAuthGet.ino: -------------------------------------------------------------------------------- 1 | /* 2 | GET client with HTTP basic authentication for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a GET request 4 | 5 | created 14 Feb 2016 6 | by Tom Igoe 7 | modified 3 Jan 2017 to add HTTP basic authentication 8 | by Sandeep Mistry 9 | modified 22 Jan 2019 10 | by Tom Igoe 11 | 12 | this example is in the public domain 13 | */ 14 | #include 15 | #include 16 | #include "arduino_secrets.h" 17 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 18 | /////// WiFi Settings /////// 19 | char ssid[] = SECRET_SSID; 20 | char pass[] = SECRET_PASS; 21 | 22 | char serverAddress[] = "192.168.0.3"; // server address 23 | int port = 8080; 24 | 25 | WiFiClient wifi; 26 | HttpClient client = HttpClient(wifi, serverAddress, port); 27 | int status = WL_IDLE_STATUS; 28 | 29 | void setup() { 30 | Serial.begin(9600); 31 | while ( status != WL_CONNECTED) { 32 | Serial.print("Attempting to connect to Network named: "); 33 | Serial.println(ssid); // print the network name (SSID); 34 | 35 | // Connect to WPA/WPA2 network: 36 | status = WiFi.begin(ssid, pass); 37 | } 38 | 39 | // print the SSID of the network you're attached to: 40 | Serial.print("SSID: "); 41 | Serial.println(WiFi.SSID()); 42 | 43 | // print your WiFi shield's IP address: 44 | IPAddress ip = WiFi.localIP(); 45 | Serial.print("IP Address: "); 46 | Serial.println(ip); 47 | } 48 | 49 | void loop() { 50 | Serial.println("making GET request with HTTP basic authentication"); 51 | client.beginRequest(); 52 | client.get("/secure"); 53 | client.sendBasicAuth("username", "password"); // send the username and password for authentication 54 | client.endRequest(); 55 | 56 | // read the status code and body of the response 57 | int statusCode = client.responseStatusCode(); 58 | String response = client.responseBody(); 59 | 60 | Serial.print("Status code: "); 61 | Serial.println(statusCode); 62 | Serial.print("Response: "); 63 | Serial.println(response); 64 | Serial.println("Wait five seconds"); 65 | delay(5000); 66 | } 67 | -------------------------------------------------------------------------------- /examples/BasicAuthGet/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/CustomHeader/CustomHeader.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Custom request header example for the ArduinoHttpClient 3 | library. This example sends a GET and a POST request with a custom header every 5 seconds. 4 | 5 | based on SimpleGet example by Tom Igoe 6 | header modifications by Todd Treece 7 | modified 22 Jan 2019 8 | by Tom Igoe 9 | 10 | this example is in the public domain 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "arduino_secrets.h" 17 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 18 | /////// WiFi Settings /////// 19 | char ssid[] = SECRET_SSID; 20 | char pass[] = SECRET_PASS; 21 | 22 | char serverAddress[] = "192.168.0.3"; // server address 23 | int port = 8080; 24 | 25 | WiFiClient wifi; 26 | HttpClient client = HttpClient(wifi, serverAddress, port); 27 | int status = WL_IDLE_STATUS; 28 | 29 | void setup() { 30 | Serial.begin(9600); 31 | while ( status != WL_CONNECTED) { 32 | Serial.print("Attempting to connect to Network named: "); 33 | Serial.println(ssid); // print the network name (SSID); 34 | 35 | // Connect to WPA/WPA2 network: 36 | status = WiFi.begin(ssid, pass); 37 | } 38 | 39 | // print the SSID of the network you're attached to: 40 | Serial.print("SSID: "); 41 | Serial.println(WiFi.SSID()); 42 | 43 | // print your WiFi shield's IP address: 44 | IPAddress ip = WiFi.localIP(); 45 | Serial.print("IP Address: "); 46 | Serial.println(ip); 47 | } 48 | 49 | void loop() { 50 | Serial.println("making GET request"); 51 | client.beginRequest(); 52 | client.get("/"); 53 | client.sendHeader("X-CUSTOM-HEADER", "custom_value"); 54 | client.endRequest(); 55 | 56 | // read the status code and body of the response 57 | int statusCode = client.responseStatusCode(); 58 | String response = client.responseBody(); 59 | 60 | Serial.print("GET Status code: "); 61 | Serial.println(statusCode); 62 | Serial.print("GET Response: "); 63 | Serial.println(response); 64 | 65 | Serial.println("Wait five seconds"); 66 | delay(5000); 67 | 68 | Serial.println("making POST request"); 69 | String postData = "name=Alice&age=12"; 70 | client.beginRequest(); 71 | client.post("/"); 72 | client.sendHeader(HTTP_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); 73 | client.sendHeader(HTTP_HEADER_CONTENT_LENGTH, postData.length()); 74 | client.sendHeader("X-CUSTOM-HEADER", "custom_value"); 75 | client.endRequest(); 76 | client.write((const byte*)postData.c_str(), postData.length()); 77 | // note: the above line can also be achieved with the simpler line below: 78 | //client.print(postData); 79 | 80 | // read the status code and body of the response 81 | statusCode = client.responseStatusCode(); 82 | response = client.responseBody(); 83 | 84 | Serial.print("POST Status code: "); 85 | Serial.println(statusCode); 86 | Serial.print("POST Response: "); 87 | Serial.println(response); 88 | 89 | Serial.println("Wait five seconds"); 90 | delay(5000); 91 | } 92 | -------------------------------------------------------------------------------- /examples/CustomHeader/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/DweetGet/DweetGet.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Dweet.io GET client for ArduinoHttpClient library 3 | Connects to dweet.io once every ten seconds, 4 | sends a GET request and a request body. Uses SSL 5 | 6 | Shows how to use Strings to assemble path and parse content 7 | from response. dweet.io expects: 8 | https://dweet.io/get/latest/dweet/for/thingName 9 | 10 | For more on dweet.io, see https://dweet.io/play/ 11 | 12 | created 15 Feb 2016 13 | updated 22 Jan 2019 14 | by Tom Igoe 15 | 16 | this example is in the public domain 17 | */ 18 | #include 19 | #include 20 | 21 | #include "arduino_secrets.h" 22 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 23 | /////// WiFi Settings /////// 24 | char ssid[] = SECRET_SSID; 25 | char pass[] = SECRET_PASS; 26 | 27 | const char serverAddress[] = "dweet.io"; // server address 28 | int port = 80; 29 | String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here 30 | 31 | WiFiClient wifi; 32 | HttpClient client = HttpClient(wifi, serverAddress, port); 33 | int status = WL_IDLE_STATUS; 34 | 35 | void setup() { 36 | Serial.begin(9600); 37 | while (!Serial); 38 | while ( status != WL_CONNECTED) { 39 | Serial.print("Attempting to connect to Network named: "); 40 | Serial.println(ssid); // print the network name (SSID); 41 | 42 | // Connect to WPA/WPA2 network: 43 | status = WiFi.begin(ssid, pass); 44 | } 45 | 46 | // print the SSID of the network you're attached to: 47 | Serial.print("SSID: "); 48 | Serial.println(WiFi.SSID()); 49 | 50 | // print your WiFi shield's IP address: 51 | IPAddress ip = WiFi.localIP(); 52 | Serial.print("IP Address: "); 53 | Serial.println(ip); 54 | } 55 | 56 | void loop() { 57 | // assemble the path for the GET message: 58 | String path = "/get/latest/dweet/for/" + dweetName; 59 | 60 | // send the GET request 61 | Serial.println("making GET request"); 62 | client.get(path); 63 | 64 | // read the status code and body of the response 65 | int statusCode = client.responseStatusCode(); 66 | String response = client.responseBody(); 67 | Serial.print("Status code: "); 68 | Serial.println(statusCode); 69 | Serial.print("Response: "); 70 | Serial.println(response); 71 | 72 | /* 73 | Typical response is: 74 | {"this":"succeeded", 75 | "by":"getting", 76 | "the":"dweets", 77 | "with":[{"thing":"my-thing-name", 78 | "created":"2016-02-16T05:10:36.589Z", 79 | "content":{"sensorValue":456}}]} 80 | 81 | You want "content": numberValue 82 | */ 83 | // now parse the response looking for "content": 84 | int labelStart = response.indexOf("content\":"); 85 | // find the first { after "content": 86 | int contentStart = response.indexOf("{", labelStart); 87 | // find the following } and get what's between the braces: 88 | int contentEnd = response.indexOf("}", labelStart); 89 | String content = response.substring(contentStart + 1, contentEnd); 90 | Serial.println(content); 91 | 92 | // now get the value after the colon, and convert to an int: 93 | int valueStart = content.indexOf(":"); 94 | String valueString = content.substring(valueStart + 1); 95 | int number = valueString.toInt(); 96 | Serial.print("Value string: "); 97 | Serial.println(valueString); 98 | Serial.print("Actual value: "); 99 | Serial.println(number); 100 | 101 | Serial.println("Wait ten seconds\n"); 102 | delay(10000); 103 | } 104 | -------------------------------------------------------------------------------- /examples/DweetGet/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/DweetPost/DweetPost.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Dweet.io POST client for ArduinoHttpClient library 3 | Connects to dweet.io once every ten seconds, 4 | sends a POST request and a request body. 5 | 6 | Shows how to use Strings to assemble path and body 7 | 8 | created 15 Feb 2016 9 | modified 22 Jan 2019 10 | by Tom Igoe 11 | 12 | this example is in the public domain 13 | */ 14 | #include 15 | #include 16 | 17 | #include "arduino_secrets.h" 18 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 19 | /////// WiFi Settings /////// 20 | char ssid[] = SECRET_SSID; 21 | char pass[] = SECRET_PASS; 22 | 23 | const char serverAddress[] = "dweet.io"; // server address 24 | int port = 80; 25 | 26 | WiFiClient wifi; 27 | HttpClient client = HttpClient(wifi, serverAddress, port); 28 | int status = WL_IDLE_STATUS; 29 | 30 | void setup() { 31 | Serial.begin(9600); 32 | while(!Serial); 33 | while ( status != WL_CONNECTED) { 34 | Serial.print("Attempting to connect to Network named: "); 35 | Serial.println(ssid); // print the network name (SSID); 36 | 37 | // Connect to WPA/WPA2 network: 38 | status = WiFi.begin(ssid, pass); 39 | } 40 | 41 | // print the SSID of the network you're attached to: 42 | Serial.print("SSID: "); 43 | Serial.println(WiFi.SSID()); 44 | 45 | // print your WiFi shield's IP address: 46 | IPAddress ip = WiFi.localIP(); 47 | Serial.print("IP Address: "); 48 | Serial.println(ip); 49 | } 50 | 51 | void loop() { 52 | // assemble the path for the POST message: 53 | String dweetName = "scandalous-cheese-hoarder"; 54 | String path = "/dweet/for/" + dweetName; 55 | String contentType = "application/json"; 56 | 57 | // assemble the body of the POST message: 58 | int sensorValue = analogRead(A0); 59 | String postData = "{\"sensorValue\":\""; 60 | postData += sensorValue; 61 | postData += "\"}"; 62 | 63 | Serial.println("making POST request"); 64 | 65 | // send the POST request 66 | client.post(path, contentType, postData); 67 | 68 | // read the status code and body of the response 69 | int statusCode = client.responseStatusCode(); 70 | String response = client.responseBody(); 71 | 72 | Serial.print("Status code: "); 73 | Serial.println(statusCode); 74 | Serial.print("Response: "); 75 | Serial.println(response); 76 | 77 | Serial.println("Wait ten seconds\n"); 78 | delay(10000); 79 | } 80 | -------------------------------------------------------------------------------- /examples/DweetPost/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/HueBlink/HueBlink.ino: -------------------------------------------------------------------------------- 1 | /* HueBlink example for ArduinoHttpClient library 2 | 3 | Uses ArduinoHttpClient library to control Philips Hue 4 | For more on Hue developer API see http://developer.meethue.com 5 | 6 | To control a light, the Hue expects a HTTP PUT request to: 7 | 8 | http://hue.hub.address/api/hueUserName/lights/lightNumber/state 9 | 10 | The body of the PUT request looks like this: 11 | {"on": true} or {"on":false} 12 | 13 | This example shows how to concatenate Strings to assemble the 14 | PUT request and the body of the request. 15 | 16 | modified 15 Feb 2016 17 | by Tom Igoe (tigoe) to match new API 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "arduino_secrets.h" 24 | 25 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 26 | /////// WiFi Settings /////// 27 | char ssid[] = SECRET_SSID; 28 | char pass[] = SECRET_PASS; 29 | 30 | int status = WL_IDLE_STATUS; // the WiFi radio's status 31 | 32 | char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge 33 | String hueUserName = "huebridgeusername"; // hue bridge username 34 | 35 | // make a WiFiClient instance and a HttpClient instance: 36 | WiFiClient wifi; 37 | HttpClient httpClient = HttpClient(wifi, hueHubIP); 38 | 39 | 40 | void setup() { 41 | //Initialize serial and wait for port to open: 42 | Serial.begin(9600); 43 | while (!Serial); // wait for serial port to connect. 44 | 45 | // attempt to connect to WiFi network: 46 | while ( status != WL_CONNECTED) { 47 | Serial.print("Attempting to connect to WPA SSID: "); 48 | Serial.println(ssid); 49 | // Connect to WPA/WPA2 network: 50 | status = WiFi.begin(ssid, pass); 51 | } 52 | 53 | // you're connected now, so print out the data: 54 | Serial.print("You're connected to the network IP = "); 55 | IPAddress ip = WiFi.localIP(); 56 | Serial.println(ip); 57 | } 58 | 59 | void loop() { 60 | sendRequest(3, "on", "true"); // turn light on 61 | delay(2000); // wait 2 seconds 62 | sendRequest(3, "on", "false"); // turn light off 63 | delay(2000); // wait 2 seconds 64 | } 65 | 66 | void sendRequest(int light, String cmd, String value) { 67 | // make a String for the HTTP request path: 68 | String request = "/api/" + hueUserName; 69 | request += "/lights/"; 70 | request += light; 71 | request += "/state/"; 72 | 73 | String contentType = "application/json"; 74 | 75 | // make a string for the JSON command: 76 | String hueCmd = "{\"" + cmd; 77 | hueCmd += "\":"; 78 | hueCmd += value; 79 | hueCmd += "}"; 80 | // see what you assembled to send: 81 | Serial.print("PUT request to server: "); 82 | Serial.println(request); 83 | Serial.print("JSON command to server: "); 84 | 85 | // make the PUT request to the hub: 86 | httpClient.put(request, contentType, hueCmd); 87 | 88 | // read the status code and body of the response 89 | int statusCode = httpClient.responseStatusCode(); 90 | String response = httpClient.responseBody(); 91 | 92 | Serial.println(hueCmd); 93 | Serial.print("Status code from server: "); 94 | Serial.println(statusCode); 95 | Serial.print("Server response: "); 96 | Serial.println(response); 97 | Serial.println(); 98 | } 99 | -------------------------------------------------------------------------------- /examples/HueBlink/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/ParseURL/ParseURL.ino: -------------------------------------------------------------------------------- 1 | #include "URLParser.h" 2 | 3 | void setup() { 4 | 5 | Serial.begin(9600); 6 | 7 | while(!Serial); 8 | 9 | Serial.println("starting"); 10 | 11 | ParsedUrl url( 12 | "https://www.google.com/search?q=arduino" 13 | ); 14 | 15 | Serial.print("parsed URL schema: \""); 16 | Serial.print(url.schema()); 17 | Serial.print("\"\nparsed URL host: \""); 18 | Serial.print(url.host()); 19 | Serial.print("\"\nparsed URL path: \""); 20 | Serial.print(url.path()); 21 | Serial.print("\"\nparsed URL query: \""); 22 | Serial.print(url.query()); 23 | Serial.print("\"\nparsed URL userinfo: \""); 24 | Serial.print(url.userinfo()); 25 | Serial.println("\""); 26 | 27 | } 28 | 29 | void loop() { } -------------------------------------------------------------------------------- /examples/PostWithHeaders/PostWithHeaders.ino: -------------------------------------------------------------------------------- 1 | /* 2 | POST with headers client for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a POST request 4 | with custom headers and a request body 5 | 6 | created 14 Feb 2016 7 | by Tom Igoe 8 | modified 18 Mar 2017 9 | by Sandeep Mistry 10 | modified 22 Jan 2019 11 | by Tom Igoe 12 | 13 | this example is in the public domain 14 | */ 15 | #include 16 | #include 17 | 18 | #include "arduino_secrets.h" 19 | 20 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 21 | /////// WiFi Settings /////// 22 | char ssid[] = SECRET_SSID; 23 | char pass[] = SECRET_PASS; 24 | 25 | 26 | char serverAddress[] = "192.168.0.3"; // server address 27 | int port = 8080; 28 | 29 | WiFiClient wifi; 30 | HttpClient client = HttpClient(wifi, serverAddress, port); 31 | int status = WL_IDLE_STATUS; 32 | 33 | void setup() { 34 | Serial.begin(9600); 35 | while ( status != WL_CONNECTED) { 36 | Serial.print("Attempting to connect to Network named: "); 37 | Serial.println(ssid); // print the network name (SSID); 38 | 39 | // Connect to WPA/WPA2 network: 40 | status = WiFi.begin(ssid, pass); 41 | } 42 | 43 | // print the SSID of the network you're attached to: 44 | Serial.print("SSID: "); 45 | Serial.println(WiFi.SSID()); 46 | 47 | // print your WiFi shield's IP address: 48 | IPAddress ip = WiFi.localIP(); 49 | Serial.print("IP Address: "); 50 | Serial.println(ip); 51 | } 52 | 53 | void loop() { 54 | Serial.println("making POST request"); 55 | String postData = "name=Alice&age=12"; 56 | 57 | client.beginRequest(); 58 | client.post("/"); 59 | client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); 60 | client.sendHeader("Content-Length", postData.length()); 61 | client.sendHeader("X-Custom-Header", "custom-header-value"); 62 | client.beginBody(); 63 | client.print(postData); 64 | client.endRequest(); 65 | 66 | // read the status code and body of the response 67 | int statusCode = client.responseStatusCode(); 68 | String response = client.responseBody(); 69 | 70 | Serial.print("Status code: "); 71 | Serial.println(statusCode); 72 | Serial.print("Response: "); 73 | Serial.println(response); 74 | 75 | Serial.println("Wait five seconds"); 76 | delay(5000); 77 | } 78 | -------------------------------------------------------------------------------- /examples/PostWithHeaders/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimpleDelete/SimpleDelete.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple DELETE client for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a DELETE request 4 | and a request body 5 | 6 | created 14 Feb 2016 7 | modified 22 Jan 2019 8 | by Tom Igoe 9 | 10 | this example is in the public domain 11 | */ 12 | #include 13 | #include 14 | 15 | #include "arduino_secrets.h" 16 | 17 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 18 | /////// WiFi Settings /////// 19 | char ssid[] = SECRET_SSID; 20 | char pass[] = SECRET_PASS; 21 | 22 | 23 | char serverAddress[] = "192.168.0.3"; // server address 24 | int port = 8080; 25 | 26 | WiFiClient wifi; 27 | HttpClient client = HttpClient(wifi, serverAddress, port); 28 | int status = WL_IDLE_STATUS; 29 | 30 | void setup() { 31 | Serial.begin(9600); 32 | while ( status != WL_CONNECTED) { 33 | Serial.print("Attempting to connect to Network named: "); 34 | Serial.println(ssid); // print the network name (SSID); 35 | 36 | // Connect to WPA/WPA2 network: 37 | status = WiFi.begin(ssid, pass); 38 | } 39 | 40 | // print the SSID of the network you're attached to: 41 | Serial.print("SSID: "); 42 | Serial.println(WiFi.SSID()); 43 | 44 | // print your WiFi shield's IP address: 45 | IPAddress ip = WiFi.localIP(); 46 | Serial.print("IP Address: "); 47 | Serial.println(ip); 48 | } 49 | 50 | void loop() { 51 | Serial.println("making DELETE request"); 52 | String contentType = "application/x-www-form-urlencoded"; 53 | String delData = "name=light&age=46"; 54 | 55 | client.del("/", contentType, delData); 56 | 57 | // read the status code and body of the response 58 | int statusCode = client.responseStatusCode(); 59 | String response = client.responseBody(); 60 | 61 | Serial.print("Status code: "); 62 | Serial.println(statusCode); 63 | Serial.print("Response: "); 64 | Serial.println(response); 65 | 66 | Serial.println("Wait five seconds"); 67 | delay(5000); 68 | } 69 | -------------------------------------------------------------------------------- /examples/SimpleDelete/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimpleGet/SimpleGet.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple GET client for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a GET request 4 | 5 | created 14 Feb 2016 6 | modified 22 Jan 2019 7 | by Tom Igoe 8 | 9 | this example is in the public domain 10 | */ 11 | #include 12 | #include 13 | 14 | #include "arduino_secrets.h" 15 | 16 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 17 | /////// WiFi Settings /////// 18 | char ssid[] = SECRET_SSID; 19 | char pass[] = SECRET_PASS; 20 | 21 | char serverAddress[] = "192.168.0.3"; // server address 22 | int port = 8080; 23 | 24 | WiFiClient wifi; 25 | HttpClient client = HttpClient(wifi, serverAddress, port); 26 | int status = WL_IDLE_STATUS; 27 | 28 | void setup() { 29 | Serial.begin(9600); 30 | while ( status != WL_CONNECTED) { 31 | Serial.print("Attempting to connect to Network named: "); 32 | Serial.println(ssid); // print the network name (SSID); 33 | 34 | // Connect to WPA/WPA2 network: 35 | status = WiFi.begin(ssid, pass); 36 | } 37 | 38 | // print the SSID of the network you're attached to: 39 | Serial.print("SSID: "); 40 | Serial.println(WiFi.SSID()); 41 | 42 | // print your WiFi shield's IP address: 43 | IPAddress ip = WiFi.localIP(); 44 | Serial.print("IP Address: "); 45 | Serial.println(ip); 46 | } 47 | 48 | void loop() { 49 | Serial.println("making GET request"); 50 | client.get("/"); 51 | 52 | // read the status code and body of the response 53 | int statusCode = client.responseStatusCode(); 54 | String response = client.responseBody(); 55 | 56 | Serial.print("Status code: "); 57 | Serial.println(statusCode); 58 | Serial.print("Response: "); 59 | Serial.println(response); 60 | Serial.println("Wait five seconds"); 61 | delay(5000); 62 | } 63 | -------------------------------------------------------------------------------- /examples/SimpleGet/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimpleHttpExample/SimpleHttpExample.ino: -------------------------------------------------------------------------------- 1 | // (c) Copyright 2010-2012 MCQN Ltd. 2 | // Released under Apache License, version 2.0 3 | // 4 | // Simple example to show how to use the HttpClient library 5 | // Gets the web page given at http:// and 6 | // outputs the content to the serial port 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // This example downloads the URL "http://arduino.cc/" 13 | 14 | #include "arduino_secrets.h" 15 | 16 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 17 | /////// WiFi Settings /////// 18 | char ssid[] = SECRET_SSID; 19 | char pass[] = SECRET_PASS; 20 | 21 | 22 | 23 | // Name of the server we want to connect to 24 | const char kHostname[] = "arduino.cc"; 25 | // Path to download (this is the bit after the hostname in the URL 26 | // that you want to download 27 | const char kPath[] = "/"; 28 | 29 | // Number of milliseconds to wait without receiving any data before we give up 30 | const int kNetworkTimeout = 30*1000; 31 | // Number of milliseconds to wait if no data is available before trying again 32 | const int kNetworkDelay = 1000; 33 | 34 | WiFiClient c; 35 | HttpClient http(c, kHostname); 36 | 37 | void setup() 38 | { 39 | //Initialize serial and wait for port to open: 40 | Serial.begin(9600); 41 | while (!Serial) { 42 | ; // wait for serial port to connect. Needed for native USB port only 43 | } 44 | 45 | // attempt to connect to WiFi network: 46 | Serial.print("Attempting to connect to WPA SSID: "); 47 | Serial.println(ssid); 48 | while (WiFi.begin(ssid, pass) != WL_CONNECTED) { 49 | // unsuccessful, retry in 4 seconds 50 | Serial.print("failed ... "); 51 | delay(4000); 52 | Serial.print("retrying ... "); 53 | } 54 | 55 | Serial.println("connected"); 56 | } 57 | 58 | void loop() 59 | { 60 | int err =0; 61 | 62 | err = http.get(kPath); 63 | if (err == 0) 64 | { 65 | Serial.println("startedRequest ok"); 66 | 67 | err = http.responseStatusCode(); 68 | if (err >= 0) 69 | { 70 | Serial.print("Got status code: "); 71 | Serial.println(err); 72 | 73 | // Usually you'd check that the response code is 200 or a 74 | // similar "success" code (200-299) before carrying on, 75 | // but we'll print out whatever response we get 76 | 77 | // If you are interesting in the response headers, you 78 | // can read them here: 79 | //while(http.headerAvailable()) 80 | //{ 81 | // String headerName = http.readHeaderName(); 82 | // String headerValue = http.readHeaderValue(); 83 | //} 84 | 85 | int bodyLen = http.contentLength(); 86 | Serial.print("Content length is: "); 87 | Serial.println(bodyLen); 88 | Serial.println(); 89 | Serial.println("Body returned follows:"); 90 | 91 | // Now we've got to the body, so we can print it out 92 | unsigned long timeoutStart = millis(); 93 | char c; 94 | // Whilst we haven't timed out & haven't reached the end of the body 95 | while ( (http.connected() || http.available()) && 96 | (!http.endOfBodyReached()) && 97 | ((millis() - timeoutStart) < kNetworkTimeout) ) 98 | { 99 | if (http.available()) 100 | { 101 | c = http.read(); 102 | // Print out this character 103 | Serial.print(c); 104 | 105 | // We read something, reset the timeout counter 106 | timeoutStart = millis(); 107 | } 108 | else 109 | { 110 | // We haven't got any data, so let's pause to allow some to 111 | // arrive 112 | delay(kNetworkDelay); 113 | } 114 | } 115 | } 116 | else 117 | { 118 | Serial.print("Getting response failed: "); 119 | Serial.println(err); 120 | } 121 | } 122 | else 123 | { 124 | Serial.print("Connect failed: "); 125 | Serial.println(err); 126 | } 127 | http.stop(); 128 | 129 | // And just stop, now that we've tried a download 130 | while(1); 131 | } 132 | -------------------------------------------------------------------------------- /examples/SimpleHttpExample/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimplePost/SimplePost.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple POST client for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a POST request 4 | and a request body 5 | 6 | created 14 Feb 2016 7 | modified 22 Jan 2019 8 | by Tom Igoe 9 | 10 | this example is in the public domain 11 | */ 12 | #include 13 | #include 14 | #include "arduino_secrets.h" 15 | 16 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 17 | /////// WiFi Settings /////// 18 | char ssid[] = SECRET_SSID; 19 | char pass[] = SECRET_PASS; 20 | 21 | char serverAddress[] = "192.168.0.3"; // server address 22 | int port = 8080; 23 | 24 | WiFiClient wifi; 25 | HttpClient client = HttpClient(wifi, serverAddress, port); 26 | int status = WL_IDLE_STATUS; 27 | 28 | void setup() { 29 | Serial.begin(9600); 30 | while ( status != WL_CONNECTED) { 31 | Serial.print("Attempting to connect to Network named: "); 32 | Serial.println(ssid); // print the network name (SSID); 33 | 34 | // Connect to WPA/WPA2 network: 35 | status = WiFi.begin(ssid, pass); 36 | } 37 | 38 | // print the SSID of the network you're attached to: 39 | Serial.print("SSID: "); 40 | Serial.println(WiFi.SSID()); 41 | 42 | // print your WiFi shield's IP address: 43 | IPAddress ip = WiFi.localIP(); 44 | Serial.print("IP Address: "); 45 | Serial.println(ip); 46 | } 47 | 48 | void loop() { 49 | Serial.println("making POST request"); 50 | String contentType = "application/x-www-form-urlencoded"; 51 | String postData = "name=Alice&age=12"; 52 | 53 | client.post("/", contentType, postData); 54 | 55 | // read the status code and body of the response 56 | int statusCode = client.responseStatusCode(); 57 | String response = client.responseBody(); 58 | 59 | Serial.print("Status code: "); 60 | Serial.println(statusCode); 61 | Serial.print("Response: "); 62 | Serial.println(response); 63 | 64 | Serial.println("Wait five seconds"); 65 | delay(5000); 66 | } 67 | -------------------------------------------------------------------------------- /examples/SimplePost/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimplePut/SimplePut.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple PUT client for ArduinoHttpClient library 3 | Connects to server once every five seconds, sends a PUT request 4 | and a request body 5 | 6 | created 14 Feb 2016 7 | modified 22 Jan 2019 8 | by Tom Igoe 9 | 10 | this example is in the public domain 11 | */ 12 | #include 13 | #include 14 | #include "arduino_secrets.h" 15 | 16 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 17 | /////// WiFi Settings /////// 18 | char ssid[] = SECRET_SSID; 19 | char pass[] = SECRET_PASS; 20 | 21 | char serverAddress[] = "192.168.0.3"; // server address 22 | int port = 8080; 23 | 24 | WiFiClient wifi; 25 | HttpClient client = HttpClient(wifi, serverAddress, port); 26 | int status = WL_IDLE_STATUS; 27 | 28 | void setup() { 29 | Serial.begin(9600); 30 | while ( status != WL_CONNECTED) { 31 | Serial.print("Attempting to connect to Network named: "); 32 | Serial.println(ssid); // print the network name (SSID); 33 | 34 | // Connect to WPA/WPA2 network: 35 | status = WiFi.begin(ssid, pass); 36 | } 37 | 38 | // print the SSID of the network you're attached to: 39 | Serial.print("SSID: "); 40 | Serial.println(WiFi.SSID()); 41 | 42 | // print your WiFi shield's IP address: 43 | IPAddress ip = WiFi.localIP(); 44 | Serial.print("IP Address: "); 45 | Serial.println(ip); 46 | } 47 | 48 | void loop() { 49 | Serial.println("making PUT request"); 50 | String contentType = "application/x-www-form-urlencoded"; 51 | String putData = "name=light&age=46"; 52 | 53 | client.put("/", contentType, putData); 54 | 55 | // read the status code and body of the response 56 | int statusCode = client.responseStatusCode(); 57 | String response = client.responseBody(); 58 | 59 | Serial.print("Status code: "); 60 | Serial.println(statusCode); 61 | Serial.print("Response: "); 62 | Serial.println(response); 63 | 64 | Serial.println("Wait five seconds"); 65 | delay(5000); 66 | } 67 | -------------------------------------------------------------------------------- /examples/SimplePut/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/SimpleWebSocket/SimpleWebSocket.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple WebSocket client for ArduinoHttpClient library 3 | Connects to the WebSocket server, and sends a hello 4 | message every 5 seconds 5 | 6 | created 28 Jun 2016 7 | by Sandeep Mistry 8 | modified 22 Jan 2019 9 | by Tom Igoe 10 | 11 | this example is in the public domain 12 | */ 13 | #include 14 | #include 15 | #include "arduino_secrets.h" 16 | 17 | ///////please enter your sensitive data in the Secret tab/arduino_secrets.h 18 | /////// WiFi Settings /////// 19 | char ssid[] = SECRET_SSID; 20 | char pass[] = SECRET_PASS; 21 | 22 | char serverAddress[] = "echo.websocket.org"; // server address 23 | int port = 80; 24 | 25 | WiFiClient wifi; 26 | WebSocketClient client = WebSocketClient(wifi, serverAddress, port); 27 | int status = WL_IDLE_STATUS; 28 | int count = 0; 29 | 30 | void setup() { 31 | Serial.begin(9600); 32 | while ( status != WL_CONNECTED) { 33 | Serial.print("Attempting to connect to Network named: "); 34 | Serial.println(ssid); // print the network name (SSID); 35 | 36 | // Connect to WPA/WPA2 network: 37 | status = WiFi.begin(ssid, pass); 38 | } 39 | 40 | // print the SSID of the network you're attached to: 41 | Serial.print("SSID: "); 42 | Serial.println(WiFi.SSID()); 43 | 44 | // print your WiFi shield's IP address: 45 | IPAddress ip = WiFi.localIP(); 46 | Serial.print("IP Address: "); 47 | Serial.println(ip); 48 | } 49 | 50 | void loop() { 51 | Serial.println("starting WebSocket client"); 52 | client.begin(); 53 | 54 | while (client.connected()) { 55 | Serial.print("Sending hello "); 56 | Serial.println(count); 57 | 58 | // send a hello # 59 | client.beginMessage(TYPE_TEXT); 60 | client.print("hello "); 61 | client.print(count); 62 | client.endMessage(); 63 | 64 | // increment count for next message 65 | count++; 66 | 67 | // check if a message is available to be received 68 | int messageSize = client.parseMessage(); 69 | 70 | if (messageSize > 0) { 71 | Serial.println("Received a message:"); 72 | Serial.println(client.readString()); 73 | } 74 | 75 | // wait 5 seconds 76 | delay(5000); 77 | } 78 | 79 | Serial.println("disconnected"); 80 | } 81 | -------------------------------------------------------------------------------- /examples/SimpleWebSocket/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define SECRET_SSID "" 2 | #define SECRET_PASS "" 3 | 4 | -------------------------------------------------------------------------------- /examples/node_test_server/getPostPutDelete.js: -------------------------------------------------------------------------------- 1 | /* 2 | Express.js GET/POST example 3 | Shows how handle GET, POST, PUT, DELETE 4 | in Express.js 4.0 5 | 6 | created 14 Feb 2016 7 | by Tom Igoe 8 | */ 9 | 10 | var express = require('express'); // include express.js 11 | var app = express(); // a local instance of it 12 | var bodyParser = require('body-parser'); // include body-parser 13 | var WebSocketServer = require('ws').Server // include Web Socket server 14 | 15 | // you need a body parser: 16 | app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded 17 | 18 | // this runs after the server successfully starts: 19 | function serverStart() { 20 | var port = server.address().port; 21 | console.log('Server listening on port '+ port); 22 | } 23 | 24 | app.get('/chunked', function(request, response) { 25 | response.write('\n'); 26 | response.write(' `:;;;,` .:;;:. \n'); 27 | response.write(' .;;;;;;;;;;;` :;;;;;;;;;;: TM \n'); 28 | response.write(' `;;;;;;;;;;;;;;;` :;;;;;;;;;;;;;;; \n'); 29 | response.write(' :;;;;;;;;;;;;;;;;;; `;;;;;;;;;;;;;;;;;; \n'); 30 | response.write(' ;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;; \n'); 31 | response.write(' ;;;;;;;;:` `;;;;;;;;; ,;;;;;;;;.` .;;;;;;;; \n'); 32 | response.write(' .;;;;;;, :;;;;;;; .;;;;;;; ;;;;;;; \n'); 33 | response.write(' ;;;;;; ;;;;;;; ;;;;;;, ;;;;;;. \n'); 34 | response.write(' ,;;;;; ;;;;;;.;;;;;;` ;;;;;; \n'); 35 | response.write(' ;;;;;. ;;;;;;;;;;;` ``` ;;;;;`\n'); 36 | response.write(' ;;;;; ;;;;;;;;;, ;;; .;;;;;\n'); 37 | response.write('`;;;;: `;;;;;;;; ;;; ;;;;;\n'); 38 | response.write(',;;;;` `,,,,,,,, ;;;;;;; .,,;;;,,, ;;;;;\n'); 39 | response.write(':;;;;` .;;;;;;;; ;;;;;, :;;;;;;;; ;;;;;\n'); 40 | response.write(':;;;;` .;;;;;;;; `;;;;;; :;;;;;;;; ;;;;;\n'); 41 | response.write('.;;;;. ;;;;;;;. ;;; ;;;;;\n'); 42 | response.write(' ;;;;; ;;;;;;;;; ;;; ;;;;;\n'); 43 | response.write(' ;;;;; .;;;;;;;;;; ;;; ;;;;;,\n'); 44 | response.write(' ;;;;;; `;;;;;;;;;;;; ;;;;; \n'); 45 | response.write(' `;;;;;, .;;;;;; ;;;;;;; ;;;;;; \n'); 46 | response.write(' ;;;;;;: :;;;;;;. ;;;;;;; ;;;;;; \n'); 47 | response.write(' ;;;;;;;` .;;;;;;;, ;;;;;;;; ;;;;;;;: \n'); 48 | response.write(' ;;;;;;;;;:,:;;;;;;;;;: ;;;;;;;;;;:,;;;;;;;;;; \n'); 49 | response.write(' `;;;;;;;;;;;;;;;;;;;. ;;;;;;;;;;;;;;;;;;;; \n'); 50 | response.write(' ;;;;;;;;;;;;;;;;; :;;;;;;;;;;;;;;;;: \n'); 51 | response.write(' ,;;;;;;;;;;;;;, ;;;;;;;;;;;;;; \n'); 52 | response.write(' .;;;;;;;;;` ,;;;;;;;;: \n'); 53 | response.write(' \n'); 54 | response.write(' \n'); 55 | response.write(' \n'); 56 | response.write(' \n'); 57 | response.write(' ;;; ;;;;;` ;;;;: .;; ;; ,;;;;;, ;;. `;, ;;;; \n'); 58 | response.write(' ;;; ;;:;;; ;;;;;; .;; ;; ,;;;;;: ;;; `;, ;;;:;; \n'); 59 | response.write(' ,;:; ;; ;; ;; ;; .;; ;; ,;, ;;;,`;, ;; ;; \n'); 60 | response.write(' ;; ;: ;; ;; ;; ;; .;; ;; ,;, ;;;;`;, ;; ;;. \n'); 61 | response.write(' ;: ;; ;;;;;: ;; ;; .;; ;; ,;, ;;`;;;, ;; ;;` \n'); 62 | response.write(' ,;;;;; ;;`;; ;; ;; .;; ;; ,;, ;; ;;;, ;; ;; \n'); 63 | response.write(' ;; ,;, ;; .;; ;;;;;: ;;;;;: ,;;;;;: ;; ;;, ;;;;;; \n'); 64 | response.write(' ;; ;; ;; ;;` ;;;;. `;;;: ,;;;;;, ;; ;;, ;;;; \n'); 65 | response.write('\n'); 66 | response.end(); 67 | }); 68 | 69 | // this is the POST handler: 70 | app.all('/*', function (request, response) { 71 | console.log('Got a ' + request.method + ' request'); 72 | // the parameters of a GET request are passed in 73 | // request.body. Pass that to formatResponse() 74 | // for formatting: 75 | console.log(request.headers); 76 | if (request.method == 'GET') { 77 | console.log(request.query); 78 | } else { 79 | console.log(request.body); 80 | } 81 | 82 | // send the response: 83 | response.send('OK'); 84 | response.end(); 85 | }); 86 | 87 | // start the server: 88 | var server = app.listen(8080, serverStart); 89 | 90 | // create a WebSocket server and attach it to the server 91 | var wss = new WebSocketServer({server: server}); 92 | 93 | wss.on('connection', function connection(ws) { 94 | // new connection, add message listener 95 | ws.on('message', function incoming(message) { 96 | // received a message 97 | console.log('received: %s', message); 98 | 99 | // echo it back 100 | ws.send(message); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /examples/node_test_server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_test_server", 3 | "version": "0.0.1", 4 | "author": { 5 | "name": "Tom Igoe" 6 | }, 7 | "dependencies": { 8 | "body-parser": ">=1.11.0", 9 | "express": ">=4.0.0", 10 | "multer": "*", 11 | "ws": "^1.1.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For ArduinoHttpClient 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ArduinoHttpClient KEYWORD1 10 | HttpClient KEYWORD1 11 | WebSocketClient KEYWORD1 12 | URLEncoder KEYWORD1 13 | 14 | ####################################### 15 | # Methods and Functions (KEYWORD2) 16 | ####################################### 17 | 18 | get KEYWORD2 19 | post KEYWORD2 20 | put KEYWORD2 21 | patch KEYWORD2 22 | startRequest KEYWORD2 23 | beginRequest KEYWORD2 24 | beginBody KEYWORD2 25 | sendHeader KEYWORD2 26 | sendBasicAuth KEYWORD2 27 | endRequest KEYWORD2 28 | responseStatusCode KEYWORD2 29 | readHeader KEYWORD2 30 | skipResponseHeaders KEYWORD2 31 | endOfHeadersReached KEYWORD2 32 | endOfBodyReached KEYWORD2 33 | completed KEYWORD2 34 | contentLength KEYWORD2 35 | isResponseChunked KEYWORD2 36 | connectionKeepAlive KEYWORD2 37 | noDefaultRequestHeaders KEYWORD2 38 | headerAvailable KEYWORD2 39 | readHeaderName KEYWORD2 40 | readHeaderValue KEYWORD2 41 | responseBody KEYWORD2 42 | 43 | beginMessage KEYWORD2 44 | endMessage KEYWORD2 45 | parseMessage KEYWORD2 46 | messageType KEYWORD2 47 | isFinal KEYWORD2 48 | readString KEYWORD2 49 | ping KEYWORD2 50 | 51 | encode KEYWORD2 52 | 53 | ####################################### 54 | # Constants (LITERAL1) 55 | ####################################### 56 | HTTP_SUCCESS LITERAL1 57 | HTTP_ERROR_CONNECTION_FAILED LITERAL1 58 | HTTP_ERROR_API LITERAL1 59 | HTTP_ERROR_TIMED_OUT LITERAL1 60 | HTTP_ERROR_INVALID_RESPONSE LITERAL1 61 | 62 | TYPE_CONTINUATION LITERAL1 63 | TYPE_TEXT LITERAL1 64 | TYPE_BINARY LITERAL1 65 | TYPE_CONNECTION_CLOSE LITERAL1 66 | TYPE_PING LITERAL1 67 | TYPE_PONG LITERAL1 68 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ArduinoHttpClient 2 | version=0.6.1 3 | author=Arduino 4 | maintainer=Arduino 5 | sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. 6 | paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. 7 | category=Communication 8 | url=https://github.com/arduino-libraries/ArduinoHttpClient 9 | architectures=* 10 | includes=ArduinoHttpClient.h 11 | -------------------------------------------------------------------------------- /src/ArduinoHttpClient.h: -------------------------------------------------------------------------------- 1 | // Library to simplify HTTP fetching on Arduino 2 | // (c) Copyright Arduino. 2016 3 | // Released under Apache License, version 2.0 4 | 5 | #ifndef ArduinoHttpClient_h 6 | #define ArduinoHttpClient_h 7 | 8 | #include "HttpClient.h" 9 | #include "WebSocketClient.h" 10 | #include "URLEncoder.h" 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/HttpClient.cpp: -------------------------------------------------------------------------------- 1 | // Class to simplify HTTP fetching on Arduino 2 | // (c) Copyright 2010-2011 MCQN Ltd 3 | // Released under Apache License, version 2.0 4 | 5 | #include "HttpClient.h" 6 | #include "b64.h" 7 | 8 | // Initialize constants 9 | const char* HttpClient::kUserAgent = "Arduino/2.2.0"; 10 | const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; 11 | const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED; 12 | 13 | HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) 14 | : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), 15 | iConnectionClose(true), iSendDefaultRequestHeaders(true) 16 | { 17 | resetState(); 18 | } 19 | 20 | HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort) 21 | : HttpClient(aClient, aServerName.c_str(), aServerPort) 22 | { 23 | } 24 | 25 | HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) 26 | : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), 27 | iConnectionClose(true), iSendDefaultRequestHeaders(true) 28 | { 29 | resetState(); 30 | } 31 | 32 | void HttpClient::resetState() 33 | { 34 | iState = eIdle; 35 | iStatusCode = 0; 36 | iContentLength = kNoContentLengthHeader; 37 | iBodyLengthConsumed = 0; 38 | iContentLengthPtr = kContentLengthPrefix; 39 | iTransferEncodingChunkedPtr = kTransferEncodingChunked; 40 | iIsChunked = false; 41 | iChunkLength = 0; 42 | iHttpResponseTimeout = kHttpResponseTimeout; 43 | iHttpWaitForDataDelay = kHttpWaitForDataDelay; 44 | } 45 | 46 | void HttpClient::stop() 47 | { 48 | iClient->stop(); 49 | resetState(); 50 | } 51 | 52 | void HttpClient::connectionKeepAlive() 53 | { 54 | iConnectionClose = false; 55 | } 56 | 57 | void HttpClient::noDefaultRequestHeaders() 58 | { 59 | iSendDefaultRequestHeaders = false; 60 | } 61 | 62 | void HttpClient::beginRequest() 63 | { 64 | iState = eRequestStarted; 65 | } 66 | 67 | int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, 68 | const char* aContentType, int aContentLength, const byte aBody[]) 69 | { 70 | if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk) 71 | { 72 | flushClientRx(); 73 | 74 | resetState(); 75 | } 76 | 77 | tHttpState initialState = iState; 78 | 79 | if ((eIdle != iState) && (eRequestStarted != iState)) 80 | { 81 | return HTTP_ERROR_API; 82 | } 83 | 84 | if (iConnectionClose || !iClient->connected()) 85 | { 86 | if (iServerName) 87 | { 88 | if (!(iClient->connect(iServerName, iServerPort) > 0)) 89 | { 90 | #ifdef LOGGING 91 | Serial.println("Connection failed"); 92 | #endif 93 | return HTTP_ERROR_CONNECTION_FAILED; 94 | } 95 | } 96 | else 97 | { 98 | if (!(iClient->connect(iServerAddress, iServerPort) > 0)) 99 | { 100 | #ifdef LOGGING 101 | Serial.println("Connection failed"); 102 | #endif 103 | return HTTP_ERROR_CONNECTION_FAILED; 104 | } 105 | } 106 | } 107 | else 108 | { 109 | #ifdef LOGGING 110 | Serial.println("Connection already open"); 111 | #endif 112 | } 113 | 114 | // Now we're connected, send the first part of the request 115 | int ret = sendInitialHeaders(aURLPath, aHttpMethod); 116 | 117 | if (HTTP_SUCCESS == ret) 118 | { 119 | if (aContentType) 120 | { 121 | sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType); 122 | } 123 | 124 | if (aContentLength > 0) 125 | { 126 | sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); 127 | } 128 | 129 | bool hasBody = (aBody && aContentLength > 0); 130 | 131 | if (initialState == eIdle || hasBody) 132 | { 133 | // This was a simple version of the API, so terminate the headers now 134 | finishHeaders(); 135 | } 136 | // else we'll call it in endRequest or in the first call to print, etc. 137 | 138 | if (hasBody) 139 | { 140 | write(aBody, aContentLength); 141 | } 142 | } 143 | 144 | return ret; 145 | } 146 | 147 | int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod) 148 | { 149 | #ifdef LOGGING 150 | Serial.println("Connected"); 151 | #endif 152 | // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" 153 | iClient->print(aHttpMethod); 154 | iClient->print(" "); 155 | 156 | iClient->print(aURLPath); 157 | iClient->println(" HTTP/1.1"); 158 | if (iSendDefaultRequestHeaders) 159 | { 160 | // The host header, if required 161 | if (iServerName) 162 | { 163 | iClient->print("Host: "); 164 | iClient->print(iServerName); 165 | if (iServerPort != kHttpPort && iServerPort != kHttpsPort) 166 | { 167 | iClient->print(":"); 168 | iClient->print(iServerPort); 169 | } 170 | iClient->println(); 171 | } 172 | // And user-agent string 173 | sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); 174 | } 175 | 176 | if (iConnectionClose) 177 | { 178 | // Tell the server to 179 | // close this connection after we're done 180 | sendHeader(HTTP_HEADER_CONNECTION, "close"); 181 | } 182 | 183 | // Everything has gone well 184 | iState = eRequestStarted; 185 | return HTTP_SUCCESS; 186 | } 187 | 188 | void HttpClient::sendHeader(const char* aHeader) 189 | { 190 | iClient->println(aHeader); 191 | } 192 | 193 | void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue) 194 | { 195 | iClient->print(aHeaderName); 196 | iClient->print(": "); 197 | iClient->println(aHeaderValue); 198 | } 199 | 200 | void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue) 201 | { 202 | iClient->print(aHeaderName); 203 | iClient->print(": "); 204 | iClient->println(aHeaderValue); 205 | } 206 | 207 | void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword) 208 | { 209 | // Send the initial part of this header line 210 | iClient->print("Authorization: Basic "); 211 | // Now Base64 encode "aUser:aPassword" and send that 212 | // This seems trickier than it should be but it's mostly to avoid either 213 | // (a) some arbitrarily sized buffer which hopes to be big enough, or 214 | // (b) allocating and freeing memory 215 | // ...so we'll loop through 3 bytes at a time, outputting the results as we 216 | // go. 217 | // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data 218 | unsigned char input[3]; 219 | unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print 220 | int userLen = strlen(aUser); 221 | int passwordLen = strlen(aPassword); 222 | int inputOffset = 0; 223 | for (int i = 0; i < (userLen+1+passwordLen); i++) 224 | { 225 | // Copy the relevant input byte into the input 226 | if (i < userLen) 227 | { 228 | input[inputOffset++] = aUser[i]; 229 | } 230 | else if (i == userLen) 231 | { 232 | input[inputOffset++] = ':'; 233 | } 234 | else 235 | { 236 | input[inputOffset++] = aPassword[i-(userLen+1)]; 237 | } 238 | // See if we've got a chunk to encode 239 | if ( (inputOffset == 3) || (i == userLen+passwordLen) ) 240 | { 241 | // We've either got to a 3-byte boundary, or we've reached then end 242 | b64_encode(input, inputOffset, output, 4); 243 | // NUL-terminate the output string 244 | output[4] = '\0'; 245 | // And write it out 246 | iClient->print((char*)output); 247 | // FIXME We might want to fill output with '=' characters if b64_encode doesn't 248 | // FIXME do it for us when we're encoding the final chunk 249 | inputOffset = 0; 250 | } 251 | } 252 | // And end the header we've sent 253 | iClient->println(); 254 | } 255 | 256 | void HttpClient::finishHeaders() 257 | { 258 | iClient->println(); 259 | iState = eRequestSent; 260 | } 261 | 262 | void HttpClient::flushClientRx() 263 | { 264 | while (iClient->available()) 265 | { 266 | iClient->read(); 267 | } 268 | } 269 | 270 | void HttpClient::endRequest() 271 | { 272 | beginBody(); 273 | } 274 | 275 | void HttpClient::beginBody() 276 | { 277 | if (iState < eRequestSent) 278 | { 279 | // We still need to finish off the headers 280 | finishHeaders(); 281 | } 282 | // else the end of headers has already been sent, so nothing to do here 283 | } 284 | 285 | int HttpClient::get(const char* aURLPath) 286 | { 287 | return startRequest(aURLPath, HTTP_METHOD_GET); 288 | } 289 | 290 | int HttpClient::get(const String& aURLPath) 291 | { 292 | return get(aURLPath.c_str()); 293 | } 294 | 295 | int HttpClient::post(const char* aURLPath) 296 | { 297 | return startRequest(aURLPath, HTTP_METHOD_POST); 298 | } 299 | 300 | int HttpClient::post(const String& aURLPath) 301 | { 302 | return post(aURLPath.c_str()); 303 | } 304 | 305 | int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody) 306 | { 307 | return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); 308 | } 309 | 310 | int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody) 311 | { 312 | return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); 313 | } 314 | 315 | int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) 316 | { 317 | return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody); 318 | } 319 | 320 | int HttpClient::put(const char* aURLPath) 321 | { 322 | return startRequest(aURLPath, HTTP_METHOD_PUT); 323 | } 324 | 325 | int HttpClient::put(const String& aURLPath) 326 | { 327 | return put(aURLPath.c_str()); 328 | } 329 | 330 | int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody) 331 | { 332 | return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); 333 | } 334 | 335 | int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody) 336 | { 337 | return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); 338 | } 339 | 340 | int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) 341 | { 342 | return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); 343 | } 344 | 345 | int HttpClient::patch(const char* aURLPath) 346 | { 347 | return startRequest(aURLPath, HTTP_METHOD_PATCH); 348 | } 349 | 350 | int HttpClient::patch(const String& aURLPath) 351 | { 352 | return patch(aURLPath.c_str()); 353 | } 354 | 355 | int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody) 356 | { 357 | return patch(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); 358 | } 359 | 360 | int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody) 361 | { 362 | return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); 363 | } 364 | 365 | int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) 366 | { 367 | return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody); 368 | } 369 | 370 | int HttpClient::del(const char* aURLPath) 371 | { 372 | return startRequest(aURLPath, HTTP_METHOD_DELETE); 373 | } 374 | 375 | int HttpClient::del(const String& aURLPath) 376 | { 377 | return del(aURLPath.c_str()); 378 | } 379 | 380 | int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody) 381 | { 382 | return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); 383 | } 384 | 385 | int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody) 386 | { 387 | return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); 388 | } 389 | 390 | int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) 391 | { 392 | return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody); 393 | } 394 | 395 | int HttpClient::responseStatusCode() 396 | { 397 | if (iState < eRequestSent) 398 | { 399 | return HTTP_ERROR_API; 400 | } 401 | // The first line will be of the form Status-Line: 402 | // HTTP-Version SP Status-Code SP Reason-Phrase CRLF 403 | // Where HTTP-Version is of the form: 404 | // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT 405 | 406 | int c = '\0'; 407 | do 408 | { 409 | // Make sure the status code is reset, and likewise the state. This 410 | // lets us easily cope with 1xx informational responses by just 411 | // ignoring them really, and reading the next line for a proper response 412 | iStatusCode = 0; 413 | iState = eRequestSent; 414 | 415 | unsigned long timeoutStart = millis(); 416 | // Psuedo-regexp we're expecting before the status-code 417 | const char* statusPrefix = "HTTP/*.* "; 418 | const char* statusPtr = statusPrefix; 419 | // Whilst we haven't timed out & haven't reached the end of the headers 420 | while ((c != '\n') && 421 | ( (millis() - timeoutStart) < iHttpResponseTimeout )) 422 | { 423 | if (available()) 424 | { 425 | c = HttpClient::read(); 426 | if (c != -1) 427 | { 428 | switch(iState) 429 | { 430 | case eRequestSent: 431 | // We haven't reached the status code yet 432 | if ( (*statusPtr == '*') || (*statusPtr == c) ) 433 | { 434 | // This character matches, just move along 435 | statusPtr++; 436 | if (*statusPtr == '\0') 437 | { 438 | // We've reached the end of the prefix 439 | iState = eReadingStatusCode; 440 | } 441 | } 442 | else 443 | { 444 | return HTTP_ERROR_INVALID_RESPONSE; 445 | } 446 | break; 447 | case eReadingStatusCode: 448 | if (isdigit(c)) 449 | { 450 | // This assumes we won't get more than the 3 digits we 451 | // want 452 | iStatusCode = iStatusCode*10 + (c - '0'); 453 | } 454 | else 455 | { 456 | // We've reached the end of the status code 457 | // We could sanity check it here or double-check for ' ' 458 | // rather than anything else, but let's be lenient 459 | iState = eStatusCodeRead; 460 | } 461 | break; 462 | case eStatusCodeRead: 463 | // We're just waiting for the end of the line now 464 | break; 465 | 466 | default: 467 | break; 468 | }; 469 | // We read something, reset the timeout counter 470 | timeoutStart = millis(); 471 | } 472 | } 473 | else 474 | { 475 | // We haven't got any data, so let's pause to allow some to 476 | // arrive 477 | delay(iHttpWaitForDataDelay); 478 | } 479 | } 480 | if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) ) 481 | { 482 | // We've reached the end of an informational status line 483 | c = '\0'; // Clear c so we'll go back into the data reading loop 484 | } 485 | } 486 | // If we've read a status code successfully but it's informational (1xx) 487 | // loop back to the start 488 | while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) ); 489 | 490 | if ( (c == '\n') && (iState == eStatusCodeRead) ) 491 | { 492 | // We've read the status-line successfully 493 | return iStatusCode; 494 | } 495 | else if (c != '\n') 496 | { 497 | // We must've timed out before we reached the end of the line 498 | return HTTP_ERROR_TIMED_OUT; 499 | } 500 | else 501 | { 502 | // This wasn't a properly formed status line, or at least not one we 503 | // could understand 504 | return HTTP_ERROR_INVALID_RESPONSE; 505 | } 506 | } 507 | 508 | int HttpClient::skipResponseHeaders() 509 | { 510 | // Just keep reading until we finish reading the headers or time out 511 | unsigned long timeoutStart = millis(); 512 | // Whilst we haven't timed out & haven't reached the end of the headers 513 | while ((!endOfHeadersReached()) && 514 | ( (millis() - timeoutStart) < iHttpResponseTimeout )) 515 | { 516 | if (available()) 517 | { 518 | (void)readHeader(); 519 | // We read something, reset the timeout counter 520 | timeoutStart = millis(); 521 | } 522 | else 523 | { 524 | // We haven't got any data, so let's pause to allow some to 525 | // arrive 526 | delay(iHttpWaitForDataDelay); 527 | } 528 | } 529 | if (endOfHeadersReached()) 530 | { 531 | // Success 532 | return HTTP_SUCCESS; 533 | } 534 | else 535 | { 536 | // We must've timed out 537 | return HTTP_ERROR_TIMED_OUT; 538 | } 539 | } 540 | 541 | bool HttpClient::endOfHeadersReached() 542 | { 543 | return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); 544 | }; 545 | 546 | long HttpClient::contentLength() 547 | { 548 | // skip the response headers, if they haven't been read already 549 | if (!endOfHeadersReached()) 550 | { 551 | skipResponseHeaders(); 552 | } 553 | 554 | return iContentLength; 555 | } 556 | 557 | String HttpClient::responseBody() 558 | { 559 | int bodyLength = contentLength(); 560 | String response; 561 | 562 | if (bodyLength > 0) 563 | { 564 | // try to reserve bodyLength bytes 565 | if (response.reserve(bodyLength) == 0) { 566 | // String reserve failed 567 | return String((const char*)NULL); 568 | } 569 | } 570 | 571 | // keep on timedRead'ing, until: 572 | // - we have a content length: body length equals consumed or no bytes 573 | // available 574 | // - no content length: no bytes are available 575 | while (iBodyLengthConsumed != bodyLength) 576 | { 577 | int c = timedRead(); 578 | 579 | if (c == -1) { 580 | // read timed out, done 581 | break; 582 | } 583 | 584 | if (!response.concat((char)c)) { 585 | // adding char failed 586 | return String((const char*)NULL); 587 | } 588 | } 589 | 590 | if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) { 591 | // failure, we did not read in response content length bytes 592 | return String((const char*)NULL); 593 | } 594 | 595 | return response; 596 | } 597 | 598 | bool HttpClient::endOfBodyReached() 599 | { 600 | if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) 601 | { 602 | // We've got to the body and we know how long it will be 603 | return (iBodyLengthConsumed >= contentLength()); 604 | } 605 | return false; 606 | } 607 | 608 | int HttpClient::available() 609 | { 610 | if (iState == eReadingChunkLength) 611 | { 612 | while (iClient->available()) 613 | { 614 | char c = iClient->read(); 615 | 616 | if (c == '\n') 617 | { 618 | iState = eReadingBodyChunk; 619 | break; 620 | } 621 | else if (c == '\r') 622 | { 623 | // no-op 624 | } 625 | else if (isHexadecimalDigit(c)) 626 | { 627 | char digit[2] = {c, '\0'}; 628 | 629 | iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16); 630 | } 631 | } 632 | } 633 | 634 | if (iState == eReadingBodyChunk && iChunkLength == 0) 635 | { 636 | iState = eReadingChunkLength; 637 | } 638 | 639 | if (iState == eReadingChunkLength) 640 | { 641 | return 0; 642 | } 643 | 644 | int clientAvailable = iClient->available(); 645 | 646 | if (iState == eReadingBodyChunk) 647 | { 648 | return min(clientAvailable, iChunkLength); 649 | } 650 | else 651 | { 652 | return clientAvailable; 653 | } 654 | } 655 | 656 | 657 | int HttpClient::read() 658 | { 659 | if (iIsChunked && !available()) 660 | { 661 | return -1; 662 | } 663 | 664 | int ret = iClient->read(); 665 | if (ret >= 0) 666 | { 667 | if (endOfHeadersReached() && iContentLength > 0) 668 | { 669 | // We're outputting the body now and we've seen a Content-Length header 670 | // So keep track of how many bytes are left 671 | iBodyLengthConsumed++; 672 | } 673 | 674 | if (iState == eReadingBodyChunk) 675 | { 676 | iChunkLength--; 677 | 678 | if (iChunkLength == 0) 679 | { 680 | iState = eReadingChunkLength; 681 | } 682 | } 683 | } 684 | return ret; 685 | } 686 | 687 | bool HttpClient::headerAvailable() 688 | { 689 | // clear the currently stored header line 690 | iHeaderLine = ""; 691 | 692 | while (!endOfHeadersReached()) 693 | { 694 | // read a byte from the header 695 | int c = readHeader(); 696 | 697 | if (c == '\r' || c == '\n') 698 | { 699 | if (iHeaderLine.length()) 700 | { 701 | // end of the line, all done 702 | break; 703 | } 704 | else 705 | { 706 | // ignore any CR or LF characters 707 | continue; 708 | } 709 | } 710 | 711 | // append byte to header line 712 | iHeaderLine += (char)c; 713 | } 714 | 715 | return (iHeaderLine.length() > 0); 716 | } 717 | 718 | String HttpClient::readHeaderName() 719 | { 720 | int colonIndex = iHeaderLine.indexOf(':'); 721 | 722 | if (colonIndex == -1) 723 | { 724 | return ""; 725 | } 726 | 727 | return iHeaderLine.substring(0, colonIndex); 728 | } 729 | 730 | String HttpClient::readHeaderValue() 731 | { 732 | int colonIndex = iHeaderLine.indexOf(':'); 733 | int startIndex = colonIndex + 1; 734 | 735 | if (colonIndex == -1) 736 | { 737 | return ""; 738 | } 739 | 740 | // trim any leading whitespace 741 | while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex])) 742 | { 743 | startIndex++; 744 | } 745 | 746 | return iHeaderLine.substring(startIndex); 747 | } 748 | 749 | int HttpClient::read(uint8_t *buf, size_t size) 750 | { 751 | int ret =iClient->read(buf, size); 752 | if (endOfHeadersReached() && iContentLength > 0) 753 | { 754 | // We're outputting the body now and we've seen a Content-Length header 755 | // So keep track of how many bytes are left 756 | if (ret >= 0) 757 | { 758 | iBodyLengthConsumed += ret; 759 | } 760 | } 761 | return ret; 762 | } 763 | 764 | int HttpClient::readHeader() 765 | { 766 | char c = HttpClient::read(); 767 | 768 | if (endOfHeadersReached()) 769 | { 770 | // We've passed the headers, but rather than return an error, we'll just 771 | // act as a slightly less efficient version of read() 772 | return c; 773 | } 774 | 775 | // Whilst reading out the headers to whoever wants them, we'll keep an 776 | // eye out for the "Content-Length" header 777 | switch(iState) 778 | { 779 | case eStatusCodeRead: 780 | // We're at the start of a line, or somewhere in the middle of reading 781 | // the Content-Length prefix 782 | if (*iContentLengthPtr == c) 783 | { 784 | // This character matches, just move along 785 | iContentLengthPtr++; 786 | if (*iContentLengthPtr == '\0') 787 | { 788 | // We've reached the end of the prefix 789 | iState = eReadingContentLength; 790 | // Just in case we get multiple Content-Length headers, this 791 | // will ensure we just get the value of the last one 792 | iContentLength = 0; 793 | iBodyLengthConsumed = 0; 794 | } 795 | } 796 | else if (*iTransferEncodingChunkedPtr == c) 797 | { 798 | // This character matches, just move along 799 | iTransferEncodingChunkedPtr++; 800 | if (*iTransferEncodingChunkedPtr == '\0') 801 | { 802 | // We've reached the end of the Transfer Encoding: chunked header 803 | iIsChunked = true; 804 | iState = eSkipToEndOfHeader; 805 | } 806 | } 807 | else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r')) 808 | { 809 | // We've found a '\r' at the start of a line, so this is probably 810 | // the end of the headers 811 | iState = eLineStartingCRFound; 812 | } 813 | else 814 | { 815 | // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line 816 | iState = eSkipToEndOfHeader; 817 | } 818 | break; 819 | case eReadingContentLength: 820 | if (isdigit(c)) 821 | { 822 | long _iContentLength = iContentLength*10 + (c - '0'); 823 | // Only apply if the value didn't wrap around 824 | if (_iContentLength > iContentLength) { 825 | iContentLength = _iContentLength; 826 | } 827 | } 828 | else 829 | { 830 | // We've reached the end of the content length 831 | // We could sanity check it here or double-check for "\r\n" 832 | // rather than anything else, but let's be lenient 833 | iState = eSkipToEndOfHeader; 834 | } 835 | break; 836 | case eLineStartingCRFound: 837 | if (c == '\n') 838 | { 839 | if (iIsChunked) 840 | { 841 | iState = eReadingChunkLength; 842 | iChunkLength = 0; 843 | } 844 | else 845 | { 846 | iState = eReadingBody; 847 | } 848 | } 849 | break; 850 | default: 851 | // We're just waiting for the end of the line now 852 | break; 853 | }; 854 | 855 | if ( (c == '\n') && !endOfHeadersReached() ) 856 | { 857 | // We've got to the end of this line, start processing again 858 | iState = eStatusCodeRead; 859 | iContentLengthPtr = kContentLengthPrefix; 860 | iTransferEncodingChunkedPtr = kTransferEncodingChunked; 861 | } 862 | // And return the character read to whoever wants it 863 | return c; 864 | } 865 | 866 | 867 | 868 | -------------------------------------------------------------------------------- /src/HttpClient.h: -------------------------------------------------------------------------------- 1 | // Class to simplify HTTP fetching on Arduino 2 | // (c) Copyright MCQN Ltd. 2010-2012 3 | // Released under Apache License, version 2.0 4 | 5 | #ifndef HttpClient_h 6 | #define HttpClient_h 7 | 8 | #include 9 | #include 10 | #include "Client.h" 11 | 12 | static const int HTTP_SUCCESS =0; 13 | // The end of the headers has been reached. This consumes the '\n' 14 | // Could not connect to the server 15 | static const int HTTP_ERROR_CONNECTION_FAILED =-1; 16 | // This call was made when the HttpClient class wasn't expecting it 17 | // to be called. Usually indicates your code is using the class 18 | // incorrectly 19 | static const int HTTP_ERROR_API =-2; 20 | // Spent too long waiting for a reply 21 | static const int HTTP_ERROR_TIMED_OUT =-3; 22 | // The response from the server is invalid, is it definitely an HTTP 23 | // server? 24 | static const int HTTP_ERROR_INVALID_RESPONSE =-4; 25 | 26 | // Define some of the common methods and headers here 27 | // That lets other code reuse them without having to declare another copy 28 | // of them, so saves code space and RAM 29 | #define HTTP_METHOD_GET "GET" 30 | #define HTTP_METHOD_POST "POST" 31 | #define HTTP_METHOD_PUT "PUT" 32 | #define HTTP_METHOD_PATCH "PATCH" 33 | #define HTTP_METHOD_DELETE "DELETE" 34 | #define HTTP_HEADER_CONTENT_LENGTH "Content-Length" 35 | #define HTTP_HEADER_CONTENT_TYPE "Content-Type" 36 | #define HTTP_HEADER_CONNECTION "Connection" 37 | #define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" 38 | #define HTTP_HEADER_USER_AGENT "User-Agent" 39 | #define HTTP_HEADER_VALUE_CHUNKED "chunked" 40 | 41 | class HttpClient : public Client 42 | { 43 | public: 44 | static const int kNoContentLengthHeader =-1; 45 | static const int kHttpPort =80; 46 | static const int kHttpsPort =443; 47 | static const char* kUserAgent; 48 | 49 | // FIXME Write longer API request, using port and user-agent, example 50 | // FIXME Update tempToPachube example to calculate Content-Length correctly 51 | 52 | HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort); 53 | HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); 54 | HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); 55 | 56 | /** Start a more complex request. 57 | Use this when you need to send additional headers in the request, 58 | but you will also need to call endRequest() when you are finished. 59 | */ 60 | void beginRequest(); 61 | 62 | /** End a more complex request. 63 | Use this when you need to have sent additional headers in the request, 64 | but you will also need to call beginRequest() at the start. 65 | */ 66 | void endRequest(); 67 | 68 | /** Start the body of a more complex request. 69 | Use this when you need to send the body after additional headers 70 | in the request, but can optionally call endRequest() when 71 | you are finished. 72 | */ 73 | void beginBody(); 74 | 75 | /** Connect to the server and start to send a GET request. 76 | @param aURLPath Url to request 77 | @return 0 if successful, else error 78 | */ 79 | int get(const char* aURLPath); 80 | int get(const String& aURLPath); 81 | 82 | /** Connect to the server and start to send a POST request. 83 | @param aURLPath Url to request 84 | @return 0 if successful, else error 85 | */ 86 | int post(const char* aURLPath); 87 | int post(const String& aURLPath); 88 | 89 | /** Connect to the server and send a POST request 90 | with body and content type 91 | @param aURLPath Url to request 92 | @param aContentType Content type of request body 93 | @param aBody Body of the request 94 | @return 0 if successful, else error 95 | */ 96 | int post(const char* aURLPath, const char* aContentType, const char* aBody); 97 | int post(const String& aURLPath, const String& aContentType, const String& aBody); 98 | int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); 99 | 100 | /** Connect to the server and start to send a PUT request. 101 | @param aURLPath Url to request 102 | @return 0 if successful, else error 103 | */ 104 | int put(const char* aURLPath); 105 | int put(const String& aURLPath); 106 | 107 | /** Connect to the server and send a PUT request 108 | with body and content type 109 | @param aURLPath Url to request 110 | @param aContentType Content type of request body 111 | @param aBody Body of the request 112 | @return 0 if successful, else error 113 | */ 114 | int put(const char* aURLPath, const char* aContentType, const char* aBody); 115 | int put(const String& aURLPath, const String& aContentType, const String& aBody); 116 | int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); 117 | 118 | /** Connect to the server and start to send a PATCH request. 119 | @param aURLPath Url to request 120 | @return 0 if successful, else error 121 | */ 122 | int patch(const char* aURLPath); 123 | int patch(const String& aURLPath); 124 | 125 | /** Connect to the server and send a PATCH request 126 | with body and content type 127 | @param aURLPath Url to request 128 | @param aContentType Content type of request body 129 | @param aBody Body of the request 130 | @return 0 if successful, else error 131 | */ 132 | int patch(const char* aURLPath, const char* aContentType, const char* aBody); 133 | int patch(const String& aURLPath, const String& aContentType, const String& aBody); 134 | int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); 135 | 136 | /** Connect to the server and start to send a DELETE request. 137 | @param aURLPath Url to request 138 | @return 0 if successful, else error 139 | */ 140 | int del(const char* aURLPath); 141 | int del(const String& aURLPath); 142 | 143 | /** Connect to the server and send a DELETE request 144 | with body and content type 145 | @param aURLPath Url to request 146 | @param aContentType Content type of request body 147 | @param aBody Body of the request 148 | @return 0 if successful, else error 149 | */ 150 | int del(const char* aURLPath, const char* aContentType, const char* aBody); 151 | int del(const String& aURLPath, const String& aContentType, const String& aBody); 152 | int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); 153 | 154 | /** Connect to the server and start to send the request. 155 | If a body is provided, the entire request (including headers and body) will be sent 156 | @param aURLPath Url to request 157 | @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. 158 | @param aContentType Content type of request body (optional) 159 | @param aContentLength Length of request body (optional) 160 | @param aBody Body of request (optional) 161 | @return 0 if successful, else error 162 | */ 163 | int startRequest(const char* aURLPath, 164 | const char* aHttpMethod, 165 | const char* aContentType = NULL, 166 | int aContentLength = -1, 167 | const byte aBody[] = NULL); 168 | 169 | /** Send an additional header line. This can only be called in between the 170 | calls to beginRequest and endRequest. 171 | @param aHeader Header line to send, in its entirety (but without the 172 | trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" 173 | */ 174 | void sendHeader(const char* aHeader); 175 | 176 | void sendHeader(const String& aHeader) 177 | { sendHeader(aHeader.c_str()); } 178 | 179 | /** Send an additional header line. This is an alternate form of 180 | sendHeader() which takes the header name and content as separate strings. 181 | The call will add the ": " to separate the header, so for example, to 182 | send a XXXXXX header call sendHeader("XXXXX", "Something") 183 | @param aHeaderName Type of header being sent 184 | @param aHeaderValue Value for that header 185 | */ 186 | void sendHeader(const char* aHeaderName, const char* aHeaderValue); 187 | 188 | void sendHeader(const String& aHeaderName, const String& aHeaderValue) 189 | { sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); } 190 | 191 | /** Send an additional header line. This is an alternate form of 192 | sendHeader() which takes the header name and content separately but where 193 | the value is provided as an integer. 194 | The call will add the ": " to separate the header, so for example, to 195 | send a XXXXXX header call sendHeader("XXXXX", 123) 196 | @param aHeaderName Type of header being sent 197 | @param aHeaderValue Value for that header 198 | */ 199 | void sendHeader(const char* aHeaderName, const int aHeaderValue); 200 | 201 | void sendHeader(const String& aHeaderName, const int aHeaderValue) 202 | { sendHeader(aHeaderName.c_str(), aHeaderValue); } 203 | 204 | /** Send a basic authentication header. This will encode the given username 205 | and password, and send them in suitable header line for doing Basic 206 | Authentication. 207 | @param aUser Username for the authorization 208 | @param aPassword Password for the user aUser 209 | */ 210 | void sendBasicAuth(const char* aUser, const char* aPassword); 211 | 212 | void sendBasicAuth(const String& aUser, const String& aPassword) 213 | { sendBasicAuth(aUser.c_str(), aPassword.c_str()); } 214 | 215 | /** Get the HTTP status code contained in the response. 216 | For example, 200 for successful request, 404 for file not found, etc. 217 | */ 218 | int responseStatusCode(); 219 | 220 | /** Check if a header is available to be read. 221 | Use readHeaderName() to read header name, and readHeaderValue() to 222 | read the header value 223 | MUST be called after responseStatusCode() and before contentLength() 224 | */ 225 | bool headerAvailable(); 226 | 227 | /** Read the name of the current response header. 228 | Returns empty string if a header is not available. 229 | */ 230 | String readHeaderName(); 231 | 232 | /** Read the value of the current response header. 233 | Returns empty string if a header is not available. 234 | */ 235 | String readHeaderValue(); 236 | 237 | /** Read the next character of the response headers. 238 | This functions in the same way as read() but to be used when reading 239 | through the headers. Check whether or not the end of the headers has 240 | been reached by calling endOfHeadersReached(), although after that point 241 | this will still return data as read() would, but slightly less efficiently 242 | MUST be called after responseStatusCode() and before contentLength() 243 | @return The next character of the response headers 244 | */ 245 | int readHeader(); 246 | 247 | /** Skip any response headers to get to the body. 248 | Use this if you don't want to do any special processing of the headers 249 | returned in the response. You can also use it after you've found all of 250 | the headers you're interested in, and just want to get on with processing 251 | the body. 252 | MUST be called after responseStatusCode() 253 | @return HTTP_SUCCESS if successful, else an error code 254 | */ 255 | int skipResponseHeaders(); 256 | 257 | /** Test whether all of the response headers have been consumed. 258 | @return true if we are now processing the response body, else false 259 | */ 260 | bool endOfHeadersReached(); 261 | 262 | /** Test whether the end of the body has been reached. 263 | Only works if the Content-Length header was returned by the server 264 | @return true if we are now at the end of the body, else false 265 | */ 266 | bool endOfBodyReached(); 267 | virtual bool endOfStream() { return endOfBodyReached(); }; 268 | virtual bool completed() { return endOfBodyReached(); }; 269 | 270 | /** Return the length of the body. 271 | Also skips response headers if they have not been read already 272 | MUST be called after responseStatusCode() 273 | @return Length of the body, in bytes, or kNoContentLengthHeader if no 274 | Content-Length header was returned by the server 275 | */ 276 | long contentLength(); 277 | 278 | /** Returns if the response body is chunked 279 | @return true if response body is chunked, false otherwise 280 | */ 281 | int isResponseChunked() { return iIsChunked; } 282 | 283 | /** Return the response body as a String 284 | Also skips response headers if they have not been read already 285 | MUST be called after responseStatusCode() 286 | @return response body of request as a String 287 | */ 288 | String responseBody(); 289 | 290 | /** Enables connection keep-alive mode 291 | */ 292 | void connectionKeepAlive(); 293 | 294 | /** Disables sending the default request headers (Host and User Agent) 295 | */ 296 | void noDefaultRequestHeaders(); 297 | 298 | // Inherited from Print 299 | // Note: 1st call to these indicates the user is sending the body, so if need 300 | // Note: be we should finish the header first 301 | virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); }; 302 | virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); }; 303 | // Inherited from Stream 304 | virtual int available(); 305 | /** Read the next byte from the server. 306 | @return Byte read or -1 if there are no bytes available. 307 | */ 308 | virtual int read(); 309 | virtual int read(uint8_t *buf, size_t size); 310 | virtual int peek() { return iClient->peek(); }; 311 | virtual void flush() { iClient->flush(); }; 312 | 313 | // Inherited from Client 314 | virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); }; 315 | virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); }; 316 | virtual void stop(); 317 | virtual uint8_t connected() { return iClient->connected(); }; 318 | virtual operator bool() { return bool(iClient); }; 319 | virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; }; 320 | virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; }; 321 | virtual uint32_t httpWaitForDataDelay() { return iHttpWaitForDataDelay; }; 322 | virtual void setHttpWaitForDataDelay(uint32_t delay) { iHttpWaitForDataDelay = delay; }; 323 | protected: 324 | /** Reset internal state data back to the "just initialised" state 325 | */ 326 | void resetState(); 327 | 328 | /** Send the first part of the request and the initial headers. 329 | @param aURLPath Url to request 330 | @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. 331 | @return 0 if successful, else error 332 | */ 333 | int sendInitialHeaders(const char* aURLPath, 334 | const char* aHttpMethod); 335 | 336 | /* Let the server know that we've reached the end of the headers 337 | */ 338 | void finishHeaders(); 339 | 340 | /** Reading any pending data from the client (used in connection keep alive mode) 341 | */ 342 | void flushClientRx(); 343 | 344 | // Number of milliseconds that we wait each time there isn't any data 345 | // available to be read (during status code and header processing) 346 | static const int kHttpWaitForDataDelay = 100; 347 | // Number of milliseconds that we'll wait in total without receiving any 348 | // data before returning HTTP_ERROR_TIMED_OUT (during status code and header 349 | // processing) 350 | static const int kHttpResponseTimeout = 30*1000; 351 | static const char* kContentLengthPrefix; 352 | static const char* kTransferEncodingChunked; 353 | typedef enum { 354 | eIdle, 355 | eRequestStarted, 356 | eRequestSent, 357 | eReadingStatusCode, 358 | eStatusCodeRead, 359 | eReadingContentLength, 360 | eSkipToEndOfHeader, 361 | eLineStartingCRFound, 362 | eReadingBody, 363 | eReadingChunkLength, 364 | eReadingBodyChunk 365 | } tHttpState; 366 | // Client we're using 367 | Client* iClient; 368 | // Server we are connecting to 369 | const char* iServerName; 370 | IPAddress iServerAddress; 371 | // Port of server we are connecting to 372 | uint16_t iServerPort; 373 | // Current state of the finite-state-machine 374 | tHttpState iState; 375 | // Stores the status code for the response, once known 376 | int iStatusCode; 377 | // Stores the value of the Content-Length header, if present 378 | long iContentLength; 379 | // How many bytes of the response body have been read by the user 380 | int iBodyLengthConsumed; 381 | // How far through a Content-Length header prefix we are 382 | const char* iContentLengthPtr; 383 | // How far through a Transfer-Encoding chunked header we are 384 | const char* iTransferEncodingChunkedPtr; 385 | // Stores if the response body is chunked 386 | bool iIsChunked; 387 | // Stores the value of the current chunk length, if present 388 | int iChunkLength; 389 | uint32_t iHttpResponseTimeout; 390 | uint32_t iHttpWaitForDataDelay; 391 | bool iConnectionClose; 392 | bool iSendDefaultRequestHeaders; 393 | String iHeaderLine; 394 | }; 395 | 396 | #endif 397 | -------------------------------------------------------------------------------- /src/URLEncoder.cpp: -------------------------------------------------------------------------------- 1 | // Library to simplify HTTP fetching on Arduino 2 | // (c) Copyright Arduino. 2019 3 | // Released under Apache License, version 2.0 4 | 5 | #include "URLEncoder.h" 6 | 7 | URLEncoderClass::URLEncoderClass() 8 | { 9 | } 10 | 11 | URLEncoderClass::~URLEncoderClass() 12 | { 13 | } 14 | 15 | String URLEncoderClass::encode(const char* str) 16 | { 17 | return encode(str, strlen(str)); 18 | } 19 | 20 | String URLEncoderClass::encode(const String& str) 21 | { 22 | return encode(str.c_str(), str.length()); 23 | } 24 | 25 | String URLEncoderClass::encode(const char* str, int length) 26 | { 27 | String encoded; 28 | 29 | encoded.reserve(length); 30 | 31 | for (int i = 0; i < length; i++) { 32 | char c = str[i]; 33 | 34 | const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 35 | 36 | if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) { 37 | encoded += c; 38 | } else { 39 | char s[4]; 40 | 41 | s[0] = '%'; 42 | s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf]; 43 | s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)]; 44 | s[3] = 0; 45 | 46 | encoded += s; 47 | } 48 | } 49 | 50 | return encoded; 51 | } 52 | 53 | URLEncoderClass URLEncoder; 54 | -------------------------------------------------------------------------------- /src/URLEncoder.h: -------------------------------------------------------------------------------- 1 | // Library to simplify HTTP fetching on Arduino 2 | // (c) Copyright Arduino. 2019 3 | // Released under Apache License, version 2.0 4 | 5 | #ifndef URL_ENCODER_H 6 | #define URL_ENCODER_H 7 | 8 | #include 9 | 10 | class URLEncoderClass 11 | { 12 | public: 13 | URLEncoderClass(); 14 | virtual ~URLEncoderClass(); 15 | 16 | static String encode(const char* str); 17 | static String encode(const String& str); 18 | 19 | private: 20 | static String encode(const char* str, int length); 21 | }; 22 | 23 | extern URLEncoderClass URLEncoder; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/URLParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PackageLicenseDeclared: Apache-2.0 3 | * Copyright (c) 2017 ARM Limited 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * The following class is defined in mbed libraries, in case of STM32H7 include the original library 20 | */ 21 | #if defined __has_include 22 | # if __has_include() 23 | # include 24 | # else 25 | # define NO_HTTP_PARSED 26 | # endif 27 | #endif 28 | 29 | #ifdef NO_HTTP_PARSED 30 | #ifndef _MBED_HTTP_PARSED_URL_H_ 31 | #define _MBED_HTTP_PARSED_URL_H_ 32 | 33 | #include "utility/URLParser/http_parser.h" 34 | 35 | class ParsedUrl { 36 | public: 37 | ParsedUrl(const char* url) { 38 | struct http_parser_url parsed_url; 39 | http_parser_parse_url(url, strlen(url), false, &parsed_url); 40 | 41 | for (size_t ix = 0; ix < UF_MAX; ix++) { 42 | char* value; 43 | if (parsed_url.field_set & (1 << ix)) { 44 | value = (char*)calloc(parsed_url.field_data[ix].len + 1, 1); 45 | memcpy(value, url + parsed_url.field_data[ix].off, 46 | parsed_url.field_data[ix].len); 47 | } 48 | else { 49 | value = (char*)calloc(1, 1); 50 | } 51 | 52 | switch ((http_parser_url_fields)ix) { 53 | case UF_SCHEMA: _schema = value; break; 54 | case UF_HOST: _host = value; break; 55 | case UF_PATH: _path = value; break; 56 | case UF_QUERY: _query = value; break; 57 | case UF_USERINFO: _userinfo = value; break; 58 | default: 59 | // PORT is already parsed, FRAGMENT is not relevant for HTTP requests 60 | free(value); 61 | break; 62 | } 63 | } 64 | 65 | _port = parsed_url.port; 66 | if (!_port) { 67 | if (strcmp(_schema, "https") == 0 || strcmp(_schema, "wss") == 0) { 68 | _port = 443; 69 | } 70 | else { 71 | _port = 80; 72 | } 73 | } 74 | 75 | if (strcmp(_path, "") == 0) { 76 | free(_path); 77 | _path = (char*)calloc(2, 1); 78 | _path[0] = '/'; 79 | } 80 | } 81 | 82 | ~ParsedUrl() { 83 | if (_schema) free(_schema); 84 | if (_host) free(_host); 85 | if (_path) free(_path); 86 | if (_query) free(_query); 87 | if (_userinfo) free(_userinfo); 88 | } 89 | 90 | uint16_t port() const { return _port; } 91 | char* schema() const { return _schema; } 92 | char* host() const { return _host; } 93 | char* path() const { return _path; } 94 | char* query() const { return _query; } 95 | char* userinfo() const { return _userinfo; } 96 | 97 | private: 98 | uint16_t _port; 99 | char* _schema; 100 | char* _host; 101 | char* _path; 102 | char* _query; 103 | char* _userinfo; 104 | }; 105 | 106 | #endif // _MBED_HTTP_PARSED_URL_H_ 107 | #endif // NO_HTTP_PARSED 108 | #undef NO_HTTP_PARSED -------------------------------------------------------------------------------- /src/WebSocketClient.cpp: -------------------------------------------------------------------------------- 1 | // (c) Copyright Arduino. 2016 2 | // Released under Apache License, version 2.0 3 | 4 | #include "b64.h" 5 | 6 | #include "WebSocketClient.h" 7 | 8 | WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) 9 | : HttpClient(aClient, aServerName, aServerPort), 10 | iTxStarted(false), 11 | iRxSize(0) 12 | { 13 | } 14 | 15 | WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) 16 | : HttpClient(aClient, aServerName, aServerPort), 17 | iTxStarted(false), 18 | iRxSize(0) 19 | { 20 | } 21 | 22 | WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) 23 | : HttpClient(aClient, aServerAddress, aServerPort), 24 | iTxStarted(false), 25 | iRxSize(0) 26 | { 27 | } 28 | 29 | int WebSocketClient::begin(const char* aPath) 30 | { 31 | // start the GET request 32 | beginRequest(); 33 | connectionKeepAlive(); 34 | int status = get(aPath); 35 | 36 | if (status == 0) 37 | { 38 | uint8_t randomKey[16]; 39 | char base64RandomKey[25]; 40 | 41 | // create a random key for the connection upgrade 42 | for (int i = 0; i < (int)sizeof(randomKey); i++) 43 | { 44 | randomKey[i] = random(0x01, 0xff); 45 | } 46 | memset(base64RandomKey, 0x00, sizeof(base64RandomKey)); 47 | b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey)); 48 | 49 | // start the connection upgrade sequence 50 | sendHeader("Upgrade", "websocket"); 51 | sendHeader("Connection", "Upgrade"); 52 | sendHeader("Sec-WebSocket-Key", base64RandomKey); 53 | sendHeader("Sec-WebSocket-Version", "13"); 54 | endRequest(); 55 | 56 | status = responseStatusCode(); 57 | 58 | if (status > 0) 59 | { 60 | skipResponseHeaders(); 61 | } 62 | } 63 | 64 | iRxSize = 0; 65 | 66 | // status code of 101 means success 67 | return (status == 101) ? 0 : status; 68 | } 69 | 70 | int WebSocketClient::begin(const String& aPath) 71 | { 72 | return begin(aPath.c_str()); 73 | } 74 | 75 | int WebSocketClient::beginMessage(int aType) 76 | { 77 | if (iTxStarted) 78 | { 79 | // fail TX already started 80 | return 1; 81 | } 82 | 83 | iTxStarted = true; 84 | iTxMessageType = (aType & 0xf); 85 | iTxSize = 0; 86 | 87 | return 0; 88 | } 89 | 90 | int WebSocketClient::endMessage() 91 | { 92 | if (!iTxStarted) 93 | { 94 | // fail TX not started 95 | return 1; 96 | } 97 | 98 | // send FIN + the message type (opcode) 99 | HttpClient::write(0x80 | iTxMessageType); 100 | 101 | // the message is masked (0x80) 102 | // send the length 103 | if (iTxSize < 126) 104 | { 105 | HttpClient::write(0x80 | (uint8_t)iTxSize); 106 | } 107 | else if (iTxSize < 0xffff) 108 | { 109 | HttpClient::write(0x80 | 126); 110 | HttpClient::write((iTxSize >> 8) & 0xff); 111 | HttpClient::write((iTxSize >> 0) & 0xff); 112 | } 113 | else 114 | { 115 | HttpClient::write(0x80 | 127); 116 | HttpClient::write((iTxSize >> 56) & 0xff); 117 | HttpClient::write((iTxSize >> 48) & 0xff); 118 | HttpClient::write((iTxSize >> 40) & 0xff); 119 | HttpClient::write((iTxSize >> 32) & 0xff); 120 | HttpClient::write((iTxSize >> 24) & 0xff); 121 | HttpClient::write((iTxSize >> 16) & 0xff); 122 | HttpClient::write((iTxSize >> 8) & 0xff); 123 | HttpClient::write((iTxSize >> 0) & 0xff); 124 | } 125 | 126 | uint8_t maskKey[4]; 127 | 128 | // create a random mask for the data and send 129 | for (int i = 0; i < (int)sizeof(maskKey); i++) 130 | { 131 | maskKey[i] = random(0xff); 132 | } 133 | HttpClient::write(maskKey, sizeof(maskKey)); 134 | 135 | // mask the data and send 136 | for (int i = 0; i < (int)iTxSize; i++) { 137 | iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; 138 | } 139 | 140 | size_t txSize = iTxSize; 141 | 142 | iTxStarted = false; 143 | iTxSize = 0; 144 | 145 | return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1; 146 | } 147 | 148 | size_t WebSocketClient::write(uint8_t aByte) 149 | { 150 | return write(&aByte, sizeof(aByte)); 151 | } 152 | 153 | size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) 154 | { 155 | if (iState < eReadingBody) 156 | { 157 | // have not upgraded the connection yet 158 | return HttpClient::write(aBuffer, aSize); 159 | } 160 | 161 | if (!iTxStarted) 162 | { 163 | // fail TX not started 164 | return 0; 165 | } 166 | 167 | // check if the write size, fits in the buffer 168 | if ((iTxSize + aSize) > sizeof(iTxBuffer)) 169 | { 170 | aSize = sizeof(iTxSize) - iTxSize; 171 | } 172 | 173 | // copy data into the buffer 174 | memcpy(iTxBuffer + iTxSize, aBuffer, aSize); 175 | 176 | iTxSize += aSize; 177 | 178 | return aSize; 179 | } 180 | 181 | int WebSocketClient::parseMessage() 182 | { 183 | flushRx(); 184 | 185 | // make sure 2 bytes (opcode + length) 186 | // are available 187 | if (HttpClient::available() < 2) 188 | { 189 | return 0; 190 | } 191 | 192 | // read open code and length 193 | uint8_t opcode = HttpClient::read(); 194 | int length = HttpClient::read(); 195 | 196 | if ((opcode & 0x0f) == 0) 197 | { 198 | // continuation, use previous opcode and update flags 199 | iRxOpCode |= opcode; 200 | } 201 | else 202 | { 203 | iRxOpCode = opcode; 204 | } 205 | 206 | iRxMasked = (length & 0x80); 207 | length &= 0x7f; 208 | 209 | // read the RX size 210 | if (length < 126) 211 | { 212 | iRxSize = length; 213 | } 214 | else if (length == 126) 215 | { 216 | iRxSize = (HttpClient::read() << 8) | HttpClient::read(); 217 | } 218 | else 219 | { 220 | iRxSize = ((uint64_t)HttpClient::read() << 56) | 221 | ((uint64_t)HttpClient::read() << 48) | 222 | ((uint64_t)HttpClient::read() << 40) | 223 | ((uint64_t)HttpClient::read() << 32) | 224 | ((uint64_t)HttpClient::read() << 24) | 225 | ((uint64_t)HttpClient::read() << 16) | 226 | ((uint64_t)HttpClient::read() << 8) | 227 | (uint64_t)HttpClient::read(); 228 | } 229 | 230 | // read in the mask, if present 231 | if (iRxMasked) 232 | { 233 | for (int i = 0; i < (int)sizeof(iRxMaskKey); i++) 234 | { 235 | iRxMaskKey[i] = HttpClient::read(); 236 | } 237 | } 238 | 239 | iRxMaskIndex = 0; 240 | 241 | if (TYPE_CONNECTION_CLOSE == messageType()) 242 | { 243 | flushRx(); 244 | stop(); 245 | iRxSize = 0; 246 | } 247 | else if (TYPE_PING == messageType()) 248 | { 249 | beginMessage(TYPE_PONG); 250 | while(available()) 251 | { 252 | write(read()); 253 | } 254 | endMessage(); 255 | 256 | iRxSize = 0; 257 | } 258 | else if (TYPE_PONG == messageType()) 259 | { 260 | flushRx(); 261 | iRxSize = 0; 262 | } 263 | 264 | return iRxSize; 265 | } 266 | 267 | int WebSocketClient::messageType() 268 | { 269 | return (iRxOpCode & 0x0f); 270 | } 271 | 272 | bool WebSocketClient::isFinal() 273 | { 274 | return ((iRxOpCode & 0x80) != 0); 275 | } 276 | 277 | String WebSocketClient::readString() 278 | { 279 | int avail = available(); 280 | String s; 281 | 282 | if (avail > 0) 283 | { 284 | s.reserve(avail); 285 | 286 | for (int i = 0; i < avail; i++) 287 | { 288 | s += (char)read(); 289 | } 290 | } 291 | 292 | return s; 293 | } 294 | 295 | int WebSocketClient::ping() 296 | { 297 | uint8_t pingData[16]; 298 | 299 | // create random data for the ping 300 | for (int i = 0; i < (int)sizeof(pingData); i++) 301 | { 302 | pingData[i] = random(0xff); 303 | } 304 | 305 | beginMessage(TYPE_PING); 306 | write(pingData, sizeof(pingData)); 307 | return endMessage(); 308 | } 309 | 310 | int WebSocketClient::available() 311 | { 312 | if (iState < eReadingBody) 313 | { 314 | return HttpClient::available(); 315 | } 316 | 317 | return iRxSize; 318 | } 319 | 320 | int WebSocketClient::read() 321 | { 322 | byte b; 323 | 324 | if (read(&b, sizeof(b))) 325 | { 326 | return b; 327 | } 328 | 329 | return -1; 330 | } 331 | 332 | int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) 333 | { 334 | int readCount = HttpClient::read(aBuffer, aSize); 335 | 336 | if (readCount > 0) 337 | { 338 | iRxSize -= readCount; 339 | 340 | // unmask the RX data if needed 341 | if (iRxMasked) 342 | { 343 | for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) 344 | { 345 | aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; 346 | } 347 | } 348 | } 349 | 350 | return readCount; 351 | } 352 | 353 | int WebSocketClient::peek() 354 | { 355 | int p = HttpClient::peek(); 356 | 357 | if (p != -1 && iRxMasked) 358 | { 359 | // unmask the RX data if needed 360 | p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; 361 | } 362 | 363 | return p; 364 | } 365 | 366 | void WebSocketClient::flushRx() 367 | { 368 | while(available()) 369 | { 370 | read(); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/WebSocketClient.h: -------------------------------------------------------------------------------- 1 | // (c) Copyright Arduino. 2016 2 | // Released under Apache License, version 2.0 3 | 4 | #ifndef WebSocketClient_h 5 | #define WebSocketClient_h 6 | 7 | #include 8 | 9 | #include "HttpClient.h" 10 | 11 | #ifndef WS_TX_BUFFER_SIZE 12 | #define WS_TX_BUFFER_SIZE 128 13 | #endif 14 | 15 | static const int TYPE_CONTINUATION = 0x0; 16 | static const int TYPE_TEXT = 0x1; 17 | static const int TYPE_BINARY = 0x2; 18 | static const int TYPE_CONNECTION_CLOSE = 0x8; 19 | static const int TYPE_PING = 0x9; 20 | static const int TYPE_PONG = 0xa; 21 | 22 | class WebSocketClient : public HttpClient 23 | { 24 | public: 25 | WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort); 26 | WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort); 27 | WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort); 28 | 29 | /** Start the Web Socket connection to the specified path 30 | @param aURLPath Path to use in request (optional, "/" is used by default) 31 | @return 0 if successful, else error 32 | */ 33 | int begin(const char* aPath = "/"); 34 | int begin(const String& aPath); 35 | 36 | /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY) 37 | Use the write or Stream API's to set message content, followed by endMessage 38 | to complete the message. 39 | @param aURLPath Path to use in request 40 | @return 0 if successful, else error 41 | */ 42 | int beginMessage(int aType); 43 | 44 | /** Completes sending of a message started by beginMessage 45 | @return 0 if successful, else error 46 | */ 47 | int endMessage(); 48 | 49 | /** Try to parse an incoming messages 50 | @return 0 if no message available, else size of parsed message 51 | */ 52 | int parseMessage(); 53 | 54 | /** Returns type of current parsed message 55 | @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY) 56 | */ 57 | int messageType(); 58 | 59 | /** Returns if the current message is the final chunk of a split 60 | message 61 | @return true for final message, false otherwise 62 | */ 63 | bool isFinal(); 64 | 65 | /** Read the current messages as a string 66 | @return current message as a string 67 | */ 68 | String readString(); 69 | 70 | /** Send a ping 71 | @return 0 if successful, else error 72 | */ 73 | int ping(); 74 | 75 | // Inherited from Print 76 | virtual size_t write(uint8_t aByte); 77 | virtual size_t write(const uint8_t *aBuffer, size_t aSize); 78 | // Inherited from Stream 79 | virtual int available(); 80 | /** Read the next byte from the server. 81 | @return Byte read or -1 if there are no bytes available. 82 | */ 83 | virtual int read(); 84 | virtual int read(uint8_t *buf, size_t size); 85 | virtual int peek(); 86 | 87 | private: 88 | void flushRx(); 89 | 90 | private: 91 | bool iTxStarted; 92 | uint8_t iTxMessageType; 93 | uint8_t iTxBuffer[WS_TX_BUFFER_SIZE]; 94 | uint64_t iTxSize; 95 | 96 | uint8_t iRxOpCode; 97 | uint64_t iRxSize; 98 | bool iRxMasked; 99 | int iRxMaskIndex; 100 | uint8_t iRxMaskKey[4]; 101 | }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/b64.cpp: -------------------------------------------------------------------------------- 1 | // Simple Base64 code 2 | // (c) Copyright 2010 MCQN Ltd. 3 | // Released under Apache License, version 2.0 4 | 5 | #include "b64.h" 6 | 7 | /* Simple test program 8 | #include 9 | void main() 10 | { 11 | char* in = "amcewen"; 12 | char out[22]; 13 | 14 | b64_encode(in, 15, out, 22); 15 | out[21] = '\0'; 16 | 17 | printf(out); 18 | } 19 | */ 20 | 21 | int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen) 22 | { 23 | // Work out if we've got enough space to encode the input 24 | // Every 6 bits of input becomes a byte of output 25 | if (aOutputLen < (aInputLen*8)/6) 26 | { 27 | // FIXME Should we return an error here, or just the length 28 | return (aInputLen*8)/6; 29 | } 30 | 31 | // If we get here we've got enough space to do the encoding 32 | 33 | const char* b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 34 | if (aInputLen == 3) 35 | { 36 | aOutput[0] = b64_dictionary[aInput[0] >> 2]; 37 | aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; 38 | aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2|(aInput[2]>>6)]; 39 | aOutput[3] = b64_dictionary[aInput[2]&0x3F]; 40 | } 41 | else if (aInputLen == 2) 42 | { 43 | aOutput[0] = b64_dictionary[aInput[0] >> 2]; 44 | aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; 45 | aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2]; 46 | aOutput[3] = '='; 47 | } 48 | else if (aInputLen == 1) 49 | { 50 | aOutput[0] = b64_dictionary[aInput[0] >> 2]; 51 | aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4]; 52 | aOutput[2] = '='; 53 | aOutput[3] = '='; 54 | } 55 | else 56 | { 57 | // Break the input into 3-byte chunks and process each of them 58 | int i; 59 | for (i = 0; i < aInputLen/3; i++) 60 | { 61 | b64_encode(&aInput[i*3], 3, &aOutput[i*4], 4); 62 | } 63 | if (aInputLen % 3 > 0) 64 | { 65 | // It doesn't fit neatly into a 3-byte chunk, so process what's left 66 | b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4)); 67 | } 68 | } 69 | 70 | return ((aInputLen+2)/3)*4; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/b64.h: -------------------------------------------------------------------------------- 1 | #ifndef b64_h 2 | #define b64_h 3 | 4 | int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/utility/URLParser/LICENSE: -------------------------------------------------------------------------------- 1 | http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright 2 | Igor Sysoev. 3 | 4 | Additional changes are licensed under the same terms as NGINX and 5 | copyright Joyent, Inc. and other Node contributors. All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to 9 | deal in the Software without restriction, including without limitation the 10 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | sell copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/utility/URLParser/README.md: -------------------------------------------------------------------------------- 1 | # http_parser library 2 | 3 | This code is imported from: https://github.com/arduino/ArduinoCore-mbed/tree/4.1.1/libraries/SocketWrapper/src/utility/http_parser 4 | 5 | The code is shrinked in size by deleting all the unrelated code to url parse. 6 | -------------------------------------------------------------------------------- /src/utility/URLParser/http_parser.c: -------------------------------------------------------------------------------- 1 | #if defined __has_include 2 | # if ! __has_include() && ! __has_include() 3 | # define NO_HTTP_PARSER 4 | # endif 5 | #endif 6 | 7 | #ifdef NO_HTTP_PARSER 8 | /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev 9 | * 10 | * Additional changes are licensed under the same terms as NGINX and 11 | * copyright Joyent, Inc. and other Node contributors. All rights reserved. 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to 15 | * deal in the Software without restriction, including without limitation the 16 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | * sell copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | * IN THE SOFTWARE. 30 | */ 31 | #include "http_parser.h" 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #ifndef BIT_AT 40 | # define BIT_AT(a, i) \ 41 | (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ 42 | (1 << ((unsigned int) (i) & 7)))) 43 | #endif 44 | 45 | #define SET_ERRNO(e) \ 46 | do { \ 47 | parser->http_errno = (e); \ 48 | } while(0) 49 | 50 | #if HTTP_PARSER_STRICT 51 | # define T(v) 0 52 | #else 53 | # define T(v) v 54 | #endif 55 | 56 | 57 | static const uint8_t normal_url_char[32] = { 58 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 59 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 60 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 61 | 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, 62 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 63 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 64 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 65 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 66 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 67 | 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, 68 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 69 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 70 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 71 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 72 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 73 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, 74 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 75 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 76 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 77 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 78 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 79 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 80 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 81 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 82 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 83 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 84 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 85 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 86 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 87 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 88 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 89 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; 90 | 91 | #undef T 92 | 93 | enum state 94 | { s_dead = 1 /* important that this is > 0 */ 95 | 96 | , s_start_req 97 | 98 | , s_req_spaces_before_url 99 | , s_req_schema 100 | , s_req_schema_slash 101 | , s_req_schema_slash_slash 102 | , s_req_server_start 103 | , s_req_server 104 | , s_req_server_with_at 105 | , s_req_path 106 | , s_req_query_string_start 107 | , s_req_query_string 108 | , s_req_fragment_start 109 | , s_req_fragment 110 | , s_headers_done 111 | }; 112 | 113 | enum http_host_state 114 | { 115 | s_http_host_dead = 1 116 | , s_http_userinfo_start 117 | , s_http_userinfo 118 | , s_http_host_start 119 | , s_http_host_v6_start 120 | , s_http_host 121 | , s_http_host_v6 122 | , s_http_host_v6_end 123 | , s_http_host_v6_zone_start 124 | , s_http_host_v6_zone 125 | , s_http_host_port_start 126 | , s_http_host_port 127 | }; 128 | 129 | /* Macros for character classes; depends on strict-mode */ 130 | #define LOWER(c) (unsigned char)(c | 0x20) 131 | #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') 132 | #define IS_NUM(c) ((c) >= '0' && (c) <= '9') 133 | #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) 134 | #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) 135 | #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ 136 | (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ 137 | (c) == ')') 138 | #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ 139 | (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ 140 | (c) == '$' || (c) == ',') 141 | 142 | #if HTTP_PARSER_STRICT 143 | #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) 144 | #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') 145 | #else 146 | #define IS_URL_CHAR(c) \ 147 | (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) 148 | #define IS_HOST_CHAR(c) \ 149 | (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') 150 | #endif 151 | 152 | /* Our URL parser. 153 | * 154 | * This is designed to be shared by http_parser_execute() for URL validation, 155 | * hence it has a state transition + byte-for-byte interface. In addition, it 156 | * is meant to be embedded in http_parser_parse_url(), which does the dirty 157 | * work of turning state transitions URL components for its API. 158 | * 159 | * This function should only be invoked with non-space characters. It is 160 | * assumed that the caller cares about (and can detect) the transition between 161 | * URL and non-URL states by looking for these. 162 | */ 163 | static enum state 164 | parse_url_char(enum state s, const char ch) 165 | { 166 | if (ch == ' ' || ch == '\r' || ch == '\n') { 167 | return s_dead; 168 | } 169 | 170 | #if HTTP_PARSER_STRICT 171 | if (ch == '\t' || ch == '\f') { 172 | return s_dead; 173 | } 174 | #endif 175 | 176 | switch (s) { 177 | case s_req_spaces_before_url: 178 | /* Proxied requests are followed by scheme of an absolute URI (alpha). 179 | * All methods except CONNECT are followed by '/' or '*'. 180 | */ 181 | 182 | if (ch == '/' || ch == '*') { 183 | return s_req_path; 184 | } 185 | 186 | if (IS_ALPHA(ch)) { 187 | return s_req_schema; 188 | } 189 | 190 | break; 191 | 192 | case s_req_schema: 193 | if (IS_ALPHA(ch)) { 194 | return s; 195 | } 196 | 197 | if (ch == ':') { 198 | return s_req_schema_slash; 199 | } 200 | 201 | break; 202 | 203 | case s_req_schema_slash: 204 | if (ch == '/') { 205 | return s_req_schema_slash_slash; 206 | } 207 | 208 | break; 209 | 210 | case s_req_schema_slash_slash: 211 | if (ch == '/') { 212 | return s_req_server_start; 213 | } 214 | 215 | break; 216 | 217 | case s_req_server_with_at: 218 | if (ch == '@') { 219 | return s_dead; 220 | } 221 | 222 | /* FALLTHROUGH */ 223 | case s_req_server_start: 224 | case s_req_server: 225 | if (ch == '/') { 226 | return s_req_path; 227 | } 228 | 229 | if (ch == '?') { 230 | return s_req_query_string_start; 231 | } 232 | 233 | if (ch == '@') { 234 | return s_req_server_with_at; 235 | } 236 | 237 | if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { 238 | return s_req_server; 239 | } 240 | 241 | break; 242 | 243 | case s_req_path: 244 | if (IS_URL_CHAR(ch)) { 245 | return s; 246 | } 247 | 248 | switch (ch) { 249 | case '?': 250 | return s_req_query_string_start; 251 | 252 | case '#': 253 | return s_req_fragment_start; 254 | } 255 | 256 | break; 257 | 258 | case s_req_query_string_start: 259 | case s_req_query_string: 260 | if (IS_URL_CHAR(ch)) { 261 | return s_req_query_string; 262 | } 263 | 264 | switch (ch) { 265 | case '?': 266 | /* allow extra '?' in query string */ 267 | return s_req_query_string; 268 | 269 | case '#': 270 | return s_req_fragment_start; 271 | } 272 | 273 | break; 274 | 275 | case s_req_fragment_start: 276 | if (IS_URL_CHAR(ch)) { 277 | return s_req_fragment; 278 | } 279 | 280 | switch (ch) { 281 | case '?': 282 | return s_req_fragment; 283 | 284 | case '#': 285 | return s; 286 | } 287 | 288 | break; 289 | 290 | case s_req_fragment: 291 | if (IS_URL_CHAR(ch)) { 292 | return s; 293 | } 294 | 295 | switch (ch) { 296 | case '?': 297 | case '#': 298 | return s; 299 | } 300 | 301 | break; 302 | 303 | default: 304 | break; 305 | } 306 | 307 | /* We should never fall out of the switch above unless there's an error */ 308 | return s_dead; 309 | } 310 | 311 | static enum http_host_state 312 | http_parse_host_char(enum http_host_state s, const char ch) { 313 | switch(s) { 314 | case s_http_userinfo: 315 | case s_http_userinfo_start: 316 | if (ch == '@') { 317 | return s_http_host_start; 318 | } 319 | 320 | if (IS_USERINFO_CHAR(ch)) { 321 | return s_http_userinfo; 322 | } 323 | break; 324 | 325 | case s_http_host_start: 326 | if (ch == '[') { 327 | return s_http_host_v6_start; 328 | } 329 | 330 | if (IS_HOST_CHAR(ch)) { 331 | return s_http_host; 332 | } 333 | 334 | break; 335 | 336 | case s_http_host: 337 | if (IS_HOST_CHAR(ch)) { 338 | return s_http_host; 339 | } 340 | 341 | /* FALLTHROUGH */ 342 | case s_http_host_v6_end: 343 | if (ch == ':') { 344 | return s_http_host_port_start; 345 | } 346 | 347 | break; 348 | 349 | case s_http_host_v6: 350 | if (ch == ']') { 351 | return s_http_host_v6_end; 352 | } 353 | 354 | /* FALLTHROUGH */ 355 | case s_http_host_v6_start: 356 | if (IS_HEX(ch) || ch == ':' || ch == '.') { 357 | return s_http_host_v6; 358 | } 359 | 360 | if (s == s_http_host_v6 && ch == '%') { 361 | return s_http_host_v6_zone_start; 362 | } 363 | break; 364 | 365 | case s_http_host_v6_zone: 366 | if (ch == ']') { 367 | return s_http_host_v6_end; 368 | } 369 | 370 | /* FALLTHROUGH */ 371 | case s_http_host_v6_zone_start: 372 | /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ 373 | if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || 374 | ch == '~') { 375 | return s_http_host_v6_zone; 376 | } 377 | break; 378 | 379 | case s_http_host_port: 380 | case s_http_host_port_start: 381 | if (IS_NUM(ch)) { 382 | return s_http_host_port; 383 | } 384 | 385 | break; 386 | 387 | default: 388 | break; 389 | } 390 | return s_http_host_dead; 391 | } 392 | 393 | static int 394 | http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { 395 | enum http_host_state s; 396 | 397 | const char *p; 398 | uint32_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; 399 | 400 | assert(u->field_set & (1 << UF_HOST)); 401 | 402 | u->field_data[UF_HOST].len = 0; 403 | 404 | s = found_at ? s_http_userinfo_start : s_http_host_start; 405 | 406 | for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { 407 | enum http_host_state new_s = http_parse_host_char(s, *p); 408 | 409 | if (new_s == s_http_host_dead) { 410 | return 1; 411 | } 412 | 413 | switch(new_s) { 414 | case s_http_host: 415 | if (s != s_http_host) { 416 | u->field_data[UF_HOST].off = p - buf; 417 | } 418 | u->field_data[UF_HOST].len++; 419 | break; 420 | 421 | case s_http_host_v6: 422 | if (s != s_http_host_v6) { 423 | u->field_data[UF_HOST].off = p - buf; 424 | } 425 | u->field_data[UF_HOST].len++; 426 | break; 427 | 428 | case s_http_host_v6_zone_start: 429 | case s_http_host_v6_zone: 430 | u->field_data[UF_HOST].len++; 431 | break; 432 | 433 | case s_http_host_port: 434 | if (s != s_http_host_port) { 435 | u->field_data[UF_PORT].off = p - buf; 436 | u->field_data[UF_PORT].len = 0; 437 | u->field_set |= (1 << UF_PORT); 438 | } 439 | u->field_data[UF_PORT].len++; 440 | break; 441 | 442 | case s_http_userinfo: 443 | if (s != s_http_userinfo) { 444 | u->field_data[UF_USERINFO].off = p - buf ; 445 | u->field_data[UF_USERINFO].len = 0; 446 | u->field_set |= (1 << UF_USERINFO); 447 | } 448 | u->field_data[UF_USERINFO].len++; 449 | break; 450 | 451 | default: 452 | break; 453 | } 454 | s = new_s; 455 | } 456 | 457 | /* Make sure we don't end somewhere unexpected */ 458 | switch (s) { 459 | case s_http_host_start: 460 | case s_http_host_v6_start: 461 | case s_http_host_v6: 462 | case s_http_host_v6_zone_start: 463 | case s_http_host_v6_zone: 464 | case s_http_host_port_start: 465 | case s_http_userinfo: 466 | case s_http_userinfo_start: 467 | return 1; 468 | default: 469 | break; 470 | } 471 | 472 | return 0; 473 | } 474 | 475 | void 476 | http_parser_url_init(struct http_parser_url *u) { 477 | memset(u, 0, sizeof(*u)); 478 | } 479 | 480 | int 481 | http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect, 482 | struct http_parser_url *u) 483 | { 484 | enum state s; 485 | const char *p; 486 | enum http_parser_url_fields uf, old_uf; 487 | int found_at = 0; 488 | 489 | u->port = u->field_set = 0; 490 | s = is_connect ? s_req_server_start : s_req_spaces_before_url; 491 | old_uf = UF_MAX; 492 | 493 | for (p = buf; p < buf + buflen; p++) { 494 | s = parse_url_char(s, *p); 495 | 496 | /* Figure out the next field that we're operating on */ 497 | switch (s) { 498 | case s_dead: 499 | return 1; 500 | 501 | /* Skip delimeters */ 502 | case s_req_schema_slash: 503 | case s_req_schema_slash_slash: 504 | case s_req_server_start: 505 | case s_req_query_string_start: 506 | case s_req_fragment_start: 507 | continue; 508 | 509 | case s_req_schema: 510 | uf = UF_SCHEMA; 511 | break; 512 | 513 | case s_req_server_with_at: 514 | found_at = 1; 515 | 516 | /* FALLTROUGH */ 517 | case s_req_server: 518 | uf = UF_HOST; 519 | break; 520 | 521 | case s_req_path: 522 | uf = UF_PATH; 523 | break; 524 | 525 | case s_req_query_string: 526 | uf = UF_QUERY; 527 | break; 528 | 529 | case s_req_fragment: 530 | uf = UF_FRAGMENT; 531 | break; 532 | 533 | default: 534 | assert(!"Unexpected state"); 535 | return 1; 536 | } 537 | 538 | /* Nothing's changed; soldier on */ 539 | if (uf == old_uf) { 540 | u->field_data[uf].len++; 541 | continue; 542 | } 543 | 544 | u->field_data[uf].off = p - buf; 545 | u->field_data[uf].len = 1; 546 | 547 | u->field_set |= (1 << uf); 548 | old_uf = uf; 549 | } 550 | 551 | /* host must be present if there is a schema */ 552 | /* parsing http:///toto will fail */ 553 | if ((u->field_set & (1 << UF_SCHEMA)) && 554 | (u->field_set & (1 << UF_HOST)) == 0) { 555 | return 1; 556 | } 557 | 558 | if (u->field_set & (1 << UF_HOST)) { 559 | if (http_parse_host(buf, u, found_at) != 0) { 560 | return 1; 561 | } 562 | } 563 | 564 | /* CONNECT requests can only contain "hostname:port" */ 565 | if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { 566 | return 1; 567 | } 568 | 569 | if (u->field_set & (1 << UF_PORT)) { 570 | /* Don't bother with endp; we've already validated the string */ 571 | unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); 572 | 573 | /* Ports have a max value of 2^16 */ 574 | if (v > 0xffff) { 575 | return 1; 576 | } 577 | 578 | u->port = (uint16_t) v; 579 | } 580 | 581 | return 0; 582 | } 583 | 584 | unsigned long 585 | http_parser_version(void) { 586 | return HTTP_PARSER_VERSION_MAJOR * 0x10000 | 587 | HTTP_PARSER_VERSION_MINOR * 0x00100 | 588 | HTTP_PARSER_VERSION_PATCH * 0x00001; 589 | } 590 | 591 | #endif // NO_HTTP_PARSER -------------------------------------------------------------------------------- /src/utility/URLParser/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* Also update SONAME in the Makefile whenever you change these. */ 29 | #define HTTP_PARSER_VERSION_MAJOR 2 30 | #define HTTP_PARSER_VERSION_MINOR 7 31 | #define HTTP_PARSER_VERSION_PATCH 1 32 | 33 | #include 34 | 35 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 36 | * faster 37 | */ 38 | #ifndef HTTP_PARSER_STRICT 39 | # define HTTP_PARSER_STRICT 1 40 | #endif 41 | 42 | 43 | enum http_parser_url_fields 44 | { UF_SCHEMA = 0 45 | , UF_HOST = 1 46 | , UF_PORT = 2 47 | , UF_PATH = 3 48 | , UF_QUERY = 4 49 | , UF_FRAGMENT = 5 50 | , UF_USERINFO = 6 51 | , UF_MAX = 7 52 | }; 53 | 54 | 55 | /* Result structure for http_parser_parse_url(). 56 | * 57 | * Callers should index into field_data[] with UF_* values iff field_set 58 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 59 | * because we probably have padding left over), we convert any port to 60 | * a uint16_t. 61 | */ 62 | struct http_parser_url { 63 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 64 | uint16_t port; /* Converted UF_PORT string */ 65 | 66 | struct { 67 | uint16_t off; /* Offset into buffer in which field starts */ 68 | uint16_t len; /* Length of run in buffer */ 69 | } field_data[UF_MAX]; 70 | }; 71 | 72 | 73 | /* Returns the library version. Bits 16-23 contain the major version number, 74 | * bits 8-15 the minor version number and bits 0-7 the patch level. 75 | * Usage example: 76 | * 77 | * unsigned long version = http_parser_version(); 78 | * unsigned major = (version >> 16) & 255; 79 | * unsigned minor = (version >> 8) & 255; 80 | * unsigned patch = version & 255; 81 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 82 | */ 83 | unsigned long http_parser_version(void); 84 | 85 | /* Initialize all http_parser_url members to 0 */ 86 | void http_parser_url_init(struct http_parser_url *u); 87 | 88 | /* Parse a URL; return nonzero on failure */ 89 | int http_parser_parse_url(const char *buf, uint32_t buflen, 90 | int is_connect, 91 | struct http_parser_url *u); 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | #endif --------------------------------------------------------------------------------