diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 0000000..c5233c0
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,7 @@
+# See: https://github.com/codespell-project/codespell#using-a-config-file
+[codespell]
+# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here:
+ignore-words-list = ,
+check-filenames =
+check-hidden =
+skip = ./.git,./src/utility/URLParser
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..fa738ec
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file
+version: 2
+
+updates:
+  # Configure check for outdated GitHub Actions actions in workflows.
+  # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot
+  - package-ecosystem: github-actions
+    directory: / # Check the repository's workflows under /.github/workflows/
+    schedule:
+      interval: daily
+    labels:
+      - "topic: infrastructure"
diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml
new file mode 100644
index 0000000..e13b990
--- /dev/null
+++ b/.github/workflows/check-arduino.yml
@@ -0,0 +1,28 @@
+name: Check Arduino
+
+# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows
+on:
+  push:
+  pull_request:
+  schedule:
+    # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint.
+    - cron: "0 8 * * TUE"
+  workflow_dispatch:
+  repository_dispatch:
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - name: Arduino Lint
+        uses: arduino/arduino-lint-action@v2
+        with:
+          compliance: specification
+          library-manager: update
+          # Always use this setting for official repositories. Remove for 3rd party projects.
+          official: true
+          project-type: library
diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml
new file mode 100644
index 0000000..e3d6674
--- /dev/null
+++ b/.github/workflows/compile-examples.yml
@@ -0,0 +1,63 @@
+name: Compile Examples
+
+# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows
+on:
+  push:
+    paths:
+      - ".github/workflows/compile-examples.yml"
+      - "examples/**"
+      - "src/**"
+  pull_request:
+    paths:
+      - ".github/workflows/compile-examples.yml"
+      - "examples/**"
+      - "src/**"
+  schedule:
+    # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms).
+    - cron: "0 8 * * TUE"
+  workflow_dispatch:
+  repository_dispatch:
+
+jobs:
+  build:
+    name: ${{ matrix.board.fqbn }}
+    runs-on: ubuntu-latest
+
+    env:
+      SKETCHES_REPORTS_PATH: sketches-reports
+
+    strategy:
+      fail-fast: false
+
+      matrix:
+        board:
+          - fqbn: arduino:samd:mkr1000
+            platforms: |
+              - name: arduino:samd
+            artifact-name-suffix: arduino-samd-mkr1000
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - name: Compile examples
+        uses: arduino/compile-sketches@v1
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          fqbn: ${{ matrix.board.fqbn }}
+          platforms: ${{ matrix.board.platforms }}
+          libraries: |
+            # Install the library from the local path.
+            - source-path: ./
+            - name: WiFi101
+          sketch-paths: |
+            - examples
+          enable-deltas-report: true
+          sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }}
+
+      - name: Save sketches report as workflow artifact
+        uses: actions/upload-artifact@v4
+        with:
+          if-no-files-found: error
+          path: ${{ env.SKETCHES_REPORTS_PATH }}
+          name: sketches-report-${{ matrix.board.artifact-name-suffix }}
diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml
new file mode 100644
index 0000000..39e2a0a
--- /dev/null
+++ b/.github/workflows/report-size-deltas.yml
@@ -0,0 +1,24 @@
+name: Report Size Deltas
+
+# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows
+on:
+  push:
+    paths:
+      - ".github/workflows/report-size-deltas.yml"
+  schedule:
+    # Run at the minimum interval allowed by GitHub Actions.
+    # Note: GitHub Actions periodically has outages which result in workflow failures.
+    # In this event, the workflows will start passing again once the service recovers.
+    - cron: "*/5 * * * *"
+  workflow_dispatch:
+  repository_dispatch:
+
+jobs:
+  report:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Comment size deltas reports to PRs
+        uses: arduino/report-size-deltas@v1
+        with:
+          # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow
+          sketches-reports-source: ^sketches-report-.+
diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml
new file mode 100644
index 0000000..19f5d38
--- /dev/null
+++ b/.github/workflows/spell-check.yml
@@ -0,0 +1,22 @@
+name: Spell Check
+
+# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows
+on:
+  push:
+  pull_request:
+  schedule:
+    # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates.
+    - cron: "0 8 * * TUE"
+  workflow_dispatch:
+  repository_dispatch:
+
+jobs:
+  spellcheck:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - name: Spell check
+        uses: codespell-project/actions-codespell@master
diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml
new file mode 100644
index 0000000..62c898a
--- /dev/null
+++ b/.github/workflows/sync-labels.yml
@@ -0,0 +1,138 @@
+# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md
+name: Sync Labels
+
+# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
+on:
+  push:
+    paths:
+      - ".github/workflows/sync-labels.ya?ml"
+      - ".github/label-configuration-files/*.ya?ml"
+  pull_request:
+    paths:
+      - ".github/workflows/sync-labels.ya?ml"
+      - ".github/label-configuration-files/*.ya?ml"
+  schedule:
+    # Run daily at 8 AM UTC to sync with changes to shared label configurations.
+    - cron: "0 8 * * *"
+  workflow_dispatch:
+  repository_dispatch:
+
+env:
+  CONFIGURATIONS_FOLDER: .github/label-configuration-files
+  CONFIGURATIONS_ARTIFACT: label-configuration-files
+
+jobs:
+  check:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - name: Download JSON schema for labels configuration file
+        id: download-schema
+        uses: carlosperate/download-file-action@v2
+        with:
+          file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json
+          location: ${{ runner.temp }}/label-configuration-schema
+
+      - name: Install JSON schema validator
+        run: |
+          sudo npm install \
+            --global \
+            ajv-cli \
+            ajv-formats
+
+      - name: Validate local labels configuration
+        run: |
+          # See: https://github.com/ajv-validator/ajv-cli#readme
+          ajv validate \
+            --all-errors \
+            -c ajv-formats \
+            -s "${{ steps.download-schema.outputs.file-path }}" \
+            -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}"
+
+  download:
+    needs: check
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        filename:
+          # Filenames of the shared configurations to apply to the repository in addition to the local configuration.
+          # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels
+          - universal.yml
+
+    steps:
+      - name: Download
+        uses: carlosperate/download-file-action@v2
+        with:
+          file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }}
+
+      - name: Pass configuration files to next job via workflow artifact
+        uses: actions/upload-artifact@v4
+        with:
+          path: |
+            *.yaml
+            *.yml
+          if-no-files-found: error
+          name: ${{ env.CONFIGURATIONS_ARTIFACT }}
+
+  sync:
+    needs: download
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Set environment variables
+        run: |
+          # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
+          echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV"
+
+      - name: Determine whether to dry run
+        id: dry-run
+        if: >
+          github.event_name == 'pull_request' ||
+          (
+            (
+              github.event_name == 'push' ||
+              github.event_name == 'workflow_dispatch'
+            ) &&
+            github.ref != format('refs/heads/{0}', github.event.repository.default_branch)
+          )
+        run: |
+          # Use of this flag in the github-label-sync command will cause it to only check the validity of the
+          # configuration.
+          echo "::set-output name=flag::--dry-run"
+
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - name: Download configuration files artifact
+        uses: actions/download-artifact@v5
+        with:
+          name: ${{ env.CONFIGURATIONS_ARTIFACT }}
+          path: ${{ env.CONFIGURATIONS_FOLDER }}
+
+      - name: Remove unneeded artifact
+        uses: geekyeggo/delete-artifact@v5
+        with:
+          name: ${{ env.CONFIGURATIONS_ARTIFACT }}
+
+      - name: Merge label configuration files
+        run: |
+          # Merge all configuration files
+          shopt -s extglob
+          cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}"
+
+      - name: Install github-label-sync
+        run: sudo npm install --global github-label-sync
+
+      - name: Sync labels
+        env:
+          GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          # See: https://github.com/Financial-Times/github-label-sync
+          github-label-sync \
+            --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \
+            ${{ steps.dry-run.outputs.flag }} \
+            ${{ github.repository }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24a0007
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.development
+examples/node_test_server/node_modules/
+*.DS_Store
+*/.DS_Store
+examples/.DS_Store
+.idea/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fc0e879
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,33 @@
+## ArduinoHttpClient 0.4.0 - 2019.04.09
+
+* Added URLEncoder helper
+
+## ArduinoHttpClient 0.3.2 - 2019.02.04
+
+* Changed Flush return value resulting in compilation error. Thanks @forGGe
+
+## ArduinoHttpClient 0.3.1 - 2017.09.25
+
+* Changed examples to support Arduino Create secret tabs
+* Increase WebSocket secret-key length to 24 characters
+
+## ArduinoHttpClient 0.3.0 - 2017.04.20
+
+* Added support for PATCH operations
+* Added support for chunked response bodies
+* Added new beginBody API
+
+## ArduinoHttpClient 0.2.0 - 2017.01.12
+
+* Added PATCH method
+* Added basic auth example
+* Added custom header example
+
+## ArduinoHttpClient 0.1.1 - 2016.12.16
+
+* More robust response parser
+
+## ArduinoHttpClient 0.1.0 - 2016.07.05
+
+* Initial release
+
diff --git a/HttpClient.cpp b/HttpClient.cpp
deleted file mode 100644
index 5a11a45..0000000
--- a/HttpClient.cpp
+++ /dev/null
@@ -1,565 +0,0 @@
-// Class to simplify HTTP fetching on Arduino
-// (c) Copyright 2010-2011 MCQN Ltd
-// Released under Apache License, version 2.0
-
-#include "HttpClient.h"
-#include "b64.h"
-#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
-#include <Dns.h>
-#endif
-
-// Initialize constants
-const char* HttpClient::kUserAgent = "Arduino/2.2.0";
-const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
-
-#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
-HttpClient::HttpClient(Client& aClient, const char* aProxy, uint16_t aProxyPort)
- : iClient(&aClient), iProxyPort(aProxyPort)
-{
-  resetState();
-  if (aProxy)
-  {
-    // Resolve the IP address for the proxy
-    DNSClient dns;
-    dns.begin(Ethernet.dnsServerIP());
-    // Not ideal that we discard any errors here, but not a lot we can do in the ctor
-    // and we'll get a connect error later anyway
-    (void)dns.getHostByName(aProxy, iProxyAddress);
-  }
-}
-#else
-HttpClient::HttpClient(Client& aClient)
- : iClient(&aClient), iProxyPort(0)
-{
-  resetState();
-}
-#endif
-
-void HttpClient::resetState()
-{
-  iState = eIdle;
-  iStatusCode = 0;
-  iContentLength = 0;
-  iBodyLengthConsumed = 0;
-  iContentLengthPtr = kContentLengthPrefix;
-  iHttpResponseTimeout = kHttpResponseTimeout;
-}
-
-void HttpClient::stop()
-{
-  iClient->stop();
-  resetState();
-}
-
-void HttpClient::beginRequest()
-{
-  iState = eRequestStarted;
-}
-
-int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
-{
-    tHttpState initialState = iState;
-    if ((eIdle != iState) && (eRequestStarted != iState))
-    {
-        return HTTP_ERROR_API;
-    }
-
-#ifdef PROXY_ENABLED
-    if (iProxyPort)
-    {
-        if (!iClient->connect(iProxyAddress, iProxyPort) > 0)
-        {
-#ifdef LOGGING
-            Serial.println("Proxy connection failed");
-#endif
-            return HTTP_ERROR_CONNECTION_FAILED;
-        }
-    }
-    else
-#endif
-    {
-        if (!iClient->connect(aServerName, aServerPort) > 0)
-        {
-#ifdef LOGGING
-            Serial.println("Connection failed");
-#endif
-            return HTTP_ERROR_CONNECTION_FAILED;
-        }
-    }
-
-    // Now we're connected, send the first part of the request
-    int ret = sendInitialHeaders(aServerName, IPAddress(0,0,0,0), aServerPort, aURLPath, aHttpMethod, aUserAgent);
-    if ((initialState == eIdle) && (HTTP_SUCCESS == ret))
-    {
-        // This was a simple version of the API, so terminate the headers now
-        finishHeaders();
-    }
-    // else we'll call it in endRequest or in the first call to print, etc.
-
-    return ret;
-}
-
-int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
-{
-    tHttpState initialState = iState;
-    if ((eIdle != iState) && (eRequestStarted != iState))
-    {
-        return HTTP_ERROR_API;
-    }
-
-#ifdef PROXY_ENABLED
-    if (iProxyPort)
-    {
-        if (!iClient->connect(iProxyAddress, iProxyPort) > 0)
-        {
-#ifdef LOGGING
-            Serial.println("Proxy connection failed");
-#endif
-            return HTTP_ERROR_CONNECTION_FAILED;
-        }
-    }
-    else
-#endif
-    {
-        if (!iClient->connect(aServerAddress, aServerPort) > 0)
-        {
-#ifdef LOGGING
-            Serial.println("Connection failed");
-#endif
-            return HTTP_ERROR_CONNECTION_FAILED;
-        }
-    }
-
-    // Now we're connected, send the first part of the request
-    int ret = sendInitialHeaders(aServerName, aServerAddress, aServerPort, aURLPath, aHttpMethod, aUserAgent);
-    if ((initialState == eIdle) && (HTTP_SUCCESS == ret))
-    {
-        // This was a simple version of the API, so terminate the headers now
-        finishHeaders();
-    }
-    // else we'll call it in endRequest or in the first call to print, etc.
-
-    return ret;
-}
-
-int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, uint16_t aPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
-{
-#ifdef LOGGING
-    Serial.println("Connected");
-#endif
-    // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0"
-    iClient->print(aHttpMethod);
-    iClient->print(" ");
-#ifdef PROXY_ENABLED
-    if (iProxyPort)
-    {
-      // We're going through a proxy, send a full URL
-      iClient->print("http://");
-      if (aServerName)
-      {
-        // We've got a server name, so use it
-        iClient->print(aServerName);
-      }
-      else
-      {
-        // We'll have to use the IP address
-        iClient->print(aServerIP);
-      }
-      if (aPort != kHttpPort)
-      {
-        iClient->print(":");
-        iClient->print(aPort);
-      }
-    }
-#endif
-    iClient->print(aURLPath);
-    iClient->println(" HTTP/1.1");
-    // The host header, if required
-    if (aServerName)
-    {
-        iClient->print("Host: ");
-        iClient->print(aServerName);
-        if (aPort != kHttpPort)
-        {
-          iClient->print(":");
-          iClient->print(aPort);
-        }
-        iClient->println();
-    }
-    // And user-agent string
-    if (aUserAgent)
-    {
-        sendHeader(HTTP_HEADER_USER_AGENT, aUserAgent);
-    }
-    else
-    {
-        sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent);
-    }
-    // We don't support persistent connections, so tell the server to
-    // close this connection after we're done
-    sendHeader(HTTP_HEADER_CONNECTION, "close");
-
-    // Everything has gone well
-    iState = eRequestStarted;
-    return HTTP_SUCCESS;
-}
-
-void HttpClient::sendHeader(const char* aHeader)
-{
-    iClient->println(aHeader);
-}
-
-void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue)
-{
-    iClient->print(aHeaderName);
-    iClient->print(": ");
-    iClient->println(aHeaderValue);
-}
-
-void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue)
-{
-    iClient->print(aHeaderName);
-    iClient->print(": ");
-    iClient->println(aHeaderValue);
-}
-
-void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword)
-{
-    // Send the initial part of this header line
-    iClient->print("Authorization: Basic ");
-    // Now Base64 encode "aUser:aPassword" and send that
-    // This seems trickier than it should be but it's mostly to avoid either
-    // (a) some arbitrarily sized buffer which hopes to be big enough, or
-    // (b) allocating and freeing memory
-    // ...so we'll loop through 3 bytes at a time, outputting the results as we
-    // go.
-    // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data
-    unsigned char input[3];
-    unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print
-    int userLen = strlen(aUser);
-    int passwordLen = strlen(aPassword);
-    int inputOffset = 0;
-    for (int i = 0; i < (userLen+1+passwordLen); i++)
-    {
-        // Copy the relevant input byte into the input
-        if (i < userLen)
-        {
-            input[inputOffset++] = aUser[i];
-        }
-        else if (i == userLen)
-        {
-            input[inputOffset++] = ':';
-        }
-        else
-        {
-            input[inputOffset++] = aPassword[i-(userLen+1)];
-        }
-        // See if we've got a chunk to encode
-        if ( (inputOffset == 3) || (i == userLen+passwordLen) )
-        {
-            // We've either got to a 3-byte boundary, or we've reached then end
-            b64_encode(input, inputOffset, output, 4);
-            // NUL-terminate the output string
-            output[4] = '\0';
-            // And write it out
-            iClient->print((char*)output);
-// FIXME We might want to fill output with '=' characters if b64_encode doesn't
-// FIXME do it for us when we're encoding the final chunk
-            inputOffset = 0;
-        }
-    }
-    // And end the header we've sent
-    iClient->println();
-}
-
-void HttpClient::finishHeaders()
-{
-    iClient->println();
-    iState = eRequestSent;
-}
-
-void HttpClient::endRequest()
-{
-    if (iState < eRequestSent)
-    {
-        // We still need to finish off the headers
-        finishHeaders();
-    }
-    // else the end of headers has already been sent, so nothing to do here
-}
-
-int HttpClient::responseStatusCode()
-{
-    if (iState < eRequestSent)
-    {
-        return HTTP_ERROR_API;
-    }
-    // The first line will be of the form Status-Line:
-    //   HTTP-Version SP Status-Code SP Reason-Phrase CRLF
-    // Where HTTP-Version is of the form:
-    //   HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
-
-    char c = '\0';
-    do
-    {
-        // Make sure the status code is reset, and likewise the state.  This
-        // lets us easily cope with 1xx informational responses by just
-        // ignoring them really, and reading the next line for a proper response
-        iStatusCode = 0;
-        iState = eRequestSent;
-
-        unsigned long timeoutStart = millis();
-        // Psuedo-regexp we're expecting before the status-code
-        const char* statusPrefix = "HTTP/*.* ";
-        const char* statusPtr = statusPrefix;
-        // Whilst we haven't timed out & haven't reached the end of the headers
-        while ((c != '\n') && 
-               ( (millis() - timeoutStart) < iHttpResponseTimeout ))
-        {
-            if (available())
-            {
-                c = read();
-                if (c != -1)
-                {
-                    switch(iState)
-                    {
-                    case eRequestSent:
-                        // We haven't reached the status code yet
-                        if ( (*statusPtr == '*') || (*statusPtr == c) )
-                        {
-                            // This character matches, just move along
-                            statusPtr++;
-                            if (*statusPtr == '\0')
-                            {
-                                // We've reached the end of the prefix
-                                iState = eReadingStatusCode;
-                            }
-                        }
-                        else
-                        {
-                            return HTTP_ERROR_INVALID_RESPONSE;
-                        }
-                        break;
-                    case eReadingStatusCode:
-                        if (isdigit(c))
-                        {
-                            // This assumes we won't get more than the 3 digits we
-                            // want
-                            iStatusCode = iStatusCode*10 + (c - '0');
-                        }
-                        else
-                        {
-                            // We've reached the end of the status code
-                            // We could sanity check it here or double-check for ' '
-                            // rather than anything else, but let's be lenient
-                            iState = eStatusCodeRead;
-                        }
-                        break;
-                    case eStatusCodeRead:
-                        // We're just waiting for the end of the line now
-                        break;
-                    };
-                    // We read something, reset the timeout counter
-                    timeoutStart = millis();
-                }
-            }
-            else
-            {
-                // We haven't got any data, so let's pause to allow some to
-                // arrive
-                delay(kHttpWaitForDataDelay);
-            }
-        }
-        if ( (c == '\n') && (iStatusCode < 200) )
-        {
-            // We've reached the end of an informational status line
-            c = '\0'; // Clear c so we'll go back into the data reading loop
-        }
-    }
-    // If we've read a status code successfully but it's informational (1xx)
-    // loop back to the start
-    while ( (iState == eStatusCodeRead) && (iStatusCode < 200) );
-
-    if ( (c == '\n') && (iState == eStatusCodeRead) )
-    {
-        // We've read the status-line successfully
-        return iStatusCode;
-    }
-    else if (c != '\n')
-    {
-        // We must've timed out before we reached the end of the line
-        return HTTP_ERROR_TIMED_OUT;
-    }
-    else
-    {
-        // This wasn't a properly formed status line, or at least not one we
-        // could understand
-        return HTTP_ERROR_INVALID_RESPONSE;
-    }
-}
-
-int HttpClient::skipResponseHeaders()
-{
-    // Just keep reading until we finish reading the headers or time out
-    unsigned long timeoutStart = millis();
-    // Whilst we haven't timed out & haven't reached the end of the headers
-    while ((!endOfHeadersReached()) && 
-           ( (millis() - timeoutStart) < iHttpResponseTimeout ))
-    {
-        if (available())
-        {
-            (void)readHeader();
-            // We read something, reset the timeout counter
-            timeoutStart = millis();
-        }
-        else
-        {
-            // We haven't got any data, so let's pause to allow some to
-            // arrive
-            delay(kHttpWaitForDataDelay);
-        }
-    }
-    if (endOfHeadersReached())
-    {
-        // Success
-        return HTTP_SUCCESS;
-    }
-    else
-    {
-        // We must've timed out
-        return HTTP_ERROR_TIMED_OUT;
-    }
-}
-
-bool HttpClient::endOfBodyReached()
-{
-    if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader))
-    {
-        // We've got to the body and we know how long it will be
-        return (iBodyLengthConsumed >= contentLength());
-    }
-    return false;
-}
-
-int HttpClient::read()
-{
-#if 0 // Fails on WiFi because multi-byte read seems to be broken
-    uint8_t b[1];
-    int ret = read(b, 1);
-    if (ret == 1)
-    {
-        return b[0];
-    }
-    else
-    {
-        return -1;
-    }
-#else
-    int ret = iClient->read();
-    if (ret >= 0)
-    {
-        if (endOfHeadersReached() && iContentLength > 0)
-	{
-            // We're outputting the body now and we've seen a Content-Length header
-            // So keep track of how many bytes are left
-            iBodyLengthConsumed++;
-	}
-    }
-    return ret;
-#endif
-}
-
-int HttpClient::read(uint8_t *buf, size_t size)
-{
-    int ret =iClient->read(buf, size);
-    if (endOfHeadersReached() && iContentLength > 0)
-    {
-        // We're outputting the body now and we've seen a Content-Length header
-        // So keep track of how many bytes are left
-        if (ret >= 0)
-	{
-            iBodyLengthConsumed += ret;
-	}
-    }
-    return ret;
-}
-
-int HttpClient::readHeader()
-{
-    char c = read();
-
-    if (endOfHeadersReached())
-    {
-        // We've passed the headers, but rather than return an error, we'll just
-        // act as a slightly less efficient version of read()
-        return c;
-    }
-
-    // Whilst reading out the headers to whoever wants them, we'll keep an
-    // eye out for the "Content-Length" header
-    switch(iState)
-    {
-    case eStatusCodeRead:
-        // We're at the start of a line, or somewhere in the middle of reading
-        // the Content-Length prefix
-        if (*iContentLengthPtr == c)
-        {
-            // This character matches, just move along
-            iContentLengthPtr++;
-            if (*iContentLengthPtr == '\0')
-            {
-                // We've reached the end of the prefix
-                iState = eReadingContentLength;
-                // Just in case we get multiple Content-Length headers, this
-                // will ensure we just get the value of the last one
-                iContentLength = 0;
-            }
-        }
-        else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r'))
-        {
-            // We've found a '\r' at the start of a line, so this is probably
-            // the end of the headers
-            iState = eLineStartingCRFound;
-        }
-        else
-        {
-            // This isn't the Content-Length header, skip to the end of the line
-            iState = eSkipToEndOfHeader;
-        }
-        break;
-    case eReadingContentLength:
-        if (isdigit(c))
-        {
-            iContentLength = iContentLength*10 + (c - '0');
-        }
-        else
-        {
-            // We've reached the end of the content length
-            // We could sanity check it here or double-check for "\r\n"
-            // rather than anything else, but let's be lenient
-            iState = eSkipToEndOfHeader;
-        }
-        break;
-    case eLineStartingCRFound:
-        if (c == '\n')
-        {
-            iState = eReadingBody;
-        }
-        break;
-    default:
-        // We're just waiting for the end of the line now
-        break;
-    };
-
-    if ( (c == '\n') && !endOfHeadersReached() )
-    {
-        // We've got to the end of this line, start processing again
-        iState = eStatusCodeRead;
-        iContentLengthPtr = kContentLengthPrefix;
-    }
-    // And return the character read to whoever wants it
-    return c;
-}
-
-
-
diff --git a/HttpClient.h b/HttpClient.h
deleted file mode 100644
index b4c3974..0000000
--- a/HttpClient.h
+++ /dev/null
@@ -1,449 +0,0 @@
-// Class to simplify HTTP fetching on Arduino
-// (c) Copyright MCQN Ltd. 2010-2012
-// Released under Apache License, version 2.0
-
-#ifndef HttpClient_h
-#define HttpClient_h
-
-#include <Arduino.h>
-#include <IPAddress.h>
-#include "Client.h"
-
-static const int HTTP_SUCCESS =0;
-// The end of the headers has been reached.  This consumes the '\n'
-// Could not connect to the server
-static const int HTTP_ERROR_CONNECTION_FAILED =-1;
-// This call was made when the HttpClient class wasn't expecting it
-// to be called.  Usually indicates your code is using the class
-// incorrectly
-static const int HTTP_ERROR_API =-2;
-// Spent too long waiting for a reply
-static const int HTTP_ERROR_TIMED_OUT =-3;
-// The response from the server is invalid, is it definitely an HTTP
-// server?
-static const int HTTP_ERROR_INVALID_RESPONSE =-4;
-
-// Define some of the common methods and headers here
-// That lets other code reuse them without having to declare another copy
-// of them, so saves code space and RAM
-#define HTTP_METHOD_GET    "GET"
-#define HTTP_METHOD_POST   "POST"
-#define HTTP_METHOD_PUT    "PUT"
-#define HTTP_METHOD_DELETE "DELETE"
-#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
-#define HTTP_HEADER_CONNECTION     "Connection"
-#define HTTP_HEADER_USER_AGENT     "User-Agent"
-
-class HttpClient : public Client
-{
-public:
-    static const int kNoContentLengthHeader =-1;
-    static const int kHttpPort =80;
-    static const char* kUserAgent;
-
-// FIXME Write longer API request, using port and user-agent, example
-// FIXME Update tempToPachube example to calculate Content-Length correctly
-
-#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
-    HttpClient(Client& aClient, const char* aProxy =NULL, uint16_t aProxyPort =0);
-#else
-    HttpClient(Client& aClient);
-#endif
-
-    /** Start a more complex request.
-        Use this when you need to send additional headers in the request,
-        but you will also need to call endRequest() when you are finished.
-    */
-    void beginRequest();
-
-    /** End a more complex request.
-        Use this when you need to have sent additional headers in the request,
-        but you will also need to call beginRequest() at the start.
-    */
-    void endRequest();
-
-    /** Connect to the server and start to send a GET request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int get(const char* aServerName, uint16_t aServerPort, const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); }
-
-    /** Connect to the server and start to send a GET request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int get(const char* aServerName, const char* aURLPath, const char* aUserAgent =NULL)
-      { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); }
-
-    /** Connect to the server and start to send a GET request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName    Name of the server being connected to.  If NULL, the
-                            "Host" header line won't be sent
-      @param aServerPort    Port to connect to on the server
-      @param aURLPath       Url to request
-      @param aUserAgent     User-Agent string to send.  If NULL the default
-                            user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int get(const IPAddress& aServerAddress,
-            const char* aServerName, 
-            uint16_t aServerPort,
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); }
-
-    /** Connect to the server and start to send a GET request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName    Name of the server being connected to.  If NULL, the
-                            "Host" header line won't be sent
-      @param aURLPath       Url to request
-      @param aUserAgent     User-Agent string to send.  If NULL the default
-                            user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int get(const IPAddress& aServerAddress,
-            const char* aServerName, 
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); }
-
-    /** Connect to the server and start to send a POST request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int post(const char* aServerName, 
-             uint16_t aServerPort,
-             const char* aURLPath, 
-             const char* aUserAgent =NULL)
-      { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); }
-
-    /** Connect to the server and start to send a POST request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int post(const char* aServerName, 
-             const char* aURLPath, 
-             const char* aUserAgent =NULL)
-      { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); }
-
-    /** Connect to the server and start to send a POST request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int post(const IPAddress& aServerAddress,
-             const char* aServerName, 
-             uint16_t aServerPort,
-             const char* aURLPath, 
-             const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); }
-
-    /** Connect to the server and start to send a POST request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int post(const IPAddress& aServerAddress,
-             const char* aServerName, 
-             const char* aURLPath, 
-             const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); }
-
-    /** Connect to the server and start to send a PUT request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int put(const char* aServerName, 
-            uint16_t aServerPort,
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); }
-
-    /** Connect to the server and start to send a PUT request.
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int put(const char* aServerName, 
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); }
-
-    /** Connect to the server and start to send a PUT request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int put(const IPAddress& aServerAddress,
-            const char* aServerName, 
-            uint16_t aServerPort,
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); }
-
-    /** Connect to the server and start to send a PUT request.  This version connects
-      doesn't perform a DNS lookup and just connects to the given IP address.
-      @param aServerAddress IP address of the server to connect to
-      @param aServerName  Name of the server being connected to.  If NULL, the
-                          "Host" header line won't be sent
-      @param aURLPath     Url to request
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int put(const IPAddress& aServerAddress,
-            const char* aServerName, 
-            const char* aURLPath, 
-            const char* aUserAgent =NULL)
-      { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); }
-
-    /** Connect to the server and start to send the request.
-      @param aServerName  Name of the server being connected to.
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath     Url to request
-      @param aHttpMethod  Type of HTTP request to make, e.g. "GET", "POST", etc.
-      @param aUserAgent   User-Agent string to send.  If NULL the default
-                          user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int startRequest(const char* aServerName,
-                     uint16_t    aServerPort,
-                     const char* aURLPath,
-                     const char* aHttpMethod,
-                     const char* aUserAgent);
-
-    /** Connect to the server and start to send the request.
-      @param aServerAddress IP address of the server to connect to.
-      @param aServerName Name of the server being connected to.  If NULL, the
-                         "Host" header line won't be sent
-      @param aServerPort  Port to connect to on the server
-      @param aURLPath	Url to request
-      @param aHttpMethod  Type of HTTP request to make, e.g. "GET", "POST", etc.
-      @param aUserAgent User-Agent string to send.  If NULL the default
-                        user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int startRequest(const IPAddress& aServerAddress,
-                     const char* aServerName,
-                     uint16_t    aServerPort,
-                     const char* aURLPath,
-                     const char* aHttpMethod,
-                     const char* aUserAgent);
-
-    /** Send an additional header line.  This can only be called in between the
-      calls to startRequest and finishRequest.
-      @param aHeader Header line to send, in its entirety (but without the
-                     trailing CRLF.  E.g. "Authorization: Basic YQDDCAIGES" 
-    */
-    void sendHeader(const char* aHeader);
-
-    /** Send an additional header line.  This is an alternate form of
-      sendHeader() which takes the header name and content as separate strings.
-      The call will add the ": " to separate the header, so for example, to
-      send a XXXXXX header call sendHeader("XXXXX", "Something")
-      @param aHeaderName Type of header being sent
-      @param aHeaderValue Value for that header
-    */
-    void sendHeader(const char* aHeaderName, const char* aHeaderValue);
-
-    /** Send an additional header line.  This is an alternate form of
-      sendHeader() which takes the header name and content separately but where
-      the value is provided as an integer.
-      The call will add the ": " to separate the header, so for example, to
-      send a XXXXXX header call sendHeader("XXXXX", 123)
-      @param aHeaderName Type of header being sent
-      @param aHeaderValue Value for that header
-    */
-    void sendHeader(const char* aHeaderName, const int aHeaderValue);
-
-    /** Send a basic authentication header.  This will encode the given username
-      and password, and send them in suitable header line for doing Basic
-      Authentication.
-      @param aUser Username for the authorization
-      @param aPassword Password for the user aUser
-    */
-    void sendBasicAuth(const char* aUser, const char* aPassword);
-
-    /** Finish sending the HTTP request.  This basically just sends the blank
-      line to signify the end of the request
-    */
-    void finishRequest();
-
-    /** Get the HTTP status code contained in the response.
-      For example, 200 for successful request, 404 for file not found, etc.
-    */
-    int responseStatusCode();
-
-    /** Read the next character of the response headers.
-      This functions in the same way as read() but to be used when reading
-      through the headers.  Check whether or not the end of the headers has
-      been reached by calling endOfHeadersReached(), although after that point
-      this will still return data as read() would, but slightly less efficiently
-      @return The next character of the response headers
-    */
-    int readHeader();
-
-    /** Skip any response headers to get to the body.
-      Use this if you don't want to do any special processing of the headers
-      returned in the response.  You can also use it after you've found all of
-      the headers you're interested in, and just want to get on with processing
-      the body.
-      @return HTTP_SUCCESS if successful, else an error code
-    */
-    int skipResponseHeaders();
-
-    /** Test whether all of the response headers have been consumed.
-      @return true if we are now processing the response body, else false
-    */
-    bool endOfHeadersReached() { return (iState == eReadingBody); };
-
-    /** Test whether the end of the body has been reached.
-      Only works if the Content-Length header was returned by the server
-      @return true if we are now at the end of the body, else false
-    */
-    bool endOfBodyReached();
-    virtual bool endOfStream() { return endOfBodyReached(); };
-    virtual bool completed() { return endOfBodyReached(); };
-
-    /** Return the length of the body.
-      @return Length of the body, in bytes, or kNoContentLengthHeader if no
-      Content-Length header was returned by the server
-    */
-    int contentLength() { return iContentLength; };
-
-    // Inherited from Print
-    // Note: 1st call to these indicates the user is sending the body, so if need
-    // Note: be we should finish the header first
-    virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
-    virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
-    // Inherited from Stream
-    virtual int available() { return iClient->available(); };
-    /** Read the next byte from the server.
-      @return Byte read or -1 if there are no bytes available.
-    */
-    virtual int read();
-    virtual int read(uint8_t *buf, size_t size);
-    virtual int peek() { return iClient->peek(); };
-    virtual void flush() { return iClient->flush(); };
-
-    // Inherited from Client
-    virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); };
-    virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); };
-    virtual void stop();
-    virtual uint8_t connected() { return iClient->connected(); };
-    virtual operator bool() { return bool(iClient); };
-    virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; };
-    virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; };
-protected:
-    /** Reset internal state data back to the "just initialised" state
-    */
-    void resetState();
-
-    /** Send the first part of the request and the initial headers.
-      @param aServerName Name of the server being connected to.  If NULL, the
-                         "Host" header line won't be sent
-      @param aServerIP  IP address of the server (only used if we're going through a
-                        proxy and aServerName is NULL
-      @param aServerPort  Port of the server being connected to.
-      @param aURLPath	Url to request
-      @param aHttpMethod  Type of HTTP request to make, e.g. "GET", "POST", etc.
-      @param aUserAgent User-Agent string to send.  If NULL the default
-                        user-agent kUserAgent will be sent
-      @return 0 if successful, else error
-    */
-    int sendInitialHeaders(const char* aServerName,
-                     IPAddress   aServerIP,
-                     uint16_t    aPort,
-                     const char* aURLPath,
-                     const char* aHttpMethod,
-                     const char* aUserAgent);
-
-    /* Let the server know that we've reached the end of the headers
-    */
-    void finishHeaders();
-
-    // Number of milliseconds that we wait each time there isn't any data
-    // available to be read (during status code and header processing)
-    static const int kHttpWaitForDataDelay = 1000;
-    // Number of milliseconds that we'll wait in total without receiveing any
-    // data before returning HTTP_ERROR_TIMED_OUT (during status code and header
-    // processing)
-    static const int kHttpResponseTimeout = 30*1000;
-    static const char* kContentLengthPrefix;
-    typedef enum {
-        eIdle,
-        eRequestStarted,
-        eRequestSent,
-        eReadingStatusCode,
-        eStatusCodeRead,
-        eReadingContentLength,
-        eSkipToEndOfHeader,
-        eLineStartingCRFound,
-        eReadingBody
-    } tHttpState;
-    // Ethernet client we're using
-    Client* iClient;
-    // Current state of the finite-state-machine
-    tHttpState iState;
-    // Stores the status code for the response, once known
-    int iStatusCode;
-    // Stores the value of the Content-Length header, if present
-    int iContentLength;
-    // How many bytes of the response body have been read by the user
-    int iBodyLengthConsumed;
-    // How far through a Content-Length header prefix we are
-    const char* iContentLengthPtr;
-    // Address of the proxy to use, if we're using one
-    IPAddress iProxyAddress;
-    uint16_t iProxyPort;
-    uint32_t iHttpResponseTimeout;
-};
-
-#endif
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
index d55dfa4..e9c163b 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,29 @@
-# HttpClient
+# ArduinoHttpClient
 
-HttpClient is a library to make it easier to interact with web servers from Arduino.
+[![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)
+[![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)
+[![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)
 
-## Dependencies
+ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino.
 
-- Requires the new Ethernet library API (with DHCP and DNS) which is in Arduino 1.0 and later
+Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient)
 
-## Installation
+## Dependencies
 
-1. Download the latest version of the library from https://github.com/amcewen/HttpClient/releases and save the file somewhere
-1. In the Arduino IDE, go to the Sketch -> Import Library -> Add Library... menu option
-1. Find the zip file that you saved in the first step, and choose that
-1. Check that it has been successfully added by opening the Sketch -> Import Library menu.  You should now see HttpClient listed among the available libraries.
+- Requires a networking hardware and a library that provides transport specific `Client` instance, such as:
+  - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA)
+  - [WiFi101](https://github.com/arduino-libraries/WiFi101)
+  - [Ethernet](https://github.com/arduino-libraries/Ethernet)
+  - [MKRGSM](https://github.com/arduino-libraries/MKRGSM)
+  - [MKRNB](https://github.com/arduino-libraries/MKRNB)
+  - [WiFi](https://github.com/arduino-libraries/WiFi)
+  - [GSM](https://github.com/arduino-libraries/GSM)
 
 ## Usage
 
 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).
 
-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 EthernetClient, WiFiClient and GSMClient.
+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.
 
 See the examples for more detail on how the library is used.
 
diff --git a/examples/BasicAuthGet/BasicAuthGet.ino b/examples/BasicAuthGet/BasicAuthGet.ino
new file mode 100644
index 0000000..861acc1
--- /dev/null
+++ b/examples/BasicAuthGet/BasicAuthGet.ino
@@ -0,0 +1,66 @@
+/*
+  GET client with HTTP basic authentication for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a GET request
+
+  created 14 Feb 2016
+  by Tom Igoe
+  modified 3 Jan 2017 to add HTTP basic authentication
+  by Sandeep Mistry
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+#include "arduino_secrets.h"
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making GET request with HTTP basic authentication");
+  client.beginRequest();
+  client.get("/secure");
+  client.sendBasicAuth("username", "password"); // send the username and password for authentication
+  client.endRequest();
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/BasicAuthGet/arduino_secrets.h b/examples/BasicAuthGet/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/BasicAuthGet/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/CustomHeader/CustomHeader.ino b/examples/CustomHeader/CustomHeader.ino
new file mode 100644
index 0000000..4b8abbb
--- /dev/null
+++ b/examples/CustomHeader/CustomHeader.ino
@@ -0,0 +1,91 @@
+/*
+  Custom request header example for the ArduinoHttpClient
+  library. This example sends a GET and a POST request with a custom header every 5 seconds.
+
+  based on SimpleGet example by Tom Igoe
+  header modifications by Todd Treece
+  modified 22 Jan 2019
+  by Tom Igoe
+
+  this example is in the public domain
+*/
+
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making GET request");
+  client.beginRequest();
+  client.get("/");
+  client.sendHeader("X-CUSTOM-HEADER", "custom_value");
+  client.endRequest();
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("GET Status code: ");
+  Serial.println(statusCode);
+  Serial.print("GET Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+
+  Serial.println("making POST request");
+  String postData = "name=Alice&age=12";
+  client.beginRequest();
+  client.post("/");
+  client.sendHeader(HTTP_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded");
+  client.sendHeader(HTTP_HEADER_CONTENT_LENGTH, postData.length());
+  client.sendHeader("X-CUSTOM-HEADER", "custom_value");
+  client.endRequest();
+  client.write((const byte*)postData.c_str(), postData.length());
+  // note: the above line can also be achieved with the simpler line below:
+  //client.print(postData);
+
+  // read the status code and body of the response
+  statusCode = client.responseStatusCode();
+  response = client.responseBody();
+
+  Serial.print("POST Status code: ");
+  Serial.println(statusCode);
+  Serial.print("POST Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/CustomHeader/arduino_secrets.h b/examples/CustomHeader/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/CustomHeader/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino
new file mode 100644
index 0000000..1c26c35
--- /dev/null
+++ b/examples/DweetGet/DweetGet.ino
@@ -0,0 +1,103 @@
+/*
+  Dweet.io GET client for ArduinoHttpClient library
+  Connects to dweet.io once every ten seconds,
+  sends a GET request and a request body. Uses SSL
+
+  Shows how to use Strings to assemble path and parse content
+  from response. dweet.io expects:
+  https://dweet.io/get/latest/dweet/for/thingName
+
+  For more on dweet.io, see https://dweet.io/play/
+
+  created 15 Feb 2016
+  updated 22 Jan 2019
+  by Tom Igoe
+
+  this example is in the public domain
+*/
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+const char serverAddress[] = "dweet.io";  // server address
+int port = 80;
+String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while (!Serial);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);     // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  // assemble the path for the GET message:
+  String path = "/get/latest/dweet/for/" + dweetName;
+
+  // send the GET request
+  Serial.println("making GET request");
+  client.get(path);
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  /*
+    Typical response is:
+    {"this":"succeeded",
+    "by":"getting",
+    "the":"dweets",
+    "with":[{"thing":"my-thing-name",
+      "created":"2016-02-16T05:10:36.589Z",
+      "content":{"sensorValue":456}}]}
+
+    You want "content": numberValue
+  */
+  // now parse the response looking for "content":
+  int labelStart = response.indexOf("content\":");
+  // find the first { after "content":
+  int contentStart = response.indexOf("{", labelStart);
+  // find the following } and get what's between the braces:
+  int contentEnd = response.indexOf("}", labelStart);
+  String content = response.substring(contentStart + 1, contentEnd);
+  Serial.println(content);
+
+  // now get the value after the colon, and convert to an int:
+  int valueStart = content.indexOf(":");
+  String valueString = content.substring(valueStart + 1);
+  int number = valueString.toInt();
+  Serial.print("Value string: ");
+  Serial.println(valueString);
+  Serial.print("Actual value: ");
+  Serial.println(number);
+
+  Serial.println("Wait ten seconds\n");
+  delay(10000);
+}
diff --git a/examples/DweetGet/arduino_secrets.h b/examples/DweetGet/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/DweetGet/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino
new file mode 100644
index 0000000..17e76f5
--- /dev/null
+++ b/examples/DweetPost/DweetPost.ino
@@ -0,0 +1,79 @@
+/*
+  Dweet.io POST client for ArduinoHttpClient library
+  Connects to dweet.io once every ten seconds,
+  sends a POST request and a request body.
+
+  Shows how to use Strings to assemble path and body
+
+  created 15 Feb 2016
+  modified 22 Jan 2019
+  by Tom Igoe
+
+  this example is in the public domain
+*/
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+const char serverAddress[] = "dweet.io";  // server address
+int port = 80;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while(!Serial);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  // assemble the path for the POST message:
+  String dweetName = "scandalous-cheese-hoarder";
+  String path = "/dweet/for/" + dweetName;
+  String contentType = "application/json";
+
+  // assemble the body of the POST message:
+  int sensorValue = analogRead(A0);
+  String postData = "{\"sensorValue\":\""; 
+  postData += sensorValue;
+  postData += "\"}";
+
+  Serial.println("making POST request");
+
+  // send the POST request
+  client.post(path, contentType, postData);
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait ten seconds\n");
+  delay(10000);
+}
diff --git a/examples/DweetPost/arduino_secrets.h b/examples/DweetPost/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/DweetPost/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino
new file mode 100644
index 0000000..0583fe9
--- /dev/null
+++ b/examples/HueBlink/HueBlink.ino
@@ -0,0 +1,98 @@
+/* HueBlink example for ArduinoHttpClient library
+
+   Uses ArduinoHttpClient library to control Philips Hue
+   For more on Hue developer API see http://developer.meethue.com
+
+  To control a light, the Hue expects a HTTP PUT request to:
+
+  http://hue.hub.address/api/hueUserName/lights/lightNumber/state
+
+  The body of the PUT request looks like this:
+  {"on": true} or {"on":false}
+
+  This example shows how to concatenate Strings to assemble the
+  PUT request and the body of the request.
+
+   modified 15 Feb 2016 
+   by Tom Igoe (tigoe) to match new API
+*/
+
+#include <SPI.h>
+#include <WiFi101.h>
+#include <ArduinoHttpClient.h>
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+int status = WL_IDLE_STATUS;      // the WiFi radio's status
+
+char hueHubIP[] = "192.168.0.3";  // IP address of the HUE bridge
+String hueUserName = "huebridgeusername"; // hue bridge username
+
+// make a WiFiClient instance and a HttpClient instance:
+WiFiClient wifi;
+HttpClient httpClient = HttpClient(wifi, hueHubIP);
+
+
+void setup() {
+  //Initialize serial and wait for port to open:
+  Serial.begin(9600);
+  while (!Serial); // wait for serial port to connect.
+
+  // attempt to connect to WiFi network:
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to WPA SSID: ");
+    Serial.println(ssid);
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // you're connected now, so print out the data:
+  Serial.print("You're connected to the network IP = ");
+  IPAddress ip = WiFi.localIP();
+  Serial.println(ip);
+}
+
+void loop() {
+  sendRequest(3, "on", "true");   // turn light on
+  delay(2000);                    // wait 2 seconds
+  sendRequest(3, "on", "false");  // turn light off
+  delay(2000);                    // wait 2 seconds
+}
+
+void sendRequest(int light, String cmd, String value) {
+  // make a String for the HTTP request path:
+  String request = "/api/" + hueUserName;
+  request += "/lights/";
+  request += light;
+  request += "/state/";
+
+  String contentType = "application/json";
+
+  // make a string for the JSON command:
+  String hueCmd = "{\"" + cmd;
+  hueCmd += "\":";
+  hueCmd += value;
+  hueCmd += "}";
+  // see what you assembled to send:
+  Serial.print("PUT request to server: ");
+  Serial.println(request);
+  Serial.print("JSON command to server: ");
+
+  // make the PUT request to the hub:
+  httpClient.put(request, contentType, hueCmd);
+  
+  // read the status code and body of the response
+  int statusCode = httpClient.responseStatusCode();
+  String response = httpClient.responseBody();
+
+  Serial.println(hueCmd);
+  Serial.print("Status code from server: ");
+  Serial.println(statusCode);
+  Serial.print("Server response: ");
+  Serial.println(response);
+  Serial.println();
+}
diff --git a/examples/HueBlink/arduino_secrets.h b/examples/HueBlink/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/HueBlink/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/ParseURL/ParseURL.ino b/examples/ParseURL/ParseURL.ino
new file mode 100644
index 0000000..410ac85
--- /dev/null
+++ b/examples/ParseURL/ParseURL.ino
@@ -0,0 +1,29 @@
+#include "URLParser.h"
+
+void setup() {
+
+    Serial.begin(9600);
+
+    while(!Serial);
+
+    Serial.println("starting");
+
+    ParsedUrl url(
+        "https://www.google.com/search?q=arduino"
+    );
+
+    Serial.print("parsed URL schema: \"");
+    Serial.print(url.schema());
+    Serial.print("\"\nparsed URL host: \"");
+    Serial.print(url.host());
+    Serial.print("\"\nparsed URL path: \"");
+    Serial.print(url.path());
+    Serial.print("\"\nparsed URL query: \"");
+    Serial.print(url.query());
+    Serial.print("\"\nparsed URL userinfo: \"");
+    Serial.print(url.userinfo());
+    Serial.println("\"");
+
+}
+
+void loop() { }
\ No newline at end of file
diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino
new file mode 100644
index 0000000..f97439f
--- /dev/null
+++ b/examples/PostWithHeaders/PostWithHeaders.ino
@@ -0,0 +1,77 @@
+/*
+  POST with headers client for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a POST request
+  with custom headers and a request body
+
+  created 14 Feb 2016
+  by Tom Igoe
+  modified 18 Mar 2017
+  by Sandeep Mistry
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making POST request");
+  String postData = "name=Alice&age=12";
+
+  client.beginRequest();
+  client.post("/");
+  client.sendHeader("Content-Type", "application/x-www-form-urlencoded");
+  client.sendHeader("Content-Length", postData.length());
+  client.sendHeader("X-Custom-Header", "custom-header-value");
+  client.beginBody();
+  client.print(postData);
+  client.endRequest();
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/PostWithHeaders/arduino_secrets.h b/examples/PostWithHeaders/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/PostWithHeaders/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino
new file mode 100644
index 0000000..336cb7a
--- /dev/null
+++ b/examples/SimpleDelete/SimpleDelete.ino
@@ -0,0 +1,68 @@
+/*
+  Simple DELETE client for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a DELETE request
+  and a request body
+
+  created 14 Feb 2016
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making DELETE request");
+  String contentType = "application/x-www-form-urlencoded";
+  String delData = "name=light&age=46";
+
+  client.del("/", contentType, delData);
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/SimpleDelete/arduino_secrets.h b/examples/SimpleDelete/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimpleDelete/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino
new file mode 100644
index 0000000..759b13f
--- /dev/null
+++ b/examples/SimpleGet/SimpleGet.ino
@@ -0,0 +1,62 @@
+/*
+  Simple GET client for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a GET request
+
+  created 14 Feb 2016
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making GET request");
+  client.get("/");
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/SimpleGet/arduino_secrets.h b/examples/SimpleGet/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimpleGet/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino
index f12f987..449d6af 100644
--- a/examples/SimpleHttpExample/SimpleHttpExample.ino
+++ b/examples/SimpleHttpExample/SimpleHttpExample.ino
@@ -2,49 +2,64 @@
 // Released under Apache License, version 2.0
 //
 // Simple example to show how to use the HttpClient library
-// Get's the web page given at http://<kHostname><kPath> and
+// Gets the web page given at http://<kHostname><kPath> and
 // outputs the content to the serial port
 
 #include <SPI.h>
-#include <HttpClient.h>
-#include <Ethernet.h>
-#include <EthernetClient.h>
+#include <WiFi101.h>
+#include <ArduinoHttpClient.h>
 
 // This example downloads the URL "http://arduino.cc/"
 
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+
+
 // Name of the server we want to connect to
 const char kHostname[] = "arduino.cc";
 // Path to download (this is the bit after the hostname in the URL
 // that you want to download
 const char kPath[] = "/";
 
-byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
-
 // Number of milliseconds to wait without receiving any data before we give up
 const int kNetworkTimeout = 30*1000;
 // Number of milliseconds to wait if no data is available before trying again
 const int kNetworkDelay = 1000;
 
+WiFiClient c;
+HttpClient http(c, kHostname);
+
 void setup()
 {
-  // initialize serial communications at 9600 bps:
-  Serial.begin(9600); 
+  //Initialize serial and wait for port to open:
+  Serial.begin(9600);
+  while (!Serial) {
+    ; // wait for serial port to connect. Needed for native USB port only
+  }
 
-  while (Ethernet.begin(mac) != 1)
-  {
-    Serial.println("Error getting IP address via DHCP, trying again...");
-    delay(15000);
-  }  
+  // attempt to connect to WiFi network:
+  Serial.print("Attempting to connect to WPA SSID: ");
+  Serial.println(ssid);
+  while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
+    // unsuccessful, retry in 4 seconds
+    Serial.print("failed ... ");
+    delay(4000);
+    Serial.print("retrying ... ");
+  }
+
+  Serial.println("connected");
 }
 
 void loop()
 {
   int err =0;
   
-  EthernetClient c;
-  HttpClient http(c);
-  
-  err = http.get(kHostname, kPath);
+  err = http.get(kPath);
   if (err == 0)
   {
     Serial.println("startedRequest ok");
@@ -59,44 +74,43 @@ void loop()
       // similar "success" code (200-299) before carrying on,
       // but we'll print out whatever response we get
 
-      err = http.skipResponseHeaders();
-      if (err >= 0)
-      {
-        int bodyLen = http.contentLength();
-        Serial.print("Content length is: ");
-        Serial.println(bodyLen);
-        Serial.println();
-        Serial.println("Body returned follows:");
-      
-        // Now we've got to the body, so we can print it out
-        unsigned long timeoutStart = millis();
-        char c;
-        // Whilst we haven't timed out & haven't reached the end of the body
-        while ( (http.connected() || http.available()) &&
-               ((millis() - timeoutStart) < kNetworkTimeout) )
-        {
-            if (http.available())
-            {
-                c = http.read();
-                // Print out this character
-                Serial.print(c);
-               
-                bodyLen--;
-                // We read something, reset the timeout counter
-                timeoutStart = millis();
-            }
-            else
-            {
-                // We haven't got any data, so let's pause to allow some to
-                // arrive
-                delay(kNetworkDelay);
-            }
-        }
-      }
-      else
+      // If you are interesting in the response headers, you
+      // can read them here:
+      //while(http.headerAvailable())
+      //{
+      //  String headerName = http.readHeaderName();
+      //  String headerValue = http.readHeaderValue();
+      //}
+
+      int bodyLen = http.contentLength();
+      Serial.print("Content length is: ");
+      Serial.println(bodyLen);
+      Serial.println();
+      Serial.println("Body returned follows:");
+    
+      // Now we've got to the body, so we can print it out
+      unsigned long timeoutStart = millis();
+      char c;
+      // Whilst we haven't timed out & haven't reached the end of the body
+      while ( (http.connected() || http.available()) &&
+             (!http.endOfBodyReached()) &&
+             ((millis() - timeoutStart) < kNetworkTimeout) )
       {
-        Serial.print("Failed to skip response headers: ");
-        Serial.println(err);
+          if (http.available())
+          {
+              c = http.read();
+              // Print out this character
+              Serial.print(c);
+             
+              // We read something, reset the timeout counter
+              timeoutStart = millis();
+          }
+          else
+          {
+              // We haven't got any data, so let's pause to allow some to
+              // arrive
+              delay(kNetworkDelay);
+          }
       }
     }
     else
@@ -115,5 +129,3 @@ void loop()
   // And just stop, now that we've tried a download
   while(1);
 }
-
-
diff --git a/examples/SimpleHttpExample/arduino_secrets.h b/examples/SimpleHttpExample/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimpleHttpExample/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino
new file mode 100644
index 0000000..8717438
--- /dev/null
+++ b/examples/SimplePost/SimplePost.ino
@@ -0,0 +1,66 @@
+/*
+  Simple POST client for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a POST request
+  and a request body
+
+  created 14 Feb 2016
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making POST request");
+  String contentType = "application/x-www-form-urlencoded";
+  String postData = "name=Alice&age=12";
+
+  client.post("/", contentType, postData);
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/SimplePost/arduino_secrets.h b/examples/SimplePost/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimplePost/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino
new file mode 100644
index 0000000..06dcd15
--- /dev/null
+++ b/examples/SimplePut/SimplePut.ino
@@ -0,0 +1,66 @@
+/*
+  Simple PUT client for ArduinoHttpClient library
+  Connects to server once every five seconds, sends a PUT request
+  and a request body
+
+  created 14 Feb 2016
+  modified 22 Jan 2019
+  by Tom Igoe
+  
+  this example is in the public domain
+ */
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "192.168.0.3";  // server address
+int port = 8080;
+
+WiFiClient wifi;
+HttpClient client = HttpClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("making PUT request");
+  String contentType = "application/x-www-form-urlencoded";
+  String putData = "name=light&age=46";
+
+  client.put("/", contentType, putData);
+
+  // read the status code and body of the response
+  int statusCode = client.responseStatusCode();
+  String response = client.responseBody();
+
+  Serial.print("Status code: ");
+  Serial.println(statusCode);
+  Serial.print("Response: ");
+  Serial.println(response);
+
+  Serial.println("Wait five seconds");
+  delay(5000);
+}
diff --git a/examples/SimplePut/arduino_secrets.h b/examples/SimplePut/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimplePut/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino
new file mode 100644
index 0000000..32f74e1
--- /dev/null
+++ b/examples/SimpleWebSocket/SimpleWebSocket.ino
@@ -0,0 +1,80 @@
+/*
+  Simple WebSocket client for ArduinoHttpClient library
+  Connects to the WebSocket server, and sends a hello
+  message every 5 seconds
+
+  created 28 Jun 2016
+  by Sandeep Mistry
+  modified 22 Jan 2019
+  by Tom Igoe
+
+  this example is in the public domain
+*/
+#include <ArduinoHttpClient.h>
+#include <WiFi101.h>
+#include "arduino_secrets.h"
+
+///////please enter your sensitive data in the Secret tab/arduino_secrets.h
+/////// WiFi Settings ///////
+char ssid[] = SECRET_SSID;
+char pass[] = SECRET_PASS;
+
+char serverAddress[] = "echo.websocket.org";  // server address
+int port = 80;
+
+WiFiClient wifi;
+WebSocketClient client = WebSocketClient(wifi, serverAddress, port);
+int status = WL_IDLE_STATUS;
+int count = 0;
+
+void setup() {
+  Serial.begin(9600);
+  while ( status != WL_CONNECTED) {
+    Serial.print("Attempting to connect to Network named: ");
+    Serial.println(ssid);                   // print the network name (SSID);
+
+    // Connect to WPA/WPA2 network:
+    status = WiFi.begin(ssid, pass);
+  }
+
+  // print the SSID of the network you're attached to:
+  Serial.print("SSID: ");
+  Serial.println(WiFi.SSID());
+
+  // print your WiFi shield's IP address:
+  IPAddress ip = WiFi.localIP();
+  Serial.print("IP Address: ");
+  Serial.println(ip);
+}
+
+void loop() {
+  Serial.println("starting WebSocket client");
+  client.begin();
+
+  while (client.connected()) {
+    Serial.print("Sending hello ");
+    Serial.println(count);
+
+    // send a hello #
+    client.beginMessage(TYPE_TEXT);
+    client.print("hello ");
+    client.print(count);
+    client.endMessage();
+
+    // increment count for next message
+    count++;
+
+    // check if a message is available to be received
+    int messageSize = client.parseMessage();
+
+    if (messageSize > 0) {
+      Serial.println("Received a message:");
+      Serial.println(client.readString());
+    }
+
+    // wait 5 seconds
+    delay(5000);
+  }
+
+  Serial.println("disconnected");
+}
diff --git a/examples/SimpleWebSocket/arduino_secrets.h b/examples/SimpleWebSocket/arduino_secrets.h
new file mode 100644
index 0000000..a8ff904
--- /dev/null
+++ b/examples/SimpleWebSocket/arduino_secrets.h
@@ -0,0 +1,3 @@
+#define SECRET_SSID ""
+#define SECRET_PASS ""
+
diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js
new file mode 100644
index 0000000..f3dd4d8
--- /dev/null
+++ b/examples/node_test_server/getPostPutDelete.js
@@ -0,0 +1,102 @@
+/*
+	Express.js GET/POST example
+	Shows how handle GET, POST, PUT, DELETE
+	in Express.js 4.0
+
+	created 14 Feb 2016
+	by Tom Igoe
+*/
+
+var express = require('express');			// include express.js
+var app = express();						// a local instance of it
+var bodyParser = require('body-parser');	// include body-parser
+var WebSocketServer = require('ws').Server	// include Web Socket server
+
+// you need a  body parser:
+app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded
+
+// this runs after the server successfully starts:
+function serverStart() {
+	var port = server.address().port;
+	console.log('Server listening on port '+ port);
+}
+
+app.get('/chunked', function(request, response) {
+	response.write('\n');
+	response.write('           `:;;;,`                      .:;;:.           \n');
+	response.write('        .;;;;;;;;;;;`                :;;;;;;;;;;:     TM \n');
+	response.write('      `;;;;;;;;;;;;;;;`            :;;;;;;;;;;;;;;;      \n');
+	response.write('     :;;;;;;;;;;;;;;;;;;         `;;;;;;;;;;;;;;;;;;     \n');
+	response.write('    ;;;;;;;;;;;;;;;;;;;;;       .;;;;;;;;;;;;;;;;;;;;    \n');
+	response.write('   ;;;;;;;;:`   `;;;;;;;;;     ,;;;;;;;;.`   .;;;;;;;;   \n');
+	response.write('  .;;;;;;,         :;;;;;;;   .;;;;;;;          ;;;;;;;  \n');
+	response.write('  ;;;;;;             ;;;;;;;  ;;;;;;,            ;;;;;;. \n');
+	response.write(' ,;;;;;               ;;;;;;.;;;;;;`              ;;;;;; \n');
+	response.write(' ;;;;;.                ;;;;;;;;;;;`      ```       ;;;;;`\n');
+	response.write(' ;;;;;                  ;;;;;;;;;,       ;;;       .;;;;;\n');
+	response.write('`;;;;:                  `;;;;;;;;        ;;;        ;;;;;\n');
+	response.write(',;;;;`    `,,,,,,,,      ;;;;;;;      .,,;;;,,,     ;;;;;\n');
+	response.write(':;;;;`    .;;;;;;;;       ;;;;;,      :;;;;;;;;     ;;;;;\n');
+	response.write(':;;;;`    .;;;;;;;;      `;;;;;;      :;;;;;;;;     ;;;;;\n');
+	response.write('.;;;;.                   ;;;;;;;.        ;;;        ;;;;;\n');
+	response.write(' ;;;;;                  ;;;;;;;;;        ;;;        ;;;;;\n');
+	response.write(' ;;;;;                 .;;;;;;;;;;       ;;;       ;;;;;,\n');
+	response.write(' ;;;;;;               `;;;;;;;;;;;;                ;;;;; \n');
+	response.write(' `;;;;;,             .;;;;;; ;;;;;;;              ;;;;;; \n');
+	response.write('  ;;;;;;:           :;;;;;;.  ;;;;;;;            ;;;;;;  \n');
+	response.write('   ;;;;;;;`       .;;;;;;;,    ;;;;;;;;        ;;;;;;;:  \n');
+	response.write('    ;;;;;;;;;:,:;;;;;;;;;:      ;;;;;;;;;;:,;;;;;;;;;;   \n');
+	response.write('    `;;;;;;;;;;;;;;;;;;;.        ;;;;;;;;;;;;;;;;;;;;    \n');
+	response.write('      ;;;;;;;;;;;;;;;;;           :;;;;;;;;;;;;;;;;:     \n');
+	response.write('       ,;;;;;;;;;;;;;,              ;;;;;;;;;;;;;;       \n');
+	response.write('         .;;;;;;;;;`                  ,;;;;;;;;:         \n');
+	response.write('                                                         \n');
+	response.write('                                                         \n');
+	response.write('                                                         \n');
+	response.write('                                                         \n');
+	response.write('    ;;;   ;;;;;`  ;;;;:  .;;  ;; ,;;;;;, ;;. `;,  ;;;;   \n');
+	response.write('    ;;;   ;;:;;;  ;;;;;; .;;  ;; ,;;;;;: ;;; `;, ;;;:;;  \n');
+	response.write('   ,;:;   ;;  ;;  ;;  ;; .;;  ;;   ,;,   ;;;,`;, ;;  ;;  \n');
+	response.write('   ;; ;:  ;;  ;;  ;;  ;; .;;  ;;   ,;,   ;;;;`;, ;;  ;;. \n');
+	response.write('   ;: ;;  ;;;;;:  ;;  ;; .;;  ;;   ,;,   ;;`;;;, ;;  ;;` \n');
+	response.write('  ,;;;;;  ;;`;;   ;;  ;; .;;  ;;   ,;,   ;; ;;;, ;;  ;;  \n');
+	response.write('  ;;  ,;, ;; .;;  ;;;;;:  ;;;;;: ,;;;;;: ;;  ;;, ;;;;;;  \n');
+	response.write('  ;;   ;; ;;  ;;` ;;;;.   `;;;:  ,;;;;;, ;;  ;;,  ;;;;   \n');
+	response.write('\n');
+	response.end();
+});
+
+// this is the POST handler:
+app.all('/*', function (request, response) {
+	console.log('Got a ' + request.method + ' request');
+	// the parameters of a GET request are passed in
+	// request.body. Pass that to formatResponse()
+	// for formatting:
+	console.log(request.headers);
+	if (request.method == 'GET') {
+		console.log(request.query);
+	} else {
+		console.log(request.body);
+	}
+
+	// send the response:
+	response.send('OK');
+	response.end();
+});
+
+// start the server:
+var server = app.listen(8080, serverStart);
+
+// create a WebSocket server and attach it to the server
+var wss = new WebSocketServer({server: server});
+
+wss.on('connection', function connection(ws) {
+	// new connection, add message listener
+	ws.on('message', function incoming(message) {
+		// received a message
+		console.log('received: %s', message);
+
+		// echo it back
+		ws.send(message);
+	});
+});
diff --git a/examples/node_test_server/package.json b/examples/node_test_server/package.json
new file mode 100644
index 0000000..09f2d8b
--- /dev/null
+++ b/examples/node_test_server/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "node_test_server",
+  "version": "0.0.1",
+  "author": {
+    "name": "Tom Igoe"
+  },
+  "dependencies": {
+    "body-parser": ">=1.11.0",
+    "express": ">=4.0.0",
+    "multer": "*",
+    "ws": "^1.1.1"
+  }
+}
diff --git a/keywords.txt b/keywords.txt
index cdefda4..209c917 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -1,12 +1,15 @@
 #######################################
-# Syntax Coloring Map For HttpClient
+# Syntax Coloring Map For ArduinoHttpClient
 #######################################
 
 #######################################
 # Datatypes (KEYWORD1)
 #######################################
 
+ArduinoHttpClient	KEYWORD1
 HttpClient	KEYWORD1
+WebSocketClient	KEYWORD1
+URLEncoder	KEYWORD1
 
 #######################################
 # Methods and Functions (KEYWORD2)
@@ -15,8 +18,10 @@ HttpClient	KEYWORD1
 get	KEYWORD2
 post	KEYWORD2
 put	KEYWORD2
+patch	KEYWORD2
 startRequest	KEYWORD2
 beginRequest	KEYWORD2
+beginBody	KEYWORD2
 sendHeader	KEYWORD2
 sendBasicAuth	KEYWORD2
 endRequest	KEYWORD2
@@ -27,13 +32,36 @@ endOfHeadersReached	KEYWORD2
 endOfBodyReached	KEYWORD2
 completed	KEYWORD2
 contentLength	KEYWORD2
+isResponseChunked	KEYWORD2
+connectionKeepAlive	KEYWORD2
+noDefaultRequestHeaders	KEYWORD2
+headerAvailable	KEYWORD2
+readHeaderName	KEYWORD2
+readHeaderValue	KEYWORD2
+responseBody	KEYWORD2
+
+beginMessage	KEYWORD2
+endMessage	KEYWORD2
+parseMessage	KEYWORD2
+messageType	KEYWORD2
+isFinal	KEYWORD2
+readString	KEYWORD2
+ping	KEYWORD2
+
+encode	KEYWORD2
 
 #######################################
 # Constants (LITERAL1)
 #######################################
-HTTP_SUCCESS LITERAL1
-HTTP_ERROR_CONNECTION_FAILED LITERAL1
-HTTP_ERROR_API LITERAL1
-HTTP_ERROR_TIMED_OUT LITERAL1
-HTTP_ERROR_INVALID_RESPONSE LITERAL1
+HTTP_SUCCESS	LITERAL1
+HTTP_ERROR_CONNECTION_FAILED	LITERAL1
+HTTP_ERROR_API	LITERAL1
+HTTP_ERROR_TIMED_OUT	LITERAL1
+HTTP_ERROR_INVALID_RESPONSE	LITERAL1
 
+TYPE_CONTINUATION	LITERAL1
+TYPE_TEXT	LITERAL1
+TYPE_BINARY	LITERAL1
+TYPE_CONNECTION_CLOSE	LITERAL1
+TYPE_PING	LITERAL1
+TYPE_PONG	LITERAL1
diff --git a/library.properties b/library.properties
index 577c5d9..c93d0de 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,10 @@
-name=HttpClient
-version=2.2.0
-author=Adrian McEwen <adrianm@mcqn.com>
-maintainer=Adrian McEwen <adrianm@mcqn.com>
-sentence=Library to easily make HTTP GET, POST and PUT requests to a web server.
-paragraph=Works with any class derived from Client - so switching between Ethernet, WiFi and GSMClient requires minimal code changes.
+name=ArduinoHttpClient
+version=0.6.1
+author=Arduino
+maintainer=Arduino <info@arduino.cc>
+sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets.
+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.
 category=Communication
-url=http://github.com/amcewen/HttpClient
+url=https://github.com/arduino-libraries/ArduinoHttpClient
 architectures=*
+includes=ArduinoHttpClient.h
diff --git a/src/ArduinoHttpClient.h b/src/ArduinoHttpClient.h
new file mode 100644
index 0000000..abb8494
--- /dev/null
+++ b/src/ArduinoHttpClient.h
@@ -0,0 +1,12 @@
+// Library to simplify HTTP fetching on Arduino
+// (c) Copyright Arduino. 2016
+// Released under Apache License, version 2.0
+
+#ifndef ArduinoHttpClient_h
+#define ArduinoHttpClient_h
+
+#include "HttpClient.h"
+#include "WebSocketClient.h"
+#include "URLEncoder.h"
+
+#endif
diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp
new file mode 100644
index 0000000..31909d9
--- /dev/null
+++ b/src/HttpClient.cpp
@@ -0,0 +1,867 @@
+// Class to simplify HTTP fetching on Arduino
+// (c) Copyright 2010-2011 MCQN Ltd
+// Released under Apache License, version 2.0
+
+#include "HttpClient.h"
+#include "b64.h"
+
+// Initialize constants
+const char* HttpClient::kUserAgent = "Arduino/2.2.0";
+const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
+const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED;
+
+HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
+ : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
+   iConnectionClose(true), iSendDefaultRequestHeaders(true)
+{
+  resetState();
+}
+
+HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
+ : HttpClient(aClient, aServerName.c_str(), aServerPort)
+{
+}
+
+HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
+ : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort),
+   iConnectionClose(true), iSendDefaultRequestHeaders(true)
+{
+  resetState();
+}
+
+void HttpClient::resetState()
+{
+  iState = eIdle;
+  iStatusCode = 0;
+  iContentLength = kNoContentLengthHeader;
+  iBodyLengthConsumed = 0;
+  iContentLengthPtr = kContentLengthPrefix;
+  iTransferEncodingChunkedPtr = kTransferEncodingChunked;
+  iIsChunked = false;
+  iChunkLength = 0;
+  iHttpResponseTimeout = kHttpResponseTimeout;
+  iHttpWaitForDataDelay = kHttpWaitForDataDelay;
+}
+
+void HttpClient::stop()
+{
+  iClient->stop();
+  resetState();
+}
+
+void HttpClient::connectionKeepAlive()
+{
+  iConnectionClose = false;
+}
+
+void HttpClient::noDefaultRequestHeaders()
+{
+  iSendDefaultRequestHeaders = false;
+}
+
+void HttpClient::beginRequest()
+{
+  iState = eRequestStarted;
+}
+
+int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, 
+                                const char* aContentType, int aContentLength, const byte aBody[])
+{
+    if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk)
+    {
+        flushClientRx();
+
+        resetState();
+    }
+
+    tHttpState initialState = iState;
+
+    if ((eIdle != iState) && (eRequestStarted != iState))
+    {
+        return HTTP_ERROR_API;
+    }
+
+    if (iConnectionClose || !iClient->connected())
+    {
+        if (iServerName)
+        {
+            if (!(iClient->connect(iServerName, iServerPort) > 0))
+            {
+#ifdef LOGGING
+                Serial.println("Connection failed");
+#endif
+                return HTTP_ERROR_CONNECTION_FAILED;
+            }
+        }
+        else
+        {
+            if (!(iClient->connect(iServerAddress, iServerPort) > 0))
+            {
+#ifdef LOGGING
+                Serial.println("Connection failed");
+#endif
+                return HTTP_ERROR_CONNECTION_FAILED;
+            }    
+        }
+    }
+    else
+    {
+#ifdef LOGGING
+        Serial.println("Connection already open");
+#endif
+    }
+
+    // Now we're connected, send the first part of the request
+    int ret = sendInitialHeaders(aURLPath, aHttpMethod);
+
+    if (HTTP_SUCCESS == ret)
+    {
+        if (aContentType)
+        {
+            sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType);
+        }
+
+        if (aContentLength > 0)
+        {
+            sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength);
+        }
+
+        bool hasBody = (aBody && aContentLength > 0);
+
+        if (initialState == eIdle || hasBody)
+        {
+            // This was a simple version of the API, so terminate the headers now
+            finishHeaders();
+        }
+        // else we'll call it in endRequest or in the first call to print, etc.
+
+        if (hasBody)
+        {
+                write(aBody, aContentLength);
+        }
+    }
+
+    return ret;
+}
+
+int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod)
+{
+#ifdef LOGGING
+    Serial.println("Connected");
+#endif
+    // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0"
+    iClient->print(aHttpMethod);
+    iClient->print(" ");
+
+    iClient->print(aURLPath);
+    iClient->println(" HTTP/1.1");
+    if (iSendDefaultRequestHeaders)
+    {
+        // The host header, if required
+        if (iServerName)
+        {
+            iClient->print("Host: ");
+            iClient->print(iServerName);
+            if (iServerPort != kHttpPort && iServerPort != kHttpsPort)
+            {
+              iClient->print(":");
+              iClient->print(iServerPort);
+            }
+            iClient->println();
+        }
+        // And user-agent string
+        sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent);
+    }
+
+    if (iConnectionClose)
+    {
+        // Tell the server to
+        // close this connection after we're done
+        sendHeader(HTTP_HEADER_CONNECTION, "close");
+    }
+
+    // Everything has gone well
+    iState = eRequestStarted;
+    return HTTP_SUCCESS;
+}
+
+void HttpClient::sendHeader(const char* aHeader)
+{
+    iClient->println(aHeader);
+}
+
+void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue)
+{
+    iClient->print(aHeaderName);
+    iClient->print(": ");
+    iClient->println(aHeaderValue);
+}
+
+void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue)
+{
+    iClient->print(aHeaderName);
+    iClient->print(": ");
+    iClient->println(aHeaderValue);
+}
+
+void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword)
+{
+    // Send the initial part of this header line
+    iClient->print("Authorization: Basic ");
+    // Now Base64 encode "aUser:aPassword" and send that
+    // This seems trickier than it should be but it's mostly to avoid either
+    // (a) some arbitrarily sized buffer which hopes to be big enough, or
+    // (b) allocating and freeing memory
+    // ...so we'll loop through 3 bytes at a time, outputting the results as we
+    // go.
+    // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data
+    unsigned char input[3];
+    unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print
+    int userLen = strlen(aUser);
+    int passwordLen = strlen(aPassword);
+    int inputOffset = 0;
+    for (int i = 0; i < (userLen+1+passwordLen); i++)
+    {
+        // Copy the relevant input byte into the input
+        if (i < userLen)
+        {
+            input[inputOffset++] = aUser[i];
+        }
+        else if (i == userLen)
+        {
+            input[inputOffset++] = ':';
+        }
+        else
+        {
+            input[inputOffset++] = aPassword[i-(userLen+1)];
+        }
+        // See if we've got a chunk to encode
+        if ( (inputOffset == 3) || (i == userLen+passwordLen) )
+        {
+            // We've either got to a 3-byte boundary, or we've reached then end
+            b64_encode(input, inputOffset, output, 4);
+            // NUL-terminate the output string
+            output[4] = '\0';
+            // And write it out
+            iClient->print((char*)output);
+// FIXME We might want to fill output with '=' characters if b64_encode doesn't
+// FIXME do it for us when we're encoding the final chunk
+            inputOffset = 0;
+        }
+    }
+    // And end the header we've sent
+    iClient->println();
+}
+
+void HttpClient::finishHeaders()
+{
+    iClient->println();
+    iState = eRequestSent;
+}
+
+void HttpClient::flushClientRx()
+{
+    while (iClient->available())
+    {
+        iClient->read();
+    }
+}
+
+void HttpClient::endRequest()
+{
+    beginBody();
+}
+
+void HttpClient::beginBody()
+{
+    if (iState < eRequestSent)
+    {
+        // We still need to finish off the headers
+        finishHeaders();
+    }
+    // else the end of headers has already been sent, so nothing to do here
+}
+
+int HttpClient::get(const char* aURLPath)
+{
+    return startRequest(aURLPath, HTTP_METHOD_GET);
+}
+
+int HttpClient::get(const String& aURLPath)
+{
+    return get(aURLPath.c_str());
+}
+
+int HttpClient::post(const char* aURLPath)
+{
+    return startRequest(aURLPath, HTTP_METHOD_POST);
+}
+
+int HttpClient::post(const String& aURLPath)
+{
+    return post(aURLPath.c_str());
+}
+
+int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody)
+{
+    return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
+}
+
+int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody)
+{
+    return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
+}
+
+int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
+{
+    return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody);
+}
+
+int HttpClient::put(const char* aURLPath)
+{
+    return startRequest(aURLPath, HTTP_METHOD_PUT);
+}
+
+int HttpClient::put(const String& aURLPath)
+{
+    return put(aURLPath.c_str());
+}
+
+int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody)
+{
+    return put(aURLPath, aContentType, strlen(aBody),  (const byte*)aBody);
+}
+
+int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody)
+{
+    return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
+}
+
+int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
+{
+    return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody);
+}
+
+int HttpClient::patch(const char* aURLPath)
+{
+    return startRequest(aURLPath, HTTP_METHOD_PATCH);
+}
+
+int HttpClient::patch(const String& aURLPath)
+{
+    return patch(aURLPath.c_str());
+}
+
+int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody)
+{
+    return patch(aURLPath, aContentType, strlen(aBody),  (const byte*)aBody);
+}
+
+int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody)
+{
+    return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
+}
+
+int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
+{
+    return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody);
+}
+
+int HttpClient::del(const char* aURLPath)
+{
+    return startRequest(aURLPath, HTTP_METHOD_DELETE);
+}
+
+int HttpClient::del(const String& aURLPath)
+{
+    return del(aURLPath.c_str());
+}
+
+int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody)
+{
+    return del(aURLPath, aContentType, strlen(aBody),  (const byte*)aBody);
+}
+
+int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody)
+{
+    return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
+}
+
+int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
+{
+    return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody);
+}
+
+int HttpClient::responseStatusCode()
+{
+    if (iState < eRequestSent)
+    {
+        return HTTP_ERROR_API;
+    }
+    // The first line will be of the form Status-Line:
+    //   HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+    // Where HTTP-Version is of the form:
+    //   HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
+
+    int c = '\0';
+    do
+    {
+        // Make sure the status code is reset, and likewise the state.  This
+        // lets us easily cope with 1xx informational responses by just
+        // ignoring them really, and reading the next line for a proper response
+        iStatusCode = 0;
+        iState = eRequestSent;
+
+        unsigned long timeoutStart = millis();
+        // Psuedo-regexp we're expecting before the status-code
+        const char* statusPrefix = "HTTP/*.* ";
+        const char* statusPtr = statusPrefix;
+        // Whilst we haven't timed out & haven't reached the end of the headers
+        while ((c != '\n') && 
+               ( (millis() - timeoutStart) < iHttpResponseTimeout ))
+        {
+            if (available())
+            {
+                c = HttpClient::read();
+                if (c != -1)
+                {
+                    switch(iState)
+                    {
+                    case eRequestSent:
+                        // We haven't reached the status code yet
+                        if ( (*statusPtr == '*') || (*statusPtr == c) )
+                        {
+                            // This character matches, just move along
+                            statusPtr++;
+                            if (*statusPtr == '\0')
+                            {
+                                // We've reached the end of the prefix
+                                iState = eReadingStatusCode;
+                            }
+                        }
+                        else
+                        {
+                            return HTTP_ERROR_INVALID_RESPONSE;
+                        }
+                        break;
+                    case eReadingStatusCode:
+                        if (isdigit(c))
+                        {
+                            // This assumes we won't get more than the 3 digits we
+                            // want
+                            iStatusCode = iStatusCode*10 + (c - '0');
+                        }
+                        else
+                        {
+                            // We've reached the end of the status code
+                            // We could sanity check it here or double-check for ' '
+                            // rather than anything else, but let's be lenient
+                            iState = eStatusCodeRead;
+                        }
+                        break;
+                    case eStatusCodeRead:
+                        // We're just waiting for the end of the line now
+                        break;
+
+                    default:
+                        break;
+                    };
+                    // We read something, reset the timeout counter
+                    timeoutStart = millis();
+                }
+            }
+            else
+            {
+                // We haven't got any data, so let's pause to allow some to
+                // arrive
+                delay(iHttpWaitForDataDelay);
+            }
+        }
+        if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) )
+        {
+            // We've reached the end of an informational status line
+            c = '\0'; // Clear c so we'll go back into the data reading loop
+        }
+    }
+    // If we've read a status code successfully but it's informational (1xx)
+    // loop back to the start
+    while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) );
+
+    if ( (c == '\n') && (iState == eStatusCodeRead) )
+    {
+        // We've read the status-line successfully
+        return iStatusCode;
+    }
+    else if (c != '\n')
+    {
+        // We must've timed out before we reached the end of the line
+        return HTTP_ERROR_TIMED_OUT;
+    }
+    else
+    {
+        // This wasn't a properly formed status line, or at least not one we
+        // could understand
+        return HTTP_ERROR_INVALID_RESPONSE;
+    }
+}
+
+int HttpClient::skipResponseHeaders()
+{
+    // Just keep reading until we finish reading the headers or time out
+    unsigned long timeoutStart = millis();
+    // Whilst we haven't timed out & haven't reached the end of the headers
+    while ((!endOfHeadersReached()) && 
+           ( (millis() - timeoutStart) < iHttpResponseTimeout ))
+    {
+        if (available())
+        {
+            (void)readHeader();
+            // We read something, reset the timeout counter
+            timeoutStart = millis();
+        }
+        else
+        {
+            // We haven't got any data, so let's pause to allow some to
+            // arrive
+            delay(iHttpWaitForDataDelay);
+        }
+    }
+    if (endOfHeadersReached())
+    {
+        // Success
+        return HTTP_SUCCESS;
+    }
+    else
+    {
+        // We must've timed out
+        return HTTP_ERROR_TIMED_OUT;
+    }
+}
+
+bool HttpClient::endOfHeadersReached()
+{
+    return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
+};
+
+long HttpClient::contentLength()
+{
+    // skip the response headers, if they haven't been read already 
+    if (!endOfHeadersReached())
+    {
+        skipResponseHeaders();
+    }
+
+    return iContentLength;
+}
+
+String HttpClient::responseBody()
+{
+    int bodyLength = contentLength();
+    String response;
+
+    if (bodyLength > 0)
+    {
+        // try to reserve bodyLength bytes
+        if (response.reserve(bodyLength) == 0) {
+            // String reserve failed
+            return String((const char*)NULL);
+        }
+    }
+
+    // keep on timedRead'ing, until:
+    //  - we have a content length: body length equals consumed or no bytes
+    //                              available
+    //  - no content length:        no bytes are available
+    while (iBodyLengthConsumed != bodyLength)
+    {
+        int c = timedRead();
+
+        if (c == -1) {
+            // read timed out, done
+            break;
+        }
+
+        if (!response.concat((char)c)) {
+            // adding char failed
+            return String((const char*)NULL);
+        }
+    }
+
+    if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) {
+        // failure, we did not read in response content length bytes
+        return String((const char*)NULL);
+    }
+
+    return response;
+}
+
+bool HttpClient::endOfBodyReached()
+{
+    if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader))
+    {
+        // We've got to the body and we know how long it will be
+        return (iBodyLengthConsumed >= contentLength());
+    }
+    return false;
+}
+
+int HttpClient::available()
+{
+    if (iState == eReadingChunkLength)
+    {
+        while (iClient->available())
+        {
+            char c = iClient->read();
+
+            if (c == '\n')
+            {
+                iState = eReadingBodyChunk;
+                break;
+            }
+            else if (c == '\r')
+            {
+                // no-op
+            }
+            else if (isHexadecimalDigit(c))
+            {
+                char digit[2] = {c, '\0'};
+
+                iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16);
+            }
+        }
+    }
+
+    if (iState == eReadingBodyChunk && iChunkLength == 0)
+    {
+        iState = eReadingChunkLength;
+    }
+    
+    if (iState == eReadingChunkLength)
+    {
+        return 0;
+    }
+    
+    int clientAvailable = iClient->available();
+
+    if (iState == eReadingBodyChunk)
+    {
+        return min(clientAvailable, iChunkLength);
+    }
+    else
+    {
+        return clientAvailable;
+    }
+}
+
+
+int HttpClient::read()
+{
+    if (iIsChunked && !available())
+    {
+        return -1;
+    }
+
+    int ret = iClient->read();
+    if (ret >= 0)
+    {
+        if (endOfHeadersReached() && iContentLength > 0)
+        {
+            // We're outputting the body now and we've seen a Content-Length header
+            // So keep track of how many bytes are left
+            iBodyLengthConsumed++;
+        }
+
+        if (iState == eReadingBodyChunk)
+        {
+            iChunkLength--;
+
+            if (iChunkLength == 0)
+            {
+                iState = eReadingChunkLength;
+            }
+        }
+    }
+    return ret;
+}
+
+bool HttpClient::headerAvailable()
+{
+    // clear the currently stored header line
+    iHeaderLine = "";
+
+    while (!endOfHeadersReached())
+    {
+        // read a byte from the header
+        int c = readHeader();
+
+        if (c == '\r' || c == '\n')
+        {
+            if (iHeaderLine.length())
+            {
+                // end of the line, all done
+                break;
+            } 
+            else
+            {
+                // ignore any CR or LF characters
+                continue;
+            }
+        }
+
+        // append byte to header line
+        iHeaderLine += (char)c;
+    }
+
+    return (iHeaderLine.length() > 0);
+}
+
+String HttpClient::readHeaderName()
+{
+    int colonIndex = iHeaderLine.indexOf(':');
+
+    if (colonIndex == -1)
+    {
+        return "";
+    }
+
+    return iHeaderLine.substring(0, colonIndex);
+}
+
+String HttpClient::readHeaderValue()
+{
+    int colonIndex = iHeaderLine.indexOf(':');
+    int startIndex = colonIndex + 1;
+
+    if (colonIndex == -1)
+    {
+        return "";
+    }
+
+    // trim any leading whitespace
+    while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex]))
+    {
+        startIndex++;
+    }
+
+    return iHeaderLine.substring(startIndex);
+}
+
+int HttpClient::read(uint8_t *buf, size_t size)
+{
+    int ret =iClient->read(buf, size);
+    if (endOfHeadersReached() && iContentLength > 0)
+    {
+        // We're outputting the body now and we've seen a Content-Length header
+        // So keep track of how many bytes are left
+        if (ret >= 0)
+        {
+            iBodyLengthConsumed += ret;
+        }
+    }
+    return ret;
+}
+
+int HttpClient::readHeader()
+{
+    char c = HttpClient::read();
+
+    if (endOfHeadersReached())
+    {
+        // We've passed the headers, but rather than return an error, we'll just
+        // act as a slightly less efficient version of read()
+        return c;
+    }
+
+    // Whilst reading out the headers to whoever wants them, we'll keep an
+    // eye out for the "Content-Length" header
+    switch(iState)
+    {
+    case eStatusCodeRead:
+        // We're at the start of a line, or somewhere in the middle of reading
+        // the Content-Length prefix
+        if (*iContentLengthPtr == c)
+        {
+            // This character matches, just move along
+            iContentLengthPtr++;
+            if (*iContentLengthPtr == '\0')
+            {
+                // We've reached the end of the prefix
+                iState = eReadingContentLength;
+                // Just in case we get multiple Content-Length headers, this
+                // will ensure we just get the value of the last one
+                iContentLength = 0;
+                iBodyLengthConsumed = 0;
+            }
+        }
+        else if (*iTransferEncodingChunkedPtr == c)
+        {
+            // This character matches, just move along
+            iTransferEncodingChunkedPtr++;
+            if (*iTransferEncodingChunkedPtr == '\0')
+            {
+                // We've reached the end of the Transfer Encoding: chunked header
+                iIsChunked = true;
+                iState = eSkipToEndOfHeader;
+            }
+        }
+        else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r'))
+        {
+            // We've found a '\r' at the start of a line, so this is probably
+            // the end of the headers
+            iState = eLineStartingCRFound;
+        }
+        else
+        {
+            // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line
+            iState = eSkipToEndOfHeader;
+        }
+        break;
+    case eReadingContentLength:
+        if (isdigit(c))
+        {
+            long _iContentLength = iContentLength*10 + (c - '0');
+            // Only apply if the value didn't wrap around
+            if (_iContentLength > iContentLength) {
+                iContentLength = _iContentLength;
+            }
+        }
+        else
+        {
+            // We've reached the end of the content length
+            // We could sanity check it here or double-check for "\r\n"
+            // rather than anything else, but let's be lenient
+            iState = eSkipToEndOfHeader;
+        }
+        break;
+    case eLineStartingCRFound:
+        if (c == '\n')
+        {
+            if (iIsChunked)
+            {
+                iState = eReadingChunkLength;
+                iChunkLength = 0;
+            }
+            else
+            {
+                iState = eReadingBody;
+            }
+        }
+        break;
+    default:
+        // We're just waiting for the end of the line now
+        break;
+    };
+
+    if ( (c == '\n') && !endOfHeadersReached() )
+    {
+        // We've got to the end of this line, start processing again
+        iState = eStatusCodeRead;
+        iContentLengthPtr = kContentLengthPrefix;
+        iTransferEncodingChunkedPtr = kTransferEncodingChunked;
+    }
+    // And return the character read to whoever wants it
+    return c;
+}
+
+
+
diff --git a/src/HttpClient.h b/src/HttpClient.h
new file mode 100644
index 0000000..3d404af
--- /dev/null
+++ b/src/HttpClient.h
@@ -0,0 +1,396 @@
+// Class to simplify HTTP fetching on Arduino
+// (c) Copyright MCQN Ltd. 2010-2012
+// Released under Apache License, version 2.0
+
+#ifndef HttpClient_h
+#define HttpClient_h
+
+#include <Arduino.h>
+#include <IPAddress.h>
+#include "Client.h"
+
+static const int HTTP_SUCCESS =0;
+// The end of the headers has been reached.  This consumes the '\n'
+// Could not connect to the server
+static const int HTTP_ERROR_CONNECTION_FAILED =-1;
+// This call was made when the HttpClient class wasn't expecting it
+// to be called.  Usually indicates your code is using the class
+// incorrectly
+static const int HTTP_ERROR_API =-2;
+// Spent too long waiting for a reply
+static const int HTTP_ERROR_TIMED_OUT =-3;
+// The response from the server is invalid, is it definitely an HTTP
+// server?
+static const int HTTP_ERROR_INVALID_RESPONSE =-4;
+
+// Define some of the common methods and headers here
+// That lets other code reuse them without having to declare another copy
+// of them, so saves code space and RAM
+#define HTTP_METHOD_GET    "GET"
+#define HTTP_METHOD_POST   "POST"
+#define HTTP_METHOD_PUT    "PUT"
+#define HTTP_METHOD_PATCH  "PATCH"
+#define HTTP_METHOD_DELETE "DELETE"
+#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
+#define HTTP_HEADER_CONTENT_TYPE   "Content-Type"
+#define HTTP_HEADER_CONNECTION     "Connection"
+#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
+#define HTTP_HEADER_USER_AGENT     "User-Agent"
+#define HTTP_HEADER_VALUE_CHUNKED  "chunked"
+
+class HttpClient : public Client
+{
+public:
+    static const int kNoContentLengthHeader =-1;
+    static const int kHttpPort =80;
+    static const int kHttpsPort =443;
+    static const char* kUserAgent;
+
+// FIXME Write longer API request, using port and user-agent, example
+// FIXME Update tempToPachube example to calculate Content-Length correctly
+
+    HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort);
+    HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort);
+    HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort);
+
+    /** Start a more complex request.
+        Use this when you need to send additional headers in the request,
+        but you will also need to call endRequest() when you are finished.
+    */
+    void beginRequest();
+
+    /** End a more complex request.
+        Use this when you need to have sent additional headers in the request,
+        but you will also need to call beginRequest() at the start.
+    */
+    void endRequest();
+
+    /** Start the body of a more complex request.
+        Use this when you need to send the body after additional headers
+        in the request, but can optionally call endRequest() when
+        you are finished.
+    */
+    void beginBody();
+
+    /** Connect to the server and start to send a GET request.
+      @param aURLPath     Url to request
+      @return 0 if successful, else error
+    */
+    int get(const char* aURLPath);
+    int get(const String& aURLPath);
+
+    /** Connect to the server and start to send a POST request.
+      @param aURLPath     Url to request
+      @return 0 if successful, else error
+    */
+    int post(const char* aURLPath);
+    int post(const String& aURLPath);
+
+    /** Connect to the server and send a POST request
+        with body and content type
+      @param aURLPath     Url to request
+      @param aContentType Content type of request body
+      @param aBody        Body of the request
+      @return 0 if successful, else error
+    */
+    int post(const char* aURLPath, const char* aContentType, const char* aBody);
+    int post(const String& aURLPath, const String& aContentType, const String& aBody);
+    int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
+
+    /** Connect to the server and start to send a PUT request.
+      @param aURLPath     Url to request
+      @return 0 if successful, else error
+    */
+    int put(const char* aURLPath);
+    int put(const String& aURLPath);
+
+    /** Connect to the server and send a PUT request
+        with body and content type
+      @param aURLPath     Url to request
+      @param aContentType Content type of request body
+      @param aBody        Body of the request
+      @return 0 if successful, else error
+    */
+    int put(const char* aURLPath, const char* aContentType, const char* aBody);
+    int put(const String& aURLPath, const String& aContentType, const String& aBody);
+    int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
+
+    /** Connect to the server and start to send a PATCH request.
+      @param aURLPath     Url to request
+      @return 0 if successful, else error
+    */
+    int patch(const char* aURLPath);
+    int patch(const String& aURLPath);
+
+    /** Connect to the server and send a PATCH request
+        with body and content type
+      @param aURLPath     Url to request
+      @param aContentType Content type of request body
+      @param aBody        Body of the request
+      @return 0 if successful, else error
+    */
+    int patch(const char* aURLPath, const char* aContentType, const char* aBody);
+    int patch(const String& aURLPath, const String& aContentType, const String& aBody);
+    int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
+
+    /** Connect to the server and start to send a DELETE request.
+      @param aURLPath     Url to request
+      @return 0 if successful, else error
+    */
+    int del(const char* aURLPath);
+    int del(const String& aURLPath);
+
+    /** Connect to the server and send a DELETE request
+        with body and content type
+      @param aURLPath     Url to request
+      @param aContentType Content type of request body
+      @param aBody        Body of the request
+      @return 0 if successful, else error
+    */
+    int del(const char* aURLPath, const char* aContentType, const char* aBody);
+    int del(const String& aURLPath, const String& aContentType, const String& aBody);
+    int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
+
+    /** Connect to the server and start to send the request.
+        If a body is provided, the entire request (including headers and body) will be sent
+      @param aURLPath        Url to request
+      @param aHttpMethod     Type of HTTP request to make, e.g. "GET", "POST", etc.
+      @param aContentType    Content type of request body (optional)
+      @param aContentLength  Length of request body (optional)
+      @param aBody           Body of request (optional)
+      @return 0 if successful, else error
+    */
+    int startRequest(const char* aURLPath,
+                     const char* aHttpMethod,
+                     const char* aContentType = NULL,
+                     int aContentLength = -1,
+                     const byte aBody[] = NULL);
+
+    /** Send an additional header line.  This can only be called in between the
+      calls to beginRequest and endRequest.
+      @param aHeader Header line to send, in its entirety (but without the
+                     trailing CRLF.  E.g. "Authorization: Basic YQDDCAIGES"
+    */
+    void sendHeader(const char* aHeader);
+
+    void sendHeader(const String& aHeader)
+      { sendHeader(aHeader.c_str()); }
+
+    /** Send an additional header line.  This is an alternate form of
+      sendHeader() which takes the header name and content as separate strings.
+      The call will add the ": " to separate the header, so for example, to
+      send a XXXXXX header call sendHeader("XXXXX", "Something")
+      @param aHeaderName Type of header being sent
+      @param aHeaderValue Value for that header
+    */
+    void sendHeader(const char* aHeaderName, const char* aHeaderValue);
+
+    void sendHeader(const String& aHeaderName, const String& aHeaderValue)
+      { sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); }
+
+    /** Send an additional header line.  This is an alternate form of
+      sendHeader() which takes the header name and content separately but where
+      the value is provided as an integer.
+      The call will add the ": " to separate the header, so for example, to
+      send a XXXXXX header call sendHeader("XXXXX", 123)
+      @param aHeaderName Type of header being sent
+      @param aHeaderValue Value for that header
+    */
+    void sendHeader(const char* aHeaderName, const int aHeaderValue);
+
+    void sendHeader(const String& aHeaderName, const int aHeaderValue)
+      { sendHeader(aHeaderName.c_str(), aHeaderValue); }
+
+    /** Send a basic authentication header.  This will encode the given username
+      and password, and send them in suitable header line for doing Basic
+      Authentication.
+      @param aUser Username for the authorization
+      @param aPassword Password for the user aUser
+    */
+    void sendBasicAuth(const char* aUser, const char* aPassword);
+
+    void sendBasicAuth(const String& aUser, const String& aPassword)
+      { sendBasicAuth(aUser.c_str(), aPassword.c_str()); }
+
+    /** Get the HTTP status code contained in the response.
+      For example, 200 for successful request, 404 for file not found, etc.
+    */
+    int responseStatusCode();
+
+    /** Check if a header is available to be read.
+      Use readHeaderName() to read header name, and readHeaderValue() to
+      read the header value
+      MUST be called after responseStatusCode() and before contentLength()
+    */
+    bool headerAvailable();
+
+    /** Read the name of the current response header.
+      Returns empty string if a header is not available.
+    */
+    String readHeaderName();
+
+    /** Read the value of the current response header.
+      Returns empty string if a header is not available.
+    */
+    String readHeaderValue();
+
+    /** Read the next character of the response headers.
+      This functions in the same way as read() but to be used when reading
+      through the headers.  Check whether or not the end of the headers has
+      been reached by calling endOfHeadersReached(), although after that point
+      this will still return data as read() would, but slightly less efficiently
+      MUST be called after responseStatusCode() and before contentLength()
+      @return The next character of the response headers
+    */
+    int readHeader();
+
+    /** Skip any response headers to get to the body.
+      Use this if you don't want to do any special processing of the headers
+      returned in the response.  You can also use it after you've found all of
+      the headers you're interested in, and just want to get on with processing
+      the body.
+      MUST be called after responseStatusCode()
+      @return HTTP_SUCCESS if successful, else an error code
+    */
+    int skipResponseHeaders();
+
+    /** Test whether all of the response headers have been consumed.
+      @return true if we are now processing the response body, else false
+    */
+    bool endOfHeadersReached();
+
+    /** Test whether the end of the body has been reached.
+      Only works if the Content-Length header was returned by the server
+      @return true if we are now at the end of the body, else false
+    */
+    bool endOfBodyReached();
+    virtual bool endOfStream() { return endOfBodyReached(); };
+    virtual bool completed() { return endOfBodyReached(); };
+
+    /** Return the length of the body.
+      Also skips response headers if they have not been read already
+      MUST be called after responseStatusCode()
+      @return Length of the body, in bytes, or kNoContentLengthHeader if no
+      Content-Length header was returned by the server
+    */
+    long contentLength();
+
+    /** Returns if the response body is chunked
+      @return true if response body is chunked, false otherwise
+    */
+    int isResponseChunked() { return iIsChunked; }
+
+    /** Return the response body as a String
+      Also skips response headers if they have not been read already
+      MUST be called after responseStatusCode()
+      @return response body of request as a String
+    */
+    String responseBody();
+
+    /** Enables connection keep-alive mode
+    */
+    void connectionKeepAlive();
+
+    /** Disables sending the default request headers (Host and User Agent)
+    */
+    void noDefaultRequestHeaders();
+
+    // Inherited from Print
+    // Note: 1st call to these indicates the user is sending the body, so if need
+    // Note: be we should finish the header first
+    virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
+    virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
+    // Inherited from Stream
+    virtual int available();
+    /** Read the next byte from the server.
+      @return Byte read or -1 if there are no bytes available.
+    */
+    virtual int read();
+    virtual int read(uint8_t *buf, size_t size);
+    virtual int peek() { return iClient->peek(); };
+    virtual void flush() { iClient->flush(); };
+
+    // Inherited from Client
+    virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); };
+    virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); };
+    virtual void stop();
+    virtual uint8_t connected() { return iClient->connected(); };
+    virtual operator bool() { return bool(iClient); };
+    virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; };
+    virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; };
+    virtual uint32_t httpWaitForDataDelay() { return iHttpWaitForDataDelay; };
+    virtual void setHttpWaitForDataDelay(uint32_t delay) { iHttpWaitForDataDelay = delay; };
+protected:
+    /** Reset internal state data back to the "just initialised" state
+    */
+    void resetState();
+
+    /** Send the first part of the request and the initial headers.
+      @param aURLPath	Url to request
+      @param aHttpMethod  Type of HTTP request to make, e.g. "GET", "POST", etc.
+      @return 0 if successful, else error
+    */
+    int sendInitialHeaders(const char* aURLPath,
+                     const char* aHttpMethod);
+
+    /* Let the server know that we've reached the end of the headers
+    */
+    void finishHeaders();
+
+    /** Reading any pending data from the client (used in connection keep alive mode)
+    */
+    void flushClientRx();
+
+    // Number of milliseconds that we wait each time there isn't any data
+    // available to be read (during status code and header processing)
+    static const int kHttpWaitForDataDelay = 100;
+    // Number of milliseconds that we'll wait in total without receiving any
+    // data before returning HTTP_ERROR_TIMED_OUT (during status code and header
+    // processing)
+    static const int kHttpResponseTimeout = 30*1000;
+    static const char* kContentLengthPrefix;
+    static const char* kTransferEncodingChunked;
+    typedef enum {
+        eIdle,
+        eRequestStarted,
+        eRequestSent,
+        eReadingStatusCode,
+        eStatusCodeRead,
+        eReadingContentLength,
+        eSkipToEndOfHeader,
+        eLineStartingCRFound,
+        eReadingBody,
+        eReadingChunkLength,
+        eReadingBodyChunk
+    } tHttpState;
+    // Client we're using
+    Client* iClient;
+    // Server we are connecting to
+    const char* iServerName;
+    IPAddress iServerAddress;
+    // Port of server we are connecting to
+    uint16_t iServerPort;
+    // Current state of the finite-state-machine
+    tHttpState iState;
+    // Stores the status code for the response, once known
+    int iStatusCode;
+    // Stores the value of the Content-Length header, if present
+    long iContentLength;
+    // How many bytes of the response body have been read by the user
+    int iBodyLengthConsumed;
+    // How far through a Content-Length header prefix we are
+    const char* iContentLengthPtr;
+    // How far through a Transfer-Encoding chunked header we are
+    const char* iTransferEncodingChunkedPtr;
+    // Stores if the response body is chunked
+    bool iIsChunked;
+    // Stores the value of the current chunk length, if present
+    int iChunkLength;
+    uint32_t iHttpResponseTimeout;
+    uint32_t iHttpWaitForDataDelay;
+    bool iConnectionClose;
+    bool iSendDefaultRequestHeaders;
+    String iHeaderLine;
+};
+
+#endif
diff --git a/src/URLEncoder.cpp b/src/URLEncoder.cpp
new file mode 100644
index 0000000..7baf5a9
--- /dev/null
+++ b/src/URLEncoder.cpp
@@ -0,0 +1,53 @@
+// Library to simplify HTTP fetching on Arduino
+// (c) Copyright Arduino. 2019
+// Released under Apache License, version 2.0
+
+#include "URLEncoder.h"
+
+URLEncoderClass::URLEncoderClass()
+{
+}
+
+URLEncoderClass::~URLEncoderClass()
+{
+}
+
+String URLEncoderClass::encode(const char* str)
+{
+    return encode(str, strlen(str));
+}
+
+String URLEncoderClass::encode(const String& str)
+{
+    return encode(str.c_str(), str.length());
+}
+
+String URLEncoderClass::encode(const char* str, int length)
+{
+    String encoded;
+
+    encoded.reserve(length);
+
+    for (int i = 0; i < length; i++) {
+        char c = str[i];
+
+        const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+        if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) {
+            encoded += c;
+        } else {
+            char s[4];
+
+            s[0] = '%';
+            s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf];
+            s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)];
+            s[3] = 0;
+
+            encoded += s;
+        }
+    }
+
+    return encoded;
+}
+
+URLEncoderClass URLEncoder;
diff --git a/src/URLEncoder.h b/src/URLEncoder.h
new file mode 100644
index 0000000..bce5f17
--- /dev/null
+++ b/src/URLEncoder.h
@@ -0,0 +1,25 @@
+// Library to simplify HTTP fetching on Arduino
+// (c) Copyright Arduino. 2019
+// Released under Apache License, version 2.0
+
+#ifndef URL_ENCODER_H
+#define URL_ENCODER_H
+
+#include <Arduino.h>
+
+class URLEncoderClass
+{
+public:
+    URLEncoderClass();
+    virtual ~URLEncoderClass();
+
+    static String encode(const char* str);
+    static String encode(const String& str);
+
+private:
+    static String encode(const char* str, int length);
+};
+
+extern URLEncoderClass URLEncoder;
+
+#endif
diff --git a/src/URLParser.h b/src/URLParser.h
new file mode 100644
index 0000000..fd31e93
--- /dev/null
+++ b/src/URLParser.h
@@ -0,0 +1,108 @@
+/*
+ * PackageLicenseDeclared: Apache-2.0
+ * Copyright (c) 2017 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The following class is defined in mbed libraries, in case of STM32H7 include the original library
+ */
+#if defined __has_include
+#  if __has_include(<utility/http_parsed_url.h>)
+#    include <utility/http_parsed_url.h>
+#  else
+#    define NO_HTTP_PARSED
+#  endif
+#endif
+
+#ifdef NO_HTTP_PARSED
+#ifndef _MBED_HTTP_PARSED_URL_H_
+#define _MBED_HTTP_PARSED_URL_H_
+
+#include "utility/URLParser/http_parser.h"
+
+class ParsedUrl {
+public:
+    ParsedUrl(const char* url) {
+        struct http_parser_url parsed_url;
+        http_parser_parse_url(url, strlen(url), false, &parsed_url);
+
+        for (size_t ix = 0; ix < UF_MAX; ix++) {
+            char* value;
+            if (parsed_url.field_set & (1 << ix)) {
+                value = (char*)calloc(parsed_url.field_data[ix].len + 1, 1);
+                memcpy(value, url + parsed_url.field_data[ix].off,
+                       parsed_url.field_data[ix].len);
+            }
+            else {
+                value = (char*)calloc(1, 1);
+            }
+
+            switch ((http_parser_url_fields)ix) {
+                case UF_SCHEMA:   _schema   = value; break;
+                case UF_HOST:     _host     = value; break;
+                case UF_PATH:     _path     = value; break;
+                case UF_QUERY:    _query    = value; break;
+                case UF_USERINFO: _userinfo = value; break;
+                default:
+                    // PORT is already parsed, FRAGMENT is not relevant for HTTP requests
+                    free(value);
+                    break;
+            }
+        }
+
+        _port = parsed_url.port;
+        if (!_port) {
+            if (strcmp(_schema, "https") == 0 || strcmp(_schema, "wss") == 0) {
+                _port = 443;
+            }
+            else {
+                _port = 80;
+            }
+        }
+
+        if (strcmp(_path, "") == 0) {
+            free(_path);
+            _path = (char*)calloc(2, 1);
+            _path[0] = '/';
+        }
+    }
+
+    ~ParsedUrl() {
+        if (_schema) free(_schema);
+        if (_host) free(_host);
+        if (_path) free(_path);
+        if (_query) free(_query);
+        if (_userinfo) free(_userinfo);
+    }
+
+    uint16_t port() const { return _port; }
+    char* schema() const { return _schema; }
+    char* host() const { return _host; }
+    char* path() const { return _path; }
+    char* query() const { return _query; }
+    char* userinfo() const { return _userinfo; }
+
+private:
+    uint16_t _port;
+    char* _schema;
+    char* _host;
+    char* _path;
+    char* _query;
+    char* _userinfo;
+};
+
+#endif // _MBED_HTTP_PARSED_URL_H_
+#endif // NO_HTTP_PARSED
+#undef NO_HTTP_PARSED
\ No newline at end of file
diff --git a/src/WebSocketClient.cpp b/src/WebSocketClient.cpp
new file mode 100644
index 0000000..ab41b0a
--- /dev/null
+++ b/src/WebSocketClient.cpp
@@ -0,0 +1,372 @@
+// (c) Copyright Arduino. 2016
+// Released under Apache License, version 2.0
+
+#include "b64.h"
+
+#include "WebSocketClient.h"
+
+WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
+ : HttpClient(aClient, aServerName, aServerPort),
+   iTxStarted(false),
+   iRxSize(0)
+{
+}
+
+WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) 
+ : HttpClient(aClient, aServerName, aServerPort),
+   iTxStarted(false),
+   iRxSize(0)
+{
+}
+
+WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
+ : HttpClient(aClient, aServerAddress, aServerPort),
+   iTxStarted(false),
+   iRxSize(0)
+{
+}
+
+int WebSocketClient::begin(const char* aPath)
+{
+    // start the GET request
+    beginRequest();
+    connectionKeepAlive();
+    int status = get(aPath);
+
+    if (status == 0)
+    {
+        uint8_t randomKey[16];
+        char base64RandomKey[25];
+
+        // create a random key for the connection upgrade
+        for (int i = 0; i < (int)sizeof(randomKey); i++)
+        {
+            randomKey[i] = random(0x01, 0xff);
+        }
+        memset(base64RandomKey, 0x00, sizeof(base64RandomKey));
+        b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey));
+
+        // start the connection upgrade sequence
+        sendHeader("Upgrade", "websocket");
+        sendHeader("Connection", "Upgrade");
+        sendHeader("Sec-WebSocket-Key", base64RandomKey);
+        sendHeader("Sec-WebSocket-Version", "13");
+        endRequest();
+
+        status = responseStatusCode();
+
+        if (status > 0)
+        {
+            skipResponseHeaders();
+        }
+    }
+
+    iRxSize = 0;
+
+    // status code of 101 means success
+    return (status == 101) ? 0 : status;
+}
+
+int WebSocketClient::begin(const String& aPath)
+{
+    return begin(aPath.c_str());
+}
+
+int WebSocketClient::beginMessage(int aType)
+{
+    if (iTxStarted)
+    {
+        // fail TX already started
+        return 1;
+    }
+
+    iTxStarted = true;
+    iTxMessageType = (aType & 0xf);
+    iTxSize = 0;
+
+    return 0;
+}
+
+int WebSocketClient::endMessage()
+{
+    if (!iTxStarted)
+    {
+        // fail TX not started
+        return 1;
+    }
+
+    // send FIN + the message type (opcode)
+    HttpClient::write(0x80 | iTxMessageType);
+
+    // the message is masked (0x80)
+    // send the length
+    if (iTxSize < 126)
+    {
+        HttpClient::write(0x80 | (uint8_t)iTxSize);
+    }
+    else if (iTxSize < 0xffff)
+    {
+        HttpClient::write(0x80 | 126);
+        HttpClient::write((iTxSize >> 8) & 0xff);
+        HttpClient::write((iTxSize >> 0) & 0xff);
+    }
+    else
+    {
+        HttpClient::write(0x80 | 127);
+        HttpClient::write((iTxSize >> 56) & 0xff);
+        HttpClient::write((iTxSize >> 48) & 0xff);
+        HttpClient::write((iTxSize >> 40) & 0xff);
+        HttpClient::write((iTxSize >> 32) & 0xff);
+        HttpClient::write((iTxSize >> 24) & 0xff);
+        HttpClient::write((iTxSize >> 16) & 0xff);
+        HttpClient::write((iTxSize >> 8) & 0xff);
+        HttpClient::write((iTxSize >> 0) & 0xff);
+    }
+
+    uint8_t maskKey[4];
+
+    // create a random mask for the data and send
+    for (int i = 0; i < (int)sizeof(maskKey); i++)
+    {
+        maskKey[i] = random(0xff);
+    }
+    HttpClient::write(maskKey, sizeof(maskKey));
+
+    // mask the data and send
+    for (int i = 0; i < (int)iTxSize; i++) {
+        iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)];
+    }
+
+    size_t txSize = iTxSize;
+
+    iTxStarted = false;
+    iTxSize = 0;
+
+    return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1;
+}
+
+size_t WebSocketClient::write(uint8_t aByte)
+{
+    return write(&aByte, sizeof(aByte));
+}
+
+size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize)
+{
+    if (iState < eReadingBody)
+    {
+        // have not upgraded the connection yet
+        return HttpClient::write(aBuffer, aSize);
+    }
+
+    if (!iTxStarted)
+    {
+        // fail TX not   started
+        return 0;
+    }
+
+    // check if the write size, fits in the buffer
+    if ((iTxSize + aSize) > sizeof(iTxBuffer))
+    {
+        aSize = sizeof(iTxSize) - iTxSize;
+    }
+
+    // copy data into the buffer
+    memcpy(iTxBuffer + iTxSize, aBuffer, aSize);
+
+    iTxSize += aSize;
+    
+    return aSize;
+}
+
+int WebSocketClient::parseMessage()
+{
+    flushRx();
+
+    // make sure 2 bytes (opcode + length)
+    // are available
+    if (HttpClient::available() < 2)
+    {
+        return 0;
+    }
+
+    // read open code and length
+    uint8_t opcode = HttpClient::read();
+    int length = HttpClient::read();
+
+    if ((opcode & 0x0f) == 0)
+    {
+        // continuation, use previous opcode and update flags
+        iRxOpCode |= opcode;
+    }
+    else
+    {
+        iRxOpCode = opcode;
+    }
+
+    iRxMasked = (length & 0x80);
+    length &= 0x7f;
+
+    // read the RX size
+    if (length < 126)
+    {
+        iRxSize = length;
+    }
+    else if (length == 126)
+    {
+        iRxSize = (HttpClient::read() << 8) | HttpClient::read();
+    }
+    else
+    {
+        iRxSize = ((uint64_t)HttpClient::read() << 56) | 
+                    ((uint64_t)HttpClient::read() << 48) | 
+                    ((uint64_t)HttpClient::read() << 40) | 
+                    ((uint64_t)HttpClient::read() << 32) | 
+                    ((uint64_t)HttpClient::read() << 24) | 
+                    ((uint64_t)HttpClient::read() << 16) | 
+                    ((uint64_t)HttpClient::read() << 8)  |
+                    (uint64_t)HttpClient::read(); 
+    }
+
+    // read in the mask, if present
+    if (iRxMasked)
+    {
+        for (int i = 0; i < (int)sizeof(iRxMaskKey); i++)
+        {
+            iRxMaskKey[i] = HttpClient::read();
+        }
+    }
+
+    iRxMaskIndex = 0;
+
+    if (TYPE_CONNECTION_CLOSE == messageType())
+    {
+        flushRx();
+        stop();
+        iRxSize = 0;
+    }
+    else if (TYPE_PING == messageType())
+    {
+        beginMessage(TYPE_PONG);
+        while(available())
+        {
+            write(read());
+        }
+        endMessage();
+
+        iRxSize = 0;
+    }
+    else if (TYPE_PONG == messageType())
+    {
+        flushRx();
+        iRxSize = 0;
+    }
+
+    return iRxSize;
+}
+
+int WebSocketClient::messageType()
+{
+    return (iRxOpCode & 0x0f);
+}
+
+bool WebSocketClient::isFinal()
+{
+    return ((iRxOpCode & 0x80) != 0);
+}
+
+String WebSocketClient::readString()
+{
+    int avail = available();
+    String s;
+
+    if (avail > 0)
+    {
+        s.reserve(avail);
+
+        for (int i = 0; i < avail; i++)
+        {
+            s += (char)read();
+        }
+    }
+
+    return s;
+}
+
+int WebSocketClient::ping()
+{
+    uint8_t pingData[16];
+
+    // create random data for the ping
+    for (int i = 0; i < (int)sizeof(pingData); i++)
+    {
+        pingData[i] = random(0xff);
+    }
+
+    beginMessage(TYPE_PING);
+    write(pingData, sizeof(pingData));
+    return endMessage();
+}
+
+int WebSocketClient::available()
+{
+    if (iState < eReadingBody)
+    {
+        return HttpClient::available();
+    }
+
+    return iRxSize;
+}
+
+int WebSocketClient::read()
+{
+    byte b;
+
+    if (read(&b, sizeof(b)))
+    {
+        return b;
+    }
+
+    return -1;
+}
+
+int WebSocketClient::read(uint8_t *aBuffer, size_t aSize)
+{
+    int readCount = HttpClient::read(aBuffer, aSize);
+
+    if (readCount > 0)
+    {
+        iRxSize -= readCount;
+
+        // unmask the RX data if needed
+        if (iRxMasked)
+        {
+            for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++)
+            {
+                aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
+            }
+        }
+    }
+
+    return readCount;
+}
+
+int WebSocketClient::peek()
+{
+    int p = HttpClient::peek();
+
+    if (p != -1 && iRxMasked)
+    {
+        // unmask the RX data if needed
+        p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
+    }
+
+    return p;
+}
+
+void WebSocketClient::flushRx()
+{
+    while(available())
+    {
+        read();
+    }
+}
diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h
new file mode 100644
index 0000000..96eb6d2
--- /dev/null
+++ b/src/WebSocketClient.h
@@ -0,0 +1,103 @@
+// (c) Copyright Arduino. 2016
+// Released under Apache License, version 2.0
+
+#ifndef WebSocketClient_h
+#define WebSocketClient_h
+
+#include <Arduino.h>
+
+#include "HttpClient.h"
+
+#ifndef WS_TX_BUFFER_SIZE
+  #define WS_TX_BUFFER_SIZE 128
+#endif
+
+static const int TYPE_CONTINUATION     = 0x0;
+static const int TYPE_TEXT             = 0x1;
+static const int TYPE_BINARY           = 0x2;
+static const int TYPE_CONNECTION_CLOSE = 0x8;
+static const int TYPE_PING             = 0x9;
+static const int TYPE_PONG             = 0xa;
+
+class WebSocketClient : public HttpClient
+{
+public:
+    WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
+    WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
+    WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort);
+
+    /** Start the Web Socket connection to the specified path
+      @param aURLPath     Path to use in request (optional, "/" is used by default)
+      @return 0 if successful, else error
+     */
+    int begin(const char* aPath = "/");
+    int begin(const String& aPath);
+
+    /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY)
+        Use the write or Stream API's to set message content, followed by endMessage
+        to complete the message.
+      @param aURLPath     Path to use in request
+      @return 0 if successful, else error
+    */
+    int beginMessage(int aType);
+
+    /** Completes sending of a message started by beginMessage
+      @return 0 if successful, else error
+    */
+    int endMessage();
+
+    /** Try to parse an incoming messages
+      @return 0 if no message available, else size of parsed message
+    */
+    int parseMessage();
+
+    /** Returns type of current parsed message
+      @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY)
+    */
+    int messageType();
+
+    /** Returns if the current message is the final chunk of a split
+        message
+      @return true for final message, false otherwise
+    */
+    bool isFinal();
+
+    /** Read the current messages as a string
+      @return current message as a string
+    */
+    String readString();
+
+    /** Send a ping
+      @return 0 if successful, else error
+    */
+    int ping();
+
+    // Inherited from Print
+    virtual size_t write(uint8_t aByte);
+    virtual size_t write(const uint8_t *aBuffer, size_t aSize);
+    // Inherited from Stream
+    virtual int available();
+    /** Read the next byte from the server.
+      @return Byte read or -1 if there are no bytes available.
+    */
+    virtual int read();
+    virtual int read(uint8_t *buf, size_t size);
+    virtual int peek();
+
+private:
+    void flushRx();
+
+private:
+    bool iTxStarted;
+    uint8_t iTxMessageType;
+    uint8_t iTxBuffer[WS_TX_BUFFER_SIZE];
+    uint64_t iTxSize;
+
+    uint8_t iRxOpCode;
+    uint64_t iRxSize;
+    bool iRxMasked;
+    int iRxMaskIndex;
+    uint8_t iRxMaskKey[4];
+};
+
+#endif
diff --git a/b64.cpp b/src/b64.cpp
similarity index 98%
rename from b64.cpp
rename to src/b64.cpp
index b926cad..683d60a 100644
--- a/b64.cpp
+++ b/src/b64.cpp
@@ -66,5 +66,7 @@ int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutpu
             b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4));
         }
     }
+
+    return ((aInputLen+2)/3)*4;
 }
 
diff --git a/b64.h b/src/b64.h
similarity index 100%
rename from b64.h
rename to src/b64.h
diff --git a/src/utility/URLParser/LICENSE b/src/utility/URLParser/LICENSE
new file mode 100644
index 0000000..5baf7c0
--- /dev/null
+++ b/src/utility/URLParser/LICENSE
@@ -0,0 +1,23 @@
+http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
+Igor Sysoev.
+
+Additional changes are licensed under the same terms as NGINX and
+copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/src/utility/URLParser/README.md b/src/utility/URLParser/README.md
new file mode 100644
index 0000000..56f8c96
--- /dev/null
+++ b/src/utility/URLParser/README.md
@@ -0,0 +1,5 @@
+# http_parser library
+
+This code is imported from: https://github.com/arduino/ArduinoCore-mbed/tree/4.1.1/libraries/SocketWrapper/src/utility/http_parser
+
+The code is shrinked in size by deleting all the unrelated code to url parse.
diff --git a/src/utility/URLParser/http_parser.c b/src/utility/URLParser/http_parser.c
new file mode 100644
index 0000000..a572a4c
--- /dev/null
+++ b/src/utility/URLParser/http_parser.c
@@ -0,0 +1,591 @@
+#if defined __has_include
+#  if ! __has_include(<utility/http_parser/http_parser.h>) && ! __has_include(<http_parser.h>)
+#    define NO_HTTP_PARSER
+#  endif
+#endif
+
+#ifdef NO_HTTP_PARSER
+/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
+ *
+ * Additional changes are licensed under the same terms as NGINX and
+ * copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef BIT_AT
+# define BIT_AT(a, i)                                                \
+  (!!((unsigned int) (a)[(unsigned int) (i) >> 3] &                  \
+   (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#define SET_ERRNO(e)                                                 \
+do {                                                                 \
+  parser->http_errno = (e);                                          \
+} while(0)
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0    | T(2)   |   0    |   0    | T(16)  |   0    |   0    |   0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+        0    |   2    |   4    |   0    |   16   |   32   |   64   |  128,
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0, };
+
+#undef T
+
+enum state
+  { s_dead = 1 /* important that this is > 0 */
+
+  , s_start_req
+
+  , s_req_spaces_before_url
+  , s_req_schema
+  , s_req_schema_slash
+  , s_req_schema_slash_slash
+  , s_req_server_start
+  , s_req_server
+  , s_req_server_with_at
+  , s_req_path
+  , s_req_query_string_start
+  , s_req_query_string
+  , s_req_fragment_start
+  , s_req_fragment
+  , s_headers_done
+  };
+
+enum http_host_state
+  {
+    s_http_host_dead = 1
+  , s_http_userinfo_start
+  , s_http_userinfo
+  , s_http_host_start
+  , s_http_host_v6_start
+  , s_http_host
+  , s_http_host_v6
+  , s_http_host_v6_end
+  , s_http_host_v6_zone_start
+  , s_http_host_v6_zone
+  , s_http_host_port_start
+  , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode  */
+#define LOWER(c)            (unsigned char)(c | 0x20)
+#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \
+  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+  (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+  (c) == '$' || (c) == ',')
+
+#if HTTP_PARSER_STRICT
+#define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define IS_URL_CHAR(c)                                                         \
+  (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c)                                                        \
+  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+  if (ch == ' ' || ch == '\r' || ch == '\n') {
+    return s_dead;
+  }
+
+#if HTTP_PARSER_STRICT
+  if (ch == '\t' || ch == '\f') {
+    return s_dead;
+  }
+#endif
+
+  switch (s) {
+    case s_req_spaces_before_url:
+      /* Proxied requests are followed by scheme of an absolute URI (alpha).
+       * All methods except CONNECT are followed by '/' or '*'.
+       */
+
+      if (ch == '/' || ch == '*') {
+        return s_req_path;
+      }
+
+      if (IS_ALPHA(ch)) {
+        return s_req_schema;
+      }
+
+      break;
+
+    case s_req_schema:
+      if (IS_ALPHA(ch)) {
+        return s;
+      }
+
+      if (ch == ':') {
+        return s_req_schema_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash:
+      if (ch == '/') {
+        return s_req_schema_slash_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash_slash:
+      if (ch == '/') {
+        return s_req_server_start;
+      }
+
+      break;
+
+    case s_req_server_with_at:
+      if (ch == '@') {
+        return s_dead;
+      }
+
+    /* FALLTHROUGH */
+    case s_req_server_start:
+    case s_req_server:
+      if (ch == '/') {
+        return s_req_path;
+      }
+
+      if (ch == '?') {
+        return s_req_query_string_start;
+      }
+
+      if (ch == '@') {
+        return s_req_server_with_at;
+      }
+
+      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+        return s_req_server;
+      }
+
+      break;
+
+    case s_req_path:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_query_string_start;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_query_string_start:
+    case s_req_query_string:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_query_string;
+      }
+
+      switch (ch) {
+        case '?':
+          /* allow extra '?' in query string */
+          return s_req_query_string;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_fragment_start:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_fragment;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_fragment;
+
+        case '#':
+          return s;
+      }
+
+      break;
+
+    case s_req_fragment:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+        case '#':
+          return s;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+
+  /* We should never fall out of the switch above unless there's an error */
+  return s_dead;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+  switch(s) {
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      if (ch == '@') {
+        return s_http_host_start;
+      }
+
+      if (IS_USERINFO_CHAR(ch)) {
+        return s_http_userinfo;
+      }
+      break;
+
+    case s_http_host_start:
+      if (ch == '[') {
+        return s_http_host_v6_start;
+      }
+
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+      break;
+
+    case s_http_host:
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+    /* FALLTHROUGH */
+    case s_http_host_v6_end:
+      if (ch == ':') {
+        return s_http_host_port_start;
+      }
+
+      break;
+
+    case s_http_host_v6:
+      if (ch == ']') {
+        return s_http_host_v6_end;
+      }
+
+    /* FALLTHROUGH */
+    case s_http_host_v6_start:
+      if (IS_HEX(ch) || ch == ':' || ch == '.') {
+        return s_http_host_v6;
+      }
+
+      if (s == s_http_host_v6 && ch == '%') {
+        return s_http_host_v6_zone_start;
+      }
+      break;
+
+    case s_http_host_v6_zone:
+      if (ch == ']') {
+        return s_http_host_v6_end;
+      }
+
+    /* FALLTHROUGH */
+    case s_http_host_v6_zone_start:
+      /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
+      if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
+          ch == '~') {
+        return s_http_host_v6_zone;
+      }
+      break;
+
+    case s_http_host_port:
+    case s_http_host_port_start:
+      if (IS_NUM(ch)) {
+        return s_http_host_port;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+  return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+  enum http_host_state s;
+
+  const char *p;
+  uint32_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+  assert(u->field_set & (1 << UF_HOST));
+
+  u->field_data[UF_HOST].len = 0;
+
+  s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+    enum http_host_state new_s = http_parse_host_char(s, *p);
+
+    if (new_s == s_http_host_dead) {
+      return 1;
+    }
+
+    switch(new_s) {
+      case s_http_host:
+        if (s != s_http_host) {
+          u->field_data[UF_HOST].off = p - buf;
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_v6:
+        if (s != s_http_host_v6) {
+          u->field_data[UF_HOST].off = p - buf;
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_v6_zone_start:
+      case s_http_host_v6_zone:
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_port:
+        if (s != s_http_host_port) {
+          u->field_data[UF_PORT].off = p - buf;
+          u->field_data[UF_PORT].len = 0;
+          u->field_set |= (1 << UF_PORT);
+        }
+        u->field_data[UF_PORT].len++;
+        break;
+
+      case s_http_userinfo:
+        if (s != s_http_userinfo) {
+          u->field_data[UF_USERINFO].off = p - buf ;
+          u->field_data[UF_USERINFO].len = 0;
+          u->field_set |= (1 << UF_USERINFO);
+        }
+        u->field_data[UF_USERINFO].len++;
+        break;
+
+      default:
+        break;
+    }
+    s = new_s;
+  }
+
+  /* Make sure we don't end somewhere unexpected */
+  switch (s) {
+    case s_http_host_start:
+    case s_http_host_v6_start:
+    case s_http_host_v6:
+    case s_http_host_v6_zone_start:
+    case s_http_host_v6_zone:
+    case s_http_host_port_start:
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      return 1;
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+void
+http_parser_url_init(struct http_parser_url *u) {
+  memset(u, 0, sizeof(*u));
+}
+
+int
+http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect,
+                      struct http_parser_url *u)
+{
+  enum state s;
+  const char *p;
+  enum http_parser_url_fields uf, old_uf;
+  int found_at = 0;
+
+  u->port = u->field_set = 0;
+  s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+  old_uf = UF_MAX;
+
+  for (p = buf; p < buf + buflen; p++) {
+    s = parse_url_char(s, *p);
+
+    /* Figure out the next field that we're operating on */
+    switch (s) {
+      case s_dead:
+        return 1;
+
+      /* Skip delimeters */
+      case s_req_schema_slash:
+      case s_req_schema_slash_slash:
+      case s_req_server_start:
+      case s_req_query_string_start:
+      case s_req_fragment_start:
+        continue;
+
+      case s_req_schema:
+        uf = UF_SCHEMA;
+        break;
+
+      case s_req_server_with_at:
+        found_at = 1;
+
+      /* FALLTROUGH */
+      case s_req_server:
+        uf = UF_HOST;
+        break;
+
+      case s_req_path:
+        uf = UF_PATH;
+        break;
+
+      case s_req_query_string:
+        uf = UF_QUERY;
+        break;
+
+      case s_req_fragment:
+        uf = UF_FRAGMENT;
+        break;
+
+      default:
+        assert(!"Unexpected state");
+        return 1;
+    }
+
+    /* Nothing's changed; soldier on */
+    if (uf == old_uf) {
+      u->field_data[uf].len++;
+      continue;
+    }
+
+    u->field_data[uf].off = p - buf;
+    u->field_data[uf].len = 1;
+
+    u->field_set |= (1 << uf);
+    old_uf = uf;
+  }
+
+  /* host must be present if there is a schema */
+  /* parsing http:///toto will fail */
+  if ((u->field_set & (1 << UF_SCHEMA)) &&
+      (u->field_set & (1 << UF_HOST)) == 0) {
+    return 1;
+  }
+
+  if (u->field_set & (1 << UF_HOST)) {
+    if (http_parse_host(buf, u, found_at) != 0) {
+      return 1;
+    }
+  }
+
+  /* CONNECT requests can only contain "hostname:port" */
+  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+    return 1;
+  }
+
+  if (u->field_set & (1 << UF_PORT)) {
+    /* Don't bother with endp; we've already validated the string */
+    unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
+
+    /* Ports have a max value of 2^16 */
+    if (v > 0xffff) {
+      return 1;
+    }
+
+    u->port = (uint16_t) v;
+  }
+
+  return 0;
+}
+
+unsigned long
+http_parser_version(void) {
+  return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
+         HTTP_PARSER_VERSION_MINOR * 0x00100 |
+         HTTP_PARSER_VERSION_PATCH * 0x00001;
+}
+
+#endif // NO_HTTP_PARSER
\ No newline at end of file
diff --git a/src/utility/URLParser/http_parser.h b/src/utility/URLParser/http_parser.h
new file mode 100644
index 0000000..85a5238
--- /dev/null
+++ b/src/utility/URLParser/http_parser.h
@@ -0,0 +1,96 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Also update SONAME in the Makefile whenever you change these. */
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 7
+#define HTTP_PARSER_VERSION_PATCH 1
+
+#include <stdint.h>
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#endif
+
+
+enum http_parser_url_fields
+  { UF_SCHEMA           = 0
+  , UF_HOST             = 1
+  , UF_PORT             = 2
+  , UF_PATH             = 3
+  , UF_QUERY            = 4
+  , UF_FRAGMENT         = 5
+  , UF_USERINFO         = 6
+  , UF_MAX              = 7
+  };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+  uint16_t field_set;           /* Bitmask of (1 << UF_*) values */
+  uint16_t port;                /* Converted UF_PORT string */
+
+  struct {
+    uint16_t off;               /* Offset into buffer in which field starts */
+    uint16_t len;               /* Length of run in buffer */
+  } field_data[UF_MAX];
+};
+
+
+/* Returns the library version. Bits 16-23 contain the major version number,
+ * bits 8-15 the minor version number and bits 0-7 the patch level.
+ * Usage example:
+ *
+ *   unsigned long version = http_parser_version();
+ *   unsigned major = (version >> 16) & 255;
+ *   unsigned minor = (version >> 8) & 255;
+ *   unsigned patch = version & 255;
+ *   printf("http_parser v%u.%u.%u\n", major, minor, patch);
+ */
+unsigned long http_parser_version(void);
+
+/* Initialize all http_parser_url members to 0 */
+void http_parser_url_init(struct http_parser_url *u);
+
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, uint32_t buflen,
+                          int is_connect,
+                          struct http_parser_url *u);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
\ No newline at end of file
