diff --git a/cores/esp8266/Stream.cpp b/cores/esp8266/Stream.cpp
index b9b5b95f65..e852a1d91b 100644
--- a/cores/esp8266/Stream.cpp
+++ b/cores/esp8266/Stream.cpp
@@ -22,6 +22,7 @@
 
 #include <Arduino.h>
 #include <Stream.h>
+#include <StreamString.h>
 
 #define PARSE_TIMEOUT 1000  // default number of milli-seconds to wait
 #define NO_SKIP_CHAR  1  // a magic char not found in a valid ASCII numeric field
@@ -288,6 +289,59 @@ String Stream::readStringUntil(const char* terminator, uint32_t untilTotalNumber
     return ret;
 }
 
+String Stream::readStreamString(const ssize_t maxLen ,const oneShotMs::timeType timeoutMs) {
+    String ret;
+    S2Stream stream(ret);
+    sendGeneric(&stream, maxLen, -1, timeoutMs);
+    return ret;
+}
+
+String Stream::readStreamStringUntil(const int readUntilChar, const oneShotMs::timeType timeoutMs) {
+    String ret;
+    S2Stream stream(ret);
+    sendGeneric(&stream, -1, readUntilChar, timeoutMs);
+    return ret;
+}
+
+String Stream::readStreamStringUntil (const char* terminatorString, uint32_t untilTotalNumberOfOccurrences, const oneShotMs::timeType timeoutMs) {
+    String ret;
+    S2Stream stream(ret);
+    uint32_t occurrences = 0;
+    size_t termLen = strlen(terminatorString);
+    size_t termIndex = 0;
+    // Serial.printf("S %s\n",terminatorString);
+    while(1){
+        size_t read = sendGeneric(&stream, -1, terminatorString[termIndex], timeoutMs);
+        // Serial.printf("r %d, l %d, ti %d\n", read, termLen, termIndex);
+        if(getLastSendReport() != Report::Success) {
+            Serial.printf("Error %d\n", (int) getLastSendReport());
+            break;
+        }
+        if(termIndex == termLen - 1){
+            // Serial.printf("m %d\n", occurrences);
+            if(++occurrences == untilTotalNumberOfOccurrences){
+                break;
+            }else{
+                ret += terminatorString;
+                termIndex = 0;
+                continue;
+            }
+        }
+        int c = timedPeek();
+        // Serial.printf("c %c %02X\n", c, c);
+        if( c >= 0 && c != terminatorString[++termIndex]){
+            ret += String(terminatorString).substring(0, termIndex);
+            termIndex = 0;
+            continue;
+        };
+        if(c < 0 || (read == 0 && termIndex == 0)) break;
+    }
+    
+    return ret;
+}
+
+
+
 // read what can be read, immediate exit on unavailable data
 // prototype similar to Arduino's `int Client::read(buf, len)`
 int Stream::read (uint8_t* buffer, size_t maxLen)
diff --git a/cores/esp8266/Stream.h b/cores/esp8266/Stream.h
index 0706bec001..a752112a02 100644
--- a/cores/esp8266/Stream.h
+++ b/cores/esp8266/Stream.h
@@ -216,6 +216,10 @@ class Stream: public Print {
         size_t sendSize (Stream& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); }
         size_t sendSize (Stream&& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); }
 
+        String readStreamString (const ssize_t maxLen = -1 ,const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires);
+        String readStreamStringUntil (const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires);
+        String readStreamStringUntil (const char* terminatorString, uint32_t untilTotalNumberOfOccurrences = 1, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires);
+
         // remaining size (-1 by default = unknown)
         virtual ssize_t streamRemaining () { return -1; }
 
diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h
index 0d432aa69f..0e1e4a4604 100644
--- a/libraries/ESP8266WebServer/src/Parsing-impl.h
+++ b/libraries/ESP8266WebServer/src/Parsing-impl.h
@@ -44,9 +44,8 @@ static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t
 template <typename ServerType>
 typename ESP8266WebServerTemplate<ServerType>::ClientFuture ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
   // Read the first line of HTTP request
-  String req = client.readStringUntil('\r');
+  String req = client.readStreamStringUntil("\r\n");
   DBGWS("request: %s\n", req.c_str());
-  client.readStringUntil('\n');
   //reset header value
   for (int i = 0; i < _headerKeysCount; ++i) {
     _currentHeaders[i].value.clear();
@@ -122,8 +121,7 @@ typename ESP8266WebServerTemplate<ServerType>::ClientFuture ESP8266WebServerTemp
     uint32_t contentLength = 0;
     //parse headers
     while(1){
-      req = client.readStringUntil('\r');
-      client.readStringUntil('\n');
+      req = client.readStreamStringUntil("\r\n");
       if (req.isEmpty()) break; //no more headers
       int headerDiv = req.indexOf(':');
       if (headerDiv == -1){
@@ -198,8 +196,7 @@ typename ESP8266WebServerTemplate<ServerType>::ClientFuture ESP8266WebServerTemp
     String headerValue;
     //parse headers
     while(1){
-      req = client.readStringUntil('\r');
-      client.readStringUntil('\n');
+      req = client.readStreamStringUntil("\r\n");
       if (req.isEmpty()) break;//no moar headers
       int headerDiv = req.indexOf(':');
       if (headerDiv == -1){
@@ -351,11 +348,10 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
   String line;
   int retry = 0;
   do {
-    line = client.readStringUntil('\r');
+    line = client.readStreamStringUntil("\r\n");
     ++retry;
   } while (line.length() == 0 && retry < 3);
 
-  client.readStringUntil('\n');
   //start reading the form
   if (line == ("--"+boundary)){
     std::unique_ptr<RequestArgument[]> postArgs(new RequestArgument[WEBSERVER_MAX_POST_ARGS]);
@@ -367,8 +363,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
       String argFilename;
       bool argIsFile = false;
 
-      line = client.readStringUntil('\r');
-      client.readStringUntil('\n');
+      line = client.readStreamStringUntil("\r\n");
       if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){
         int nameStart = line.indexOf('=');
         if (nameStart != -1){
@@ -388,19 +383,16 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
           DBGWS("PostArg Name: %s\n", argName.c_str());
           using namespace mime;
           argType = FPSTR(mimeTable[txt].mimeType);
-          line = client.readStringUntil('\r');
-          client.readStringUntil('\n');
+          line = client.readStreamStringUntil("\r\n");
           if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){
             argType = line.substring(line.indexOf(':')+2);
             //skip next line
-            client.readStringUntil('\r');
-            client.readStringUntil('\n');
+            client.readStringUntil("\r\n");
           }
           DBGWS("PostArg Type: %s\n", argType.c_str());
           if (!argIsFile){
             while(1){
-              line = client.readStringUntil('\r');
-              client.readStringUntil('\n');
+              line = client.readStreamStringUntil("\r\n");
               if (line.startsWith("--"+boundary)) break;
               if (argValue.length() > 0) argValue += '\n';
               argValue += line;
@@ -474,8 +466,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
                 _currentUpload->type.c_str(),
                 (int)_currentUpload->totalSize);
             if (!client.connected()) return _parseFormUploadAborted();
-            line = client.readStringUntil('\r');
-            client.readStringUntil('\n');
+            line = client.readStreamStringUntil("\r\n");
             if (line == "--") {     // extra two dashes mean we reached the end of all form fields
                 DBGWS("Done Parsing POST\n");
                 break;